Skip to content

fix(runner): narrow the runner environment and drop subscription auto-upload#5298

Merged
mmabrouk merged 2 commits into
big-agentsfrom
fix/runner-env-narrowing
Jul 14, 2026
Merged

fix(runner): narrow the runner environment and drop subscription auto-upload#5298
mmabrouk merged 2 commits into
big-agentsfrom
fix/runner-env-narrowing

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

The problem

For any self-managed run, the runner discovered its own personal Pi login (auth.json + settings) and uploaded it into a third-party Daytona sandbox. A personal subscription credential must never leave the runner container. Separately, a local harness that shares the runner container could read a static platform API key straight out of the runner's process environment.

What changed

Phases 2 + 3 of the runner-selfhosting-cleanup:

  • Drop the subscription auto-upload. Deleted shouldUploadOwnLogin + uploadPiAuthToSandbox and their tests. Daytona never receives a personal login; managed Daytona runs authenticate from the vault keys instead.
  • Reject unsafe credential paths up front. buildRunPlan now rejects Daytona + runtime_provided, and rejects a local runtime_provided run whose subscription mount (PI_CODING_AGENT_DIR / CLAUDE_CONFIG_DIR) is unset — before any sandbox side effect.
  • Isolate the mounted login. Local runtime_provided Pi and Claude runs always copy the read-only mounted login into a per-run dir, so harness writes never touch the operator's source mount.
  • Remove the static key fallback. Dropped the static AGENTA_API_KEY OTLP exporter fallback; export auth now rides the per-run caller credential.
  • Make mount degradation observable. One structured mount degraded kind=<k> cause=<c> warning at each durable-null site (session cwd, agent mount, harness transcript). Failure behavior is unchanged.
  • Helm. Wire AGENTA_RUNNER_TOKEN via agentRunner.auth.tokenSecretRef on both the runner (verifier) and Services (sender), guarded so it renders only when set, plus a rendered-chart secret-absence test wired into CI.
  • Compose. Commented opt-in read-only subscription mount examples (OSS + EE).

Deferred (post-release)

The credentialMode strictness flip is not free on this base. A model-less agent template in the SDK handler threads no resolved_connection and omits the field, so this slice keeps the existing inference branch; the flip lands after the caller fix. AGENTA_RUNNER_INHERIT_ALL_PROVIDER_KEYS, commonEnv, and AGENTA_SESSION_HARNESS_MOUNTS are already no-ops on this base.

Tests

  • Runner unit suite: 1076/1076 pass.
  • Helm: helm lint clean + the new rendered-chart secret-absence test passes.

Note on status.md

This PR carries the 17 code + hosting files. The project's status.md update (Phase 2+3 landed marker + the deferred-flip note) could not be included here: its base version lives only in the docs/runner-selfhosting-cleanup-plan lane, and this lane is anchored on refactor/runner-config-names (which does not contain the file), so GitButler attributes the file's creation to the docs lane and cannot land the delta here without unsafe surgery. The status update should ride the docs-plan lane.

https://claude.ai/code/session_01XhENr63WL9npkKrJGnzDc1

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jul 14, 2026
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jul 14, 2026 12:45pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 33d27262-ecd2-4160-9156-09c3b49bdb20

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 60.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title matches the main change: narrowing the runner environment and removing subscription auto-upload.
Description check ✅ Passed The description accurately summarizes the runner, Helm, compose, and test changes in the PR.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/runner-env-narrowing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
hosting/kubernetes/helm/templates/runner-deployment.yaml (1)

110-117: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove optional: true for consistency and fail-fast behavior.

In _helpers.tpl (which generates servicesEnv), AGENTA_RUNNER_TOKEN is correctly injected without optional: true. Keeping it optional here introduces an inconsistency and allows the container to attempt startup when the secret is missing. Since the runner application validates this token as nonEmpty downstream, removing optional: true ensures Kubernetes catches the missing secret early (via a CreateContainerConfigError), rather than letting the application crash at runtime.

