Skip to content

Adopt ShellKit's PathMapping: authorizePath must return the translated host path (SwiftBash#83 follow-up) #6

Description

@odrobnik

Context

Cocoanetics/SwiftBash#83 moved the sandbox's virtual↔host path mapping into ShellKit (PathMapping, carried as Sandbox.pathMapping, landed in Cocoanetics/ShellKit#17). Shell.resolve(_:) now translates script-visible virtual paths (e.g. /tmp/x, /batch/x under swift-bash exec --sandbox) to the host directories that back them, and Sandbox.confined(to:) authorizes exactly that host space. SwiftPorts and SwiftBash's JS runtime are adapted (Cocoanetics/SwiftBash#88, Cocoanetics/SwiftPorts#64).

SwiftScript is not yet adapted: authorizePath (Sources/SwiftScriptInterpreter/API/HostHooks.swift) gates the literal path and the bridges then do Foundation I/O on that same literal. Under a path-mapped sandbox every virtual absolute path is therefore denied — fail-closed, safe, but FS access for SwiftScript scripts inside the SwiftBash sandbox stays limited.

The invariant that shapes the fix

Translating for the check but not the I/O (or vice versa) is an escape: authorize(translate("/tmp/x")) passes against the per-instance temp dir while FileManager would then touch the host's shared /tmp/x. The translated path must be the one the Foundation call consumes. Half-adopting is worse than not adopting — this ships whole or not at all.

Proposed change

  1. authorizePath(_:for:) (String and URL overloads) resolves first — ShellKit.Shell.current.resolve(path) (which also anchors relative paths to the shell's virtual CWD instead of the host process CWD) — authorizes the host form, and returns it.
  2. BridgeGeneratorTool (Sources/BridgeGeneratorTool/main.swift, gate emission around line 1117): fs-gated args bind as var, and the emission becomes arg0 = try await authorizePath(arg0, for: .read) so the Foundation call consumes the translated path. Network gates stay Void.
  3. Regenerate via Tools/regen-foundation-bridge.sh — NB: regeneration on a current machine produces ~2k lines of unrelated symbol-graph drift (newer SDK than the committed extract; verified 2026-06-11). Refresh the extract in a separate commit first, or pin the regen environment, so the semantic diff stays reviewable.
  4. Hand-written sites consume the return too: Interpreter+FileIO.swift, the hand-rolled String/Data bridges.
  5. Display stays virtual: FileManager.currentDirectoryPath already reports the virtual CWD; FileManager.temporaryDirectory should report ShellKit.Shell.temporaryDirectory (sandbox-aware) rather than the host's shared temp root; anything echoing resolved paths folds through Shell.displayPath(for:).
  6. Tests: mirror SwiftBash's ConfinedSandboxTests — a .confined sandbox over a PathMapping, script writes /tmp/x + relative paths, bytes land in the mapped host dirs, denials outside, no host path in script-visible output.

Until this lands, SwiftScript-in-sandbox keeps the post-SwiftBash#82 interim contract (virtual spellings denied, fail-closed).

🤖 Generated with Claude Code

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