mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-11 19:52:10 +00:00
Added path validation to the share file endpoint
This commit is contained in:
parent
e64c4f01ec
commit
76b7c588de
@ -11,9 +11,20 @@ public sealed class PluginShareService(NativeShareService nativeShareService, Ru
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginShareService).Namespace, nameof(PluginShareService));
|
||||
|
||||
/// <remarks>
|
||||
/// Keep in sync with SHARE_FILE_EXTENSION in runtime/src/share_sheet.rs: the runtime only hands
|
||||
/// archives with this extension to the native share sheet.
|
||||
/// </remarks>
|
||||
public const string PLUGIN_FILE_EXTENSION = ".mwplugin";
|
||||
|
||||
private const string PLUGIN_FILE_NAME = "plugin.lua";
|
||||
|
||||
/// <remarks>
|
||||
/// Keep in sync with SHARE_DIRECTORY_NAME in runtime/src/share_sheet.rs: the runtime only hands
|
||||
/// archives from a directory with this name to the native share sheet.
|
||||
/// </remarks>
|
||||
private const string TEMPORARY_ARCHIVE_DIRECTORY = "mindwork-ai-studio-plugin-shares";
|
||||
|
||||
private const int TEMPORARY_ARCHIVE_RETENTION_HOURS = 24;
|
||||
private const int FILE_NAME_PREFIX_MAX_LEN = 80;
|
||||
|
||||
|
||||
@ -1,9 +1,17 @@
|
||||
use axum::Json;
|
||||
use log::{error, info};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use crate::api_token::APIToken;
|
||||
|
||||
/// The directory the app creates its shareable plugin archives in. Keep in sync with
|
||||
/// PluginShareService.TEMPORARY_ARCHIVE_DIRECTORY on the .NET side.
|
||||
const SHARE_DIRECTORY_NAME: &str = "mindwork-ai-studio-plugin-shares";
|
||||
|
||||
/// The file extension of plugin archives, without the leading dot. Keep in sync with
|
||||
/// PluginShareService.PLUGIN_FILE_EXTENSION on the .NET side.
|
||||
const SHARE_FILE_EXTENSION: &str = "mwplugin";
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ShareFileRequest {
|
||||
file_path: String,
|
||||
@ -25,6 +33,18 @@ pub async fn share_file(_token: APIToken, Json(request): Json<ShareFileRequest>)
|
||||
return failure(format!("The requested path is not an existing file: {}", path.to_string_lossy()));
|
||||
}
|
||||
|
||||
// Resolve the path before validating it, so a symlink with a matching name cannot point at an
|
||||
// arbitrary file. We share the original path afterwards, though: on Windows, canonicalize
|
||||
// returns a \\?\ path, which the WinRT storage APIs do not accept.
|
||||
let resolved_path = match std::fs::canonicalize(&path) {
|
||||
Ok(resolved_path) => resolved_path,
|
||||
Err(error) => return failure(format!("The requested path could not be resolved: {error}")),
|
||||
};
|
||||
|
||||
if !is_shareable_archive(&resolved_path) {
|
||||
return failure(format!("The requested path is not a plugin archive created by AI Studio: {}", path.to_string_lossy()));
|
||||
}
|
||||
|
||||
let result = share_file_on_platform(path).await;
|
||||
match result {
|
||||
Ok(()) => {
|
||||
@ -42,6 +62,22 @@ pub async fn share_file(_token: APIToken, Json(request): Json<ShareFileRequest>)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that a path points to a plugin archive the app itself created for sharing. This keeps the
|
||||
/// endpoint from handing arbitrary readable files to the operating system's share UI.
|
||||
///
|
||||
/// We match the directory by name instead of comparing it against the temporary directory: Rust and
|
||||
/// .NET do not have to agree on where that is, and a mismatch would break sharing entirely.
|
||||
fn is_shareable_archive(path: &Path) -> bool {
|
||||
let has_archive_extension = path.extension().is_some_and(|extension| extension.eq_ignore_ascii_case(SHARE_FILE_EXTENSION));
|
||||
if !has_archive_extension {
|
||||
return false;
|
||||
}
|
||||
|
||||
path.parent()
|
||||
.and_then(|parent| parent.file_name())
|
||||
.is_some_and(|directory_name| directory_name == SHARE_DIRECTORY_NAME)
|
||||
}
|
||||
|
||||
fn failure(issue: impl Into<String>) -> Json<ShareFileResponse> {
|
||||
Json(ShareFileResponse {
|
||||
success: false,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user