Skip to content

fix(cli): strip CWD .env leakage and respect platform adapters in serve#1068

Closed
Wirasm wants to merge 1 commit intodevfrom
fix/cli-strip-cwd-env
Closed

fix(cli): strip CWD .env leakage and respect platform adapters in serve#1068
Wirasm wants to merge 1 commit intodevfrom
fix/cli-strip-cwd-env

Conversation

@Wirasm
Copy link
Copy Markdown
Collaborator

@Wirasm Wirasm commented Apr 11, 2026

Fixes the two sub-issues reported in #1067. Both violate the design rule "CLI should never load target repo env" and "archon serve should respect configured adapters".

1. CWD .env leak (partially addressed in v0.3.5, still leaked)

Bug: Bun's runtime auto-loads CWD `.env` / `.env.local` / `.env.development` / `.env.production` into `process.env` before any user code runs. When `archon` is invoked from inside a target repo, that repo's .env leaked into the Archon process. The v0.3.5 `override: true` fix only covered overlapping keys:

  • Overlapping keys (e.g. `PORT` in both) — overridden by `~/.archon/.env` ✓
  • Non-overlapping keys (`LOG_LEVEL`, `MAX_CONCURRENT_CONVERSATIONS`, random markers, etc.) — survived and contaminated logging, config, and every `process.env.X` read outside the subprocess allowlist ❌

Reproduction on archon-stable (v0.3.5):
```bash
mkdir /tmp/leak-test && cd /tmp/leak-test && git init
echo 'LOG_LEVEL=debug' > .env
archon workflow list # logs come out at level:20 (debug) — LEAKED
```

Fix: `stripCwdEnv()` parses CWD .env files and deletes matching keys from `process.env`. Must run before any module that reads env at init time — notably `@archon/paths/logger` which reads `LOG_LEVEL` in `buildLogger()` at module load. Exposed as a side-effect boot subpath `@archon/paths/strip-cwd-env-boot` so it runs during import resolution, before the logger module loads.

```typescript
// packages/cli/src/cli.ts — must be the first import
import '@archon/paths/strip-cwd-env-boot';
```

The function itself (`@archon/paths/strip-cwd-env`) is a pure testable unit; the boot wrapper is the side-effect entry point.

2. `archon serve` hardcoded `skipPlatformAdapters: true`

Bug: `packages/cli/src/commands/serve.ts:63` passed `skipPlatformAdapters: true` unconditionally, making every `archon serve` invocation Web-only regardless of what tokens are in `~/.archon/.env`. The setup wizard writes `TELEGRAM_BOT_TOKEN` / `DISCORD_BOT_TOKEN` / `SLACK_*` etc., but nothing ever read them from `serve`.

Fix: Remove the hardcoded `true`. `startServer()` already gates each adapter individually on its own token env var (`packages/server/src/index.ts:299-654`), so adapters auto-start only when configured. Users who only want the web UI simply leave the tokens unset — the server logs `no_platform_adapters_configured` and runs Web-only.

Validation

  • `bun run validate` passes (type-check, lint, format, all tests)
  • 6 new unit tests for `stripCwdEnv` covering: single .env, all four Bun-loaded files, no .env present, non-overlapping key preservation, malformed .env tolerance, and "parsed but not in process.env" no-op
  • E2E (source build, bun-linked archon): `archon workflow run archon-assist "What is 5+5?" --no-worktree` from a target repo with `LOG_LEVEL=debug` in its .env now produces info-level logs (leak gone) and completes successfully with `10`
  • E2E (dev server): `bun --filter @archon/server dev` with `~/.archon/.env` containing Discord/Slack/GitHub tokens starts all three adapters (`discord.bot_logged_in`, `slack.adapter_initialized`, `github.webhook_adapter_ready`) — previously `archon serve` would have silently skipped them
  • Control: `archon workflow list` from the Archon repo root still works (the repo's own .env is stripped, CLI uses only `~/.archon/.env`)

Files

  • `packages/paths/src/strip-cwd-env.ts` — pure function, testable
  • `packages/paths/src/strip-cwd-env-boot.ts` — side-effect wrapper for entry points
  • `packages/paths/src/strip-cwd-env.test.ts` — 6 unit tests
  • `packages/paths/package.json` — adds `dotenv` dep and two subpath exports
  • `packages/cli/src/cli.ts` — side-effect import first; drops `override: true` (no longer needed)
  • `packages/server/src/index.ts` — side-effect import first; keeps the explicit repo-root .env load for dev mode
  • `packages/cli/src/commands/serve.ts` — removes `skipPlatformAdapters: true`

Rollback

`git revert ` — single commit, isolated to env boot and one-line serve.ts change.

Summary by CodeRabbit

  • New Features

    • Platform adapters (Telegram, Discord, Slack, GitHub, Gitea, GitLab) are now automatically enabled when their authentication tokens are configured in the environment.
  • Bug Fixes

    • Enhanced environment variable handling to prevent unintended leakage of working directory configuration during module initialization.
  • Tests

    • Added comprehensive test coverage for environment variable management utilities.

…in serve

Two related bugs in the #1067 class — both violate "CLI should never load
target repo env" and "archon serve should respect configured adapters".

1. CWD .env leak (partially addressed by override:true in v0.3.5, still leaked)

Bun's runtime auto-loads `.env`/`.env.local`/`.env.development`/`.env.production`
from CWD into `process.env` before any user code runs. When `archon` is invoked
from inside a target repo, that repo's .env leaked into the Archon process:
  - Overlapping keys were overridden by `~/.archon/.env` (override:true) ✓
  - Non-overlapping keys (LOG_LEVEL, MAX_CONCURRENT_CONVERSATIONS, random
    markers, etc.) survived and contaminated logging, config, and every
    `process.env.X` read outside the subprocess allowlist.

Fix: stripCwdEnv() parses the CWD .env files, deletes matching keys from
process.env, and must run BEFORE any module that reads env at init time
(notably @archon/paths/logger which reads LOG_LEVEL in buildLogger()).
Exposed as a side-effect boot subpath @archon/paths/strip-cwd-env-boot
so it runs during import resolution, before the logger module loads.

2. archon serve hardcoded skipPlatformAdapters:true

serve.ts passed `skipPlatformAdapters: true` unconditionally, making every
`archon serve` run Web-only regardless of tokens in ~/.archon/.env.
The setup wizard writes TELEGRAM_BOT_TOKEN / DISCORD_BOT_TOKEN / etc.,
but nothing ever read them.

Fix: remove the hardcoded true. startServer() already gates each adapter
on its own token env var, so adapters auto-start only when configured.

Validation:
- All 9 package type-checks clean
- Lint clean, format clean, all tests pass (including 6 new tests)
- E2E: `archon workflow run archon-assist` from a target repo with
  LOG_LEVEL=debug in its .env now produces info-level logs (leak gone)
  and completes successfully
- E2E: `bun --filter @archon/server dev` starts github, discord, slack
  adapters when their tokens are present
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

The changes introduce a new CWD environment variable stripping module that executes during module initialization to sanitize process.env before other code reads it, update CLI and server startup to use this early boot sequence, enable platform adapter auto-start in the serve command, and adjust global environment file loading behavior by removing explicit overrides and adding existence checks.

Changes

Cohort / File(s) Summary
CWD environment stripping implementation
packages/paths/src/strip-cwd-env.ts, packages/paths/src/strip-cwd-env-boot.ts, packages/paths/src/strip-cwd-env.test.ts
New function stripCwdEnv() reads and parses CWD .env files (.env.local, .env.development, .env.production, .env) and removes parsed keys from process.env. Boot module executes stripping on import. Test suite validates removal of CWD-derived vars, preservation of unrelated vars, and error resilience with 107 lines of test coverage.
Paths package exports and manifest
packages/paths/package.json, packages/paths/src/index.ts
Expanded exports map to expose "./strip-cwd-env" and "./strip-cwd-env-boot" entry points. Added dotenv dependency at ^17.2.3. Re-exported stripCwdEnv from main index barrel.
CLI and server startup
packages/cli/src/cli.ts, packages/cli/src/commands/serve.ts, packages/server/src/index.ts
Added first-import side effect of @archon/paths/strip-cwd-env-boot to sanitize environment early. Updated global .env loading to remove override: true flag and added existence check. Removed skipPlatformAdapters: true from startServer call to enable auto-start of Telegram/Discord/Slack/GitHub/Gitea/GitLab adapters.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 A rabbit's tale of clean env

When modules load, the bootpath springs,
CWD secrets stripped like old things—
Platform adapters wake and dance,
No forced false skips—they get their chance! 🌿

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is well-structured with clear problem statements, fixes, validation evidence, and rollback plan, but does not follow the repository's required template format. Reformat the description to match the repository template: add explicit sections for Summary bullets, UX Journey (Before/After), Architecture Diagram, Label Snapshot, Change Metadata, Validation Evidence commands with results, Security Impact assessment, Compatibility/Migration, Human Verification checklist, Side Effects/Blast Radius, and Risks and Mitigations.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures both main changes: stripping CWD .env leakage and enabling platform adapters in serve command.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/cli-strip-cwd-env

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.

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/strip-cwd-env.ts`:
- Around line 34-39: The current deletion unconditionally removes any env var
whose key appears in the target repo's .env, which can wipe legitimate
shell-provided values; update the stripCwdEnv logic to only delete when the
existing process.env[key] appears to contain a CWD-leak (e.g., its value
includes or startsWith the detected cwd or the leaked path pattern you computed)
or matches the exact value read from the repo .env, otherwise leave
process.env[key] intact and skip pushing key into stripped; locate this logic in
the stripCwdEnv function where the variable key is checked and deleted and
adjust the conditional to verify the value contains the cwd/leaked path before
performing delete and pushing to stripped.
🪄 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: 05d9a28e-a28a-4403-a2ab-bacad66742ac

📥 Commits

Reviewing files that changed from the base of the PR and between 536584d and e896ac2.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • packages/cli/src/cli.ts
  • packages/cli/src/commands/serve.ts
  • packages/paths/package.json
  • packages/paths/src/index.ts
  • packages/paths/src/strip-cwd-env-boot.ts
  • packages/paths/src/strip-cwd-env.test.ts
  • packages/paths/src/strip-cwd-env.ts
  • packages/server/src/index.ts

Comment on lines +34 to +39
if (key in process.env) {
// Dynamic delete is required: keys come from the target repo's .env
// at runtime, so they cannot be known statically.
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete process.env[key];
stripped.push(key);
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

Avoid deleting shell-provided env vars while stripping CWD leakage

At Line 34, the current key in process.env check deletes any existing env var if that key is present in CWD .env*. This can wipe legitimate operator/shell values (including critical vars) that were never leaked by Bun.

💡 Proposed fix
-      for (const key of Object.keys(parsed)) {
-        if (key in process.env) {
+      for (const key of Object.keys(parsed)) {
+        const currentValue = process.env[key];
+        // Strip only when process.env currently matches the CWD .env value.
+        // This avoids deleting externally provided env vars with different values.
+        if (currentValue !== undefined && currentValue === parsed[key]) {
           // Dynamic delete is required: keys come from the target repo's .env
           // at runtime, so they cannot be known statically.
           // eslint-disable-next-line `@typescript-eslint/no-dynamic-delete`
           delete process.env[key];
           stripped.push(key);
         }
       }
📝 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
if (key in process.env) {
// Dynamic delete is required: keys come from the target repo's .env
// at runtime, so they cannot be known statically.
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete process.env[key];
stripped.push(key);
for (const key of Object.keys(parsed)) {
const currentValue = process.env[key];
// Strip only when process.env currently matches the CWD .env value.
// This avoids deleting externally provided env vars with different values.
if (currentValue !== undefined && currentValue === parsed[key]) {
// Dynamic delete is required: keys come from the target repo's .env
// at runtime, so they cannot be known statically.
// eslint-disable-next-line `@typescript-eslint/no-dynamic-delete`
delete process.env[key];
stripped.push(key);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/paths/src/strip-cwd-env.ts` around lines 34 - 39, The current
deletion unconditionally removes any env var whose key appears in the target
repo's .env, which can wipe legitimate shell-provided values; update the
stripCwdEnv logic to only delete when the existing process.env[key] appears to
contain a CWD-leak (e.g., its value includes or startsWith the detected cwd or
the leaked path pattern you computed) or matches the exact value read from the
repo .env, otherwise leave process.env[key] intact and skip pushing key into
stripped; locate this logic in the stripCwdEnv function where the variable key
is checked and deleted and adjust the conditional to verify the value contains
the cwd/leaked path before performing delete and pushing to stripped.

@Wirasm
Copy link
Copy Markdown
Collaborator Author

Wirasm commented Apr 12, 2026

Superseded by #1092 which consolidates this PR and #1071 into a single PR with docs updates.

@Wirasm
Copy link
Copy Markdown
Collaborator Author

Wirasm commented Apr 12, 2026

Superseded by #1092 (now merged).

@Wirasm Wirasm closed this Apr 12, 2026
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.

1 participant