fix(#3961): complete cloud-credential scrub set (AWS container FULL_URI + Azure identity family)

Round-3 gate found two more credential env vars the agent resolves cross-profile
that the scrub set missed, verified against the installed agent source:
- AWS_CONTAINER_CREDENTIALS_FULL_URI — read by model_switch._has_aws_creds:1360
  (botocore container-credentials provider; RELATIVE_URI was present, FULL_URI
  was not).
- Azure identity family — AZURE_CLIENT_ID / AZURE_CLIENT_SECRET / AZURE_TENANT_ID
  / AZURE_FEDERATED_TOKEN_FILE (agent/azure_identity_adapter.py service-principal
  + workload-identity model auth) and AZURE_FOUNDRY_API_KEY
  (runtime_provider.py).

Enumerated the agent's full credential-env read surface from source to close the
whole class in one pass rather than one var per gate round. Deliberately EXCLUDES
config/region vars (AWS_REGION, AWS_DEFAULT_REGION, AZURE_FOUNDRY_BASE_URL) — the
child probe may legitimately need those. Regressions extended to pin FULL_URI +
the Azure family.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
This commit is contained in:
nesquena-hermesandrodboev committed 2026-06-22 19:27:00 +00:00
1 parent 4eb069be97
commit bf548a255d
2 files changed
+23 -1

No files matched your search

+15 -1
View File
@@ -890,19 +890,33 @@ def filter_runtime_env_for_gateway_parity(env: dict[str, str]) -> dict[str, str]
# would miss them). Fail-closed list — verified against the installed agent:
# CUSTOM_API_KEY hermes_cli/models.py (generic custom provider key)
# AZURE_ANTHROPIC_KEY hermes_cli/runtime_provider.py (Azure-hosted Anthropic)
# AZURE_FOUNDRY_API_KEY hermes_cli/runtime_provider.py (Azure Foundry key)
# AZURE_* identity family agent/azure_identity_adapter.py (service-principal /
# workload-identity model auth)
# AWS_BEARER_TOKEN_BEDROCK hermes_cli/model_switch.py (Bedrock bearer token)
# AWS_* agent/bedrock_adapter.py (boto3 credential chain)
# AWS_* credential chain agent/bedrock_adapter.py + model_switch._has_aws_creds
# (boto3 access keys, session token, profile,
# container/web-identity credential providers)
# NOTE: region/base-url config vars (AWS_REGION, AWS_DEFAULT_REGION,
# AZURE_FOUNDRY_BASE_URL) are deliberately NOT included — they're configuration,
# not credentials, and the child probe may legitimately need them.
# Stripping these in a profile-scoped read prevents an empty named profile from
# inheriting the server-process credential (#3961 residual cross-profile leak).
_NON_REGISTRY_AGENT_CREDENTIAL_ENV_NAMES: tuple[str, ...] = (
"CUSTOM_API_KEY",
"AZURE_ANTHROPIC_KEY",
"AZURE_FOUNDRY_API_KEY",
"AZURE_CLIENT_ID",
"AZURE_CLIENT_SECRET",
"AZURE_TENANT_ID",
"AZURE_FEDERATED_TOKEN_FILE",
"AWS_BEARER_TOKEN_BEDROCK",
"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
"AWS_SESSION_TOKEN",
"AWS_PROFILE",
"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
"AWS_CONTAINER_CREDENTIALS_FULL_URI",
"AWS_WEB_IDENTITY_TOKEN_FILE",
)
@@ -809,6 +809,7 @@ def test_account_usage_subprocess_env_strips_non_registry_agent_creds(monkeypatc
monkeypatch.setenv("CUSTOM_API_KEY", "process-default-custom-key")
monkeypatch.setenv("AWS_PROFILE", "process-default-aws-profile")
monkeypatch.setenv("AWS_BEARER_TOKEN_BEDROCK", "process-default-bedrock-token")
monkeypatch.setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI", "http://169.254.170.2/creds")
profiles.set_request_profile("work")
try:
@@ -821,6 +822,7 @@ def test_account_usage_subprocess_env_strips_non_registry_agent_creds(monkeypatc
assert "CUSTOM_API_KEY" not in env
assert "AWS_PROFILE" not in env
assert "AWS_BEARER_TOKEN_BEDROCK" not in env
assert "AWS_CONTAINER_CREDENTIALS_FULL_URI" not in env
def test_detached_worker_scope_scrubs_non_registry_agent_creds(monkeypatch, tmp_path):
@@ -835,14 +837,20 @@ def test_detached_worker_scope_scrubs_non_registry_agent_creds(monkeypatch, tmp_
monkeypatch.setenv("CUSTOM_API_KEY", "process-default-custom-key")
monkeypatch.setenv("AWS_PROFILE", "process-default-aws-profile")
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "process-default-aws-secret")
monkeypatch.setenv("AZURE_CLIENT_SECRET", "process-default-azure-secret")
monkeypatch.setenv("AZURE_FOUNDRY_API_KEY", "process-default-foundry-key")
with profiles.profile_scope_for_detached_worker("work", "test-worker"):
assert os.environ.get("CUSTOM_API_KEY") is None
assert os.environ.get("AWS_PROFILE") is None
assert os.environ.get("AWS_SECRET_ACCESS_KEY") is None
assert os.environ.get("AZURE_CLIENT_SECRET") is None
assert os.environ.get("AZURE_FOUNDRY_API_KEY") is None
assert config._thread_local_env_value("CUSTOM_API_KEY") == ""
assert os.environ.get("CUSTOM_API_KEY") == "process-default-custom-key"
assert os.environ.get("AWS_PROFILE") == "process-default-aws-profile"
assert os.environ.get("AWS_SECRET_ACCESS_KEY") == "process-default-aws-secret"
assert os.environ.get("AZURE_CLIENT_SECRET") == "process-default-azure-secret"
assert os.environ.get("AZURE_FOUNDRY_API_KEY") == "process-default-foundry-key"