From 55a647537c0adf5cc182d9282208bbd2336ab20f Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Wed, 15 Jul 2026 18:48:06 -0400 Subject: [PATCH] Require Secrets Manager key for Anthropic client Remove the silent fallback to a plaintext ANTHROPIC_API_KEY env var in both email processors; require ANTHROPIC_API_KEY_SECRET_ARN and raise if absent so a misconfigured deploy fails loudly instead of using an unmanaged key. Adapted from f175323 on security/sweep-2026-06-17. The From-header sender-domain allowlist from that commit is intentionally dropped: the From header is spoofable (INFRA-107, confirmed critical) and sender authentication is being reworked in a separate PR. Refs: INFRA-107 --- lambdas/po/email_processor/handler.py | 23 +++++++++++++++-------- lambdas/wo/email_processor/handler.py | 23 +++++++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/lambdas/po/email_processor/handler.py b/lambdas/po/email_processor/handler.py index 9c5b620..6063c65 100644 --- a/lambdas/po/email_processor/handler.py +++ b/lambdas/po/email_processor/handler.py @@ -211,14 +211,21 @@ The Coupa commodity/category field if present in the email (e.g., "Maintenance - def get_anthropic_client() -> anthropic.Anthropic: - """Create Anthropic client, fetching API key from Secrets Manager.""" - if ANTHROPIC_API_KEY_SECRET_ARN: - secrets = boto3.client("secretsmanager") - secret = secrets.get_secret_value(SecretId=ANTHROPIC_API_KEY_SECRET_ARN) - api_key = secret["SecretString"] - return anthropic.Anthropic(api_key=api_key) - # Fall back to ANTHROPIC_API_KEY env var (for local testing) - return anthropic.Anthropic() + """Create Anthropic client, fetching API key from Secrets Manager. + + Requires ANTHROPIC_API_KEY_SECRET_ARN to be set. The previous silent + fallback to a plaintext ANTHROPIC_API_KEY env var is removed: a misconfigured + deploy must fail loudly rather than quietly run on an unmanaged key. + """ + if not ANTHROPIC_API_KEY_SECRET_ARN: + raise RuntimeError( + "ANTHROPIC_API_KEY_SECRET_ARN is not set; refusing to fall back to a " + "plaintext API key. Configure the Secrets Manager ARN." + ) + secrets = boto3.client("secretsmanager") + secret = secrets.get_secret_value(SecretId=ANTHROPIC_API_KEY_SECRET_ARN) + api_key = secret["SecretString"] + return anthropic.Anthropic(api_key=api_key) def parse_raw_email(raw_bytes: bytes) -> dict: diff --git a/lambdas/wo/email_processor/handler.py b/lambdas/wo/email_processor/handler.py index 2abdc8f..f51cc5e 100644 --- a/lambdas/wo/email_processor/handler.py +++ b/lambdas/wo/email_processor/handler.py @@ -66,14 +66,21 @@ Rules: def get_anthropic_client() -> anthropic.Anthropic: - """Create Anthropic client, fetching API key from Secrets Manager if configured.""" - if ANTHROPIC_API_KEY_SECRET_ARN: - secrets = boto3.client("secretsmanager") - secret = secrets.get_secret_value(SecretId=ANTHROPIC_API_KEY_SECRET_ARN) - api_key = secret["SecretString"] - return anthropic.Anthropic(api_key=api_key) - # Fall back to ANTHROPIC_API_KEY env var (for local testing) - return anthropic.Anthropic() + """Create Anthropic client, fetching API key from Secrets Manager. + + Requires ANTHROPIC_API_KEY_SECRET_ARN to be set. The previous silent + fallback to a plaintext ANTHROPIC_API_KEY env var is removed: a misconfigured + deploy must fail loudly rather than quietly run on an unmanaged key. + """ + if not ANTHROPIC_API_KEY_SECRET_ARN: + raise RuntimeError( + "ANTHROPIC_API_KEY_SECRET_ARN is not set; refusing to fall back to a " + "plaintext API key. Configure the Secrets Manager ARN." + ) + secrets = boto3.client("secretsmanager") + secret = secrets.get_secret_value(SecretId=ANTHROPIC_API_KEY_SECRET_ARN) + api_key = secret["SecretString"] + return anthropic.Anthropic(api_key=api_key) def parse_raw_email(raw_bytes: bytes) -> dict: