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
8 changes: 6 additions & 2 deletions src/agentacct/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,9 @@ def mark_trusted_finding_disposition(event: dict[str, Any]) -> dict[str, Any]:
r"(?-i:pypi-AgE)[A-Za-z0-9_-]{16,}", # PyPI upload token (macaroon body)
r"(?-i:AKIA|ASIA)[A-Z0-9]{16}(?![A-Za-z0-9])", # AWS access/session key id
r"(?-i:AIza)[A-Za-z0-9_-]{16,}", # Google API key
r"xai-[A-Za-z0-9]{16,}", # xAI (Grok) API key
r"gsk_[A-Za-z0-9]{16,}", # Groq API key
r"ya29\.[A-Za-z0-9_-]{20,}", # Google OAuth 2.0 access token
)
_PROVIDER_TOKEN_PATTERN = re.compile(
r"(?:\bBearer\s+)?\b(?:" + r"|".join(_PROVIDER_TOKEN_ALTERNATIVES) + r")",
Expand Down Expand Up @@ -533,8 +536,9 @@ def mark_trusted_finding_disposition(event: dict[str, Any]) -> dict[str, Any]:
("bearer_token", _BARE_BEARER_PATTERN),
# Family 1 members too -- self-identifying prefix, no context, no prose
# refusal. They keep their own class names because the redaction marker
# vocabulary is read downstream, and `sk-or-v1-` must be reported ahead of
# its own prefix-subset `sk-`.
# vocabulary is read downstream, and the specific `sk-ant-` / `sk-or-v1-`
# prefixes must be reported ahead of their own prefix-subset `sk-`.
("anthropic_api_key", re.compile(r"\bsk-ant-[A-Za-z0-9_-]{12,}")),
("openrouter_api_key", re.compile(r"\bsk-or-v1-[A-Za-z0-9_-]{12,}")),
("api_key", re.compile(r"\bsk-[A-Za-z0-9_-]{12,}")),
)
Expand Down
15 changes: 11 additions & 4 deletions tests/test_secret_value_redaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
# keeping the exact shape the patterns must still recognize.
FAKE_API_KEY = "sk-" + "fakeonly" + "0" * 32 + "AbCd"
FAKE_OPENROUTER_KEY = "sk-or-v1-" + "fakeonly" + "0" * 40 + "EfGh"
FAKE_ANTHROPIC_KEY = "sk-ant-" + "fakeonly" + "0" * 40 + "IjKl"
FAKE_BEARER_HEADER = "Bearer " + "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmYWtlIn0.c2lnbmF0dXJl"
# Credential alphabets that a fixed [A-Za-z0-9._~+/=-] charset cannot hold:
# percent-encoding and a basic-auth style colon pair.
Expand Down Expand Up @@ -124,6 +125,8 @@
("midsentence_token", f"rotated the {FAKE_BEARER_HEADER}"),
("api_key", f"OPENAI_API_KEY={FAKE_API_KEY}"),
("openrouter_key", f"OPENROUTER_API_KEY={FAKE_OPENROUTER_KEY}"),
("anthropic_key", f"ANTHROPIC_API_KEY={FAKE_ANTHROPIC_KEY}"),
("google_oauth", "ya29.FakeOnlyAbCdEfGhIjKlMnOp"),
)


Expand Down Expand Up @@ -158,6 +161,8 @@
("huggingface", "hf_" + _FAKE_BODY_20),
("replicate", "r8_" + _FAKE_BODY_20),
("vendor_opaque", "tok_" + _FAKE_BODY_20),
("xai", "xai-" + _FAKE_BODY_20), # xAI (Grok)
("groq", "gsk_" + _FAKE_BODY_20), # Groq
)

# Prose whose compound carries a BARE NUMERIC piece. RFC 6750 is the Bearer
Expand Down Expand Up @@ -280,21 +285,23 @@ def test_real_shaped_secrets_are_still_removed_from_the_stored_event(tmp_path: P
"metadata": {
"openai": FAKE_API_KEY,
"openrouter": FAKE_OPENROUTER_KEY,
"anthropic": FAKE_ANTHROPIC_KEY,
"header": FAKE_BEARER_HEADER,
},
}
)

assert recorded["metadata"]["openai"] == "[REDACTED_SECRET]"
assert recorded["metadata"]["openrouter"] == "[REDACTED_SECRET]"
assert recorded["metadata"]["anthropic"] == "[REDACTED_SECRET]"
assert recorded["metadata"]["header"] == "[REDACTED_SECRET]"
stored = _stored_text(store)
for secret in (FAKE_API_KEY, FAKE_OPENROUTER_KEY, FAKE_BEARER_HEADER):
for secret in (FAKE_API_KEY, FAKE_OPENROUTER_KEY, FAKE_ANTHROPIC_KEY, FAKE_BEARER_HEADER):
assert secret not in stored
# The openrouter shape is a prefix-superset of the plain key shape; it must
# report as the specific class, not the generic one.
# The openrouter and anthropic shapes are prefix-supersets of the plain key
# shape; each must report as its specific class, not the generic one.
classes = {row["pattern_class"] for row in recorded["metadata"]["value_redaction_fields"]}
assert classes == {"api_key", "openrouter_api_key", "bearer_token"}
assert classes == {"api_key", "openrouter_api_key", "anthropic_api_key", "bearer_token"}


def test_surrounding_prose_survives_a_redacted_secret(tmp_path: Path) -> None:
Expand Down
Loading