Skip to content

fix(json_api): enrich + dedupe signature-mismatch capture#21

Merged
dnplkndll merged 2 commits into
feature/explorer-endpointsfrom
fix/signature-mismatch-diagnostics
May 22, 2026
Merged

fix(json_api): enrich + dedupe signature-mismatch capture#21
dnplkndll merged 2 commits into
feature/explorer-endpointsfrom
fix/signature-mismatch-diagnostics

Conversation

@dnplkndll

Copy link
Copy Markdown

Why

ledoent/seer Sentry issue #76 ("No signature matches found") fired 118 events / 14d with zero context — no URL, no source IP, no body shape, no signature hint. Diagnosing required live SSH + clickhouse queries to identify the caller (compose-internal post-process-forwarder-errors at 172.18.0.56, hitting /v1/automation/summarize/issue with empty UA).

Future investigators shouldn't have to do that. This PR:

  1. Enriches the capture_message with a `request` context dict (url, method, remote_addr, X-Forwarded-For, User-Agent, Content-Type, Content-Length, body_len, first 200 bytes of body, first 16 chars of signature for eyeball compare).

  2. Dedupes via `scope.fingerprint = ["json_api.signature_mismatch"]` — all signature failures group into one Sentry issue regardless of body shape, so a recurring auth bug stays one fire instead of fanning out.

  3. Demotes both capture_message calls to `level="warning"`. The failure isn't critical — Sentry's caller gets a 401 and skips that auto-summary. No data loss.

  4. Defensive caps — `body_prefix` at 200 bytes, `signature_prefix` at 16 chars. Nothing leaks.

Doesn't change

The auth logic itself is bit-identical. Only diagnostics around failures change.

Tests (3 new cases in `TestSignatureFailureDiagnostics`)

  • `test_bad_prefix_capture_includes_request_context` — "Signature did not start with rpc0:" carries the enriched request dict
  • `test_no_match_capture_enriches_via_scope_and_fingerprint` — verifies push_scope + set_context + fingerprint + warning level
  • `test_signature_failure_context_does_not_leak_full_signature` — pins the 16-char cap on the signature prefix

All existing signature tests stay green.

Out of scope (separate investigation)

This PR doesn't fix why the post-process-forwarder occasionally produces an unsignable request (8/day average, sporadic clusters). With the enriched context, the next investigator can correlate body_len + body_prefix across events to pin the root cause — likely urllib3 connection-pool reuse or a sporadic body-mutation. The single-fingerprint dedup means the storm collapses to one fire so the noise stops in production immediately.

dnplkndll added 2 commits May 21, 2026 21:34
The signature-failure capture_message landed in Sentry as 118
events / 14d (ledoent/seer issue #76) with zero context — no URL,
no source IP, no body shape. Diagnosis required live SSH + clickhouse
queries to identify the caller (compose-internal post-process-forwarder
auto-summary). Investigators shouldn't have to do that.

This PR enriches the capture and stops the storm:

  * Adds a `request` context dict to both signature-failure branches
    (bad-prefix + no-match-found) carrying url, method, remote_addr,
    X-Forwarded-For, User-Agent, Content-Type, Content-Length,
    body_len, first 200 bytes of body, and the first 16 chars of the
    signature for quick eyeball comparison.

  * Uses `sentry_sdk.push_scope() + scope.fingerprint = [...]` to
    group all signature mismatches under one Sentry fingerprint
    regardless of body shape, so a recurring auth issue stays one
    fire instead of fanning out into many.

  * Demotes both capture_message calls to `level="warning"`. The
    failure ISN'T critical — Sentry's caller gets a 401 and skips
    the auto-summary for that issue. No data loss.

  * Defensively caps `body_prefix` at 200 bytes and `signature_prefix`
    at 16 chars so the context doesn't carry anything that could
    leak in transit.

Doesn't change the auth logic itself — only the diagnostics around
failures. The signing/verifying code path stays bit-identical.

Tests (3 new cases in `TestSignatureFailureDiagnostics`):
  * bad-prefix capture carries the request context
  * no-match capture goes through push_scope + sets fingerprint
    + uses level="warning"
  * full signature hex doesn't leak into the captured context
Bugbot pass on PR #21. Findings:

  1. DRY: bad-prefix branch used `contexts={"request": ...}` directly on
     capture_message; no-match branch used push_scope + set_context +
     fingerprint. Two patterns for the same failure class.

  2. Missing test: short-signature (<= 16 chars after rpc0:) path was
     not exercised — the conditional `signature[:16] + "..." if len(signature)
     > 16 else signature` had no positive-case test for the else branch.

Changes:

  - Extract `_capture_signature_failure(message, *, body, signature, fingerprint)`
    helper. Both call sites go through it.
  - Bad-prefix branch now gets its own fingerprint
    ("json_api.signature_bad_prefix") + push_scope + warning level for
    symmetry with the no-match case. Distinct fingerprints keep them as
    separate Sentry issues.
  - Test renamed `test_bad_prefix_capture_includes_request_context`
    -> `test_bad_prefix_capture_enriches_via_scope_and_fingerprint`,
    now asserts the new contract.
  - New `test_short_signature_no_ellipsis` pins the else-branch behavior
    (signature_prefix is the raw value when <= 16 chars).

5 tests pass (was 4 + 1 strict-mode regression test).
@dnplkndll dnplkndll merged commit 0e2adec into feature/explorer-endpoints May 22, 2026
6 checks passed
@dnplkndll dnplkndll deleted the fix/signature-mismatch-diagnostics branch May 22, 2026 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant