security(export): confine metadata-derived names (#2903)

This commit is contained in:
Earl Co
2026-10-23 10:54:22 -04:00
committed by GitHub
parent 9a0096a979
commit b1effb9ab1
3 changed files with 16 additions and 3 deletions
+10
View File
@@ -9,6 +9,16 @@ use serde_json::{Value, json};
use crate::Session;
#[test]
fn project_names_become_one_export_filename_component() {
for name in ["../../outside", "/absolute/path", r"C:\\outside", ".."] {
let safe = crate::export_tools::file_safe(name);
assert!(!safe.contains(['/', '\\']), "{name:?} became {safe:?}");
assert_ne!(safe, ".");
assert_ne!(safe, "..");
}
}
#[test]
fn hostile_export_ranges_are_rejected_without_panicking() {
let mut session = demo();
+3 -2
View File
@@ -385,10 +385,11 @@ pub fn default_export_dir(s: &Session) -> PathBuf {
crate::temp_dir()
}
fn file_safe(n: &str) -> String {
/// Convert project-derived display text into exactly one safe output filename component.
pub fn file_safe(n: &str) -> String {
let s: String = n.chars().map(|c| if c.is_alphanumeric() || " -_().".contains(c) { c } else { '_' }).collect();
let s = s.trim().to_string();
if s.is_empty() { "Export".into() } else { s }
if s.is_empty() || matches!(s.as_str(), "." | "..") { "Export".into() } else { s }
}
/// `path` with the settings' extension (added when missing; image sequences keep theirs).
+3 -1
View File
@@ -1414,7 +1414,9 @@ fn quick_file(app: &FilmcraftApp, path: &str, seq_name: &str, ext: &str) -> Opti
return None;
}
let file = match name {
None => format!("{seq_name}.{ext}"),
// Sequence names come from the project, not from this Quick Export interaction. Keep
// metadata in one filename component before joining it to the selected/default folder.
None => format!("{}.{}", filmcraft_engine::export_tools::file_safe(seq_name), ext),
Some(n) if cur.eq_ignore_ascii_case(ext) => n.to_string(),
Some(n) if !cur.is_empty() && app.session.export_presets.all().iter().any(|x| x.settings.extension().eq_ignore_ascii_case(cur)) => {
std::path::Path::new(n).with_extension(ext).to_string_lossy().to_string()