Merge pull request #2 from gavrielc/claude/fix-dotenv-exposure-LEzJ8

Fix security: only expose auth vars to containers, not full .env
This commit is contained in:
gavrielc
2026-02-01 20:39:59 +02:00
committed by GitHub
3 changed files with 21 additions and 8 deletions

View File

@@ -82,7 +82,7 @@ cat .env # Should show one of:
**Apple Container Bug:** Environment variables passed via `-e` are lost when using `-i` (interactive/piped stdin).
**Workaround:** The system mounts `.env` as a file and sources it inside the container.
**Workaround:** The system extracts only authentication variables (`CLAUDE_CODE_OAUTH_TOKEN`, `ANTHROPIC_API_KEY`) from `.env` and mounts them for sourcing inside the container. Other env vars are not exposed.
To verify env vars are reaching the container:
```bash

View File

@@ -230,7 +230,7 @@ The token can be extracted from `~/.claude/.credentials.json` if you're logged i
ANTHROPIC_API_KEY=sk-ant-api03-...
```
The `.env` file is automatically mounted into the container at `/workspace/env-dir/env` and sourced by the entrypoint script. This workaround is needed because Apple Container loses `-e` environment variables when using `-i` (interactive mode with piped stdin).
Only the authentication variables (`CLAUDE_CODE_OAUTH_TOKEN` and `ANTHROPIC_API_KEY`) are extracted from `.env` and mounted into the container at `/workspace/env-dir/env`, then sourced by the entrypoint script. This ensures other environment variables in `.env` are not exposed to the agent. This workaround is needed because Apple Container loses `-e` environment variables when using `-i` (interactive mode with piped stdin).
### Changing the Assistant Name

View File

@@ -111,17 +111,30 @@ function buildVolumeMounts(group: RegisteredGroup, isMain: boolean): VolumeMount
});
// Environment file directory (workaround for Apple Container -i env var bug)
// Only expose specific auth variables needed by Claude Code, not the entire .env
const envDir = path.join(DATA_DIR, 'env');
fs.mkdirSync(envDir, { recursive: true });
const envFile = path.join(projectRoot, '.env');
if (fs.existsSync(envFile)) {
fs.copyFileSync(envFile, path.join(envDir, 'env'));
const envContent = fs.readFileSync(envFile, 'utf-8');
const allowedVars = ['CLAUDE_CODE_OAUTH_TOKEN', 'ANTHROPIC_API_KEY'];
const filteredLines = envContent
.split('\n')
.filter(line => {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) return false;
return allowedVars.some(v => trimmed.startsWith(`${v}=`));
});
if (filteredLines.length > 0) {
fs.writeFileSync(path.join(envDir, 'env'), filteredLines.join('\n') + '\n');
mounts.push({
hostPath: envDir,
containerPath: '/workspace/env-dir',
readonly: true
});
}
}
if (group.containerConfig?.additionalMounts) {
for (const mount of group.containerConfig.additionalMounts) {