From d880cdabb3cc935f9f284c7670ea7f4527f7509a Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Mon, 29 Jun 2026 11:37:38 -0400 Subject: [PATCH] chore: suppress test-fixture credential false positive; document AUTHZ-002 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a machine-level suppression for the fake Datadog key in the test_team_credentials encryption-roundtrip fixture (CWE-798, not a real credential). Clarify that the within-org thread-write path is intentional by design (AUTHZ-002) — comment only, no behavior change. --- .security-review/suppressions.json | 26 ++++++++++++++++++++++++++ agent/dashboard/thread_api.py | 2 ++ 2 files changed, 28 insertions(+) diff --git a/.security-review/suppressions.json b/.security-review/suppressions.json index e13ea036..9e754dcb 100644 --- a/.security-review/suppressions.json +++ b/.security-review/suppressions.json @@ -19,6 +19,32 @@ "suppression_justification": "ACCEPTED LOW residual, metadata-only. fetch-config.sh materializes the .env via `batch-get-secret-value --filters Key=name,Values=open-swe-/`. With a name FILTER, both BatchGetSecretValue (a collection call) and ListSecrets are authorized by AWS against `*`, NOT a per-secret ARN — a prefix-scoped ARN AccessDenies the call (confirmed empirically on i-0af4e03e8bf70e6c3). So the two `*` grants are operation-level, not value-level. Secret VALUES remain strictly gated by the PREFIX-scoped GetSecretValue/DescribeSecret on secret:open-swe-/* (GetSecretValue is checked per-secret even within the batch), so cross-env VALUE isolation is preserved; only NAMES/tags/descriptions are enumerable, within Sea Haven's own single-tenant account 328440206208. Confirmed by GPT-4.1 IAM cross-review (BLOCK: none) and the iac-iam detector (one low residual, no critical/high). Future hardening to eliminate BOTH `*` grants: switch fetch-config.sh to an explicit `--secret-id-list` (no filter), which lets BatchGetSecretValue be prefix-scoped and needs no ListSecrets.", "owner": "adam@seahavenind.com", "added": "2026-06-26" + }, + { + "id": "gitleaks-generic-api-key-89", + "title": "Hardcoded credential flagged in encryption-roundtrip test fixture (CWE-798)", + "file": "tests/test_team_credentials.py", + "line": 89, + "rule": "CWE-798", + "severity": "low", + "status": "false-positive", + "justification": "Test fixture, not a real credential. The value \"secret-api-1234\" is a fake Datadog API key used by test_datadog_roundtrip_and_redaction to assert that the plaintext key is recoverable after an encrypt/decrypt round-trip (and that the stored record holds ciphertext, not the plaintext). It is never a live secret and is scoped to the unit test only.", + "suppression_justification": "Test fixture, not a real credential. The value \"secret-api-1234\" is a fake Datadog API key used by test_datadog_roundtrip_and_redaction to assert that the plaintext key is recoverable after an encrypt/decrypt round-trip. It is never a live secret and is scoped to the unit test only.", + "owner": "adam@seahavenind.com", + "added": "2026-06-29" + }, + { + "id": "gitleaks-generic-api-key-79", + "title": "Hardcoded credential flagged in encryption-roundtrip test fixture (CWE-798)", + "file": "tests/test_team_credentials.py", + "line": 79, + "rule": "CWE-798", + "severity": "low", + "status": "false-positive", + "justification": "Test fixture, not a real credential. Same fake Datadog API key \"secret-api-1234\" passed into connect_datadog by test_datadog_roundtrip_and_redaction. Never a live secret; scoped to the unit test only.", + "suppression_justification": "Test fixture, not a real credential. Same fake Datadog API key \"secret-api-1234\" passed into connect_datadog by test_datadog_roundtrip_and_redaction. Never a live secret; scoped to the unit test only.", + "owner": "adam@seahavenind.com", + "added": "2026-06-29" } ] } diff --git a/agent/dashboard/thread_api.py b/agent/dashboard/thread_api.py index cdad0161..e99ac78d 100644 --- a/agent/dashboard/thread_api.py +++ b/agent/dashboard/thread_api.py @@ -1206,6 +1206,8 @@ async def send_dashboard_message( raise HTTPException(404, "thread not found") from exc metadata = thread.get("metadata") if isinstance(thread.get("metadata"), dict) else {} + # AUTHZ-002 (intentional): any org-gated member who can read a surfaced thread + # may also post into it; non-owners are attributed via _attribution_prefix. _assert_thread_readable(metadata) prompt = f"{_attribution_prefix(metadata, login, email)}{body.content.strip()}"