Skip to content

MCP: SVG export by a confined caller refuses script links outside its read roots - #7264

Merged
bflatastic merged 2 commits into
storytold:mainfrom
dotCooCoo:fix/confined-svg-export-script-paths
Nov 14, 2026
Merged

bflatastic merged 2 commits into
storytold:mainfrom
dotCooCoo:fix/confined-svg-export-script-paths

Conversation

@dotCooCoo

@dotCooCoo dotCooCoo commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

What and why

SVG Interactivity links JavaScript files to a document: its JavaScript Files dialog adds a file picked from the computer or a URL that is typed in (Work with SVG interactivity). SVG export writes each linked file into a <script xlink:href> element as stored (crates/svg/src/export.rs). An automation caller confined with --automation-read-root could therefore read the path of a script file outside its read roots from the SVG or SVGZ it exported, although document.json and svgInteractivity.info already name such a file by its file name alone.

With read roots in force, every caller-driven SVG and SVGZ output of a document that links a JavaScript file outside them now fails with "a linked JavaScript file is outside the read roots". The error names no file or folder. The href is not shortened and the element is not dropped, and the document keeps the link. This is VectorCraft's own rule for confined callers; no public source describes it. Callers without read roots, and the person at the keyboard, export every link as stored, as before.

This covers SVG and SVGZ only. The native format, PDF with Preserve Editing, .ai, EPS and file.package still carry the stored script paths, and an SVG export still writes the path of a linked image outside the read roots; this pull request does not change those.

  • fileio::check_linked_scripts uses links::script_hidden (crates/engine/src/cmd/links.rs), the test document.json and svgInteractivity.info use (SVG Interactivity: replies name JavaScript files outside the read roots by file name #7205): a local path, a file: URL, or a relative path taken from the document's folder is checked against the read roots; another URL is not checked.
  • fileio::check_linked_scripts (crates/engine/src/cmd/fileio/access.rs) applies it for the SVG and SVGZ formats. It runs before any encoding or writing in document.serialize, document.export (MCP's export), document.exportSelection (fileio/export.rs), the SVG and SVGZ saves of document.save, file.save, file.saveAs and file.saveCopy, including a save without a path of a document whose own format is SVG (fileio/save.rs), and document.exportForScreens and assets.export, for every format row before the folder is made or any row is written (fileio/screens.rs).
  • In the app, file.exportAs and app.export with a path run it (crates/ui-egui/src/io/export.rs); app.export without a path runs document.export. The SVG Options dialog's Show Code shows the error instead of the SVG text (crates/ui-egui/src/dialogs/svg_options.rs). The code view's cache now also depends on the read roots in force and the document's path.
  • The params text of document.serialize, document.export, document.exportSelection, document.exportForScreens, document.save and file.exportAs, docs/mcp.md (the read roots list and SVG Interactivity) and one line in docs/target-app-parity.md describe the refusal.

How it was tested

New tests, each failing without the change (the check disabled):

  • confined_svg_output_refuses_a_linked_javascript_file_outside_the_read_roots (crates/engine/src/tests/interactivity.rs): for SVG and SVGZ, document.serialize, document.export with and without a path and with selectedOnly, document.exportSelection, document.save, file.saveAs, file.saveCopy, document.exportForScreens (a folder with a PNG and an SVG row, and a ZIP) and assets.export are refused with an error that names neither the script's folder, the read root nor the file name. A pathless save of a document saved as SVG leaves its file unchanged, no other file is written, and the document keeps the link.
  • control_svg_export_refuses_a_linked_javascript_file_outside_the_read_root (crates/ui-egui/src/tests/automation.rs): app.export to SVG and SVGZ with a path and to SVG without a path, and file.exportAs through engine.execute are refused and write nothing; the person's own file.exportAs writes the link as stored.
  • show_code_leaves_out_a_linked_javascript_file_outside_the_read_roots (crates/ui-egui/src/dialogs/svg_options.rs): Show Code under read roots shows the error without the file name or folder, and without roots shows the <script> element.

svg_output_keeps_linked_javascript_files_a_caller_may_read (crates/engine/src/tests/interactivity.rs) checks that a confined caller's SVG and SVGZ keep a file inside the read roots, a relative path that leads inside them and a URL as stored, and that a caller without roots writes a file outside them.

Focused checks on the build host, on Linux. The branch is one commit on main at 08b6cfd3e8 and merges cleanly with it.

  • cargo fmt --all -- --check
  • cargo clippy -p vectorcraft-engine --all-targets -- -D warnings and cargo clippy -p vectorcraft-ui-egui --all-targets -- -D warnings
  • cargo check --workspace --tests
  • cargo test -p vectorcraft-engine --lib tests::interactivity: 12 passed
  • cargo test -p vectorcraft-engine --lib tests::placed_document::document_ (the document.json rule): 2 passed
  • cargo test -p vectorcraft-ui-egui --lib linked_javascript_file_outside_the_read_root: 2 passed
  • cargo test -p vectorcraft-ui-egui --lib export_notes_an_option_the_format_does_not_read (beside the new automation test): 2 passed
  • cargo test -p vectorcraft-ui-egui --lib i18n: 106 passed
  • cargo xtask brands, layers (30 crates) and assets (337 files)
  • cargo xtask ci runs in this pull request's CI.

cargo xtask ci passed in this pull request's CI run on 38127af144: https://github.com/storytold/vectorcraft/actions/runs/43525492884/job/132230087367

Checklist

  • cargo xtask ci passes (fmt, clippy, tests, assets, brands, layers, wasm).
  • No panics in shipped code: no unwrap(), expect(), panic!, unreachable!, todo! or
    unimplemented! outside tests, and no unchecked indexing on data from files, parameters or
    messages. Errors are returned as Result and shown to the user
    (why and how).
  • Clean-room: nothing copied from Adobe products or GPL/AGPL code (AGENTS.md).
  • Every new asset has a row in ASSETS.md.

… read roots

An SVG or SVGZ writes each linked JavaScript file of SVG Interactivity
into a <script xlink:href> element as stored, so a caller with
automation read roots could read a path outside them from the exported
text. With read roots in force, an SVG or SVGZ of a document that links
such a file is now refused with "a linked JavaScript file is outside the
read roots", an error that names no file or folder. No file or folder is
written, and the document keeps the link.

- The check uses the rule document.json already applies to
  svg_scripts: a relative path is taken from the document's folder, a
  file: URL is a file, and another URL is not checked.
- It covers document.serialize, document.export, document.exportSelection,
  document.exportForScreens and assets.export (before the folder is made
  or any row is written), the SVG and SVGZ saves (also a save without a
  path of a document saved as SVG), file.exportAs and the SVG Options
  dialog's Show Code.
- Callers without roots, and the person at the keyboard, export the
  links as stored.

Docs: docs/mcp.md, docs/target-app-parity.md.
@coderabbitai

coderabbitai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 3 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 08b6cfd and 38127af.

Files selected for processing: 14

  • crates/engine/src/cmd/fileio.rs
  • crates/engine/src/cmd/fileio/access.rs
  • crates/engine/src/cmd/fileio/commands.rs
  • crates/engine/src/cmd/fileio/export.rs
  • crates/engine/src/cmd/fileio/save.rs
  • crates/engine/src/cmd/fileio/screens.rs
  • crates/engine/src/tests/interactivity.rs
  • crates/mcp/src/ui_catalog.rs
  • crates/ui-egui/src/dialogs/svg_options.rs
  • crates/ui-egui/src/io/export.rs
  • crates/ui-egui/src/menus/ui_commands.rs
  • crates/ui-egui/src/tests/automation.rs
  • docs/mcp.md
  • docs/target-app-parity.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

crates/mcp/src/ui_catalog.rs now carries the file.exportAs params text
from crates/ui-egui/src/menus/ui_commands.rs, so `cargo xtask
ui-commands --check` passes.
@bflatastic
bflatastic merged commit 92d9604 into storytold:main Nov 14, 2026
7 checks passed
@bflatastic

Copy link
Copy Markdown
Member

Merged, thanks @dotCooCoo! It passed its CI checks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants