Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions tests/tools/test_local_env_blocklist.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,59 @@ def test_registry_derived_vars_are_stripped(self):
for var in registry_vars:
assert var not in result_env, f"{var} leaked into subprocess env"

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_ROLE_ARN": "arn:aws:iam::123456789012:role/example",
}
result_env = _run_with_env(extra_os_env=general_chain)

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."""
extra_provider_vars = {
Expand Down Expand Up @@ -213,6 +266,36 @@ def test_registry_vars_are_in_blocklist(self):
f"(provider={pconfig.id}) missing from blocklist"
)

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_PROFILE",
"AWS_DEFAULT_REGION",
"AWS_REGION",
"AWS_SHARED_CREDENTIALS_FILE",
"AWS_CONFIG_FILE",
"AWS_WEB_IDENTITY_TOKEN_FILE",
"AWS_ROLE_ARN",
}
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)
must also be in the blocklist."""
Expand Down
23 changes: 23 additions & 0 deletions tools/environments/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,27 @@ 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_BEARER_TOKEN_BEDROCK",
})


def _build_provider_env_blocklist() -> frozenset:
"""Derive the blocklist from provider, tool, and gateway config."""
Expand All @@ -84,6 +105,8 @@ def _build_provider_env_blocklist() -> frozenset:
from hermes_cli.auth import PROVIDER_REGISTRY
for pconfig in PROVIDER_REGISTRY.values():
blocked.update(pconfig.api_key_env_vars)
if pconfig.auth_type == "aws_sdk":
blocked.update(_AWS_SDK_CREDENTIAL_ENV_VARS)
if pconfig.base_url_env_var:
blocked.add(pconfig.base_url_env_var)
except ImportError:
Expand Down
Loading