Skip to content

security: prevent target repo .env from leaking into subprocesses (#1135)#1169

Merged
Wirasm merged 1 commit intodevfrom
feature/remove-env-consent-model
Apr 13, 2026
Merged

security: prevent target repo .env from leaking into subprocesses (#1135)#1169
Wirasm merged 1 commit intodevfrom
feature/remove-env-consent-model

Conversation

@Wirasm
Copy link
Copy Markdown
Collaborator

@Wirasm Wirasm commented Apr 13, 2026

Summary

  • Problem: Target repo .env files leaked into both Claude Code and Bun script subprocesses. Claude Code runs via Bun, which auto-loads .env from CWD. This caused (1) repo secrets leaking into AI subprocesses, and (2) repo API keys silently overriding Archon's auth (breaking global auth).
  • What changed: Added --no-env-file to both Claude Code subprocess spawning (executableArgs) and Bun script node execution. Removed the entire scanning/consent model which was the wrong fix for this problem.
  • What did not change: stripCwdEnv() boot protection, managed env injection (config.yaml env: + DB vars), { ...process.env } subprocess env construction

E2E Evidence

Tested from within a Claude Code session against a repo with contaminated .env:

Claude prompt node — before fix:

LEAKED_PROMPT_TEST: SHOULD_NOT_SEE_THIS
SECRET_SAUCE: repo-only
+ "Invalid API key" error (repo's fake key overrode global auth)

Claude prompt node — after fix (executableArgs: ['--no-env-file']):

LEAKED_PROMPT_TEST: not set
SECRET_SAUCE: not set
+ global auth works correctly

Bun script node — before fix:

LEAKED: LEAKED_FROM_SCRIPT_CWD = yes_this_leaked

Bun script node — after fix (bun --no-env-file -e):

CLEAN: LEAKED_FROM_SCRIPT_CWD not set

Bash node — always clean (bash doesn't auto-load .env).

Late registration via curl — registered repo after server boot, ran workflow, zero leaks in both bash and bun nodes.

Architecture

Archon boot:
  stripCwdEnv() ──▶ clean process.env ──▶ load ~/.archon/.env

Subprocess paths (all now protected):
  Claude provider ─▶ executableArgs: ['--no-env-file'] ─▶ Claude Code skips .env
  Bun script nodes ▶ bun --no-env-file -e <code>        ─▶ Bun skips .env
  Bash nodes ──────▶ inherited process.env               ─▶ bash doesn't auto-load

Registration:
  ✅ always succeeds (no scanning, no consent)

Change Metadata

Changes

Fix: Claude Code subprocess env leak (packages/providers/src/claude/provider.ts)

  • Added executableArgs: ['--no-env-file'] to SDK Options
  • Prevents Bun runtime inside Claude Code from auto-loading target repo .env
  • Fixes both secret leakage AND auth override (repo API key no longer overrides global auth)

Fix: Bun script node env leak (packages/workflows/src/dag-executor.ts)

  • Added --no-env-file flag to bun -e (inline) and bun run (named) script invocations
  • Prevents Bun from auto-loading target repo .env in script node subprocesses

Remove: Consent/scanner model (20+ files, -935 lines)

  • Deleted env-leak-scanner.ts entirely
  • Removed allow_env_keys from DB types, API schemas, Codebase interface
  • Removed allow_target_repo_keys from config types and loader
  • Removed PATCH consent route, --allow-env-keys CLI flag, UI consent toggle
  • Removed startup env-leak scan

Tests added:

  • Regression test: bun script node with .env in CWD verifies output is CLEAN
  • Updated command construction tests for --no-env-file in args
  • 6 new integration tests: multi-file stripping, shell env preservation, Bedrock/Vertex auth, managed env merge

Validation Evidence

```bash
bun run validate # all pass, exit 0
```

  • 31 files changed, 206 insertions, 935 deletions
  • E2E tested: contaminated repo with Claude prompt node, bun script node, bash node
  • Late registration tested: curl POST after server boot, workflow run, zero leaks

Security Impact

  • --no-env-file on Claude Code subprocess: prevents repo .env from overriding Archon auth tokens (this was silently breaking global auth when repos had ANTHROPIC_API_KEY)
  • --no-env-file on Bun script nodes: prevents repo secrets from leaking into script execution
  • Removed consent model: no longer possible to "allow" repo env leakage

Compatibility / Migration

  • Repos previously blocked by scanner now register freely
  • --allow-env-keys CLI flag removed (no longer needed)
  • allow_target_repo_keys config silently ignored
  • allow_env_keys DB column remains as dead column
  • Bun script nodes that relied on repo .env vars should use managed env injection instead

Known remaining gaps

  • Codex provider: no executableArgs equivalent yet (envInjection: false)
  • Direct orchestrator chat: no managed env injection
  • These are pre-existing, not regressions

Summary by CodeRabbit

  • Refactor
    • Removed per-project "allow env keys" consent across CLI, Web UI, API and persistence; registration/creation no longer accept or store env-key consent; env-leak scanner and related APIs removed.
  • Bug Fixes
    • Script/workflow execution now prevents repo .env from being auto-loaded (subprocesses run with no .env loading).
  • Documentation
    • Updated UI/API docs and schemas to remove allowEnvKeys.
  • Tests
    • Removed scanner-related tests; added/updated tests to assert .env is not loaded.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Removes the environment-consent/bypass model end-to-end: deletes allow_env_keys / allow_target_repo_keys / --allow-env-keys surfaces, removes the env-leak scanner and its exports, eliminates API/UI/CLI consent endpoints and tests, and hardens subprocess invocation to avoid auto-loading repo .env files.

Changes

Cohort / File(s) Summary
CLI
packages/cli/src/cli.ts, packages/cli/src/commands/workflow.ts
Removed --allow-env-keys from CLI help/parsing; dropped allowEnvKeys from WorkflowRunOptions and call sites.
Config types & loader
packages/core/src/config/config-types.ts, packages/core/src/config/config-loader.ts
Removed allow_target_repo_keys types and loader merge/warn logic; deleted test-only reset helper.
Database & migrations
packages/core/src/db/adapters/sqlite.ts, packages/core/src/db/codebases.ts, packages/core/src/db/codebases.test.ts
Removed migration for allow_env_keys, stopped persisting it in createCodebase, and deleted updateCodebaseAllowEnvKeys and related tests.
Clone / registration handlers
packages/core/src/handlers/clone.ts, packages/core/src/handlers/clone.test.ts
Removed env-leak scanning, EnvLeakError handling, consent/audit logging; dropped allowEnvKeys/context params and updated tests.
Env-leak scanner
packages/core/src/utils/env-leak-scanner.ts, packages/core/src/utils/env-leak-scanner.test.ts
Deleted scanner module, types, EnvLeakError, scanPathForSensitiveKeys, formatLeakError, and all scanner tests.
Core exports & types
packages/core/src/index.ts, packages/core/src/types/index.ts
Removed scanner re-exports and removed allow_env_keys from exported Codebase type.
Server routes & schemas
packages/server/src/routes/api.ts, packages/server/src/routes/schemas/codebase.schemas.ts, packages/server/src/routes/api.codebases.test.ts
Deleted PATCH /api/codebases/{id} route/schema/tests; removed allowEnvKeys from POST /api/codebases flow, schemas, error handling, and tests.
Server startup
packages/server/src/index.ts
Removed startup code that listed codebases and scanned default_cwd paths for repo .env files.
Web frontend & API client
packages/web/src/lib/api.ts, packages/web/src/routes/SettingsPage.tsx
Removed allow_env_keys from CodebaseResponse; narrowed addCodebase() input types; deleted updateCodebase() and removed per-project env-key UI and related error handling.
Workflows & error classification
packages/workflows/src/executor-shared.ts
classifyError no longer special-cases EnvLeakError; classification uses message patterns only.
Bun/script execution & tests
packages/workflows/src/dag-executor.ts, packages/workflows/src/script-node-deps.test.ts, packages/workflows/src/dag-executor.test.ts
Bun subprocess invocations updated to include --no-env-file; tests updated/added to assert .env not loaded into script nodes.
Env isolation tests & fixtures
packages/paths/src/env-integration.test.ts, packages/core/src/.../*.test.ts, packages/core/src/orchestrator/.../*.test.ts
Expanded env-integration tests; removed allow_env_keys fixtures and consent-related tests across multiple suites.
Docs
CLAUDE.md
Removed CLI and API docs referencing --allow-env-keys, allowEnvKeys fields, and the PATCH consent route.
Misc package
packages/core/package.json
Adjusted scripts.test Bun test invocation ordering.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant Server as Server
  participant CloneSvc as Clone/Register
  participant DB as Database

  Client->>Server: POST /api/codebases { url | path }
  Server->>CloneSvc: cloneRepository(url) / registerRepository(path)
  CloneSvc->>Server: registration result (name, repository_url, default_cwd, ai_assistant_type)
  Server->>DB: createCodebase(data)  %% no allow_env_keys field
  DB-->>Server: created Codebase
  Server-->>Client: 201 Created (codebase payload)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • coleam00

Poem

🐇 I nibbled bytes and hopped through trees,

I chased out secrets on the breeze,
No repo .env shall find our den,
Clean runs now, again and again,
A happy thump — code safe with me!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly describes the main security fix: preventing target repo .env from leaking into subprocesses, which aligns with all code changes.
Description check ✅ Passed PR description covers problem, solution, evidence, security impact, and compatibility; mostly follows template with clear sections and E2E validation.
Linked Issues check ✅ Passed All acceptance criteria from #1135 are met: consent model removed end-to-end, target repo env blocked from process.env/subprocess, explicit Archon-managed env retained, all surfaces (DB, API, CLI, UI) updated.
Out of Scope Changes check ✅ Passed All changes directly support the security objective: removing consent/scanner infrastructure and adding --no-env-file flags to prevent env leakage; no unrelated modifications present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/remove-env-consent-model

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 and usage tips.

@Wirasm Wirasm force-pushed the feature/remove-env-consent-model branch from 003d3c6 to 983c5b5 Compare April 13, 2026 09:31
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/server/src/routes/api.ts (1)

1504-1510: ⚠️ Potential issue | 🟠 Major

Remove allowEnvKeys from the published request schema too.

The handler no longer honors the flag, but the generated client contract still exposes AddCodebaseBody.allowEnvKeys in packages/web/src/lib/api.generated.d.ts:2021-2025. That leaves a silent no-op bypass knob in the public API. Please drop the field from packages/server/src/routes/schemas/codebase.schemas.ts and regenerate the frontend types before merging; if you want stale clients to fail loudly instead of being ignored, make the object schema strict as well.

🧩 Suggested fix
// packages/server/src/routes/schemas/codebase.schemas.ts
 export const addCodebaseBodySchema = z.object({
   url: z.string().optional(),
   path: z.string().optional(),
-  allowEnvKeys: z.boolean().optional(),
 });
bun generate:types

As per coding guidelines, packages/server/src/**/*.ts: "Use Hono OpenAPI integration with Zod schemas for REST API routes; generate OpenAPI spec and frontend types via bun generate:types."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/routes/api.ts` around lines 1504 - 1510, Remove the
deprecated allowEnvKeys field from the published request schema by deleting it
from the Zod schema export (addCodebaseBodySchema) in codebase.schemas.ts so the
handler and OpenAPI contract match; then regenerate frontend types (run the
project type generation, e.g. bun generate:types) so packages/web no longer
exposes AddCodebaseBody.allowEnvKeys — optionally make the object schema strict
to cause stale clients to fail loudly instead of being ignored.
packages/web/src/lib/api.ts (1)

159-166: ⚠️ Potential issue | 🟠 Major

Regenerate the OpenAPI contract instead of narrowing this wrapper by hand.

packages/web/src/lib/api.generated.d.ts:1-50 still advertises CreateCodebaseBody.allowEnvKeys, UpdateCodebaseBody, and the PATCH consent route. Narrowing addCodebase() locally hides that drift in the handwritten client, but the published web contract is still stale and still exposes the removed consent model. Please update/regenerate the OpenAPI types in this PR and then type this wrapper from the generated schema again so the web client has a single source of truth.

Based on learnings, archon/web derives types from src/lib/api.generated.d.ts (generated OpenAPI spec).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/lib/api.ts` around lines 159 - 166, The handwritten
addCodebase wrapper was narrowed locally and hides that the generated OpenAPI
types (packages/web/src/lib/api.generated.d.ts) are stale and still expose
removed fields like CreateCodebaseBody.allowEnvKeys, UpdateCodebaseBody and the
PATCH consent route; regenerate the OpenAPI client/types so the generated file
is up-to-date, then change addCodebase to consume the generated type (e.g., use
the generated CreateCodebaseBody / CodebaseResponse types from
api.generated.d.ts) instead of a manually authored union, and remove any
handwritten type overrides so the web client derives its types from the single
generated contract.
packages/web/src/routes/SettingsPage.tsx (1)

264-283: ⚠️ Potential issue | 🟡 Minor

Keep add-project input classification consistent across screens.

packages/web/src/routes/ChatPage.tsx and packages/web/src/components/layout/Sidebar.tsx still choose { url } for remote repos and { path } for local ones before calling addCodebase(). This mutation now always posts { path }, so the same Git URL will be classified differently depending on where the user adds the project. Please reuse the existing URL-vs-path logic here, or extract a shared helper now that the pattern exists in three entry points.

As per coding guidelines, "Extract shared utilities only after the same pattern appears at least three times and has stabilized (DRY + Rule of Three principle)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/routes/SettingsPage.tsx` around lines 264 - 283, The
add-project payload in SettingsPage (handleAddSubmit -> addMutation ->
addCodebase) always sends { path }, causing inconsistent classification versus
ChatPage.tsx and Sidebar.tsx which send { url } for remotes; update SettingsPage
to use the same URL-vs-path logic used in ChatPage.tsx and Sidebar.tsx (or
extract a small shared helper like buildCodebasePayload or isRemoteRepo and call
it from handleAddSubmit) so addMutation calls addCodebase with either { url }
for remote repos or { path } for local ones, preserving the existing behavior of
addCodebase across all entry points.
🧹 Nitpick comments (3)
CLAUDE.md (1)

767-767: Add the migration note here.

This removes the old flag from the docs, but it still doesn’t tell users what to do instead. A short note that target-repo .env values are never forwarded anymore, and that managed env injection (config.yaml env: / codebase env vars) is the supported replacement, would make upgrades much less surprising.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` at line 767, Update the documentation entry for "POST
/api/codebases" to include a short migration note informing users that the old
flag has been removed and that target-repo `.env` values are no longer
forwarded; instruct them to use managed env injection instead by setting env
values in `config.yaml` under `env:` or by using the codebase-level environment
variables mechanism as the supported replacement, and mention any necessary
steps to move existing `.env` keys to the new location.
packages/providers/src/env.test.ts (1)

7-12: Add XDG_CONFIG_HOME to saved env vars for consistent cleanup.

The XDG_CONFIG_HOME test (lines 40-46) sets this env var but relies on inline cleanup. If the test fails before delete, it could pollute subsequent tests. Adding it to savedEnv ensures consistent cleanup via afterEach.

♻️ Suggested fix
   beforeEach(() => {
     // Save keys we'll modify
-    for (const key of ['CUSTOM_REPO_KEY', 'ANTHROPIC_API_KEY', 'PATH', 'HOME']) {
+    for (const key of ['CUSTOM_REPO_KEY', 'ANTHROPIC_API_KEY', 'PATH', 'HOME', 'XDG_CONFIG_HOME']) {
       savedEnv[key] = process.env[key];
     }
   });

Then remove the manual cleanup at line 45.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/src/env.test.ts` around lines 7 - 12, Add
"XDG_CONFIG_HOME" to the array of keys saved in the beforeEach so savedEnv
captures it (update the for loop initialization that currently lists
'CUSTOM_REPO_KEY','ANTHROPIC_API_KEY','PATH','HOME'); this ensures afterEach
restores XDG_CONFIG_HOME if a test fails. Also remove the manual delete of
process.env.XDG_CONFIG_HOME inside the XDG_CONFIG_HOME test (the inline cleanup
referenced in the test) since global savedEnv/afterEach will handle restoration.
packages/providers/src/env.ts (1)

28-53: Consider adding Windows-specific environment variables.

The allowlist covers Unix/macOS essentials but may be incomplete for Windows subprocess functionality. Windows uses different variables like USERPROFILE (instead of HOME), COMSPEC, SYSTEMROOT, PATHEXT, and APPDATA.

Since the learnings indicate this is a single-developer tool, this may not be a priority, but worth noting if Windows support is intended.

♻️ Windows-compatible allowlist additions
 const OS_ESSENTIALS: ReadonlySet<string> = new Set([
   'PATH',
   'HOME',
   'USER',
   'LOGNAME',
   'SHELL',
   'TERM',
   'COLORTERM',
   'FORCE_COLOR',
   'NO_COLOR',
   'TMPDIR',
   'TEMP',
   'TMP',
   'LANG',
   'LC_ALL',
   'LC_COLLATE',
   'LC_CTYPE',
   'LC_MESSAGES',
   'LC_MONETARY',
   'LC_NUMERIC',
   'LC_TIME',
   'HOSTNAME',
   'EDITOR',
   'VISUAL',
   'PAGER',
+  // Windows equivalents
+  'USERPROFILE',
+  'APPDATA',
+  'LOCALAPPDATA',
+  'SYSTEMROOT',
+  'COMSPEC',
+  'PATHEXT',
+  'PROGRAMFILES',
+  'WINDIR',
 ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/src/env.ts` around lines 28 - 53, The OS_ESSENTIALS
allowlist omits common Windows environment variables which can break
subprocesses on Windows; update the ReadonlySet named OS_ESSENTIALS to include
Windows-specific entries such as USERPROFILE, COMSPEC, SYSTEMROOT, PATHEXT,
APPDATA (and optionally LOCALAPPDATA, PROGRAMFILES, PROGRAMFILES(X86), and
WINDIR) so that Windows child processes receive expected environment keys;
modify the OS_ESSENTIALS declaration in the same module to add these symbols to
the set and keep the list ReadonlySet semantics intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/db/codebases.ts`:
- Around line 23-24: The INSERT currently uses RETURNING * and the module's
SELECT helpers still use SELECT *, which causes the deprecated allow_env_keys to
be returned; update the INSERT and all SELECT helpers in this file to use the
existing codebaseColumns projection instead of * so the allow_env_keys column is
excluded — locate the queries in functions like the create/insert method (where
RETURNING * is used) and any get/list/select helpers and swap their
RETURNING/SELECT * to reference the codebaseColumns symbol (or the same column
list) so responses no longer include allow_env_keys.

---

Outside diff comments:
In `@packages/server/src/routes/api.ts`:
- Around line 1504-1510: Remove the deprecated allowEnvKeys field from the
published request schema by deleting it from the Zod schema export
(addCodebaseBodySchema) in codebase.schemas.ts so the handler and OpenAPI
contract match; then regenerate frontend types (run the project type generation,
e.g. bun generate:types) so packages/web no longer exposes
AddCodebaseBody.allowEnvKeys — optionally make the object schema strict to cause
stale clients to fail loudly instead of being ignored.

In `@packages/web/src/lib/api.ts`:
- Around line 159-166: The handwritten addCodebase wrapper was narrowed locally
and hides that the generated OpenAPI types
(packages/web/src/lib/api.generated.d.ts) are stale and still expose removed
fields like CreateCodebaseBody.allowEnvKeys, UpdateCodebaseBody and the PATCH
consent route; regenerate the OpenAPI client/types so the generated file is
up-to-date, then change addCodebase to consume the generated type (e.g., use the
generated CreateCodebaseBody / CodebaseResponse types from api.generated.d.ts)
instead of a manually authored union, and remove any handwritten type overrides
so the web client derives its types from the single generated contract.

In `@packages/web/src/routes/SettingsPage.tsx`:
- Around line 264-283: The add-project payload in SettingsPage (handleAddSubmit
-> addMutation -> addCodebase) always sends { path }, causing inconsistent
classification versus ChatPage.tsx and Sidebar.tsx which send { url } for
remotes; update SettingsPage to use the same URL-vs-path logic used in
ChatPage.tsx and Sidebar.tsx (or extract a small shared helper like
buildCodebasePayload or isRemoteRepo and call it from handleAddSubmit) so
addMutation calls addCodebase with either { url } for remote repos or { path }
for local ones, preserving the existing behavior of addCodebase across all entry
points.

---

Nitpick comments:
In `@CLAUDE.md`:
- Line 767: Update the documentation entry for "POST /api/codebases" to include
a short migration note informing users that the old flag has been removed and
that target-repo `.env` values are no longer forwarded; instruct them to use
managed env injection instead by setting env values in `config.yaml` under
`env:` or by using the codebase-level environment variables mechanism as the
supported replacement, and mention any necessary steps to move existing `.env`
keys to the new location.

In `@packages/providers/src/env.test.ts`:
- Around line 7-12: Add "XDG_CONFIG_HOME" to the array of keys saved in the
beforeEach so savedEnv captures it (update the for loop initialization that
currently lists 'CUSTOM_REPO_KEY','ANTHROPIC_API_KEY','PATH','HOME'); this
ensures afterEach restores XDG_CONFIG_HOME if a test fails. Also remove the
manual delete of process.env.XDG_CONFIG_HOME inside the XDG_CONFIG_HOME test
(the inline cleanup referenced in the test) since global savedEnv/afterEach will
handle restoration.

In `@packages/providers/src/env.ts`:
- Around line 28-53: The OS_ESSENTIALS allowlist omits common Windows
environment variables which can break subprocesses on Windows; update the
ReadonlySet named OS_ESSENTIALS to include Windows-specific entries such as
USERPROFILE, COMSPEC, SYSTEMROOT, PATHEXT, APPDATA (and optionally LOCALAPPDATA,
PROGRAMFILES, PROGRAMFILES(X86), and WINDIR) so that Windows child processes
receive expected environment keys; modify the OS_ESSENTIALS declaration in the
same module to add these symbols to the set and keep the list ReadonlySet
semantics intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f82d3a69-e4ee-4f43-8ac7-f07e79294c94

📥 Commits

Reviewing files that changed from the base of the PR and between c9c6ab4 and 003d3c6.

📒 Files selected for processing (31)
  • CLAUDE.md
  • packages/cli/src/cli.ts
  • packages/cli/src/commands/workflow.ts
  • packages/core/package.json
  • packages/core/src/config/config-loader.ts
  • packages/core/src/config/config-types.ts
  • packages/core/src/db/adapters/sqlite.ts
  • packages/core/src/db/codebases.test.ts
  • packages/core/src/db/codebases.ts
  • packages/core/src/handlers/clone.test.ts
  • packages/core/src/handlers/clone.ts
  • packages/core/src/handlers/command-handler.test.ts
  • packages/core/src/index.ts
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/core/src/orchestrator/orchestrator-isolation.test.ts
  • packages/core/src/orchestrator/orchestrator.test.ts
  • packages/core/src/types/index.ts
  • packages/core/src/utils/env-leak-scanner.test.ts
  • packages/core/src/utils/env-leak-scanner.ts
  • packages/providers/src/claude/provider.test.ts
  • packages/providers/src/claude/provider.ts
  • packages/providers/src/env.test.ts
  • packages/providers/src/env.ts
  • packages/providers/src/index.ts
  • packages/server/src/index.ts
  • packages/server/src/routes/api.codebases.test.ts
  • packages/server/src/routes/api.ts
  • packages/server/src/routes/schemas/codebase.schemas.ts
  • packages/web/src/lib/api.ts
  • packages/web/src/routes/SettingsPage.tsx
  • packages/workflows/src/executor-shared.ts
💤 Files with no reviewable changes (16)
  • packages/core/src/index.ts
  • packages/core/src/types/index.ts
  • packages/core/src/db/adapters/sqlite.ts
  • packages/workflows/src/executor-shared.ts
  • packages/core/src/orchestrator/orchestrator.test.ts
  • packages/cli/src/cli.ts
  • packages/core/src/config/config-types.ts
  • packages/core/src/handlers/command-handler.test.ts
  • packages/core/src/orchestrator/orchestrator-isolation.test.ts
  • packages/server/src/index.ts
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/core/src/config/config-loader.ts
  • packages/server/src/routes/schemas/codebase.schemas.ts
  • packages/core/src/handlers/clone.test.ts
  • packages/core/src/utils/env-leak-scanner.test.ts
  • packages/core/src/utils/env-leak-scanner.ts

Comment on lines +23 to +24
'INSERT INTO remote_agent_codebases (name, repository_url, default_cwd, ai_assistant_type) VALUES ($1, $2, $3, $4) RETURNING *',
[data.name, data.repository_url ?? null, data.default_cwd, assistantType]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Stop returning the legacy allow_env_keys field.

Dropping the column from the INSERT is only half of the removal. Because the table still keeps allow_env_keys, this RETURNING * and the other SELECT * queries in this module will continue to surface the deprecated consent flag at runtime. packages/server/src/routes/api.ts returns those Codebase rows directly, so the removed primitive can still leak back out over GET/POST responses.

🧩 Suggested fix
+const codebaseColumns =
+  'id, name, repository_url, default_cwd, ai_assistant_type, commands, created_at, updated_at';
+
 export async function createCodebase(data: {
   name: string;
   repository_url?: string;
   default_cwd: string;
   ai_assistant_type?: string;
 }): Promise<Codebase> {
   const assistantType = data.ai_assistant_type ?? 'claude';
   const result = await pool.query<Codebase>(
-    'INSERT INTO remote_agent_codebases (name, repository_url, default_cwd, ai_assistant_type) VALUES ($1, $2, $3, $4) RETURNING *',
+    `INSERT INTO remote_agent_codebases (name, repository_url, default_cwd, ai_assistant_type)
+     VALUES ($1, $2, $3, $4)
+     RETURNING ${codebaseColumns}`,
     [data.name, data.repository_url ?? null, data.default_cwd, assistantType]
   );

Apply the same codebaseColumns projection to the SELECT * helpers in this file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/db/codebases.ts` around lines 23 - 24, The INSERT currently
uses RETURNING * and the module's SELECT helpers still use SELECT *, which
causes the deprecated allow_env_keys to be returned; update the INSERT and all
SELECT helpers in this file to use the existing codebaseColumns projection
instead of * so the allow_env_keys column is excluded — locate the queries in
functions like the create/insert method (where RETURNING * is used) and any
get/list/select helpers and swap their RETURNING/SELECT * to reference the
codebaseColumns symbol (or the same column list) so responses no longer include
allow_env_keys.

@Wirasm Wirasm force-pushed the feature/remove-env-consent-model branch from 983c5b5 to c755a96 Compare April 13, 2026 09:41
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/paths/src/env-integration.test.ts`:
- Around line 224-229: The test currently loads fixtures with config({ path:
join(cwdDir, '.env.local'), processEnv: {} }) and then blindly injects
localParsed.parsed into process.env, which can produce false-positive passes if
loading fails; update the test to assert the parse succeeded immediately after
each config call (e.g., assert/expect that localParsed.parsed is defined or
throw a clear Error) before mutating process.env for the '.env.local' case and
do the same for the corresponding '.env.development' load (the other parsed
variable), so the test fails fast when a fixture fails to parse.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c96b405c-daa6-49dd-9e32-c34d81a79c48

📥 Commits

Reviewing files that changed from the base of the PR and between 983c5b5 and c755a96.

📒 Files selected for processing (27)
  • CLAUDE.md
  • packages/cli/src/cli.ts
  • packages/cli/src/commands/workflow.ts
  • packages/core/package.json
  • packages/core/src/config/config-loader.ts
  • packages/core/src/config/config-types.ts
  • packages/core/src/db/adapters/sqlite.ts
  • packages/core/src/db/codebases.test.ts
  • packages/core/src/db/codebases.ts
  • packages/core/src/handlers/clone.test.ts
  • packages/core/src/handlers/clone.ts
  • packages/core/src/handlers/command-handler.test.ts
  • packages/core/src/index.ts
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/core/src/orchestrator/orchestrator-isolation.test.ts
  • packages/core/src/orchestrator/orchestrator.test.ts
  • packages/core/src/types/index.ts
  • packages/core/src/utils/env-leak-scanner.test.ts
  • packages/core/src/utils/env-leak-scanner.ts
  • packages/paths/src/env-integration.test.ts
  • packages/server/src/index.ts
  • packages/server/src/routes/api.codebases.test.ts
  • packages/server/src/routes/api.ts
  • packages/server/src/routes/schemas/codebase.schemas.ts
  • packages/web/src/lib/api.ts
  • packages/web/src/routes/SettingsPage.tsx
  • packages/workflows/src/executor-shared.ts
💤 Files with no reviewable changes (16)
  • packages/core/src/types/index.ts
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/workflows/src/executor-shared.ts
  • packages/core/src/db/adapters/sqlite.ts
  • packages/server/src/index.ts
  • packages/core/src/handlers/command-handler.test.ts
  • packages/core/src/index.ts
  • packages/core/src/config/config-types.ts
  • packages/core/src/config/config-loader.ts
  • packages/server/src/routes/schemas/codebase.schemas.ts
  • packages/core/src/handlers/clone.test.ts
  • packages/core/src/utils/env-leak-scanner.test.ts
  • packages/core/src/utils/env-leak-scanner.ts
  • packages/cli/src/cli.ts
  • packages/core/src/orchestrator/orchestrator-isolation.test.ts
  • packages/core/src/orchestrator/orchestrator.test.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/core/src/db/codebases.test.ts
  • packages/web/src/routes/SettingsPage.tsx
  • CLAUDE.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/core/package.json
  • packages/web/src/lib/api.ts
  • packages/core/src/db/codebases.ts
  • packages/server/src/routes/api.codebases.test.ts
  • packages/core/src/handlers/clone.ts

Comment on lines +224 to +229
const localParsed = config({ path: join(cwdDir, '.env.local'), processEnv: {} });
if (localParsed.parsed) {
for (const [key, value] of Object.entries(localParsed.parsed)) {
process.env[key] = value;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Assert fixture parse success to avoid false-positive test passes.

If the fixture load ever fails, these tests can still pass while not actually validating strip behavior for .env.local/.env.development.

✅ Proposed test-hardening diff
-    const localParsed = config({ path: join(cwdDir, '.env.local'), processEnv: {} });
-    if (localParsed.parsed) {
-      for (const [key, value] of Object.entries(localParsed.parsed)) {
-        process.env[key] = value;
-      }
-    }
+    const localParsed = config({ path: join(cwdDir, '.env.local'), processEnv: {} });
+    expect(localParsed.error).toBeUndefined();
+    expect(localParsed.parsed).toBeDefined();
+    for (const [key, value] of Object.entries(localParsed.parsed ?? {})) {
+      process.env[key] = value;
+    }

-    const devParsed = config({ path: join(cwdDir, '.env.development'), processEnv: {} });
-    if (devParsed.parsed) {
-      for (const [key, value] of Object.entries(devParsed.parsed)) {
-        process.env[key] = value;
-      }
-    }
+    const devParsed = config({ path: join(cwdDir, '.env.development'), processEnv: {} });
+    expect(devParsed.error).toBeUndefined();
+    expect(devParsed.parsed).toBeDefined();
+    for (const [key, value] of Object.entries(devParsed.parsed ?? {})) {
+      process.env[key] = value;
+    }

Based on learnings: "Applies to **/*.test.ts : Keep tests deterministic — no flaky timing or network dependence without guardrails. Ensure local validation commands (bun run validate) map directly to CI expectations."

Also applies to: 243-248

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/paths/src/env-integration.test.ts` around lines 224 - 229, The test
currently loads fixtures with config({ path: join(cwdDir, '.env.local'),
processEnv: {} }) and then blindly injects localParsed.parsed into process.env,
which can produce false-positive passes if loading fails; update the test to
assert the parse succeeded immediately after each config call (e.g.,
assert/expect that localParsed.parsed is defined or throw a clear Error) before
mutating process.env for the '.env.local' case and do the same for the
corresponding '.env.development' load (the other parsed variable), so the test
fails fast when a fixture fails to parse.

@Wirasm Wirasm force-pushed the feature/remove-env-consent-model branch from c755a96 to 2d019b8 Compare April 13, 2026 09:51
@Wirasm Wirasm changed the title security: build subprocess env from trusted base, remove consent model security: remove env-leak consent model, fix bun script node env leak (#1135) Apr 13, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/core/src/db/codebases.ts (1)

23-24: ⚠️ Potential issue | 🟠 Major

RETURNING * still exposes deprecated consent fields.

Even after removing allow_env_keys from the INSERT input, wildcard projections (RETURNING * here and SELECT * in this module) can still surface the legacy column at runtime.

Suggested fix (explicit column projection)
+const codebaseColumns =
+  'id, name, repository_url, default_cwd, ai_assistant_type, commands, created_at, updated_at';

 const result = await pool.query<Codebase>(
-  'INSERT INTO remote_agent_codebases (name, repository_url, default_cwd, ai_assistant_type) VALUES ($1, $2, $3, $4) RETURNING *',
+  `INSERT INTO remote_agent_codebases (name, repository_url, default_cwd, ai_assistant_type)
+   VALUES ($1, $2, $3, $4)
+   RETURNING ${codebaseColumns}`,
   [data.name, data.repository_url ?? null, data.default_cwd, assistantType]
 );

Also replace remaining SELECT * queries in this file with SELECT ${codebaseColumns}.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/db/codebases.ts` around lines 23 - 24, The INSERT uses
"RETURNING *" which can leak deprecated columns; update the insert query in the
code that constructs the SQL for remote_agent_codebases to explicitly project
columns using the existing codebaseColumns projection (replace RETURNING * with
RETURNING ${codebaseColumns} or return the inserted row via the same
codebaseColumns list), and also replace any "SELECT *" usages in this module
with "SELECT ${codebaseColumns}" so only the allowed columns (as defined by the
codebaseColumns symbol) are returned.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/workflows/src/dag-executor.test.ts`:
- Around line 5266-5313: The test "bun script node does not leak repo .env from
execution cwd (`#1135`)" can be flaky if LEAKED_REPO_SECRET exists in the host
env; modify the test to save process.env.LEAKED_REPO_SECRET at the start,
delete/unset process.env.LEAKED_REPO_SECRET before creating the repo .env and
before calling executeDagWorkflow, and restore the original value in a finally
block after the test completes; ensure the cleanup covers all early returns so
the environment is always restored (reference the test name, the
LEAKED_REPO_SECRET variable, the writeFile call that creates join(testDir,
'.env'), and the executeDagWorkflow invocation to locate where to clear/restore
the env var).

---

Duplicate comments:
In `@packages/core/src/db/codebases.ts`:
- Around line 23-24: The INSERT uses "RETURNING *" which can leak deprecated
columns; update the insert query in the code that constructs the SQL for
remote_agent_codebases to explicitly project columns using the existing
codebaseColumns projection (replace RETURNING * with RETURNING
${codebaseColumns} or return the inserted row via the same codebaseColumns
list), and also replace any "SELECT *" usages in this module with "SELECT
${codebaseColumns}" so only the allowed columns (as defined by the
codebaseColumns symbol) are returned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 17377761-65a5-4e5c-860d-e22be0d8cb93

📥 Commits

Reviewing files that changed from the base of the PR and between c755a96 and 2d019b8.

📒 Files selected for processing (30)
  • CLAUDE.md
  • packages/cli/src/cli.ts
  • packages/cli/src/commands/workflow.ts
  • packages/core/package.json
  • packages/core/src/config/config-loader.ts
  • packages/core/src/config/config-types.ts
  • packages/core/src/db/adapters/sqlite.ts
  • packages/core/src/db/codebases.test.ts
  • packages/core/src/db/codebases.ts
  • packages/core/src/handlers/clone.test.ts
  • packages/core/src/handlers/clone.ts
  • packages/core/src/handlers/command-handler.test.ts
  • packages/core/src/index.ts
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/core/src/orchestrator/orchestrator-isolation.test.ts
  • packages/core/src/orchestrator/orchestrator.test.ts
  • packages/core/src/types/index.ts
  • packages/core/src/utils/env-leak-scanner.test.ts
  • packages/core/src/utils/env-leak-scanner.ts
  • packages/paths/src/env-integration.test.ts
  • packages/server/src/index.ts
  • packages/server/src/routes/api.codebases.test.ts
  • packages/server/src/routes/api.ts
  • packages/server/src/routes/schemas/codebase.schemas.ts
  • packages/web/src/lib/api.ts
  • packages/web/src/routes/SettingsPage.tsx
  • packages/workflows/src/dag-executor.test.ts
  • packages/workflows/src/dag-executor.ts
  • packages/workflows/src/executor-shared.ts
  • packages/workflows/src/script-node-deps.test.ts
💤 Files with no reviewable changes (16)
  • packages/core/src/orchestrator/orchestrator-isolation.test.ts
  • packages/core/src/handlers/command-handler.test.ts
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/cli/src/cli.ts
  • packages/core/src/db/adapters/sqlite.ts
  • packages/workflows/src/executor-shared.ts
  • packages/core/src/types/index.ts
  • packages/core/src/index.ts
  • packages/server/src/index.ts
  • packages/core/src/orchestrator/orchestrator.test.ts
  • packages/core/src/handlers/clone.test.ts
  • packages/core/src/config/config-loader.ts
  • packages/server/src/routes/schemas/codebase.schemas.ts
  • packages/core/src/utils/env-leak-scanner.test.ts
  • packages/core/src/config/config-types.ts
  • packages/core/src/utils/env-leak-scanner.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/core/package.json
  • packages/core/src/db/codebases.test.ts
  • packages/server/src/routes/api.codebases.test.ts
  • CLAUDE.md
  • packages/web/src/lib/api.ts
  • packages/paths/src/env-integration.test.ts

Comment on lines +5266 to +5313
it('bun script node does not leak repo .env from execution cwd (#1135)', async () => {
// Regression test: place a .env with a marker in the execution cwd.
// The bun script must NOT see it because --no-env-file is passed.
const mockDeps = createMockDeps();
const platform = createMockPlatform();
const workflowRun = makeWorkflowRun('env-leak-run-id', {
workflow_name: 'env-leak-test',
conversation_id: 'conv-env-leak',
user_message: 'env leak test',
});

// Write a .env with a marker in the script execution cwd
await writeFile(join(testDir, '.env'), 'LEAKED_REPO_SECRET=should_not_appear\n');

const scriptNode: ScriptNode = {
id: 'env-check',
script: 'console.log(process.env.LEAKED_REPO_SECRET ?? "CLEAN")',
runtime: 'bun',
};

await executeDagWorkflow(
mockDeps,
platform,
'conv-env-leak',
testDir,
{ name: 'env-leak-test', nodes: [scriptNode] },
workflowRun,
'claude',
undefined,
join(testDir, 'artifacts'),
join(testDir, 'logs'),
'main',
'docs/',
minimalConfig
);

// The node output should be "CLEAN" — the repo .env was not loaded
const completedEvents = (mockDeps.store.createWorkflowEvent as ReturnType<typeof mock>).mock
.calls;
const completedEvent = completedEvents.find(
(call: unknown[]) =>
(call[0] as { event_type: string }).event_type === 'node_completed' &&
(call[0] as { step_name: string }).step_name === 'env-check'
);
expect(completedEvent).toBeDefined();
const eventData = (completedEvent![0] as { data: { node_output: string } }).data;
expect(eventData.node_output).toBe('CLEAN');
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Stabilize this regression test against host environment leakage.

The test can fail spuriously if LEAKED_REPO_SECRET is already set in the CI/runtime environment. Clear and restore it within the test to make the assertion deterministic.

✅ Suggested deterministic guard
 it('bun script node does not leak repo .env from execution cwd (`#1135`)', async () => {
+  const previousLeakedRepoSecret = process.env.LEAKED_REPO_SECRET;
+  delete process.env.LEAKED_REPO_SECRET;
+  try {
   // Regression test: place a .env with a marker in the execution cwd.
   // The bun script must NOT see it because --no-env-file is passed.
   const mockDeps = createMockDeps();
   const platform = createMockPlatform();
   const workflowRun = makeWorkflowRun('env-leak-run-id', {
@@
   const eventData = (completedEvent![0] as { data: { node_output: string } }).data;
   expect(eventData.node_output).toBe('CLEAN');
+  } finally {
+    if (previousLeakedRepoSecret === undefined) {
+      delete process.env.LEAKED_REPO_SECRET;
+    } else {
+      process.env.LEAKED_REPO_SECRET = previousLeakedRepoSecret;
+    }
+  }
 });

Based on learnings: "Applies to **/*.test.ts : Keep tests deterministic — no flaky timing or network dependence without guardrails. Ensure local validation commands (bun run validate) map directly to CI expectations."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('bun script node does not leak repo .env from execution cwd (#1135)', async () => {
// Regression test: place a .env with a marker in the execution cwd.
// The bun script must NOT see it because --no-env-file is passed.
const mockDeps = createMockDeps();
const platform = createMockPlatform();
const workflowRun = makeWorkflowRun('env-leak-run-id', {
workflow_name: 'env-leak-test',
conversation_id: 'conv-env-leak',
user_message: 'env leak test',
});
// Write a .env with a marker in the script execution cwd
await writeFile(join(testDir, '.env'), 'LEAKED_REPO_SECRET=should_not_appear\n');
const scriptNode: ScriptNode = {
id: 'env-check',
script: 'console.log(process.env.LEAKED_REPO_SECRET ?? "CLEAN")',
runtime: 'bun',
};
await executeDagWorkflow(
mockDeps,
platform,
'conv-env-leak',
testDir,
{ name: 'env-leak-test', nodes: [scriptNode] },
workflowRun,
'claude',
undefined,
join(testDir, 'artifacts'),
join(testDir, 'logs'),
'main',
'docs/',
minimalConfig
);
// The node output should be "CLEAN" — the repo .env was not loaded
const completedEvents = (mockDeps.store.createWorkflowEvent as ReturnType<typeof mock>).mock
.calls;
const completedEvent = completedEvents.find(
(call: unknown[]) =>
(call[0] as { event_type: string }).event_type === 'node_completed' &&
(call[0] as { step_name: string }).step_name === 'env-check'
);
expect(completedEvent).toBeDefined();
const eventData = (completedEvent![0] as { data: { node_output: string } }).data;
expect(eventData.node_output).toBe('CLEAN');
});
it('bun script node does not leak repo .env from execution cwd (`#1135`)', async () => {
const previousLeakedRepoSecret = process.env.LEAKED_REPO_SECRET;
delete process.env.LEAKED_REPO_SECRET;
try {
// Regression test: place a .env with a marker in the execution cwd.
// The bun script must NOT see it because --no-env-file is passed.
const mockDeps = createMockDeps();
const platform = createMockPlatform();
const workflowRun = makeWorkflowRun('env-leak-run-id', {
workflow_name: 'env-leak-test',
conversation_id: 'conv-env-leak',
user_message: 'env leak test',
});
// Write a .env with a marker in the script execution cwd
await writeFile(join(testDir, '.env'), 'LEAKED_REPO_SECRET=should_not_appear\n');
const scriptNode: ScriptNode = {
id: 'env-check',
script: 'console.log(process.env.LEAKED_REPO_SECRET ?? "CLEAN")',
runtime: 'bun',
};
await executeDagWorkflow(
mockDeps,
platform,
'conv-env-leak',
testDir,
{ name: 'env-leak-test', nodes: [scriptNode] },
workflowRun,
'claude',
undefined,
join(testDir, 'artifacts'),
join(testDir, 'logs'),
'main',
'docs/',
minimalConfig
);
// The node output should be "CLEAN" — the repo .env was not loaded
const completedEvents = (mockDeps.store.createWorkflowEvent as ReturnType<typeof mock>).mock
.calls;
const completedEvent = completedEvents.find(
(call: unknown[]) =>
(call[0] as { event_type: string }).event_type === 'node_completed' &&
(call[0] as { step_name: string }).step_name === 'env-check'
);
expect(completedEvent).toBeDefined();
const eventData = (completedEvent![0] as { data: { node_output: string } }).data;
expect(eventData.node_output).toBe('CLEAN');
} finally {
if (previousLeakedRepoSecret === undefined) {
delete process.env.LEAKED_REPO_SECRET;
} else {
process.env.LEAKED_REPO_SECRET = previousLeakedRepoSecret;
}
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/workflows/src/dag-executor.test.ts` around lines 5266 - 5313, The
test "bun script node does not leak repo .env from execution cwd (`#1135`)" can be
flaky if LEAKED_REPO_SECRET exists in the host env; modify the test to save
process.env.LEAKED_REPO_SECRET at the start, delete/unset
process.env.LEAKED_REPO_SECRET before creating the repo .env and before calling
executeDagWorkflow, and restore the original value in a finally block after the
test completes; ensure the cleanup covers all early returns so the environment
is always restored (reference the test name, the LEAKED_REPO_SECRET variable,
the writeFile call that creates join(testDir, '.env'), and the
executeDagWorkflow invocation to locate where to clear/restore the env var).

@Wirasm Wirasm force-pushed the feature/remove-env-consent-model branch from 2d019b8 to 370633b Compare April 13, 2026 10:00
@Wirasm Wirasm changed the title security: remove env-leak consent model, fix bun script node env leak (#1135) security: prevent target repo .env from leaking into subprocesses (#1135) Apr 13, 2026
@Wirasm Wirasm force-pushed the feature/remove-env-consent-model branch from 370633b to 8d9b03e Compare April 13, 2026 10:08
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/workflows/src/dag-executor.test.ts (1)

5266-5313: ⚠️ Potential issue | 🟡 Minor

Make this env-leak regression test deterministic against host env state.

At Line 5278 onward, this can fail spuriously if process.env.LEAKED_REPO_SECRET is already set by CI/runner. Save, clear, and restore that env var around the test body.

Proposed fix
   it('bun script node does not leak repo .env from execution cwd (`#1135`)', async () => {
+    const previousLeakedRepoSecret = process.env.LEAKED_REPO_SECRET;
+    delete process.env.LEAKED_REPO_SECRET;
+    try {
     // Regression test: place a .env with a marker in the execution cwd.
     // The bun script must NOT see it because --no-env-file is passed.
     const mockDeps = createMockDeps();
     const platform = createMockPlatform();
@@
     );
     expect(completedEvent).toBeDefined();
     const eventData = (completedEvent![0] as { data: { node_output: string } }).data;
     expect(eventData.node_output).toBe('CLEAN');
+    } finally {
+      if (previousLeakedRepoSecret === undefined) {
+        delete process.env.LEAKED_REPO_SECRET;
+      } else {
+        process.env.LEAKED_REPO_SECRET = previousLeakedRepoSecret;
+      }
+    }
   });

Based on learnings: "Applies to **/*.test.ts : Keep tests deterministic — no flaky timing or network dependence without guardrails. Ensure local validation commands (bun run validate) map directly to CI expectations."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/workflows/src/dag-executor.test.ts` around lines 5266 - 5313, The
test 'bun script node does not leak repo .env from execution cwd (`#1135`)' can be
flaky if process.env.LEAKED_REPO_SECRET is already set on the host; before
writing the repo .env and invoking executeDagWorkflow, capture the original
value of process.env.LEAKED_REPO_SECRET, delete/clear it (so the test starts
with a clean env), run the test body, and finally restore the original value in
a finally block so CI state is preserved; update the test surrounding the
ScriptNode invocation (the it block with id 'env-check' and calls to writeFile
and executeDagWorkflow) to perform save/clear/restore of
process.env.LEAKED_REPO_SECRET to make the test deterministic.
🧹 Nitpick comments (2)
packages/web/src/routes/SettingsPage.tsx (1)

264-266: Prefer a named interface for the mutation input.

The new inline { path: string } object shape drifts from the repo’s TS convention. Pull this into a small interface/type alias so the mutation input is reusable and consistent.

♻️ Suggested refactor
+interface AddCodebaseInput {
+  path: string;
+}
+
   const addMutation = useMutation({
-    mutationFn: ({ path }: { path: string }) => addCodebase({ path }),
+    mutationFn: ({ path }: AddCodebaseInput) => addCodebase({ path }),
     onSuccess: () => {

As per coding guidelines: **/*.{ts,tsx}: Use interface for defining object shapes in TypeScript.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/routes/SettingsPage.tsx` around lines 264 - 266, Replace the
inline mutation input type in the useMutation call with a named interface to
follow the repo convention: define an interface (e.g., AddCodebaseInput)
describing { path: string } and use that as the generic/parameter type for the
mutationFn and any callers; update the mutation declaration (addMutation /
mutationFn) to reference AddCodebaseInput and ensure addCodebase({ path }) still
accepts the same shape.
packages/server/src/routes/api.ts (1)

1508-1510: Use explicit field checks here for clarity instead of truthiness + ?? ''.

The ?? '' fallback is unnecessary—the schema's .refine() already guarantees exactly one of url or path is present and non-empty (both use .min(1)). The current code is safe, but explicit branching would make the XOR constraint more obvious to readers.

♻️ Suggested refactor
-      const result = body.url
-        ? await cloneRepository(body.url)
-        : await registerRepository(body.path ?? '');
+      let result;
+      if (body.url !== undefined) {
+        result = await cloneRepository(body.url);
+      } else {
+        result = await registerRepository(body.path!);
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/routes/api.ts` around lines 1508 - 1510, Replace the
terse ternary that uses truthiness plus the `?? ''` fallback with an explicit
branch to make the XOR constraint clear: check if body.url is present and call
cloneRepository(body.url), otherwise call registerRepository(body.path) (no `??
''`); reference the existing symbols cloneRepository, registerRepository, and
the request body fields body.url/body.path and keep the branching logic
consistent with the schema's refine() that guarantees exactly one is provided.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/workflows/src/dag-executor.test.ts`:
- Around line 5266-5313: The test 'bun script node does not leak repo .env from
execution cwd (`#1135`)' can be flaky if process.env.LEAKED_REPO_SECRET is already
set on the host; before writing the repo .env and invoking executeDagWorkflow,
capture the original value of process.env.LEAKED_REPO_SECRET, delete/clear it
(so the test starts with a clean env), run the test body, and finally restore
the original value in a finally block so CI state is preserved; update the test
surrounding the ScriptNode invocation (the it block with id 'env-check' and
calls to writeFile and executeDagWorkflow) to perform save/clear/restore of
process.env.LEAKED_REPO_SECRET to make the test deterministic.

---

Nitpick comments:
In `@packages/server/src/routes/api.ts`:
- Around line 1508-1510: Replace the terse ternary that uses truthiness plus the
`?? ''` fallback with an explicit branch to make the XOR constraint clear: check
if body.url is present and call cloneRepository(body.url), otherwise call
registerRepository(body.path) (no `?? ''`); reference the existing symbols
cloneRepository, registerRepository, and the request body fields
body.url/body.path and keep the branching logic consistent with the schema's
refine() that guarantees exactly one is provided.

In `@packages/web/src/routes/SettingsPage.tsx`:
- Around line 264-266: Replace the inline mutation input type in the useMutation
call with a named interface to follow the repo convention: define an interface
(e.g., AddCodebaseInput) describing { path: string } and use that as the
generic/parameter type for the mutationFn and any callers; update the mutation
declaration (addMutation / mutationFn) to reference AddCodebaseInput and ensure
addCodebase({ path }) still accepts the same shape.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 07b95212-4df4-4a79-bc29-c63f5aa9c059

📥 Commits

Reviewing files that changed from the base of the PR and between 2d019b8 and 370633b.

📒 Files selected for processing (31)
  • CLAUDE.md
  • packages/cli/src/cli.ts
  • packages/cli/src/commands/workflow.ts
  • packages/core/package.json
  • packages/core/src/config/config-loader.ts
  • packages/core/src/config/config-types.ts
  • packages/core/src/db/adapters/sqlite.ts
  • packages/core/src/db/codebases.test.ts
  • packages/core/src/db/codebases.ts
  • packages/core/src/handlers/clone.test.ts
  • packages/core/src/handlers/clone.ts
  • packages/core/src/handlers/command-handler.test.ts
  • packages/core/src/index.ts
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/core/src/orchestrator/orchestrator-isolation.test.ts
  • packages/core/src/orchestrator/orchestrator.test.ts
  • packages/core/src/types/index.ts
  • packages/core/src/utils/env-leak-scanner.test.ts
  • packages/core/src/utils/env-leak-scanner.ts
  • packages/paths/src/env-integration.test.ts
  • packages/providers/src/claude/provider.ts
  • packages/server/src/index.ts
  • packages/server/src/routes/api.codebases.test.ts
  • packages/server/src/routes/api.ts
  • packages/server/src/routes/schemas/codebase.schemas.ts
  • packages/web/src/lib/api.ts
  • packages/web/src/routes/SettingsPage.tsx
  • packages/workflows/src/dag-executor.test.ts
  • packages/workflows/src/dag-executor.ts
  • packages/workflows/src/executor-shared.ts
  • packages/workflows/src/script-node-deps.test.ts
💤 Files with no reviewable changes (16)
  • packages/cli/src/cli.ts
  • packages/core/src/types/index.ts
  • packages/core/src/index.ts
  • packages/workflows/src/executor-shared.ts
  • packages/core/src/handlers/command-handler.test.ts
  • packages/core/src/orchestrator/orchestrator-isolation.test.ts
  • packages/core/src/orchestrator/orchestrator.test.ts
  • packages/core/src/config/config-loader.ts
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/server/src/index.ts
  • packages/core/src/db/adapters/sqlite.ts
  • packages/core/src/config/config-types.ts
  • packages/server/src/routes/schemas/codebase.schemas.ts
  • packages/core/src/handlers/clone.test.ts
  • packages/core/src/utils/env-leak-scanner.test.ts
  • packages/core/src/utils/env-leak-scanner.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/core/src/db/codebases.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/workflows/src/dag-executor.ts
  • packages/workflows/src/script-node-deps.test.ts
  • packages/core/package.json
  • packages/core/src/db/codebases.ts
  • packages/web/src/lib/api.ts
  • packages/paths/src/env-integration.test.ts

@Wirasm
Copy link
Copy Markdown
Collaborator Author

Wirasm commented Apr 13, 2026

PR Review Summary

Reviewed by: code-reviewer, docs-impact, silent-failure-hunter, pr-test-analyzer


Critical Issues (0 found)

None.

Important Issues (3 found)

Agent Issue Location
pr-test-analyzer executableArgs: ['--no-env-file'] in Claude provider is untested — the most security-critical new line in the PR has zero test coverage. The existing provider.test.ts already captures callArgs.options; adding an executableArgs assertion is a one-liner. packages/providers/src/claude/provider.ts:519
docs-impact security.md lines 127-137 still describe the old scanning/consent model — heading says "Env-leak gate", body says "Archon scans target repos", lists scanned keys and filenames. This contradicts lines 141-148 which correctly describe the new --no-env-file approach. packages/docs-web/src/content/docs/reference/security.md:127-137
docs-impact cli.md still has --allow-env-keys flag row in the workflow run flags table. The flag was removed from the CLI in this PR. Also links to a heading anchor that will be stale. packages/docs-web/src/content/docs/reference/cli.md:125

Suggestions (3 found)

Agent Suggestion Location
code-reviewer Dead allow_env_keys column in SQLite CREATE TABLE — The PR intentionally leaves the column for PostgreSQL (migrations are append-only), but the SQLite CREATE TABLE IF NOT EXISTS could be cleaned up since it only applies to fresh databases. packages/core/src/db/adapters/sqlite.ts:239
silent-failure-hunter Stale TODO comment in Codex provider references #1135 as unresolved, but this PR resolves it. Update to note the structural fix is in place but Codex has no --no-env-file equivalent. packages/providers/src/codex/provider.ts:485-486
pr-test-analyzer Named bun script path not covered by live leak test — The regression test covers inline -e but not bun --no-env-file run <path>. Both paths are in the same function, but they're distinct branches. packages/workflows/src/dag-executor.ts:1312

Strengths

  • Core fix is correct: executableArgs: ['--no-env-file'] is a valid SDK option, confirmed in @anthropic-ai/claude-agent-sdk/sdk.d.ts:772, and cannot be overridden by node-level config
  • Both bun script paths (inline -e and named run) have --no-env-file prepended
  • Consent model removal is thorough and consistent: types, DB, API, schemas, CLI, UI, docs, tests
  • dag-executor.test.ts regression test is excellent — spawns real bun --no-env-file -e against a .env file and asserts CLEAN
  • env-integration.test.ts additions are strong: multi-file stripping, shell env preservation, Bedrock/Vertex auth, key-collision semantics
  • No error handling was silently dropped — the removed EnvLeakError 422 catch is no longer needed
  • All callers of simplified cloneRepository()/registerRepository() signatures are updated correctly
  • Known Codex gap is explicitly disclosed in PR description

Documentation Issues

  • security.md:127-137 — Stale scanning/consent description contradicts the new structural fix described below it
  • cli.md:125 — Dead --allow-env-keys flag row with stale cross-reference

Verdict

NEEDS FIXES — The 2 stale doc pages and the missing executableArgs test assertion should be addressed before merge. All are small fixes.

Recommended Actions

  1. Add executableArgs assertion to existing Claude provider test (one-liner)
  2. Remove stale scanning description from security.md:127-137, rename section heading
  3. Remove --allow-env-keys row from cli.md flags table
  4. Consider cleaning up dead allow_env_keys column from SQLite CREATE TABLE
  5. Update stale Codex TODO comment referencing security: zero env leaks from managed repos into provider subprocesses #1135

@Wirasm Wirasm force-pushed the feature/remove-env-consent-model branch from 8d9b03e to 1065a01 Compare April 13, 2026 10:24
…ction (#1135)

Remove the entire env-leak scanning/consent infrastructure: scanner,
allow_env_keys DB column usage, allow_target_repo_keys config, PATCH
consent route, --allow-env-keys CLI flag, and UI consent toggle.

The env-leak gate was the wrong primitive. Target repo .env protection
is already structural:
- stripCwdEnv() at boot removes Bun-auto-loaded CWD .env keys
- Archon loads its own env sources afterward (~/.archon/.env)
- process.env is clean before any subprocess spawns
- Managed env injection (config.yaml env: + DB vars) is unchanged

No scanning, no consent, no blocking. Any repo can be registered and
used. Subprocesses receive the already-clean process.env.
@Wirasm Wirasm force-pushed the feature/remove-env-consent-model branch from 1065a01 to 7f65d17 Compare April 13, 2026 10:36
@Wirasm Wirasm merged commit a8ac3f0 into dev Apr 13, 2026
4 checks passed
@Wirasm Wirasm deleted the feature/remove-env-consent-model branch April 13, 2026 11:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security: zero env leaks from managed repos into provider subprocesses

1 participant