MCP: an agent's folders hold for file loads through the app and for a design's font files (#1112)

doc.open and the STEP/mesh imports run on the file loader's worker with the request's path scope, and a font named by file path in a design is checked against the scope before it is read.
This commit is contained in:
Mateusz Sych authored and GitHub committed 2026-10-14 13:58:56 -04:00
1 parent 6de8f6db95
commit c469adf456
8 files changed
+122 -30

No files matched your search

+13 -7
View File
@@ -24,6 +24,15 @@ const MAX_CACHED_BYTES: usize = 128 << 20;
/// Longest font name or path accepted.
const MAX_FONT_NAME: usize = 1024;
/// Asked before a font file named by its path is read. A design names such files, and an
/// agent's folders (`vfs::PathScope`) live above this crate; the first check set stays.
static FILE_CHECK: OnceLock<fn(&Path) -> std::result::Result<(), String>> = OnceLock::new();
/// Set the check [`font_data`] runs on a font file's path before reading it.
pub fn set_font_file_check(check: fn(&Path) -> std::result::Result<(), String>) {
let _ = FILE_CHECK.set(check);
}
/// The bytes of a font file and the face to use in it (collections hold several faces).
#[derive(Clone)]
pub struct FontData {
@@ -59,6 +68,10 @@ pub fn font_data(name: Option<&str>) -> Result<FontData> {
}
if is_path(name) {
let path = expand(name);
// Before the cache too: a file read once for the user is not the agent's to read.
if let Some(check) = FILE_CHECK.get() {
check(&path).map_err(|e| SketchError::Invalid(format!("font file `{name}`: {e}")))?;
}
let bytes = read_font(&path).map_err(|e| SketchError::Invalid(format!("font file `{name}`: {e}")))?;
let index = 0;
ttf_parser::Face::parse(&bytes, index)
@@ -306,13 +319,6 @@ fn normalize(s: &str) -> String {
s.chars().filter(|c| c.is_alphanumeric()).flat_map(char::to_lowercase).collect()
}
/// The file a font name refers to (after `~` and `%VAR%` expansion), or `None` for the built-in
/// font or an installed family, so callers can vet the path before [`font_data`] reads it.
pub fn font_file_path(name: &str) -> Option<PathBuf> {
let name = name.trim();
(is_path(name) && name.len() <= MAX_FONT_NAME).then(|| expand(name))
}
fn is_path(name: &str) -> bool {
name.contains('/') || name.contains('\\') || has_font_extension(name)
}
+1 -1
View File
@@ -43,7 +43,7 @@ pub use curves::{
};
pub use decompose::Budget;
pub use dims::{DimFrame, DimLayout, chain_centres, default_text, dim_frame, dim_layout, encode_text};
pub use fonts::{DEFAULT_FONT_FAMILY, FontData, families_in, find_style_in, font_data, font_data_styled, font_file_path, installed_families};
pub use fonts::{DEFAULT_FONT_FAMILY, FontData, families_in, find_style_in, font_data, font_data_styled, installed_families, set_font_file_check};
pub use freeform_offset::{FreeformOffset, OFFSET_TOLERANCE};
pub use link::{Link, LinkGeom, LinkKind, LinkSource, MAX_LINK_ENTITIES, SKETCH_AXIS_HALF, sketch_axis_geom};
pub use model::{