Skip to content

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

@odrobnik

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions