Background
When the deploy/destroy tools spawn a cloud-CLI sidecar, they pass credentials to it as docker run -e NAME=value flags where value is the literal secret. See backend/src/claude/custom-tools.ts:
- Azure SP secret:
-e AZURE_CLIENT_SECRET=${config.AZURE_CLIENT_SECRET} in runDestroy (~line 711) and runBicepDeploy (~line 886), alongside tenant/client/subscription ids.
- AWS keys:
-e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} / AWS_SESSION_TOKEN in awsCliDockerArgs (~1035-1041) and runDestroyAws finalArgs (~1366-1373).
Problem / Goal
Passing secrets as -e NAME=value on the docker run command line exposes them. Command-line arguments are world-readable: any process on the host can see them via ps auxww / /proc/<pid>/cmdline, they show up in docker inspect of the sidecar, and they can be captured by process-accounting or audit logging. For a tool whose entire value proposition is holding cloud credentials, leaking the SP secret / IAM secret key this way is a meaningful hardening gap even on a single-user host.
Goal: pass secrets to the sidecar without placing their values on the argv.
Where to look
backend/src/claude/custom-tools.ts: runDestroy docker args (~703-719), runBicepDeploy docker args (~877-894), awsCliDockerArgs (~1026-1053), runDestroyAws finalArgs (~1360-1388).
docker-compose.yml:46-51 — the backend already receives these secrets via env_file: .env, so they're present in the backend process environment.
Suggested approach
Two clean options (pick one; env-passthrough is simplest):
- Env passthrough by name. Because the backend already has
AZURE_CLIENT_SECRET etc. in its own environment (env_file: .env), pass -e AZURE_CLIENT_SECRET (name only, no =value). Docker forwards the value from the backend's environment without it ever appearing on the sidecar's argv.
--env-file. Write the secrets to a 0600 temp file and pass --env-file /path; delete it in finally. More moving parts than option 1.
Apply the chosen approach consistently to all four spawn sites; keep non-secret values (region, subscription id if you consider it non-secret) as-is or move them too for uniformity.
Reproduce: run a deploy/destroy and ps auxww | grep AZURE_CLIENT_SECRET (or docker inspect the transient sidecar) — today the secret value is visible; after the fix it is not.
Acceptance criteria
- No secret value (
AZURE_CLIENT_SECRET, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) appears in any docker run argv produced by the tools.
- The sidecars still authenticate and deploy/destroy successfully.
Testing
- Assert in a unit test that the generated
dockerArgs for each handler contain no secret value (only -e NAME name-only entries, or an --env-file path).
- Manual: run a deploy and confirm
ps/docker inspect no longer shows the secret.
Out of scope
- Rotating/scoping the service principal's Azure RBAC.
- Removing the Docker-socket mount.
Sub-issues: (1) Azure SP secret passthrough, (2) AWS credential passthrough.
Background
When the deploy/destroy tools spawn a cloud-CLI sidecar, they pass credentials to it as
docker run -e NAME=valueflags wherevalueis the literal secret. Seebackend/src/claude/custom-tools.ts:-e AZURE_CLIENT_SECRET=${config.AZURE_CLIENT_SECRET}inrunDestroy(~line 711) andrunBicepDeploy(~line 886), alongside tenant/client/subscription ids.-e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}/AWS_SESSION_TOKENinawsCliDockerArgs(~1035-1041) andrunDestroyAwsfinalArgs(~1366-1373).Problem / Goal
Passing secrets as
-e NAME=valueon thedocker runcommand line exposes them. Command-line arguments are world-readable: any process on the host can see them viaps auxww//proc/<pid>/cmdline, they show up indocker inspectof the sidecar, and they can be captured by process-accounting or audit logging. For a tool whose entire value proposition is holding cloud credentials, leaking the SP secret / IAM secret key this way is a meaningful hardening gap even on a single-user host.Goal: pass secrets to the sidecar without placing their values on the argv.
Where to look
backend/src/claude/custom-tools.ts:runDestroydocker args (~703-719),runBicepDeploydocker args (~877-894),awsCliDockerArgs(~1026-1053),runDestroyAwsfinalArgs(~1360-1388).docker-compose.yml:46-51— the backend already receives these secrets viaenv_file: .env, so they're present in the backend process environment.Suggested approach
Two clean options (pick one; env-passthrough is simplest):
AZURE_CLIENT_SECRETetc. in its own environment (env_file: .env), pass-e AZURE_CLIENT_SECRET(name only, no=value). Docker forwards the value from the backend's environment without it ever appearing on the sidecar's argv.--env-file. Write the secrets to a0600temp file and pass--env-file /path; delete it infinally. More moving parts than option 1.Apply the chosen approach consistently to all four spawn sites; keep non-secret values (region, subscription id if you consider it non-secret) as-is or move them too for uniformity.
Reproduce: run a deploy/destroy and
ps auxww | grep AZURE_CLIENT_SECRET(ordocker inspectthe transient sidecar) — today the secret value is visible; after the fix it is not.Acceptance criteria
AZURE_CLIENT_SECRET,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN) appears in anydocker runargv produced by the tools.Testing
dockerArgsfor each handler contain no secret value (only-e NAMEname-only entries, or an--env-filepath).ps/docker inspectno longer shows the secret.Out of scope
Sub-issues: (1) Azure SP secret passthrough, (2) AWS credential passthrough.