TL;DR

CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 là flag bảo mật có từ Claude Code v2.1.83, được mở rộng dần. Khi bật, nó strip 21 biến môi trường nhạy cảm (Anthropic, AWS, GCP, Azure, GitHub Actions, SSH signing key) khỏi env của subprocess do CLI spawn ra (Bash tool, hook, MCP stdio server). Trên Linux, nó còn kích hoạt subprocess sandbox với PID namespace isolation từ v2.1.98. Và nó ép permission mode về default ở thời điểm session khởi tạo, bỏ qua --permission-mode, --dangerously-skip-permissions, agent frontmatter, nếu các giá trị này khác default. Bật nếu bạn dùng máy chia sẻ hoặc chạy script không tự audit. Có thể tắt nếu bạn dùng máy cá nhân, single-user, đã quản lý credentials qua keychain hoặc file gitignored.

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, quản lý MCP server. Để xác thực với Anthropic API, nó cần một trong các biến ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, CLAUDE_CODE_OAUTH_TOKEN, hoặc một biến gateway cho Bedrock / Vertex / Foundry trong môi trường.

Vấn đề nằm ở subprocess. Khi CLI spawn Bash command, hook script, MCP server, subprocess đó mặc định kế thừa toàn bộ env của parent. Đây là class lỗ hổng âm thầm, hiện ra trong những tình huống rất bình thường.

Bạn (hoặc agent) chạy curl -H "X-Debug: $ANTHROPIC_API_KEY" https://example.com/log để debug một thứ gì đó. API key vừa bị log ở phía server bên kia. Bạn dùng một script Python từ npm hoặc pip mà chưa kịp audit; script đó đọc os.environ.get("ANTHROPIC_API_KEY") rồi ghi ra /tmp/debug.log. Một hook git pre-commit gọi tool bên thứ ba, tool crash và dump env ra stderr, CI capture stderr và lưu artifact công khai. Bạn dùng claude trên shared server với nhiều SSH user khác; bất kỳ ai nhìn được process tree của bạn (ps eww) đều đọc được env của subprocess đang chạy. GitHub Actions workflow chạy claude, một step bên thứ ba trong cùng job đọc ACTIONS_ID_TOKEN_REQUEST_TOKEN và mạo danh job.

Token Anthropic không dễ rotate. Nó gắn billing account, leak ra có thể bị abuse trước khi bạn kịp invalidate. Credentials AWS, GCP, Azure thì còn nặng hơn nữa. Flag này là phản ứng của Anthropic trước class lỗi đó: thay vì tin subprocess tử tế, mặc định loại bỏ secret trước khi spawn, kèm thêm vài lớp defense-in-depth.

Cơ chế hoạt động

Flag không chỉ làm một việc. Nó làm ba việc cùng lúc, mỗi việc có scope riêng. Hiểu cả ba là chìa khoá vì friction người dùng cảm nhận thường đến từ phần thứ ba chứ không phải hai phần đầu.

Env var scrubbing (mọi platform)

Khi flag bật, trước mỗi lần CLI spawn subprocess, nó xoá 21 biến môi trường nhạy cảm 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

Mỗi biến trong danh sách trên còn được scrub thêm dạng prefix INPUT_<NAME> (vì GitHub Actions expose inputs theo format INPUT_FOO_BAR), nên INPUT_ANTHROPIC_API_KEY, INPUT_AWS_SECRET_ACCESS_KEY cũng biến mất khỏi subprocess.

Subprocess vẫn nhận đủ PATH, HOME, LANG, biến app-specific. Bạn không mất khả năng chạy tool nào, chỉ mất khả năng để tool đó tự nhặt secret từ env. Scope áp dụng cho Bash tool, mọi hook script khai báo trong settings.json (Stop, PreToolUse, WorktreeCreate…), MCP server spawn theo stdio, và process con của tất cả những thứ 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ó.

