Skip to content

plan: sandbox_info tool — structured active-bind facts + safe env #697

Description

@btipling

Review notes (2026-08-19)

Reviewed issue #697 for correctness / performance / architecture / testing / cloud ops N/A / living docs / layers / cap governance.

Second pass (same day) re-grounded against main @ 4bc831b. Prior majors (optional bind, non-throwing daemonInfo, host-abs HOME/PWD omit, no stat in path_tools) stay locked.

Issue Severity Resolution
CreateAgentToolsOptions.bind required would break every existing createAgentTools test; only one of two runAgent/runAgentStream call sites was implied Major bind is optional. Missing bind: still emit cwd, permissions, capabilities, env. Omit id/name/slug/status/image lines; backend omitted. Thread bind at both createAgentTools sites (runAgent ~286 and runAgentStream ~392).
daemonInfo / env exec throw (426, timeout) could fail the whole tool Major daemonInfo is non-throwing (like workspaceRoot) and never returns workspaceRoot. Env spawn errors → env: unavailable with a generic reason (timeout / exit=N / error) — no stdout/stderr echo (may contain R). Bind/cwd/caps still print.
Omit-name regex /^(DEK|AMK|KEK)\b/i misses DEK_WRAP (_ is a word char so \b does not fire); suffix $ misses AWS_ACCESS_KEY_ID Major Lock /^(DEK|AMK|KEK)(_|$)/i and /(_TOKEN|_SECRET|_KEY|_PASSWORD|_CIPHERTEXT)(_|$)/i. Explicit denylist unchanged. Test both names.
Env algorithm listed cap-then-omit Minor Parse → omit (name + value-contains-secret) → sort → cap. Secret keys must not occupy a 512 slot. env.omitted counts only non-omitted extras past the cap.
daemon.protocol vs daemon.version both 2 today — a swap would pass Minor daemon.protocolHealthInfo.version (protocol); daemon.versionHealthInfo.daemonVersion (revision). Test 8 uses unequal values (e.g. protocol=2, daemonVersion=1).
Thrown exec 504/Abort classified as (error) if threw is checked first Minor result.timedOut or thrown abort/504 → (timeout); exitCode !== 0(exit=N); else (error). Never echo stdout/stderr/exception text.
HOME/PWD remaining host-absolute leak home-layout paths (#469 non-goal) Minor After canonicalize, omit PWD/HOME/OLDPWD/TMPDIR if the value still starts with /. PATH-like arrays keep out-of-jail OS entries (/usr/bin).
path_tools must not list stat (not a model-facing tool) Minor capabilities.path_tools = tools the model actually has this turn. describeSandboxTools still lists stat (pre-existing display contract) plus sandbox_info.
Baseline SHA stale (76678f1) Nit main @ 4bc831b.
Output id= vs bind field sandboxId; image source Nit Print id=${bind.sandboxId}. bind.image = resolved.value.resolvedImage (omit line when null/empty). Never pass whole resolved.value (would carry baseUrl / client / secrets).

Status: HANDOFF-READY
Reviewed: 2026-08-19 (second pass: omit matchers + cap order + daemon field mapping)

Plan header

Field Value
Status HANDOFF-READY
Date 2026-08-19
Type single
Parent N/A
Source issue #469 — feat: sandbox_info tool — structured active-bind facts + safe env
Branch plan/sandbox-info-tool
Layers Vercel backend (agent tools + bind resolve + L1 salient). Harness paint: none (existing L2 body path).
Reusability impact none — additive read-only tool; no single-tenant bind
Production mutate? no
Cloud ops path N/A — no Production mutate
Living docs docs/sandbox.md, docs/agent-stream.md, AGENTS.md (tool list / standing order), SECURITY.md (omit R / tokens / host inventory)

Summary

Add a read-only agent tool sandbox_info on the existing FS tool surface (createAgentTools). One call returns structured facts about the active bind: backend, name/slug/status, logical cwd, grant permissions, capabilities (path tools / exec / stdin / stat), daemon protocol+version when BYO, and a redacted env map whose PATH-like values are rewritten per colon-separated entry via workspaceAbsToRel — never by running the joined PATH= line through rewriteExecRootToRel (#468). Host-absolute jail root R is not in the payload. No new daemon route. Not inventory/switch (meta_sandbox_* / #330 stay as they are).

Goals

# Goal Success signal
1 Model can call sandbox_info (no args) with read grant and get backend, id, name, slug, status, optional image, logical cwd, permissions Status line + labeled fields; pwd coordinate = cwd field
2 Capabilities are explicit so the model stops probing exec, stdin (BYO daemon protocol ≥ 2 / Vercel false), stat, path-tool list without stat as a model tool name
3 BYO daemon facts from existing GET /health cache (no new route) daemon.protocol + daemon.version + daemon.out_of_date; Vercel omits daemon (daemon=none)
4 Env dump is structured and #468-safe PATH / NODE_PATH / PYTHONPATH (and other PATH-like) are JSON string arrays of per-entry canonicalized values; /usr/bin:<R>/node_modules/.bin["/usr/bin","node_modules/.bin"]not PATH=/usr/bin:node_modules/.bin
5 Secrets and jail root stay out No SANDBOX_TOKEN / GITHUB_TOKEN / GH_TOKEN / Gateway / DEK/AMK; no host IP / droplet id / base_url; no R string; PWD/HOME under R become workspace-relative; remaining host-absolute PWD/HOME/OLDPWD/TMPDIR omitted
6 Soft-fail like other FS tools Read deny; env exec failure omits env with env: unavailable; R null skips path rewrite; missing bind still returns cwd/permissions/env
7 Docs + standing orders teach this tool instead of exec env docs/sandbox.md tool table; DEFAULT_AGENT_SYSTEM names sandbox_info; describeSandboxTools includes it

Non-goals / out of scope

Architectural decisions

Decision Options considered Choice Why
New tool vs extend meta_sandbox_active A) grow meta_sandbox_active B) new sandbox_info on FS surface B Active-bind introspection needs the live SandboxClient, cwdState, and workspaceRoot. meta_sandbox_* is grant/envelope inventory and must run when FS tools are skipped. #469 is complementary to #330, not a phase of it.
Env source A) new /v1/env B) host-only execEnv (GH_TOKEN) C) internal client.exec({ cmd: 'env' }) then parse C Issue forbids a daemon route. Host execEnv is not a dump. Argv-only env is POSIX on both backends; argv is a constant (not model-controlled) so a read grant may spawn it.
PATH canonicalize A) rewriteExecRootToRel on the joined line B) split on :, workspaceAbsToRel each entry, JSON array B #468: : is a path terminator, so PATH=/usr/bin:<R>/node_modules/.bin becomes PATH=/usr/bin:node_modules/.bin. Per-entry rewrite keeps /usr/bin and maps only entries under R.
Result shape A) JSON blob B) labeled key=value like other FS tools; PATH-like as JSON arrays B Matches pwd / read_file status lines; arrays avoid :-joined corruption.
Bind metadata A) omit name/slug B) thread from resolveAgentSandbox (select name/slug/status already on sandboxes) B Issue asks for name/slug/status. Columns exist (db/schema.ts sandboxes). Resolve today drops them. bind is optional on the tool factory so unit tests without a route resolve still run.
Daemon facts A) re-probe in the tool B) additive optional SandboxClient.daemonInfo() on the HTTP client (reuse health cache; omit workspaceRoot; non-throwing) B Host already has /health. Do not leak R. Vercel client omits the method → daemon=none. No protocol bump.
Secret env keys A) redact values in place B) omit keys whose name matches a denylist / _*TOKEN/_*SECRET/_*KEY or whose value contains a secrets[] entry B Showing GH_TOKEN=[redacted] still teaches the key exists. finalize/redactSecrets remains a belt on the whole payload.
R in payload A) optional admin field B) never B Reopens #403 / fights #467.
Remaining host-absolute HOME/PWD A) print B) omit B #469 non-goal: home-layout paths. PATH keeps /usr/bin.

Layer placement

Concern Layer Path(s) Rationale
Pure parse + per-entry canonicalize + omit secrets Vercel backend new lib/agent/sandboxInfo.ts Testable without PGlite / live sandbox (DI)
Tool schema + execute Vercel backend lib/agent/tools.ts createAgentTools Same owner as pwd
Bind name/slug/status Vercel backend lib/tenancy/resolveSandbox.ts ResolvedAgentSandbox Already querying sandboxes; today drops the columns
Thread bind + backend into tools Vercel backend lib/agent/runAgent.ts (both generate + stream sites), app/api/agent/route.ts Route already has resolved.value
Daemon snapshot without R Vercel backend lib/sandbox/client.ts optional daemonInfo Reuse fetchHealth cache; catch → null
Tool surface descriptor Vercel backend lib/tenancy/sandboxTools.ts READ_TOOLS Inventory / meta_sandbox_active stay honest
L1 salient Vercel backend lib/agent/agentStream.ts salientToolBits Must not dump env into the 320-char summary
Standing order Vercel backend lib/agent/runAgent.ts DEFAULT_AGENT_SYSTEM Stop teaching exec env
Docs docs docs/sandbox.md, docs/agent-stream.md, AGENTS.md, SECURITY.md Timeless

Current baseline (live code)

main @ 4bc831b (merge #692).

Claim Path / symbol Notes
FS tools are createAgentTools lib/agent/tools.ts ~259 Return { pwd, change_dir, list_dir, read_file, write_file, str_replace, exec } (~772). pwd is read-only, empty schema (~285–299). exec requires write (~708–710) and rewrites stdout/stderr with rewriteExecRootToRel (~754–755)
CreateAgentToolsOptions has client, freshness, secrets, permissions, cwd, workspaceRoot tools.ts ~33–65 No backend / name / slug today. Tests call it without bind (tools.test.ts mockClient already stubs exec)
Two createAgentTools call sites lib/agent/runAgent.ts ~286 and ~392 generateText path and streamText path — both must receive bind
rewriteExecRootToRel treats : as a path terminator lib/agent/workPath.ts isPathChar ~121–128, rewrite ~161–205 This is the #468 PATH bug; do not reuse it on joined env
workspaceAbsToRel maps one absolute under R → relative (. if equal) workPath.ts ~87–109 Throws out-of-jail — catch and keep /usr/bin
ResolvedAgentSandbox lib/tenancy/resolveSandbox.ts ~39–57 Has backend, sandboxId, permissions, workspaceRoot, resolvedImage. Query already selects status/image (~205–214) but not name/slug; return value drops status
sandboxes has name, slug, status, image db/schema.ts ~86–103 verified
meta_sandbox_active lib/agent/metaSandboxTools.ts Inventory descriptor only; no env/cwd/client
describeSandboxTools READ_TOOLS lib/tenancy/sandboxTools.ts ~22–26 list_dir, read_file, stat — no sandbox_info. stat is display-only (no model stat tool)
HTTP client health cache lib/sandbox/client.ts HealthInfo ~37, workspaceRoot ~380–386 Parses version, daemonVersion, workspaceRoot. No public daemon snapshot that omits R
Vercel client lib/sandbox/vercelClient.ts No checkDaemonCurrent / daemon health. workspaceRoot() returns /vercel/workspace. exec stdin 400 (~708–722)
GET /health docs/sandbox.md ~317 { ok, version, daemonVersion, workspaceRoot? } — no env route
redactSecrets / finalize lib/agent/redact.ts, tools.ts finalize 2M TOOL_RESULT_MAX_CHARS (lib/sandbox/config.ts ~10)
DEFAULT_AGENT_SYSTEM lib/agent/runAgent.ts ~158–167 Lists list_dir, read_file, write_file, str_replace, exec, change_dir, pwd — not sandbox_info
salientToolBits lib/agent/agentStream.ts ~270 Has pwd / read_file / exec shapes; unknown names fall through to a slice — would leak env without a new branch
Route threads workspaceRoot + sandboxId into runParams app/api/agent/route.ts ~520–531 Does not pass backend / name / slug
MIN_SANDBOX_PROTOCOL_STDIN lib/sandbox/config.ts ~31 2
EXPECTED_SANDBOX_DAEMON_VERSION lib/sandbox/daemonVersion.ts ~8 2
Existing GHA .github/workflows/ci.yml (vitest) No new workflow — no Production mutate

Design

Tool

sandbox_info in createAgentTools, next to pwd. Add it to the returned tool set.

  • Permission: canRead (same deny string family as pwd). Write not required.
  • Input: empty object (additionalProperties: false), no args.
  • Soft-fail: never throw; ERROR sandbox_info: … / permission denied (need read).

Bind (CreateAgentToolsOptions.bind + RunAgentParams.bind) — optional

export type SandboxInfoBind = {
  backend: 'byo' | 'vercel';
  sandboxId: string;
  name: string;
  slug: string;
  status: string;
  image?: string | null;
};

Filled by the route from a projection of resolved.value (extend ResolvedAgentSandbox with name, slug, status from the sandboxes row). Map image: resolved.value.resolvedImage. Print id=${bind.sandboxId} (not sandboxId=). Never pass the whole resolved.value into tools (it carries baseUrl, client, secrets, workspaceRoot). Never: baseUrl, token, droplet id, IP, workspaceRoot in the bind object.

When bind is omitted (unit tests, legacy inject): emit cwd, permissions, capabilities, env; skip id/name/slug/status/image; skip backend= line.

Cwd / permissions / capabilities

  • cwd = cwdState.current (same string pwd prints).
  • permissions.read / permissions.write from opts.permissions.
  • capabilities.exec = canWrite (model exec tool gate — not whether the internal env spawn ran).
  • capabilities.stdin = backend === 'byo' and daemonInfo.version >= MIN_SANDBOX_PROTOCOL_STDIN; if backend omitted and daemonInfo present, use protocol ≥ 2; else false (Vercel SDK has no stdin).
  • capabilities.stat = true (both clients implement stat; mtime may still be omitted on Vercel — do not overclaim fingerprints).
  • capabilities.path_tools = names the model actually has this turn (list_dir/read_file/sandbox_info on read; plus write_file/str_replace/exec on write; always change_dir/pwd). Do not list stat.

Daemon (BYO only)

Additive optional SandboxClient.daemonInfo?(init?) => Promise<{ version: number; daemonVersion: number } | null>:

  • HTTP client: reuse fetchHealth; return { version, daemonVersion } without workspaceRoot.
  • Non-throwing: probe fail / 426 / abort → null → tool prints daemon: unavailable and still emits bind/cwd/caps.
  • Tool lines: daemon.protocol=${info.version} (protocol / HealthInfo.version); daemon.version=${info.daemonVersion} (revision). Do not swap — tests must use unequal values.
  • daemon.out_of_date = daemonVersion < EXPECTED_SANDBOX_DAEMON_VERSION.
  • Vercel: method absent → daemon=none.
  • backend === 'vercel' short-circuits to daemon=none even if a mock implemented daemonInfo.

No /health contract change. No daemonVersion bump.

Env

  1. If canRead: client.exec({ cmd: 'env', timeoutMs: SANDBOX_INFO_ENV_EXEC_TIMEOUT_MS }, { signal }).
  2. Do not pass stdout through rewriteExecRootToRel.
  3. Parse KEY=VALUE lines (^([A-Za-z_][A-Za-z0-9_]*)=(.*)$lowercase z; a-Z is an invalid JS range). Skip malformed. First = splits. No printenv -0 in v1 (newline-in-value residual).
  4. Omit a key when:
    • name is in SANDBOX_INFO_OMIT_ENV_KEYS (SANDBOX_TOKEN, GITHUB_TOKEN, GH_TOKEN, AI_GATEWAY_API_KEY, …) or matches /(_TOKEN|_SECRET|_KEY|_PASSWORD|_CIPHERTEXT)(_|$)/i or /^(DEK|AMK|KEK)(_|$)/i (DEK_WRAP and AWS_ACCESS_KEY_ID omitted; \b is wrong because _ is a word char)
    • or value contains a non-empty member of opts.secrets (length ≥ 4, same floor as redactSecrets)
  5. Sort remaining keys. Cap at SANDBOX_INFO_ENV_MAX_KEYS; if truncated, env.omitted=<n> (count of kept-eligible extras past the cap — secret-omitted keys do not occupy a slot and do not increment env.omitted).
  6. Canonicalize remaining values (canonicalizeEnvValue in sandboxInfo.ts):
    • PATH-like keys (PATH, NODE_PATH, PYTHONPATH, LD_LIBRARY_PATH, MANPATH, GOPATH, CDPATH): split on :, map each entry: if it starts with / and R is set, workspaceAbsToRel or keep on throw (out-of-jail /usr/bin); relative entries unchanged. Emit JSON array (JSON.stringify).
    • Single-path keys (PWD, HOME, OLDPWD, TMPDIR): one-shot workspaceAbsToRel. If the result still starts with /, omit the key.
    • Other values that are a single absolute path: same one-shot workspaceAbsToRel when under R; if still absolute, leave as-is (then redactSecrets on the formatted blob).
  7. If R is null/empty: skip abs-to-rel; still omit secrets; PATH stays a JSON array of raw entries; single-path keys that start with / are still omitted (home-layout).
  8. Exec fail / timeout / exitCode !== 0: env: unavailable (timeout|exit=N|error)do not echo stdout/stderr. Do not fail the whole tool. Classify: result.timedOut or thrown abort/504 → (timeout); exitCode !== 0(exit=N); else (error). Exit 0 with empty stdout → no env.* lines (empty map), not unavailable.

Internal env spawn is not the model exec tool: read-only grants may call sandbox_info. Argv is fixed (cmd: 'env', no args). Do not forward model input into that spawn.

Output shape (locked)

sandbox_info:
backend=byo
id=<uuid>
name=prod
slug=prod
status=active
cwd=invincible
permissions.read=true
permissions.write=true
daemon.protocol=2
daemon.version=2
daemon.out_of_date=false
capabilities.exec=true
capabilities.stdin=true
capabilities.stat=true
capabilities.path_tools=list_dir,read_file,write_file,str_replace,exec,change_dir,pwd,sandbox_info
env.PATH=["/usr/bin","/bin","node_modules/.bin"]
env.LANG=C.UTF-8

Vercel: daemon=none, capabilities.stdin=false. Empty/null image line omitted. Missing bind: no backend=/id=/name=/slug=/status= lines.

Then finalize(..., secrets) (redact + 2M cap).

L1 salient

New salientToolBits branch before the generic fallback:

sandbox_info · backend=byo · cwd=invincible · 24 env

When backend/cwd missing, skip that bit. Count = number of env.KEY= lines (0 if unavailable). No env values. unavailable bit allowed. Errors: first line only (existing ERROR path).

Standing order

DEFAULT_AGENT_SYSTEM: add sandbox_info to the prefer-tools list. One clause: use sandbox_info for bind/cwd/caps/env — do not exec env / printenv / uname to learn the sandbox.

describeSandboxTools READ_TOOLS: append { name: 'sandbox_info', requiresPermission: 'read' } (backend note as the others). Tests in sandboxTools.test.ts update.

Edge cases

Case Behavior
No read grant ERROR sandbox_info: permission denied (need read)
No FS tools (soft path) Tool absent — meta_sandbox_* still works
bind omitted cwd + permissions + caps + env; no id/name/slug
R null Bind + cwd + caps; PATH entries not rewritten; host-absolute HOME/PWD omitted; no R field
env binary missing / throw env: unavailable (error)
env timeout env: unavailable (timeout)
env exit ≠ 0 env: unavailable (exit=N)
Value contains a secret Key omitted
PATH entry /usr/bin Unchanged (out of jail)
PATH entry <R>/node_modules/.bin node_modules/.bin
PWD=<R> .
PWD=<R>/src src
HOME=/home/foo (outside R) Key omitted
Refresh / restore Stateless per call; no SessionStore
Mobile ~390 px L1 salient only in the harness header; L2 is existing body wrap — no new scroller

Caps table

Cap / ceiling Value Rationale Code location
SANDBOX_INFO_ENV_MAX_KEYS 512 (NEW) Generous vs a typical Unix env (~50–150). Whole payload still finalizes at 2M chars. lib/agent/sandboxInfo.ts
SANDBOX_INFO_ENV_EXEC_TIMEOUT_MS 10_000 (NEW) Same order as BYO health probe (HEALTH_PROBE_TIMEOUT_MS); must not use the 5 min exec default lib/agent/sandboxInfo.ts / tool execute
TOOL_RESULT_MAX_CHARS 2_000_000 Unchanged lib/sandbox/config.ts
MIN_SANDBOX_PROTOCOL_STDIN / EXPECTED_SANDBOX_DAEMON_VERSION 2 Unchanged — read, not modified lib/sandbox/config.ts, daemonVersion.ts

No existing cap is raised or lowered.

Cloud ops path

N/A — no Production mutate.

Living docs plan

Surface Change Notes
docs/sandbox.md Tool table: add sandbox_info. State: structured active-bind facts + env; PATH rewritten per entry; not exec env; R / tokens omitted. describeSandboxTools bullet includes it. No /v1/env protocol row Timeless; no issue numbers as the explanation
docs/agent-stream.md Salient: sandbox_info is backend · cwd · env-count, not the dump One sentence
AGENTS.md Prefer-tools / cwd ownership row mentions sandbox_info as the bind/env introspector Not a phase narrative
README.md N/A — not visitor-facing
SECURITY.md One sentence on the sandbox inventory / tool-surface paragraph: sandbox_info omits tokens, R, host IPs, droplet ids, base_url Public inventory
.env.example N/A — no new env

Implementation order

  1. lib/agent/sandboxInfo.ts — parse env, PATH-like split, omit keys, canonicalize; unit tests (no PGlite).
  2. lib/sandbox/client.ts — optional daemonInfo() on HTTP client (health cache, no R, non-throwing).
  3. lib/tenancy/resolveSandbox.ts — select + return name, slug, status.
  4. runAgent (both sites) / route / CreateAgentToolsOptions — thread optional bind.
  5. tools.tssandbox_info execute; sandboxTools.ts READ_TOOLS; DEFAULT_AGENT_SYSTEM.
  6. salientToolBits + tests.
  7. Docs (sandbox.md, agent-stream.md, AGENTS.md, SECURITY.md).
  8. Gates in agent/CI: npm run typecheck + vitest run --changed / full vitest run. No Zig.

Testing

# Case Layer Type Command / method
1 PATH /usr/bin:<R>/node_modules/.bin["/usr/bin","node_modules/.bin"] (not the #468 joined rewrite) backend unit lib/agent/sandboxInfo.test.ts
2 PWD=<R>.; HOME=<R>/xx; HOME=/home/foo omitted backend unit same
3 R null → PATH entries unchanged; no R in output; host-abs PWD omitted backend unit same
4 Secret key names + values containing secrets[] omitted — including DEK_WRAP and AWS_ACCESS_KEY_ID backend unit same
5 Env exec fail → bind/cwd still present, env: unavailable backend unit tools.test.ts mock client.exec reject
6 Read deny backend unit tools.test.ts
7 Write-false + read-true still dumps env (internal exec) backend unit tools.test.ts
8 BYO daemonInfo → protocol/version/out_of_date; Vercel omit. Use unequal protocol vs daemonVersion so a field swap fails backend unit mock daemonInfo present vs absent / bind.backend=vercel
9 salientToolBits has backend/cwd/count, not PATH= backend unit agentStream.test.ts
10 describeSandboxTools includes sandbox_info on read backend unit sandboxTools.test.ts
11 Resolve projection includes name/slug/status backend unit existing resolveSandbox.test.ts shape (inject connect — no new PGlite)
12 Standing order names sandbox_info and forbids learning env via exec env backend unit runAgent.test.ts
13 Missing bind still returns cwd + env backend unit tools.test.ts
14 typecheck + vitest CI gate npm run typecheck + vitest run (CI) / --changed (iteration)
15 Cap: 513 eligible keys → 512 lines + env.omitted=1; a secret key in the set does not consume a slot backend unit lib/agent/sandboxInfo.test.ts
16 Thrown exec 504/Abort → env: unavailable (timeout) not (error) backend unit tools.test.ts

No new PGlite() in new tests. resolveSandbox tests already use the shared DI connect seam — extend assertions only.

Operator checklist (Preview; no Wasm rebuild required)

Definition of done

  • sandbox_info tool on the FS surface, read-only, empty args
  • Payload: backend (when bind present), id/name/slug/status, cwd, permissions, capabilities, daemon-or-none, env map
  • PATH-like values are per-entry arrays; feat(agent): canonicalize exec absolute output to workspace-relative (exec pwd ≡ pwd) — #467 phase 1 #468 joined-rewrite is not used
  • No R, tokens, host IPs, droplet ids, base_url in the result
  • Env spawn failure is soft (env: unavailable); tool still returns bind facts
  • R null skips rewrite; missing bind does not throw
  • describeSandboxTools + DEFAULT_AGENT_SYSTEM + L1 salient updated
  • Named living docs updated (timeless)
  • Tests green in agent/CI (commands above)
  • Cloud ops: N/A
  • No dual-chat regression (no UI)
  • No Zig / harness rebuild required

Risks & mitigations

Risk Mitigation
Internal env is still a process spawn on a read grant Fixed argv env only; 10s timeout; not model-controlled; documented
env missing on a minimal image env: unavailable; bind facts still ship
Newline inside an env value splits lines Residual v1; no printenv -0 (Vercel/BYO variance). Malformed lines skipped
R null leaves host-absolute PATH entries Honest: we cannot rewrite without R. Still omit secrets and host-abs HOME/PWD. Do not print R
L1 leaks env if salient regex is skipped Dedicated salientToolBits branch + test; name === sandbox_info fallback without body
Confusion with meta_sandbox_active Standing order: inventory/switch = meta_*; bind/env/cwd/caps = sandbox_info
Laptop-only cutover / missing GHA N/A — no Production mutate

Open questions

None — in-scope choices are locked.

References

  • Source: #469
  • Trigger: #468 / #467 exec canonicalize (: / PATH)
  • Coordinate system: #403, #464
  • Inventory + switch (do not implement here): #330
  • docs/sandbox.md protocol + cwd tools

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsandboxAgent sandbox / workspace (BYO + Vercel Sandbox backends)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions