Skip to content

fix: prevent .env secret leakage into containers#419

Closed
roeeho-tr wants to merge 1 commit intoqwibitai:mainfrom
roeeho-tr:fix/prevent-env-secret-leakage
Closed

fix: prevent .env secret leakage into containers#419
roeeho-tr wants to merge 1 commit intoqwibitai:mainfrom
roeeho-tr:fix/prevent-env-secret-leakage

Conversation

@roeeho-tr
Copy link
Copy Markdown

Summary

  • Mask .env file inside the main group container by mounting /dev/null over /workspace/project/.env. Despite secrets being delivered via stdin and sanitized from environment variables by the Bash hook, the project-root bind mount exposed the .env file directly — allowing the agent to read API keys with a simple cat /workspace/project/.env.
  • Wrap JSON.parse(container_config) in db.ts with try/catch to prevent a persistent crash loop if the SQLite database contains corrupt JSON. Without this, a single corrupted row causes getAllRegisteredGroups() / getRegisteredGroup() to throw on every startup attempt with no recovery path.

Details

Secret leakage via mounted .env (HIGH severity)

The existing secret protection architecture is thorough:

  1. Secrets passed via stdin only (container.stdin.write)
  2. Deleted from input after delivery (delete input.secrets)
  3. Bash hook unsets secret env vars before every command (createSanitizeBashHook)

However, buildVolumeMounts() bind-mounts the entire project root read-only at /workspace/project, which includes .env. Since the agent runs with permissionMode: 'bypassPermissions', it can read the file directly, completely bypassing all three protections above.

Fix: Mount /dev/null over /workspace/project/.env inside the container. Docker's mount overlay behavior ensures the more specific file mount takes precedence, replacing .env with empty content.

Unguarded JSON.parse crash loop (MEDIUM severity)

getAllRegisteredGroups() and getRegisteredGroup() in db.ts call JSON.parse(row.container_config) without error handling. If the SQLite DB is corrupted (power failure, disk error), a single bad row causes an unhandled exception during loadState() at startup — creating a persistent crash loop.

Fix: Wrap in try/catch, log a warning, and skip/return undefined for corrupt rows.

Test plan

  • Verify main container cannot read .env contents (cat /workspace/project/.env should return empty)
  • Verify secrets are still delivered correctly via stdin (agent can authenticate)
  • Verify startup succeeds with a corrupted container_config row in SQLite
  • Run npx tsc --noEmit — passes cleanly

🤖 Generated with Claude Code

…ing in db

Mask the .env file inside the main group container by mounting /dev/null
over it. Despite secrets being delivered via stdin and sanitized from env
vars, the project-root bind mount exposed .env to the agent — allowing a
simple `cat /workspace/project/.env` to read API keys.

Also wrap JSON.parse(container_config) in db.ts with try/catch to prevent
a persistent crash loop if the SQLite database contains corrupt JSON.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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