Có hai chi tiết người dùng hay bỏ qua. 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 21 biến trên vào danh sách. Và danh sách này hard-coded trong binary, bạn không extend được; nếu muốn scrub token của SaaS riêng, phải tự wrap env ở phía bash command.

Subprocess sandboxing (chỉ Linux, v2.1.98+)

Trên Linux, khi flag bật, Claude Code spawn subprocess trong một PID namespace cô lập. 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.

Cùng đợt release đó, Anthropic thêm flag riêng CLAUDE_CODE_SCRIPT_CAPS để giới hạn số lần invocation script trong một session, dùng cho trường hợp 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 này không áp dụng vì kernel không có PID namespace dạng Linux. Bạn vẫn được phần một (env scrub) và phần ba (mode forcing), không có sandbox layer.

”allowed_non_write_users” hardening

Đây là phần kèm theo gây bối rối nhất. Khi flag bật, ở 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.

Dù bạn đặt --permission-mode auto, --dangerously-skip-permissions, defaultMode: auto trong settings, hay agent có frontmatter permissionMode: auto, CLI vẫn start session ở mode default. Nếu bạn yêu cầu mode khác qua bất kỳ kênh nào ở trên, notification ở trên được in ra stderr kèm prefix . Còn nếu bạn không yêu cầu gì (mặc nhiên đã là default), mode vẫn bị force về default nhưng silent, không có thông báo.

Có vài hiểu nhầm rất phổ biến quanh phần này. Thứ nhất, Shift+Tab cycle trong session vẫn chạy bình thường: function 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 vào session rồi Shift+Tab chuyển sang auto thủ công vẫn được, miễn model/plan/settings cho phép auto mode. Chỉ là session mới luôn start ở default.

Thứ hai, dòng “Declare allowedTools explicitly” trong message là gợi ý cách giảm friction, không phải bypass. Nếu bạn liệt kê tường minh các tool được phép trong permissions.allow, default mode sẽ auto-approve chúng. Mode vẫn default, không phải auto. Sự khác biệt nhỏ nhưng quan trọng.

Thứ ba, cách duy nhất để mode không bị force về default là tắt flag đi. Nếu workflow chính của bạn cần auto mode liên tục, set CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=0 và chấp nhận mất luôn phần một và phần hai.

Khi nào bạn nên bật

Quyết định phụ thuộc threat model:

Môi trườngKhuyến nghịLý do
Shared dev box (nhiều user SSH vào)BẬTps eww của user khác đọc được env subprocess; PID namespace sandbox cô lập process tree
CI runner (GitHub Actions, GitLab CI…)BẬTStep bên thứ ba có thể đọc env, log artifact công khai; INPUT_*ACTIONS_* đã trong scrub list
Cloud workstation, máy mượnBẬTKhông kiểm soát được ai có physical/remote access
Single-user Mac/Linux cá nhân, FileVault bậtTÙYRisk thấp; phụ thuộc bạn có chạy script bên thứ ba qua Bash tool không
Homelab single-user, không SSH ngoàiTÙYTương tự trên
Homelab nhiều user shareBẬTSame lý do shared dev box
Multi-cloud (AWS + GCP + Azure cùng máy)BẬT mạnhScrub list cover sẵn tất cả các cloud provider

Nếu có user UID khác trên máy đọc được process list, hoặc bạn để Claude chạy script chưa tự audit (npm install hooks, gist, AI-generated code chạy lần đầu), hoặc bạn dùng CI tự động chạy claude với credentials inject qua secret manager, hoặc máy có credentials nhiều cloud provider, bật là an toàn hơn. Nếu bạn không chắc credentials hiện đang nằm ở đâu (env trực tiếp, .env file, keychain…), cũng cứ bật trước, debug sau.

Trade-off thực tế

