Skip to content

feat(agentcore): add opt-in SRT sandbox and harness tool logs - #651

Open
jeromevdl wants to merge 5 commits into
mainfrom
feat/srt-runtime
Open

jeromevdl wants to merge 5 commits into
mainfrom
feat/srt-runtime

Conversation

@jeromevdl

@jeromevdl jeromevdl commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Collaborative AIDLC agents need to run commands, read source files, install dependencies and execute repository code without asking for approval at every step. Those capabilities also let an agent misled by prompt injection, or a malicious dependency it executes, read accessible secrets, modify unrelated files or send data to an unintended destination.

We want platform admins to control which files and network destinations agents can access through one platform-wide configuration, while preserving their ability to work autonomously. Disabling individual tools is insufficient: blocking the AWS CLI, for example, still leaves a Python script using the AWS SDK. The restriction needs to apply to the underlying resource access, including child processes.

Why SRT

Anthropic Sandbox Runtime (SRT) fits this requirement because:

  • The same boundary works across harnesses. It wraps Claude Code, Kiro, Codex or OpenCode and their local child processes, independently of each harness's tool approval settings.
  • It combines filesystem restrictions with network egress control. On Linux, bubblewrap provides isolation and SRT's HTTP/SOCKS proxies filter destinations. The network namespace prevents commands from obtaining direct egress simply by ignoring proxy variables.
  • It fits the existing AgentCore runtime. We can add it around the current process launchers without introducing another execution service or a separately deployed proxy.
  • We have tested the approach. The standalone AgentCore experiment completed real model-backed command and file tasks with all four harnesses, both with and without SRT. Separate paired tests verified filesystem and network blocking. Full application integration remains to be validated.

SRT's JSON settings also give us one configuration format for the initial admin editor and the later visual UI. It complements IAM and MCP/Gateway authorization; it does not prevent misuse of files or destinations explicitly allowed by its settings.

This is the first of three planned PRs, with an optional fourth. It adds the runtime foundation and troubleshooting logs. Sandboxing remains disabled by default, so this PR alone does not mitigate the risk for normal intents; the next PR connects admin configuration and enables enforcement.

What this PR adds

  • SRT pinned to 0.0.81, including the Linux fixes from 0.0.80. Nested user/mount namespaces remain blocked; the runtime rejects allowNestedUserNamespaces: true. Tools requiring nested namespaces need a separate execution environment.
  • A per-invocation SRT worker, filtered environment handling, the numeric-loopback proxy fix from the AgentCore experiment, and process cleanup. An enabled sandbox that fails to start never falls back to running unconfined.
  • Two image integration scenarios in the existing E2E runner: unrestricted execution with SRT disabled, and restricted execution with SRT enabled. The test fixture stays outside the production image; there is no application verification probe.
  • Structured tool-result logs for Claude Code, Kiro, Codex and OpenCode, reusing the existing output parsers and credential redaction. These work with or without SRT and omit conversation events, file-write contents and successful tool output.
  • Correlated sandbox events and useful log levels: successful tools and zero exits at INFO; tool failures and SRT warnings at WARN; sandbox setup failures and nonzero exits at ERROR. The existing powertools_log_level controls filtering.

Planned stack

PR Scope
1 — This PR: runtime foundation and logs Install SRT, add the opt-in launch path and integration tests, and make tool failures visible. Normal jobs remain unsandboxed.
2 — Configure and enable through JSON Add a platform-admin JSON editor and backend validation/storage for one platform-wide configuration, with no space-level overrides. Autosave drafts, apply validated configuration, and enable/disable SRT independently using the applied configuration. Capture settings with each run and apply supported network, filesystem and proxy settings across agent/script launch paths, preserving retry/resume behavior.
3 — Visual configuration Add a Visual tab alongside JSON for supported network, proxy, filesystem and process/socket options. Both editors use the same settings and API, without dropping JSON fields.
4 — Other runtime types/images, if needed Run the same tests on additional supported hosts/images and fix concrete packaging or compatibility gaps. Omit this PR if those runtimes are already covered.

Each step should be independently mergeable after its predecessors. JSON configuration provides a complete usable feature before the visual editor arrives. The separate MCP/Gateway migration remains outside this PR; compatibility with its final integration must be validated before enabling sandboxing for normal jobs.

