Skip to content

Prevent layout sidecar paths from escaping workspace mutations #9

Description

@dremnik

Summary

Layout paths are loaded from the user-editable .canvas/layout.json sidecar and used by Move to Trash without validating the resulting file target. A malformed path such as ../../outside.txt can cause Terrazzo to trash an entry outside the active workspace.

Evidence

  • src-tauri/src/commands.rs:100-107 validates only the canvas directory passed to move_to_trash.
  • src-tauri/src/workspace/layout.rs:276-296 joins LayoutItem.path onto that directory and passes the result to trash::delete.
  • LayoutItem.path is deserialized from a file that the user or another process can edit.
  • The v0 exit criteria require that no known path permanently delete a file outside an explicit, correctly scoped filesystem command.

Proposed change

  1. Treat every sidecar path as untrusted input.
  2. Require an item path to be one normal child component with no prefix, root, current-directory, or parent-directory components.
  3. Pass the canonical workspace root into mutation helpers and validate the target boundary immediately before mutation.
  4. Preserve safe deletion of a symlink entry itself without following its destination.
  5. Apply the same validation rule to every sidecar-driven filesystem operation.
  6. Return a structured malformed-sidecar error without mutating any file.

Acceptance criteria

  • A layout item containing ../../outside.txt cannot read, open, move, or trash the outside target.
  • Absolute paths and multi-component paths in a layout item are rejected safely.
  • Trashing an ordinary in-directory file still succeeds.
  • Trashing an in-directory symlink removes only the symlink, even when its destination is outside the workspace.
  • Tests cover parent traversal, absolute paths, nested paths, ordinary files, broken symlinks, and escaping symlinks.
  • No failed validation modifies the sidecar or filesystem.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions