From 6bebab4761e853010ad32d48e9d3aacebd72ca46 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Fri, 29 May 2026 01:29:23 -0700 Subject: [PATCH] fix(security): narrow Bedrock subprocess strip to inference bearer token only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scopes the AWS_SDK subprocess strip down from the full AWS credential chain to just AWS_BEARER_TOKEN_BEDROCK — the only Hermes-managed *inference* secret (analogous to OPENAI_API_KEY). The general AWS credential chain (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN / AWS_PROFILE / config + role pointers) is intentionally left inheritable. Why: per SECURITY.md §3.2 the local terminal is the user's trusted operator shell. Hard-blocklisting the general chain would (a) regress *every* user who runs aws/terraform/cdk/boto3 in the agent terminal — not just Bedrock users, since PROVIDER_REGISTRY is iterated unconditionally at import — and (b) be unrecoverable, because env_passthrough.py refuses to re-allow anything in _HERMES_PROVIDER_ENV_BLOCKLIST (GHSA-rhgp-j443-p4rf). The narrow strip closes the reported leak (opencode enumerating the Bedrock catalog off the leaked bearer token) with no capability loss. Keeps zapabob's self-healing auth_type=="aws_sdk" mechanism so any future SDK-cred provider is covered automatically. Tests: bearer token stripped + general chain preserved (no-regression guard), on both the runtime strip path and the blocklist-membership path. Co-authored-by: zapabob <1920071390@campus.ouj.ac.jp> --- tests/tools/test_local_env_blocklist.py | 85 ++++++++++++++++++------- tools/environments/local.py | 32 +++++----- 2 files changed, 80 insertions(+), 37 deletions(-) diff --git a/tests/tools/test_local_env_blocklist.py b/tests/tools/test_local_env_blocklist.py index 1e437911da7..0e0520387e1 100644 --- a/tests/tools/test_local_env_blocklist.py +++ b/tests/tools/test_local_env_blocklist.py @@ -93,25 +93,58 @@ class TestProviderEnvBlocklist: for var in registry_vars: assert var not in result_env, f"{var} leaked into subprocess env" - def test_aws_sdk_provider_vars_are_stripped(self): - """AWS SDK credential-chain vars must not leak to subprocesses.""" - aws_vars = { + def test_bedrock_bearer_token_is_stripped(self): + """The Bedrock-specific bearer token is a Hermes inference secret + (analogous to OPENAI_API_KEY) and must not leak into subprocesses. + + Regression for #32314: AWS_BEARER_TOKEN_BEDROCK leaked into terminal / + execute_code children because the ``bedrock`` ProviderConfig declares + ``api_key_env_vars=()`` (auth_type="aws_sdk") and the blocklist builder + only consulted that field. The reporter caught it when ``opencode + models`` run inside a Hermes terminal enumerated the entire Bedrock + catalog off the leaked bearer token. + """ + result_env = _run_with_env(extra_os_env={ + "AWS_BEARER_TOKEN_BEDROCK": "bedrock-bearer-secret", + }) + + assert "AWS_BEARER_TOKEN_BEDROCK" not in result_env, ( + "AWS_BEARER_TOKEN_BEDROCK leaked into subprocess env (see #32314)" + ) + + def test_general_aws_credential_chain_is_preserved(self): + """The GENERAL AWS credential chain must STILL pass through to + subprocesses — this is the no-regression guard for #32314. + + Per SECURITY.md §3.2 the local terminal is the user's trusted operator + shell. A user running ``aws``/``terraform``/``cdk``/``boto3`` in the + agent terminal must keep the same AWS access their own shell has. + Stripping these would (a) break every user who does AWS work in the + agent terminal — not just Bedrock users, since the registry is iterated + unconditionally — and (b) be unrecoverable, because env_passthrough.py + refuses to re-allow anything in _HERMES_PROVIDER_ENV_BLOCKLIST + (GHSA-rhgp-j443-p4rf). Only the Bedrock inference bearer token is + Hermes-managed; the rest belongs to the user. + """ + general_chain = { "AWS_ACCESS_KEY_ID": "AKIAIOSFODNN7EXAMPLE", "AWS_SECRET_ACCESS_KEY": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "AWS_SESSION_TOKEN": "session-token", "AWS_PROFILE": "production", + "AWS_DEFAULT_REGION": "us-east-1", + "AWS_REGION": "us-east-1", "AWS_SHARED_CREDENTIALS_FILE": "/home/user/.aws/credentials", "AWS_CONFIG_FILE": "/home/user/.aws/config", "AWS_WEB_IDENTITY_TOKEN_FILE": "/var/run/secrets/token", - "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI": "/v2/credentials/123", - "AWS_CONTAINER_CREDENTIALS_FULL_URI": "http://169.254.170.2/v2/credentials/123", - "AWS_CONTAINER_AUTHORIZATION_TOKEN": "container-token", - "AWS_BEARER_TOKEN_BEDROCK": "bedrock-bearer", + "AWS_ROLE_ARN": "arn:aws:iam::123456789012:role/example", } - result_env = _run_with_env(extra_os_env=aws_vars) + result_env = _run_with_env(extra_os_env=general_chain) - for var in aws_vars: - assert var not in result_env, f"{var} leaked into subprocess env" + for var, value in general_chain.items(): + assert result_env.get(var) == value, ( + f"{var} was stripped from subprocess env — this is a " + f"capability regression (see #32314 discussion)" + ) def test_non_registry_provider_vars_are_stripped(self): """Extra provider vars not in PROVIDER_REGISTRY must also be blocked.""" @@ -233,27 +266,35 @@ class TestBlocklistCoverage: f"(provider={pconfig.id}) missing from blocklist" ) - def test_aws_sdk_provider_vars_are_in_blocklist(self): - """auth_type='aws_sdk' providers rely on credential-chain vars, not API keys.""" - aws_vars = { + def test_bedrock_bearer_token_is_in_blocklist(self): + """auth_type='aws_sdk' providers contribute their Hermes-managed + inference token (the Bedrock bearer) to the blocklist, keyed off + auth_type so any future SDK-cred provider is covered automatically.""" + assert "AWS_BEARER_TOKEN_BEDROCK" in _HERMES_PROVIDER_ENV_BLOCKLIST + + def test_general_aws_chain_not_in_blocklist(self): + """The general AWS credential chain must NOT be in the blocklist — + no-regression guard for #32314. These belong to the user's trusted + operator shell (SECURITY.md §3.2), not to Hermes, and blocklisting + them would be unrecoverable via env_passthrough (GHSA-rhgp-j443-p4rf). + """ + general_chain = { "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN", - "AWS_SECURITY_TOKEN", "AWS_PROFILE", - "AWS_DEFAULT_PROFILE", + "AWS_DEFAULT_REGION", + "AWS_REGION", "AWS_SHARED_CREDENTIALS_FILE", "AWS_CONFIG_FILE", "AWS_WEB_IDENTITY_TOKEN_FILE", "AWS_ROLE_ARN", - "AWS_ROLE_SESSION_NAME", - "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", - "AWS_CONTAINER_CREDENTIALS_FULL_URI", - "AWS_CONTAINER_AUTHORIZATION_TOKEN", - "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", - "AWS_BEARER_TOKEN_BEDROCK", } - assert aws_vars.issubset(_HERMES_PROVIDER_ENV_BLOCKLIST) + leaked_block = general_chain & _HERMES_PROVIDER_ENV_BLOCKLIST + assert not leaked_block, ( + f"General AWS chain vars must stay inheritable, but these are " + f"blocklisted: {sorted(leaked_block)} (capability regression, #32314)" + ) def test_extra_auth_vars_covered(self): """Non-registry auth vars (ANTHROPIC_TOKEN, CLAUDE_CODE_OAUTH_TOKEN) diff --git a/tools/environments/local.py b/tools/environments/local.py index 6bc4caa4eb6..4cc65d80af5 100644 --- a/tools/environments/local.py +++ b/tools/environments/local.py @@ -75,22 +75,24 @@ def _resolve_safe_cwd(cwd: str) -> str: # Hermes-internal env vars that should NOT leak into terminal subprocesses. _HERMES_PROVIDER_ENV_FORCE_PREFIX = "_HERMES_FORCE_" +# Hermes-managed AWS *inference* credentials for ``auth_type="aws_sdk"`` +# providers (Bedrock). Scoped DELIBERATELY NARROW: this lists only the +# Bedrock-specific bearer token, which is a Hermes inference secret exactly +# analogous to ``OPENAI_API_KEY`` — nobody drives the ``aws``/``terraform``/ +# ``boto3`` toolchain off it, so stripping it from terminal/execute_code +# subprocesses costs no user capability. +# +# The GENERAL AWS credential chain (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, +# AWS_SESSION_TOKEN, AWS_PROFILE, and the config/role pointers) is INTENTIONALLY +# left inheritable. Per SECURITY.md §3.2 the local terminal is the user's +# trusted operator shell; the agent having the same general AWS access the +# user's own shell has is the intended posture, not a leak. Hard-blocklisting +# those vars would (a) regress every user who runs aws/terraform/cdk/boto3 in +# the agent terminal — not just Bedrock users, since the registry is iterated +# unconditionally — and (b) be unrecoverable, because env_passthrough.py +# refuses to re-allow anything in this blocklist (GHSA-rhgp-j443-p4rf). See +# issue #32314 discussion. _AWS_SDK_CREDENTIAL_ENV_VARS = frozenset({ - "AWS_ACCESS_KEY_ID", - "AWS_SECRET_ACCESS_KEY", - "AWS_SESSION_TOKEN", - "AWS_SECURITY_TOKEN", - "AWS_PROFILE", - "AWS_DEFAULT_PROFILE", - "AWS_SHARED_CREDENTIALS_FILE", - "AWS_CONFIG_FILE", - "AWS_WEB_IDENTITY_TOKEN_FILE", - "AWS_ROLE_ARN", - "AWS_ROLE_SESSION_NAME", - "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", - "AWS_CONTAINER_CREDENTIALS_FULL_URI", - "AWS_CONTAINER_AUTHORIZATION_TOKEN", - "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", "AWS_BEARER_TOKEN_BEDROCK", })