Skip to content

Commit b799b74

Browse files
committed
Add environment variable support to CLI and Docker configuration
Signed-off-by: Jiaxiao (mossaka) Zhou <duibao55328@gmail.com>
1 parent 4021adc commit b799b74

5 files changed

Lines changed: 146 additions & 24 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This is a firewall for GitHub Copilot CLI (package name: `@github/awf`) that pro
99
### Documentation Files
1010

1111
- **[README.md](README.md)** - Main project documentation and usage guide
12+
- **[ENVIRONMENT.md](ENVIRONMENT.md)** - Environment variable configuration and security best practices
1213
- **[LOGGING.md](LOGGING.md)** - Comprehensive logging documentation
1314
- **[docs/LOGGING_QUICKREF.md](docs/LOGGING_QUICKREF.md)** - Quick reference for log queries and monitoring
1415

ENVIRONMENT.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Environment Variables
2+
3+
## Usage
4+
5+
```bash
6+
# Pass specific variables
7+
awf -e MY_API_KEY=secret 'command'
8+
9+
# Pass multiple variables
10+
awf -e FOO=1 -e BAR=2 'command'
11+
12+
# Pass all host variables (development only)
13+
awf --env-all 'command'
14+
```
15+
16+
## Default Behavior
17+
18+
When using `sudo -E`, these host variables are automatically passed: `GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_PERSONAL_ACCESS_TOKEN`, `USER`, `TERM`, `HOME`, `XDG_CONFIG_HOME`.
19+
20+
The following are always set/overridden: `HTTP_PROXY`, `HTTPS_PROXY` (Squid proxy), `PATH`, `DOCKER_HOST`, `DOCKER_CONTEXT` (container values).
21+
22+
Variables from `--env` flags override everything else.
23+
24+
## Security Warning: `--env-all`
25+
26+
Using `--env-all` passes all host environment variables to the container, which creates security risks:
27+
28+
1. **Credential Exposure**: All variables (API keys, tokens, passwords) are written to `/tmp/awf-<timestamp>/docker-compose.yml` in plaintext
29+
2. **Log Leakage**: Sharing logs or debug output exposes sensitive credentials
30+
3. **Unnecessary Access**: Extra variables increase attack surface (violates least privilege)
31+
4. **Accidental Sharing**: Easy to forget what's in your environment when sharing commands
32+
33+
**Excluded variables** (even with `--env-all`): `PATH`, `DOCKER_HOST`, `DOCKER_CONTEXT`, `DOCKER_CONFIG`, `PWD`, `OLDPWD`, `SHLVL`, `_`, `SUDO_*`
34+
35+
## Best Practices
36+
37+
**Use `--env` for specific variables:**
38+
```bash
39+
sudo awf --allow-domains github.com -e MY_API_KEY="$MY_API_KEY" 'command'
40+
```
41+
42+
**Use `sudo -E` for auth tokens:**
43+
```bash
44+
sudo -E awf --allow-domains github.com 'copilot --prompt "..."'
45+
```
46+
47+
⚠️ **Use `--env-all` only in trusted local development** (never in production/CI/CD)
48+
49+
**Avoid `--env-all` when:**
50+
- Sharing logs or configs
51+
- Working with untrusted code
52+
- In production/CI environments
53+
54+
## Troubleshooting
55+
56+
**Variable not accessible:** Use `sudo -E` or pass explicitly with `--env VAR="$VAR"`
57+
58+
**Variable empty:** Check if it's in the excluded list or wasn't exported on host (`export VAR=value`)

