TL;DR
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 là flag bảo mật của Claude Code, introduced ở v2.1.83 và mở rộng dần đến nay. Khi bật, CLI làm ba việc song song. Một, strip một danh sách 21 biến môi trường nhạy cảm (Anthropic API keys, AWS, GCP, Azure, GitHub Actions tokens, SSH signing key) khỏi env của mọi subprocess mà nó spawn (Bash tool, hook, MCP stdio server). Hai, trên Linux kích hoạt subprocess sandboxing với PID namespace isolation (introduced v2.1.98). Ba, ép permission mode về default ở thời điểm khởi tạo session, bỏ qua --permission-mode, --dangerously-skip-permissions và agent frontmatter nếu chúng khác default. Bật khi máy có nhiều user hoặc chạy script bên thứ ba; cân nhắc tắt nếu máy cá nhân, single-user, và đã quản lý credentials qua file gitignore hoặc keychain.
Bối cảnh: vì sao có flag này
Claude Code là CLI agent có quyền chạy shell, đọc ghi file, gọi hook, và quản lý MCP server. Để xác thực với Anthropic API, nó cần ít nhất một trong các biến sau trong môi trường:
ANTHROPIC_API_KEYANTHROPIC_AUTH_TOKENCLAUDE_CODE_OAUTH_TOKEN- Hoặc một trong các biến gateway/cloud (Bedrock, Vertex, Foundry…)
Vấn đề: khi CLI spawn subprocess (Bash tool, hook script, MCP server), subprocess đó mặc định kế thừa toàn bộ env của parent. Điều này mở ra một class lỗ hổng âm thầm.
Vài kịch bản thực tế:
- Bạn (hoặc agent) chạy
curl -H "X-Debug: $ANTHROPIC_API_KEY" https://example.com/logđể debug. API key vừa bị log ở phía server bên kia. - Bạn dùng script Python từ npm/pip mà không tự audit. Script
os.environ.get("ANTHROPIC_API_KEY")và ghi ra/tmp/debug.log(file world-readable). - Hook git pre-commit gọi tool bên thứ ba; tool đó dump env vào stderr khi crash; CI capture stderr lưu vĩnh viễn artifact.
- Bạn dùng
claudetrên shared server với nhiều SSH user. Bất kỳ user nào nhìn được process tree của bạn (ps eww) đều có thể đọc env vars của subprocess đang chạy. - GitHub Actions workflow chạy
claude; một step bên thứ ba trong cùng job có thể đọcACTIONS_ID_TOKEN_REQUEST_TOKENvà mạo danh job.
Token Anthropic không phải loại revoke-and-forget rẻ tiền: nó gắn với billing account, một khi leak có thể dẫn đến API abuse trước khi bạn kịp rotate. Credentials AWS, GCP, Azure còn nặng hơn.
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB là phản ứng của Anthropic trước class lỗi này. Idea: thay vì tin tưởng mọi subprocess không làm bậy, mặc định loại bỏ secret trước khi spawn, plus thêm vài lớp defense-in-depth khác.
Cơ chế hoạt động
Có ba phần tách biệt, mỗi phần có scope riêng. Hiểu rõ cả ba quan trọng vì friction người dùng cảm nhận thường đến từ phần thứ ba, không phải phần đầu.
Phần A: env var scrubbing (mọi platform)
Khi CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1, trước mỗi lần CLI spawn subprocess, nó xóa 21 biến môi trường sau khỏi env truyền xuống:
ANTHROPIC_API_KEY
ANTHROPIC_AUTH_TOKEN
ANTHROPIC_FOUNDRY_API_KEY
ANTHROPIC_AWS_API_KEY
ANTHROPIC_BEDROCK_MANTLE_API_KEY
ANTHROPIC_CUSTOM_HEADERS
CLAUDE_CODE_OAUTH_TOKEN
AWS_SECRET_ACCESS_KEY
AWS_SESSION_TOKEN
AWS_BEARER_TOKEN_BEDROCK
GOOGLE_APPLICATION_CREDENTIALS
AZURE_CLIENT_SECRET
AZURE_CLIENT_CERTIFICATE_PATH
ACTIONS_ID_TOKEN_REQUEST_TOKEN
ACTIONS_ID_TOKEN_REQUEST_URL
ACTIONS_RUNTIME_TOKEN
ACTIONS_RUNTIME_URL
ALL_INPUTS
OVERRIDE_GITHUB_TOKEN
DEFAULT_WORKFLOW_TOKEN
SSH_SIGNING_KEY
Plus prefix INPUT_<NAME> cho mỗi biến (GitHub Actions expose inputs theo format INPUT_FOO_BAR), nên INPUT_ANTHROPIC_API_KEY, INPUT_AWS_SECRET_ACCESS_KEY, vv cũng bị strip.
Subprocess vẫn nhận đầy đủ các biến khác (PATH, HOME, LANG, biến app-specific…). Bạn không mất khả năng chạy bất kỳ tool nào; chỉ mất khả năng để tool đó tự nhặt secret từ env.
Phạm vi áp dụng:
- Bash tool (mọi lệnh shell CLI chạy thay bạn)
- Hook script khai báo trong
settings.json(Stop, PreToolUse, WorktreeCreate, …) - MCP server spawn theo stdio
- Các process con của process trên (env scrub không recursive ở phía CLI, nhưng vì child không có biến đó nên grandchild cũng không có)
Hai chi tiết đáng nhớ. Thứ nhất, một số biến nội bộ Claude Code (CLAUDE_CODE_OAUTH_TOKEN, CLAUDE_BG_*, OTEL_*, CLAUDE_CODE_SESSION_KIND) luôn bị strip khỏi subprocess env, kể cả khi flag OFF. Flag chỉ thêm vào danh sách 21 biến trên. Thứ hai, danh sách hard-coded trong binary; muốn scrub thêm biến custom của bạn (ví dụ token của một SaaS riêng) thì hiện chưa có cách extend, phải tự dùng env wrapping ở phía bash command.
Phần B: subprocess sandboxing (chỉ Linux, v2.1.98+)
Khi flag bật trên Linux, Claude Code spawn subprocess trong một PID namespace cô lập. Tác dụng: subprocess không nhìn thấy các process khác trong host. ps aux chạy bên trong subprocess chỉ list các process do nó (hoặc parent của nó) spawn, không có process của user khác trên cùng host.
Đi kèm là CLAUDE_CODE_SCRIPT_CAPS, flag riêng giới hạn số lần invocation script trong một session, để hạn chế abuse khi flag scrub bật mà bạn vẫn muốn chạy nhiều tool một lúc.
Trên macOS và Windows, phần B không áp dụng (kernel không có PID namespace dạng Linux). Chỉ phần A và C có hiệu lực.
Phần C: “allowed_non_write_users” hardening
Đây là phần kèm theo gây bối rối nhiều nhất. Khi CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1, ở thời điểm CLI khởi tạo permission mode (function initialPermissionModeFromCLI trong binary), nó kiểm tra flag và ép mode về default bất kể bạn truyền gì:
Permission mode forced to default — CLAUDE_CODE_SUBPROCESS_ENV_SCRUB is set
(allowed_non_write_users hardening). Declare allowedTools explicitly,
or set CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=0 to opt out.
Cụ thể: dù bạn đặt --permission-mode auto, --dangerously-skip-permissions, defaultMode: auto trong settings, hoặc agent có frontmatter permissionMode: auto, CLI vẫn start session ở mode default.
Trigger của notification: nếu user yêu cầu mode khác default (qua bất kỳ kênh nào ở trên), notification được in ra stderr với prefix ⚠. Nếu user không yêu cầu gì (mặc nhiên default), mode vẫn bị force về default nhưng silent, không có thông báo.
Ba điểm cần nắm:
- Shift+Tab cycle trong session vẫn hoạt động bình thường. Đường code xử lý Shift+Tab (
setPermissionModeWithGuards) chỉ kiểm tra auto mode availability và bypassPermissions disabled-by-settings, không kiểm tra scrub flag. Bạn có thể vào session, bấm Shift+Tab để chuyển sangautothủ công, miễn là model/plan/settings của bạn cho phép auto mode. Nhưng mỗi session mới sẽ lại start ởdefault. - “Declare allowedTools explicitly” là remediation gợi ý, không phải bypass. Error message khuyên bạn list tường minh các tool được phép trong
permissions.allowđể default mode auto-approve chúng. Việc declare allowedTools không unlock mode vềauto; mode vẫndefault, chỉ là default mode giờ silently approve các tool bạn đã list. - Cách duy nhất bypass mode forcing là tắt flag. Nếu cần
automode trong workflow chính, setCLAUDE_CODE_SUBPROCESS_ENV_SCRUB=0. Mất phần A và phần B kèm theo.
Khi nào bạn nên bật
Quyết định bật tắt phụ thuộc threat model. Một bảng tham khảo:
| Môi trường | Khuyến nghị | Lý do |
|---|---|---|
| Shared dev box (nhiều user SSH vào) | BẬT | ps eww của user khác có thể đọc env subprocess; PID namespace sandbox cô lập process tree |
| CI runner (GitHub Actions, GitLab CI…) | BẬT | Step bên thứ ba có thể đọc env, log artifact công khai; INPUT_* và ACTIONS_* cụ thể trong scrub list cho CI |
| Cloud workstation, máy mượn | BẬT | Không kiểm soát ai có physical/remote access |
| Single-user Mac/Linux cá nhân, FileVault bật | TÙY | Risk thấp; quyết theo việc bạn có chạy script bên thứ ba qua Bash tool không |
| Homelab single-user, không SSH ngoài | TÙY | Tương tự trên |
| Homelab có nhiều user share | BẬT | Same lý do shared dev box |
| Multi-cloud setup (AWS + GCP + Azure cùng máy) | BẬT mạnh | Scrub list cover sẵn tất cả các cloud provider; phần A có giá trị nhất ở đây |
Quy tắc đơn giản: nếu có bất kỳ scenario nào sau đây đúng, BẬT là an toàn hơn:
- Có user UID khác trên máy có thể đọc process list của bạn
- Bạn để Claude chạy script bạn không tự audit (npm package, gist, AI-generated code chạy lần đầu)
- Bạn dùng CI tự động chạy
claudevới credentials inject qua secret manager - Máy có credentials của nhiều cloud provider cùng lúc
- Bạn không chắc credentials hiện đang nằm ở đâu (env trực tiếp,
.envfile, keychain…)
Trade-off thực tế
Bật flag có giá phải trả, và giá đó đa phần đến từ phần C (mode forcing) chứ không phải phần A (scrub) hay phần B (sandbox).
Cost của phần A (env scrub): gần như zero cho 99% workflow. Subprocess không cần token Anthropic; chỉ CLI parent mới gọi API. Tool nào thực sự cần API key (ví dụ một MCP server kết nối Anthropic riêng) thì bạn khai báo biến đó ở config riêng cho MCP đó, không inherit từ parent env.
Cost của phần B (PID namespace sandbox): rất thấp, gần như không cảm nhận được. Trong subprocess ps aux chỉ thấy ít process hơn, đây là một benefit, không phải cost. Chỉ trở thành vấn đề nếu bạn có tool dependence vào việc list process toàn host, dạng monitoring agent embedded.
Cost của phần C (permission mode forcing):
defaultMode: autotrong settings không còn hiệu lực ở session start. Mỗi session mới phải Shift+Tab thủ công nếu muốn vào auto.- Workflow agent chạy nền (FleetView,
--bg, agent spawn từ Agent tool) inherit setting này. Agent start ởdefaultmode, có thể đứng chờ user approve tool call nếu permissions không khai báo đủ. - Hook script không truy cập được token Anthropic. Nếu bạn có hook gọi lại Anthropic API (hiếm nhưng có), nó sẽ vỡ. Workaround: pass token qua argument hoặc file thay vì env.
Một số behavior cần lưu ý:
- Shift+Tab vẫn hoạt động trong session. Bạn không bị khóa cứng trong default mode; chỉ là default mode được force khi start.
permissions.allowtrong settings tính bình thường ở default mode. Nếu bạn đã liệt kê tường minh pattern được phép (Bash(git status:*),Bash(npm test:*)…), default mode sẽ silently approve chúng. Đây là điểm mà error message hint tới khi gợi ý “Declare allowedTools explicitly”.--allowed-toolsCLI flag không bypass mode forcing. Nó chỉ là cách truyền allow list từ command line, tương đương với khai báo trong settings.
Cách opt out hoặc config
Có ba cách giảm friction:
1. Opt out hoàn toàn
Set CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=0 trong settings.json hoặc shell rc. Mất luôn phần A, B, C. Chấp nhận risk leak qua subprocess.
{
"env": {
"CLAUDE_CODE_SUBPROCESS_ENV_SCRUB": "0"
}
}
2. Giữ flag bật, declare allowedTools explicit
Giữ scrub + sandbox + mode forcing nguyên vẹn (bảo mật), nhưng cấu hình settings để default mode silently approve các tool bạn tin. Cách này keep tất cả lợi ích bảo mật, đổi lại bạn phải maintain một list tường minh.
claude --allowed-tools "Bash(git:*),Read,Edit,Write"
Hoặc trong settings.json:
{
"permissions": {
"allow": [
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(npm test:*)",
"Read",
"Edit"
]
}
}
Lưu ý: mode vẫn default. Bạn không vào auto được, chỉ là default mode bây giờ tự duyệt các pattern trên mà không hỏi.
3. Sống chung với default mode
Giữ flag bật, accept rằng mỗi session mới start ở default. Shift+Tab khi cần auto. Đây là setup an toàn nhất, đổi lại một chút friction hàng ngày.
Quyết định cho riêng bạn
Không có lời khuyên một chiều. Decision tree:
- Có ai khác đăng nhập được vào máy này không? Có thì BẬT.
- Có để CLI chạy script bạn không tự viết hay audit không (npm install hooks, AI-generated code chạy lần đầu…)? Có thì BẬT.
- Bạn dùng CI? BẬT, không bàn.
- Máy có credentials nhiều cloud provider? BẬT, vì scrub list cover sẵn AWS, GCP, Azure.
- Bạn đã quản lý credentials qua keychain hoặc gitignored folder, env chỉ load lúc cần, máy single-user, FileVault bật? Có thể TẮT, đánh đổi việc mất layer defense-in-depth.
Khi BẬT mà bị friction với defaultMode: auto: thử option 2 (declare allowedTools explicit trong settings). Đó là lựa chọn cân bằng nhất.
Một nhận xét nhỏ về behavior
Tên flag (CLAUDE_CODE_SUBPROCESS_ENV_SCRUB) phản ánh đúng phần A: env scrubbing. Nhưng nó cũng kích hoạt phần B (PID sandbox) và phần C (mode forcing), điều mà tên flag không gợi ý. Lần đầu bật, dev thường thấy defaultMode: auto bị bỏ qua và phải đọc kỹ message error mới hiểu vì sao. Phần C ở đây là một “non-write users hardening”: heuristic của binary detect khả năng có UID khác trên máy có thể đọc binary, và phản ứng bằng cách force mode an toàn nhất ở thời điểm khởi tạo.
Phần C trên máy single-user, nơi không có “non-write user” thật sự, có vẻ hơi conservative. Heuristic không thực sự kiểm tra UID trên hệ thống. Chỉ cần flag bật là force. Trên một Mac cá nhân với FileVault, không SSH server, hardening này có cảm giác hơi aggressive. Đây là feature đang maturing; có thể tương lai sẽ refine: ví dụ chỉ fire phần C khi thật sự detect được UID khác có quyền đọc, hoặc cho user opt-out hardening mà giữ scrub bật.
Không phải bug. Là trade-off design hợp lý từ Anthropic: thà conservative quá còn hơn để credentials leak. Nhưng nếu bạn là single-user và biết mình đang làm gì, biết cách workaround sẽ tiết kiệm khá nhiều ma sát hàng ngày.
Đọc thêm
- Changelog Claude Code v2.1.83 (introduction của flag) và v2.1.98 (extension thêm PID namespace sandbox + script caps).
- Tài liệu environment variables của Claude Code (search keyword
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB). - Phần
permissions.allowtrong settings reference của Claude Code, để hiểu cách declare allowedTools explicit khi default mode bị force.