DET-BETA-001 — preview token enumeration
Rule ID: DET-BETA-001
Title: Beta preview HMAC-token brute-force / enumeration on /api/beta/preview/*
Category: beta
Last validated: 2026-06-12 (beta-launch campaign)
State: live — FLAG_BETA_MARKETING_FEEDBACK is ON in prod; endpoints are internet-reachable (CF-Access-bypassed for NDA-gated invite flow). Token-validation calls log to Raptor stdout. Monitoring starts today.
Why this detection exists
/api/beta/preview/<token>/ is the entry seam for every beta invite sent today. The token is an HMAC-SHA256 value minted per tester. The surface is intentionally internet-reachable — CF Access was removed so invitees can visit without a CF account. That means any actor who can craft or enumerate valid tokens can access preview screens and submit feedback that contaminates the rubric dataset, extract the motive copy, or probe for internal state leaks. The 2026-06-05 account-merge D1 threat-model memo established HMAC verification failure rate as a leading indicator for all token-gated surfaces; this rule operationalizes that principle here.
Telemetry source
- Raptor app logs (stdout to Heroku Logplex): every call to
/api/beta/preview/<token>/statusor/api/beta/preview/<token>/screen/*logs: beta_preview._guard: invalid_token— on failed token verification (401 path frombeta_token_verifier.py)beta_token_verifier: Console request timed out— on network errors to Console's verify endpointbeta_token_verifier: unexpected Console response status=...— on unexpected Console responses- HTTP response-code distribution from Heroku router log (
status=401,status=403,status=200) - Console app logs: Console's internal
/internal/api/verify-walkthrough-tokenhandler logs HMAC validation failures as 403 (internal_auth_failed) and invalid-token outcomes as 401. - Sentry (once
sentry_backendflag is on):RuntimeErrorfromverify_walkthrough_tokenwhen Console returns 403 surfaces to Sentry automatically.
Telemetry gap — CRITICAL: Heroku Logplex drain to a queryable store is not yet wired (prerequisite P1 from the June 4 catalog). Until the drain exists, this detection runs as a manual heroku logs grep, not an automated query. Rule is marked live but manual until the drain is wired.
Statistical method + baseline window
- Method: cardinality drift + Poisson tail on 401 responses to
/api/beta/preview/*/statusor/api/beta/preview/*/screen/*per source IP per rolling 10-minute window. - Baseline window: rolling 7 days per source-IP /24 prefix. Pre-launch baseline is zero-seeded; the absolute-floor threshold governs until 7 days of real traffic accumulate.
- Primary signal: count of distinct
<token>values attempted from one source IP per 10-minute window. Legitimate testers arrive with exactly one token (the one minted for them). An actor probing for valid tokens will exhibit high cardinality of attempted token values; successful brute-forcing is undetectable by cardinality alone, but the 401-rate surge from failed attempts is the observable seam. - Secondary signal: rate of 401 responses on the
/api/beta/preview/path across all IPs per 5-minute window vs. the rolling baseline. A sudden 5× spike in 401s (from near-zero baseline) = enumeration attempt or misconfigured invite batch.
Token space analysis
The HMAC-SHA256 token is 256 bits of keyspace. Random brute-forcing is computationally infeasible. The realistic attack surface is:
- Token prediction from minting parameters — if HMAC key (
INTERNAL_API_SECRETor equivalent signing key) is weak or leaked, tokens for known tester emails are predictable. This is a key-rotation concern for security-agent, not a detection problem. The detection's job is to observe the probing behavior. - Token harvesting from email forwarding / link sharing — a tester forwards the invite link. Not an attack; not detectable at this seam. Policy concern for operator.
- URL scanner / click-tracker bot — Postmark delivery of invite emails may trigger link-preview requests from email clients or security proxies. These are single-request 200s from known provider ASNs (Google, Microsoft, Barracuda), not 401 bursts. Distinguish by: provider ASN lookup + single-request pattern.
Threshold + expected FP rate
- Absolute threshold (pre-baseline): >= 5 distinct token values from one source IP /24 in any 10-minute window OR >= 20 401 responses to
/api/beta/preview/*from one source IP /24 in any 10-minute window. - Soft threshold (informational): >= 3 distinct token values from one source IP /24 in 10 minutes → log to
_log/as LOW. - Expected FP rate: very low. Legitimate testers make 1 token request. False positives arise from:
- Operator testing multiple invites from the same IP (known scenario; suppress from operator's VPN IPs per
user_uses_vpn) - Postmark link-preview bots (single-request pattern; not a burst)
- Mis-typed or mis-sent invite links leading a tester to reuse the same token from multiple devices (same token, not multiple tokens — cardinality stays 1)
Alert route
- HIGH (>= 5 distinct tokens or >= 20 401s per IP per 10 min):
#raxx-ops-alert-sev2-5(ET hours, 13:00–20:00 UTC) /#raxx-ops-alert-sev2(off-hours). Per-event exception to pre-launch digest rule — active token enumeration during beta launch window is a real-time finding. - MEDIUM (3–4 distinct tokens per IP per 10 min): ops@ daily digest.
- LOW (single 401 cluster without cardinality signal):
docs/detections/_log/silent log.
Escalation owner
- security-agent — if token-space attack looks sophisticated (same tester-email repeated with varying token suffixes); potential HMAC-key exposure scenario.
- sre-agent — if 401-rate spike is homogeneous across all IPs (could indicate Console's verify endpoint is down, causing all verifications to fail-open to None, producing 401s that are actually a Console outage).
- operator — if IP resolves to a known beta tester's ISP/VPN; may be a tester clicking the link multiple times or sharing it.
VPN operator cross-check
Per user_uses_vpn: Datacamp/CDN77 ASN ranges are operator VPN exits. Before filing any alert, verify source IP is not in those ranges. Script:
# synthetic check — not for production use; run manually
OPERATOR_VPN_ASNS = {"AS60068", "AS60614"} # CDN77, Datacamp
# resolve source IP ASN via maxmind or ipinfo before alerting
Test fixture / synthetic positive
See _fixtures/preview_token_enumeration_positive.json — 8 distinct token values from one synthetic IP (192.0.2.1) within 7 minutes, all returning 401, with tester emails synth-enum-01@example.test through synth-enum-08@example.test as context.
Manual query (until Heroku drain exists)
heroku logs --app raxx-api-prod --num 1500 \
| grep 'beta_preview\|beta/preview' \
| grep -E 'invalid_token|status=401'
Cluster output by the preceding X-Forwarded-For header to identify the source IP. This is a manual procedure until the drain is wired.
Baseline reset trigger
Re-baseline after 7 days of real beta-tester traffic. Once the empirical token-validation failure rate establishes a mean (expected: very close to zero from legitimate testers), switch from the absolute-floor threshold to a Poisson tail at the 99.9th percentile.