src/cli.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ program
7575
'Container image tag',
7676
'latest'
7777
)
78+
.option(
79+
'-e, --env <KEY=VALUE>',
80+
'Additional environment variables to pass to container (can be specified multiple times)',
81+
(value, previous: string[] = []) => [...previous, value],
82+
[]
83+
)
84+
.option(
85+
'--env-all',
86+
'Pass all host environment variables to container (excludes system vars like PATH, DOCKER_HOST)',
87+
false
88+
)
7889
.argument('<command>', 'Copilot command to execute (wrap in quotes)')
7990
.action(async (copilotCommand: string, options) => {
8091
// Parse and validate options
@@ -96,6 +107,20 @@ program
96107
process.exit(1);
97108
}
98109

110+
// Parse additional environment variables from --env flags
111+
const additionalEnv: Record<string, string> = {};
112+
if (options.env && Array.isArray(options.env)) {
113+
for (const envVar of options.env) {
114+
const match = envVar.match(/^([^=]+)=(.*)$/);
115+
if (!match) {
116+
logger.error(`Invalid environment variable format: ${envVar} (expected KEY=VALUE)`);
117+
process.exit(1);
118+
}
119+
const [, key, value] = match;
120+
additionalEnv[key] = value;
121+
}
122+
}
123+
99124
const config: WrapperConfig = {
100125
allowedDomains,
101126
copilotCommand,
@@ -105,8 +130,16 @@ program
105130
buildLocal: options.buildLocal,
106131
imageRegistry: options.imageRegistry,
107132
imageTag: options.imageTag,
133+
additionalEnv: Object.keys(additionalEnv).length > 0 ? additionalEnv : undefined,
134+
envAll: options.envAll,
108135
};
109136

137+
// Warn if --env-all is used
138+
if (config.envAll) {
139+
logger.warn('⚠️ Using --env-all: All host environment variables will be passed to container');
140+
logger.warn(' This may expose sensitive credentials if logs or configs are shared');
141+
}
142+
110143
// Log config with redacted secrets
111144
const redactedConfig = {
112145
...config,

src/docker-manager.ts

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,57 @@ export function generateDockerCompose(
154154
};
155155
}
156156

157+
// Build environment variables for copilot container
158+
// System variables that must be overridden or excluded (would break container operation)
159+
const EXCLUDED_ENV_VARS = new Set([
160+
'PATH', // Must use container's PATH
161+
'DOCKER_HOST', // Must use container's socket path
162+
'DOCKER_CONTEXT', // Must use default context
163+
'DOCKER_CONFIG', // Must use clean config
164+
'PWD', // Container's working directory
165+
'OLDPWD', // Not relevant in container
166+
'SHLVL', // Shell level not relevant
167+
'_', // Last command executed
168+
'SUDO_COMMAND', // Sudo metadata
169+
'SUDO_USER', // Sudo metadata
170+
'SUDO_UID', // Sudo metadata
171+
'SUDO_GID', // Sudo metadata
172+
]);
173+
174+
// Start with required/overridden environment variables
175+
const environment: Record<string, string> = {
176+
HTTP_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`,
177+
HTTPS_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`,
178+
SQUID_PROXY_HOST: 'squid-proxy',
179+
SQUID_PROXY_PORT: SQUID_PORT.toString(),
180+
HOME: process.env.HOME || '/root',
181+
PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
182+
DOCKER_HOST: 'unix:///var/run/docker.sock',
183+
DOCKER_CONTEXT: 'default',
184+
};
185+
186+
// If --env-all is specified, pass through all host environment variables (except excluded ones)
187+
if (config.envAll) {
188+
for (const [key, value] of Object.entries(process.env)) {
189+
if (value !== undefined && !EXCLUDED_ENV_VARS.has(key) && !environment.hasOwnProperty(key)) {
190+
environment[key] = value;
191+
}
192+
}
193+
} else {
194+
// Default behavior: selectively pass through specific variables
195+
if (process.env.GITHUB_TOKEN) environment.GITHUB_TOKEN = process.env.GITHUB_TOKEN;
196+
if (process.env.GH_TOKEN) environment.GH_TOKEN = process.env.GH_TOKEN;
197+
if (process.env.GITHUB_PERSONAL_ACCESS_TOKEN) environment.GITHUB_PERSONAL_ACCESS_TOKEN = process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
198+
if (process.env.USER) environment.USER = process.env.USER;
199+
if (process.env.TERM) environment.TERM = process.env.TERM;
200+
if (process.env.XDG_CONFIG_HOME) environment.XDG_CONFIG_HOME = process.env.XDG_CONFIG_HOME;
201+
}
202+
203+
// Additional environment variables from --env flags (these override everything)
204+
if (config.additionalEnv) {
205+
Object.assign(environment, config.additionalEnv);
206+
}
207+
157208
// Copilot service configuration
158209
const copilotService: any = {
159210
container_name: 'awf-copilot',
@@ -179,30 +230,7 @@ export function generateDockerCompose(
179230
// Mount copilot logs directory to workDir for persistence
180231
`${config.workDir}/copilot-logs:${process.env.HOME}/.copilot/logs:rw`,
181232
],
182-
environment: {
183-
HTTP_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`,
184-
HTTPS_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`,
185-
SQUID_PROXY_HOST: 'squid-proxy',
186-
SQUID_PROXY_PORT: SQUID_PORT.toString(),
187-
// Preserve important env vars
188-
HOME: process.env.HOME || '/root',
189-
// Use container's PATH, not host's PATH
190-
PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
191-
// Docker socket path - override host's DOCKER_HOST to use mounted socket
192-
// Clean .docker config is mounted over $HOME/.docker to prevent reading host's context
193-
DOCKER_HOST: 'unix:///var/run/docker.sock',
194-
// Force default context to prevent Docker CLI from using host's context (e.g., desktop-linux)
195-
// which may point to incorrect socket paths like ~/.docker/run/docker.sock
196-
DOCKER_CONTEXT: 'default',
197-
// Pass through GitHub authentication tokens
198-
...(process.env.GITHUB_TOKEN && { GITHUB_TOKEN: process.env.GITHUB_TOKEN }),
199-
...(process.env.GH_TOKEN && { GH_TOKEN: process.env.GH_TOKEN }),
200-
...(process.env.GITHUB_PERSONAL_ACCESS_TOKEN && { GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GITHUB_PERSONAL_ACCESS_TOKEN }),
201-
// Pass through other common environment variables
202-
...(process.env.USER && { USER: process.env.USER }),
203-
...(process.env.TERM && { TERM: process.env.TERM }),
204-
...(process.env.XDG_CONFIG_HOME && { XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME }),
205-
},
233+
environment,
206234
depends_on: {
207235
'squid-proxy': {
208236
condition: 'service_healthy',

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export interface WrapperConfig {
1111
imageRegistry?: string; // Default: 'ghcr.io/mossaka/gh-aw-firewall'
1212
imageTag?: string; // Default: 'latest'
1313
buildLocal?: boolean; // Default: false (use GHCR images)
14+
additionalEnv?: Record<string, string>; // Additional environment variables to pass to container
15+
envAll?: boolean; // Pass all host environment variables (excluding system vars)
1416
}
1517

1618
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';

0 commit comments

Comments
 (0)