Cost không đối xứng. Phần env scrub gần như không cost: 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ụ MCP server kết nối Anthropic riêng) thì khai báo biến đó ở config riêng của MCP đó, không inherit từ parent env. PID namespace sandbox cũng không cảm nhận được trong workflow thường: subprocess nhìn ít process hơn thì lại là một lợi ích, không phải cost. Nó 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 thật nằm ở phần mode forcing. defaultMode: auto trong settings mất 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, claude --bg, agent spawn từ Agent tool) inherit setting này, agent start ở default mode và có thể đứng chờ bạn approve tool call nếu permissions chưa 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 là pass token qua argument hoặc file thay vì env.

Vài điểm dễ tưởng lầm. Shift+Tab không bị khoá, chỉ là default mode được force khi start. permissions.allow trong settings vẫn tính bình thường ở default mode: nếu bạn liệt kê pattern (Bash(git status:*), Bash(npm test:*)…), default mode silently approve chúng, không hỏi gì. Đây cũng là cái mà error message hint tới khi nói “Declare allowedTools explicitly”. --allowed-tools CLI flag không bypass được mode forcing, nó chỉ là cách truyền allow list từ command line, tương đương khai báo trong settings.

Cách opt out hoặc giảm friction

Ba lối thoát, mỗi cái đánh đổi khác.

Cách triệt để nhất là opt out hoàn toàn. Set CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=0 trong settings.json hoặc shell rc. Mất luôn cả ba phần. Chấp nhận risk leak qua subprocess.

{
  "env": {
    "CLAUDE_CODE_SUBPROCESS_ENV_SCRUB": "0"
  }
}

Cách cân bằng nhất là giữ flag bật và declare allowedTools tường minh. Bạn giữ env scrub, sandbox, mode forcing nguyên vẹn, đổi lại settings cấu hình default mode silently approve các tool bạn tin.

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"
    ]
  }
}

Mode vẫn là default, không lên được auto, nhưng default mode bây giờ tự duyệt các pattern bạn đã list mà không hỏi.

Cách thứ ba là giữ flag bật và chấp nhận default mode mỗi session mới. Shift+Tab khi cần auto. An toàn nhất, đổi lại một chút ma sát hàng ngày.

Quyết định cho riêng bạn

Có ai khác đăng nhập được vào máy này không, có để CLI chạy script bạn không tự audit không, có dùng CI không, máy có credentials nhiều cloud provider không. Nếu trả lời “có” cho bất kỳ câu nào, bật. Nếu bạn dùng CI thì khỏi bàn, bật.

Còn nếu 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, bạn có quyền tắt và đánh đổi mất một layer defense-in-depth. Khi bật mà bị friction với defaultMode: auto, đường ra cân bằng nhất là declare allowedTools tường minh trong settings. Bạn giữ nguyên bảo mật và giảm phần lớn ma sát.

Một nhận xét nhỏ về behavior

Tên flag (CLAUDE_CODE_SUBPROCESS_ENV_SCRUB) phản ánh đúng phần env scrubbing. Nhưng cùng một cờ đó kích hoạt thêm PID sandbox và mode forcing, không có gì trong tên gợi ý chuyện đó. Lần đầu bật, người ta thường thấy defaultMode: auto bị bỏ qua, phải đọc kỹ message error mới hiểu vì sao. Phần mode forcing được Anthropic gọi là “non-write users hardening”: heuristic 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.

Trên máy single-user, nơi không có “non-write user” thật sự, hardening có cảm giác 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 Mac cá nhân với FileVault, không SSH server, mode forcing có thể aggressive hơn cần thiết. Tương lai có lẽ sẽ refine, ví dụ chỉ fire mode forcing khi detect được UID khác có quyền đọc, hoặc cho user opt-out riêng phần đó mà giữ scrub bật. Hiện tại lựa chọn là cả ba bật hoặc cả ba tắt, trừ phi đi đường workaround declare allowedTools.

Đây không phải bug. Đây là trade-off design hợp lý: 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 đường workaround sẽ tiết kiệm khá nhiều ma sát.

Đọc thêm

  • Changelog Claude Code v2.1.83 (introduction) và v2.1.98 (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.allow trong settings reference, để hiểu cách declare allowedTools khi default mode bị force.