Optional ReadonlycwdOptional ReadonlyegressAllowlisted egress: let the hook actually reach the network, but ONLY the
listed hosts, with the boundary at the packet layer (an nft allowlist
inside the sandbox netns, fed by slirp4netns) — so a raw socket to an
off-list host is dropped too, which a recordEgress/HTTP_PROXY allowlist
cannot guarantee. Use it to test a hook/skill whose setup needs a real
npm install from a registry you expect, and nothing else. Implies
confinement (it needs the netns), and refuses if bubblewrap + slirp4netns
allowed: true) and HookRunResult.egressDropped (how much off-list
traffic was blocked). See docs/sandboxing.md.Optional ReadonlyenvExtra env vars (merged over process.env). {cwd} in values is left as-is.
Optional ReadonlyrecordRecord the hook's network egress. Implies confinement (the recorder lives in
the sandbox netns, so this forces a sandboxed run and refuses if no sandbox is
available). A recording proxy on loopback captures every host:port a
proxy-honoring tool (npm/pip/curl/fetch) tries to reach — surfaced as
HookRunResult.egress — while the netns still blocks it (nothing
actually leaves). Use it to test what a hook/skill phones home to, or which
registry an install would hit. Raw-socket egress is blocked but not recorded
(it never reaches the proxy) — the block is the boundary, the record is
best-effort observability over it.
Optional ReadonlysandboxConfine the hook under bubblewrap (Linux). When unset, the mode follows
RunHookOptions.trusted: a trusted hook runs directly (false), an
untrusted one is confined-or-refused ("auto"). Set it explicitly to
override: "auto"/"strict" force confinement (a no-egress namespace with a
cleared environment — your opts.env is added back — or a refusal if no
bwrap is available), and false is the opt-out that runs even untrusted code
unconfined. macOS/Windows have no bwrap, so "auto"/"strict" throw there —
see src/sandbox.ts.
Optional ReadonlytimeoutPer-run timeout ms. Default 10000.
Optional ReadonlytrustedProvenance of the hook command. true (default) means YOU authored it — the
usual case at this tier, a command written inline in the test — so it runs
directly. false marks it foreign (a vendored third-party hook script),
which makes confinement the DEFAULT: with no explicit sandbox, an untrusted
hook behaves as sandbox: "auto" — confined under bubblewrap, or refused if
none is available — so foreign code is never run unconfined by accident. This
mirrors the harness tier, where trust follows plugin/pluginDir
provenance (specTrusted in src/sandbox.ts); the unit tier takes a raw
command string with no provenance signal, so you declare it here.
Why this is opt-in (untrusted) and not always-on: the sandbox isn't always
available (Linux + working userns only — forcing it would refuse a hook you
wrote on macOS/hardened CI), it's deliberately hostile (no egress,
--clearenv, empty HOME, read-only fs — a false failure for trusted code that
needs the network or an env var), trust follows provenance (you already
vouched for inline code), and direct exec is ms vs. the confined path's
setup+spawn. See docs/sandboxing.md ("Why confinement is opt-in"). Use
sandbox: "strict" to force confinement even on trusted code.
Working directory for the hook process. Default: a value won't be set.