♻️ Proposed refactor
             {{- if $auth.tokenSecretRef }}
             - name: AGENTA_RUNNER_TOKEN
               valueFrom:
                 secretKeyRef:
                   name: {{ $auth.tokenSecretRef.name | quote }}
                   key: {{ $auth.tokenSecretRef.key | quote }}
-                  optional: true
             {{- end }}
services/runner/tests/unit/sandbox-agent-run-plan.test.ts (1)

1149-1174: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing createLocalCwd stub causes real filesystem side effects.

Both new success-path tests reach ok: true and therefore exercise the real defaultLocalCwd, unlike every other passing-path test in this file which injects a createLocalCwd stub (e.g. createLocalCwd: () => "/tmp/local-cwd"). This creates unmocked filesystem side effects during the unit test run.

🧪 Suggested fix
   it("accepts a local Pi runtime_provided run when PI_CODING_AGENT_DIR names a mount", () => {
     withEnv({ PI_CODING_AGENT_DIR: "/agenta/harness/pi" }, () => {
-      const result = buildRunPlan({
-        harness: "pi_core",
-        sandbox: "local",
-        messages: [{ role: "user", content: "hello" }],
-        credentialMode: "runtime_provided",
-      });
+      const result = buildRunPlan(
+        {
+          harness: "pi_core",
+          sandbox: "local",
+          messages: [{ role: "user", content: "hello" }],
+          credentialMode: "runtime_provided",
+        },
+        { createLocalCwd: () => "/tmp/local-cwd" },
+      );
       assert.equal(result.ok, true);

Apply the same pattern to the env-mode test.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5452c811-3729-4093-bee0-3daf07653596

📥 Commits

Reviewing files that changed from the base of the PR and between d57bd38 and 1404fcc.

📒 Files selected for processing (17)
  • .github/workflows/12-check-unit-tests.yml
  • hosting/docker-compose/ee/docker-compose.gh.yml
  • hosting/docker-compose/oss/docker-compose.gh.yml
  • hosting/kubernetes/helm/templates/_helpers.tpl
  • hosting/kubernetes/helm/templates/runner-deployment.yaml
  • hosting/kubernetes/helm/tests/test_runner_secret_absence.py
  • hosting/kubernetes/helm/values.schema.json
  • hosting/kubernetes/helm/values.yaml
  • services/runner/src/engines/sandbox_agent.ts
  • services/runner/src/engines/sandbox_agent/daytona.ts
  • services/runner/src/engines/sandbox_agent/mount.ts
  • services/runner/src/engines/sandbox_agent/pi-assets.ts
  • services/runner/src/engines/sandbox_agent/run-plan.ts
  • services/runner/src/tracing/otel.ts
  • services/runner/tests/unit/sandbox-agent-daytona.test.ts
  • services/runner/tests/unit/sandbox-agent-orchestration.test.ts
  • services/runner/tests/unit/sandbox-agent-run-plan.test.ts

Comment on lines +303 to +314
# === SUBSCRIPTION MOUNTS (opt-in) ========================= #
# Use your own harness subscription for LOCAL runs instead of a managed API
# key. Mount the login read-only; the runner copies it into a per-run dir, so
# the harness's own writes never touch your source. Single-operator convenience
# only — this state is never shipped to a Daytona sandbox. A runtime_provided
# local run without the matching mount fails with a clear error.
# volumes:
# - ~/.pi/agent:/agenta/harness/pi:ro # Pi / Codex login
# - ~/.claude:/agenta/harness/claude:ro # Claude login
# Then point the harness config var at the mount (override the defaults above):
# PI_CODING_AGENT_DIR: /agenta/harness/pi
# CLAUDE_CONFIG_DIR: /agenta/harness/claude

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add CLAUDE_CONFIG_DIR to the environment block.

The comment instructs users to "override the defaults above" for both PI_CODING_AGENT_DIR and CLAUDE_CONFIG_DIR. However, unlike PI_CODING_AGENT_DIR, CLAUDE_CONFIG_DIR is missing from the environment section above. Since the runner service lacks an env_file directive, Docker Compose will drop this variable if set on the host environment or a global .env file, breaking the documented feature.

💻 Proposed fix

Declare CLAUDE_CONFIG_DIR in the environment section of the runner service (around line 293):

             AGENTA_RUNNER_DEFAULT_SANDBOX_PROVIDER: ${AGENTA_RUNNER_DEFAULT_SANDBOX_PROVIDER:-local}
             PI_CODING_AGENT_DIR: ${PI_CODING_AGENT_DIR:-/pi-agent}
+            CLAUDE_CONFIG_DIR: ${CLAUDE_CONFIG_DIR:-}
             AGENTA_RUNNER_DAYTONA_API_KEY: ${AGENTA_RUNNER_DAYTONA_API_KEY:-}

Comment on lines +801 to +816
// Local Claude subscription run: copy the mounted CLAUDE_CONFIG_DIR into a per-run dir and point
// the daemon at the copy, so the harness's own writes stay off the read-only source mount
// (interface.md section 6). buildRunPlan already rejected a runtime_provided Claude run with no
// configured CLAUDE_CONFIG_DIR, so the source here is the mount.
if (
!plan.isDaytona &&
plan.acpAgent === "claude" &&
plan.credentialMode === "runtime_provided"
) {
const runClaudeConfigDir = prepareLocalClaudeConfigDir(
process.env.CLAUDE_CONFIG_DIR,
logger,
);
if (runClaudeConfigDir) env.CLAUDE_CONFIG_DIR = runClaudeConfigDir;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Prevent temporary directory and credential leak.

The temporary directory created by prepareLocalClaudeConfigDir is stored in a block-scoped const (runClaudeConfigDir). Because it is block-scoped and never attached to the environment object, it is completely inaccessible to the environment.destroy cleanup function. This results in permanently leaking a temporary directory (which contains sensitive Claude configuration/credentials) in the runner's /tmp on every local runtime_provided Claude run.

Lift the variable declaration so it can be captured by the destroy closure, and clean it up during teardown.

🔒️ Proposed fix to ensure cleanup

Declare the variable before the block (around line 799):

   const binaryPath = (deps.resolveDaemonBinary ?? resolveDaemonBinary)();
   let runAgentDir = prepareLocalPiAssets({ plan, env, log: logger });
+  let runClaudeConfigDir: string | undefined;
 
   // Local Claude subscription run: copy the mounted CLAUDE_CONFIG_DIR into a per-run dir and point
   // the daemon at the copy, so the harness's own writes stay off the read-only source mount
   // (interface.md section 6). buildRunPlan already rejected a runtime_provided Claude run with no
   // configured CLAUDE_CONFIG_DIR, so the source here is the mount.
   if (
     !plan.isDaytona &&
     plan.acpAgent === "claude" &&
     plan.credentialMode === "runtime_provided"
   ) {
-    const runClaudeConfigDir = prepareLocalClaudeConfigDir(
+    runClaudeConfigDir = prepareLocalClaudeConfigDir(
       process.env.CLAUDE_CONFIG_DIR,
       logger,
     );
     if (runClaudeConfigDir) env.CLAUDE_CONFIG_DIR = runClaudeConfigDir;
   }

Then, add the cleanup step inside the environment.destroy function (around line 964):

    // The per-run Agenta agent dir (skills isolation) is throwaway; remove it too.
    if (environment.runAgentDir)
      rmSync(environment.runAgentDir, { recursive: true, force: true });
+   if (runClaudeConfigDir)
+     rmSync(runClaudeConfigDir, { recursive: true, force: true });

@mmabrouk
mmabrouk force-pushed the fix/runner-env-narrowing branch from 1404fcc to 66b0f17 Compare July 14, 2026 01:18
@mmabrouk
mmabrouk changed the base branch from refactor/runner-config-names to big-agents July 14, 2026 01:57
@mmabrouk

Copy link
Copy Markdown
Member Author

🌙 Overnight — reviewed + backend-QA'd (21/21 on 8280), but the merge is blocked by a real 2-file code conflict with the already-merged pi-skill work: services/runner/src/engines/sandbox_agent.ts and pi-assets.ts (NOT the Helm files — that was an earlier misdiagnosis). Resolution: rebase fix/runner-env-narrowing onto big-agents, hand-merge those 2 files keeping BOTH sides (env-narrowing's per-run Claude copy + mount-degradation warnings AND the pi-skill edits), re-run runner tests, force-push, then merge. I left this for a careful pass rather than an unattended auto-merge into the trunk.

@mmabrouk mmabrouk added the needs-review Agent updated; awaiting Mahmoud's review label Jul 14, 2026
mmabrouk added 2 commits July 14, 2026 14:37
…-upload

The runner discovered its own Pi login (auth.json + settings) and uploaded it
into a third-party Daytona sandbox for any self-managed run. A personal
subscription credential must never leave the runner container, and a local
harness that shares the runner container must not be able to read a static
platform API key from the runner's process environment.

Phases 2+3 of runner-selfhosting-cleanup:

- Delete shouldUploadOwnLogin + uploadPiAuthToSandbox and their tests. Daytona
  never receives a personal login; managed Daytona runs authenticate from the
  vault keys instead.
- buildRunPlan rejects Daytona + runtime_provided, and rejects a local
  runtime_provided run whose subscription mount (PI_CODING_AGENT_DIR /
  CLAUDE_CONFIG_DIR) is unset, before any sandbox side effect.
- Local runtime_provided Pi and Claude always copy the read-only mounted login
  into a per-run dir so harness writes never touch the operator's source.
- Remove the static AGENTA_API_KEY OTLP exporter fallback; export auth rides the
  per-run caller credential.
- One structured "mount degraded kind=<k> cause=<c>" warning at each durable-null
  site (session cwd, agent mount, harness transcript). Failure behavior unchanged.
- Helm: wire AGENTA_RUNNER_TOKEN via agentRunner.auth.tokenSecretRef on both the
  runner (verifier) and Services (sender), guarded so it renders only when set,
  plus a rendered-chart secret-absence test wired into CI.
- Compose: commented opt-in read-only subscription mount examples (OSS + EE).

Deferred (post-release): the credentialMode strictness flip is NOT free on this
base. A model-less agent template in the SDK handler threads no resolved_connection
and omits the field (see status.md). This slice keeps the existing inference branch;
the flip lands after the caller fix. AGENTA_RUNNER_INHERIT_ALL_PROVIDER_KEYS,
commonEnv, and AGENTA_SESSION_HARNESS_MOUNTS are already no-ops on this base.

Claude-Session: https://claude.ai/code/session_01XhENr63WL9npkKrJGnzDc1
… a per-run copy

A local runtime_provided run authenticated from the operator's mounted harness
login, but the runner copied that login into a throwaway per-run directory and
pointed the harness at the copy. Subscription logins are OAuth logins: the
harness refreshes its access token mid-run and writes the new one back to its
config dir. The copy discarded that refresh, so as soon as the provider rotated
the refresh token the next run failed and the operator had to log in by hand.

Mount the login read-write and let the harness run directly out of it, exactly
as it does on a normal local install, so a refreshed token persists to the
source. Drops prepareLocalClaudeConfigDir and the Pi credential copy; the dev
Compose stacks stop cp -a'ing the login into a container-local path.

The subscription path deliberately reports NO throwaway dir, so teardown's
rmSync can never delete the operator's real login. Pinned by a test.

Tradeoff, now documented: concurrent local subscription runs share the harness
config dir, the same way two local harness sessions do. This path is
single-trusted-operator only.

The missing-mount guard is unchanged: a local runtime_provided run with no
configured PI_CODING_AGENT_DIR / CLAUDE_CONFIG_DIR still fails up front.
@mmabrouk
mmabrouk force-pushed the fix/runner-env-narrowing branch from 325ef16 to 07cffc7 Compare July 14, 2026 12:43
@mmabrouk
mmabrouk merged commit a9f8f47 into big-agents Jul 14, 2026
21 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review Agent updated; awaiting Mahmoud's review size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant