Raxx · internal docs

internal · gated

DET-BETA-004 — NDA acknowledgment bypass probe

Rule ID: DET-BETA-004 Title: Repeated 403 nda_not_acked responses on beta preview routes — NDA gate probe Category: beta Last validated: 2026-06-12 (beta-launch campaign) State: live — the NDA guard runs on every request via _guard() in beta_preview.py. 403 responses with {"error": "nda_not_acked"} are emitted in the response body and logged via logger.error("beta_preview._guard db_error: %s", exc) on DB failures. Manual query path until Heroku drain wired.

Why this detection exists

The beta preview walkthrough requires NDA acknowledgment before any screen content is served. The check is:

if not _nda_acked(conn, tester["tester_email"]):
    return (jsonify({"error": "nda_not_acked", "redirect_to": f"/beta/walk/{token}"}), 403)

The beta_nda_acknowledgements table is owned by Console; Raptor queries it via the Postgres connection. A 403 with nda_not_acked is the expected response for a tester who has a valid token but has not completed the NDA flow.

Two distinct adversarial patterns produce 403 clusters here:

  1. Token-valid, NDA-bypassed scraper. An actor who obtained a valid token (from a forwarded invite, a tester who shared their link) attempts to access screen content without completing the NDA flow. They receive 403s and may retry with different paths, user-agents, or methods looking for a bypass. This is distinct from the enumeration signature in DET-BETA-001 (where the token itself is invalid).

  2. NDA table unavailability probe. If beta_nda_acknowledgements is inaccessible (DB error), _guard() logs an error and returns 500. An actor who can trigger DB errors at the NDA query layer (e.g., via SQL-injection probing of the tester_email parameter passed by token verification) gets a different error code. A cluster of mixed 403/500 responses on the same token is a DB-layer probe signature.

The structural protection is correct — the guard fires. This detection confirms the guard is firing and surfaces sustained probe campaigns.

Telemetry source

Telemetry gap: Heroku drain not wired; manual query required.

Statistical method + baseline window

Threshold + expected FP rate

Alert route

Escalation owner

Invited-batch send correlation

When a batch of beta invites is sent, note the approximate UTC time. For the following 2 hours, suppress multi-token 403 alerts (they are expected — testers just received the link). Single-token bursts (>= 5 per token in 10 min) remain live.

This suppression is manual today (operator notes the send time; detection-engineer reviews the alert window). Automate once the drain is wired.

Test fixture / synthetic positive

See _fixtures/preview_nda_bypass_probe_positive.json — 7 synthetic 403 responses for token hash synth-nda-probe-token-001 (synth-tester-01@example.test) within 9 minutes, with a mix of screen endpoints probed sequentially, suggesting a scraper cycling through screens after receiving the first 403.

Manual query (until Heroku drain exists)

heroku logs --app raxx-api-prod --num 2000 \
  | grep 'beta/preview' \
  | grep 'status=403'

Group by the token slug in the URL path to identify per-token 403 clusters.

What NOT to do