Where: packages/safe-bash/src/shell/runtime.ts — the command-dispatch diagnostic path (shell: line N: <message> / <cmd>: <message>). Any Error.message (and any string throw) from a command implementation or a filesystem adapter is written to the tenant's stderr unchanged; there is no allowlist of error classes.
PoC (register a command that throws a non-Shell error, run through the Shell):
commands.register({ name: "boom", async run() { throw new TypeError("host internal detail /Users/kjopek/secret.ts"); } });
await shell.exec("boom; echo after");
await shell.exec("echo hi | boom | cat");
Measured: exit 0, stdout after, stderr shell: line 1: host internal detail /Users/kjopek/secret.ts; same text in the pipeline form; a string throw → shell: line 1: string-throw. Controls that are sound: RealFileSystem strips native causes per its docs; curl maps everything through networkError() to generic text.
Impact: (a)/(e) — any third-party or future fs adapter/command whose errors embed endpoint URLs, bucket names, host paths or presigned URLs (S3/WebDAV SDK errors, wrapper bugs) hands them to the tenant. The Shell.exec contract also silently converts host bugs into exit-1 diagnostics instead of surfacing them to the host.
Fix: relay message only for known-safe classes (FsError, UsageError, CommandFailure, CurlError, …); for anything else print a generic internal error to the tenant and expose the original via a host-side hook or ShellResult.internalErrors.
Found in security audit v3 (2026-09-07).
Where:
packages/safe-bash/src/shell/runtime.ts— the command-dispatch diagnostic path (shell: line N: <message>/<cmd>: <message>). AnyError.message(and any string throw) from a command implementation or a filesystem adapter is written to the tenant's stderr unchanged; there is no allowlist of error classes.PoC (register a command that throws a non-Shell error, run through the Shell):
Measured: exit 0, stdout
after, stderrshell: line 1: host internal detail /Users/kjopek/secret.ts; same text in the pipeline form; a string throw →shell: line 1: string-throw. Controls that are sound:RealFileSystemstrips native causes per its docs; curl maps everything throughnetworkError()to generic text.Impact: (a)/(e) — any third-party or future fs adapter/command whose errors embed endpoint URLs, bucket names, host paths or presigned URLs (S3/WebDAV SDK errors, wrapper bugs) hands them to the tenant. The
Shell.execcontract also silently converts host bugs into exit-1 diagnostics instead of surfacing them to the host.Fix: relay
messageonly for known-safe classes (FsError,UsageError,CommandFailure,CurlError, …); for anything else print a genericinternal errorto the tenant and expose the original via a host-side hook orShellResult.internalErrors.Found in security audit v3 (2026-09-07).