Validation and limits

  • ./scripts/agent-e2e-testing.sh --sandbox passed: protected reads/writes, direct TCP and blocked HTTP succeed without SRT and are blocked with restrictive SRT settings; allowed workspace writes still succeed. Both launch helpers are exercised. The 0.0.81 image tests also verify that a parent write deny defeats a nested write allow and that nested user/mount namespaces are blocked. These are deterministic image tests, not live model calls.
  • Commit hooks passed 6,163 backend tests across 276 files with two workers. Earlier full-concurrency runs hit unrelated Yjs/orchestrator timing failures; the isolated Yjs suite also passed.
  • Upgrade checks: 38 focused launcher/worker tests and all four image cases passed. Formatting, secret checks and dependency-boundary checks passed (zero errors, 11 existing warnings).
  • Live standalone AgentCore checks on 0.0.81 passed for Claude Code and OpenCode with Sonnet 5.5, and Codex with GPT-6 Sol via Mantle, each with and without SRT. All six runs called a model, executed a Python write, and passed independent file verification. The paired confinement probe confirmed five blocked accesses with SRT and the same accesses available without it.
  • Kiro starts inside 0.0.81 (--version passed), but its model/tool test is pending a test credential. The earlier successful four-harness qualification used 0.0.79; it is not evidence of a Kiro model/tool pass on 0.0.81. The standalone lab uses its documented CLI versions; these runs do not qualify full application flows.
  • A deployed Kiro reverse-engineering run with SRT disabled succeeded and emitted 25 successful tool-result logs. It did not exercise a live tool failure or prove sandbox enforcement in the application.
  • SRT does not provide a complete Linux denial audit. Confirmed proxy denials, optional file-write observations and ordinary harness tool errors remain distinct. Raw stderr/plain console output bypasses the structured log-level filter.
  • Admin configuration is not wired yet. The local Docker integration test uses host options to permit namespaces; those are not AgentCore deployment settings.
  • Dependency auditing remains non-clean, including an advisory affecting SRT's transitive Zod dependency. The existing local hook treats audit findings as advisory.

See the testing guide for setup, the test matrix and logging details.

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Automatically computed PR review signals (deterministic)

Complexity and branch coverage help focus review on functions and their tests; these signals are advisory.

Complexity and test coverage

13 changed JS/TS files analyzed · 4 flagged functions.
Coverage: before/after measurements available for the analyzed functions.

File / function Complexity Δ Branch coverage Δ Uncovered branches Depending files
🟠 lambda/agentcore/commands/run-stage.js:1777 — runStageAttempt 882 → 886 +4 85.5% → 85.6% +0.1 132 → 131 1
🟠 lambda/agentcore/output-normalizer.js:509 — createCliOutputSink / consumeLine 30 → 36 +6 86.8% → 88.1% +1.3 5 → 5 4
🟠 lambda/agentcore/cli/spawn.js:355 — captureChild / <anonymous> — → 23 — — → 86.7% — — → 4 7
🟠 lambda/agentcore/cli/spawn.js:191 — runChild / <anonymous> — → 25 — — → 92.1% — — → 3 7

🟢 Improving · 🟠 Needs attention · 🔴 High risk · 🟣 Highest risk

— = unavailable · n/a = not applicable · * = incomplete tests


Isobar repository map

PR impact map

This fixed repository map locates changed files and their possible reach through static links.
Red marks changed files; green marks static links, not proven runtime impact or a risk grade.

Isobar by r3al1tym.

Signals
  • A: lambda/agentcore/cli/codex-parser.js — modified file · 1 static name-match target.
  • B: lambda/agentcore/commands/resolve-conflict.js — modified file · 3 static name-match targets.
  • Total static reach: 14 unchanged production files; 50 unchanged test files.
  • Static reach unavailable for 4 changed files.
  • Revision: 52b176d; updated 2026-11-06 14:38 UTC.
Legend
  • ○ modified · ◇ added · × deleted. Blue markers: tests; gray markers: documentation.
  • Solid green: import-linked name matches. Dotted green: broader file-level import fallback.

Full report artifacts

@jeromevdl jeromevdl added area/agents AgentCore runtime: CLI drivers, MCP tools, workspace and Git operations area/backend Lambda APIs, lambda/shared, v2-orchestrator area/ci GitHub Actions, hooks, lint, release tooling, E2E harness size/L < 3000 changed lines (lockfiles excluded) labels Oct 30, 2026
@github-actions github-actions Bot added area/frontend React application in frontend/ area/infra Terraform, installer and deployment size/XL 3000+ changed lines (lockfiles excluded), consider splitting and removed size/L < 3000 changed lines (lockfiles excluded) labels Nov 4, 2026
@github-actions github-actions Bot added size/L < 3000 changed lines (lockfiles excluded) and removed size/XL 3000+ changed lines (lockfiles excluded), consider splitting labels Nov 4, 2026
@jeromevdl
jeromevdl marked this pull request as ready for review November 5, 2026 09:48
github-actions Bot added a commit that referenced this pull request Nov 5, 2026
@jeromevdl jeromevdl removed area/frontend React application in frontend/ area/infra Terraform, installer and deployment area/ci GitHub Actions, hooks, lint, release tooling, E2E harness labels Nov 5, 2026
@jeromevdl
jeromevdl added this pull request to stack #697 November 5, 2026 10:00

@JWThewes JWThewes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see the 3 inline finding(s) below.

Comment on lines +8 to +13
const result = { ...env };
for (const [name, value] of Object.entries(sandboxEnv)) {
if (value !== outerEnv[name]) result[name] = value;
}
for (const name of Object.keys(outerEnv)) {
if (!(name in sandboxEnv)) delete result[name];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Enforce credential environment restrictions after restoration

The command wrapper starts from the full invocation environment but only deletes names present in the safe outer environment. Provider credentials are intentionally absent from that outer environment, so an SRT credentials.envVars deny rule removes the variable and this code immediately restores its original secret. Masking likewise cannot work because SRT's worker never receives the real variable from which to build a sentinel.

Suggested fix: Enforce credential environment rules while reconstructing the harness environment, or reject credential-environment settings until they can be supported without exposing secrets to setup helpers. Test both deny and mask with a secret supplied only through the invocation environment.

Evidence: The launcher starts the worker with childEnvironment({}, process.env), excluding inference credentials, while this function begins with {...env} and only deletes Object.keys(outerEnv). A direct reproduction with KIRO_API_KEY present only in env and absent after SRT still returned KIRO_API_KEY with its original secret.

Comment on lines 162 to 164
}) =>
new Promise((resolve, reject) => {
const capture = captureStderrTail > 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Allow sandbox cleanup before terminating the worker

Sandbox enablement puts the worker on process-group termination paths that use SIGKILL for timeouts and credential cancellation. SIGKILL prevents runSandboxWorker's finally block and SRT's process-exit cleanup from removing host-side mount-point placeholders, which can leave ghost files or directories in a persistent workspace.

Suggested fix: Cancel the worker gracefully through IPC or SIGTERM, wait for its cleanup to finish, and retain group SIGKILL only as a delayed fallback. Add timeout and credential-cancellation tests that verify SRT-created host paths are removed.

Evidence: A focused sandboxed captureChild timeout sent SIGKILL to the negative worker PID. The pinned SRT 0.0.81 implementation documents that absent deny paths create persistent host mount points and that cleanupAfterCommand or its exit handler must remove them; neither runs after SIGKILL.


const exitCode = result?.exitCode ?? 0;
logger.error('cli exit', {
logger[exitCode === 0 ? 'info' : 'error']('cli exit', {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Do not log a failed spawn as a successful exit

An asynchronous child spawn error returns exitCode null, but runStageAttempt normalizes that value to zero before this new severity selection. The event is therefore logged at INFO as a successful exit, removing the previous ERROR-level signal while downstream logic also treats the launch as successful.

Suggested fix: Preserve null as a launch failure, emit an error-level cli_error, and fail the stage instead of defaulting it to zero. Add a test that emits the child-process error event rather than close with an integer code.

Evidence: A direct runChild reproduction with an asynchronous ENOENT-style error returned {exitCode:null}; the current normalization produced exit code 0 and selected the info logger. The added severity tests cover only close codes 0 and 2.

@github-actions github-actions Bot added the area/ci GitHub Actions, hooks, lint, release tooling, E2E harness label Nov 5, 2026
github-actions Bot added a commit that referenced this pull request Nov 5, 2026
github-actions Bot added a commit that referenced this pull request Nov 5, 2026
github-actions Bot added a commit that referenced this pull request Nov 5, 2026
github-actions Bot added a commit that referenced this pull request Nov 5, 2026
github-actions Bot added a commit that referenced this pull request Nov 6, 2026
github-actions Bot added a commit that referenced this pull request Nov 6, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/agents AgentCore runtime: CLI drivers, MCP tools, workspace and Git operations area/backend Lambda APIs, lambda/shared, v2-orchestrator area/ci GitHub Actions, hooks, lint, release tooling, E2E harness size/L < 3000 changed lines (lockfiles excluded)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants