#11API4 · API6 · A04
Messaging & Notifications
Email/SMS/push abuse, toll-fraud, spoofing, and outbound-comms flows.
How to use this prompt
- 1Install SecureNow in your project (then optionally
npx securenow login):$ npm install securenow - 2Copy the prompt below and paste it into your AI coding agent (Claude Code, Cursor, Codex…) opened at the root of your project.
- 3It generates four files into
threat/11-messaging-notifications/— openmessaging-notifications-code-findings.html(the audit) andmessaging-notifications-detection-mitigation.html(the defenses) in your browser.
🔒Runs entirely in your environment — your codebase is never uploaded or shared. The generated HTML reports are self-contained and work offline.
The prompt
# Messaging & Notifications Threat Model — Generator Prompt
A **copy-paste prompt** for customers. Paste the entire prompt below into an AI coding agent
(Claude Code, Cursor, Codex, …) opened at the root of **any project** that sends outbound
communications (email / SMS / voice / push / in-app notifications) and has the `securenow` CLI
installed and logged in. The agent will inventory the **send surface**, build an exhaustive
**messaging & notifications** threat model mapped to the **OWASP API Security Top 10:2023**
(**API4 Unrestricted Resource Consumption**, **API6 Unrestricted Access to Sensitive Business
Flows**) and **OWASP A04:2021 Insecure Design**, audit the code for send-layer flaws, and emit a
SecureNow-branded **two-track** deliverable set in **Markdown + self-contained HTML** — a
**Detection & Mitigation** runbook (the detection rules to create, the mitigation commands to
run, how to test each one) and a separate **Code Findings & Recommendations** audit (the
code-level findings, audited **not** fixed) — every rule and command **grounded in the
SecureNow SDK actually installed in the repo** and emitted as **ready-to-copy** blocks, plus
which threats still need the SecureNow team.
This model owns the **outbound communication channel** as both a **cost/abuse** vector and a
**spoofing/deliverability** surface: send-flow abuse, denial-of-wallet, OTP/reset/invite
flooding & enumeration, message content/header injection, sender spoofing (SPF/DKIM/DMARC),
relay/provider misconfig, delivery-receipt webhook trust, PII leakage / mis-targeting, and
unsubscribe/preference-center IDOR. It is **scoped tight** — it does **not** re-derive general
volumetric rate-limiting, inbound-webhook mechanics, or DNS-record plumbing. Those are deferred
to numbered sibling models (see the deferral subsection). Run those too for whole-surface
coverage.
> SecureNow is fundamentally an **API / traffic** security layer (firewall, rate-limit,
> challenge, exploit-signature instant-block, ASN enrichment, forensics). For messaging its
> coverage is **MEDIUM**: it natively detects **send-abuse rate**, `api.sensitive.flow` **cost
> spikes**, and **OTP / reset / invite enumeration**, and it can **challenge / rate-limit /
> block** the abusing source at the edge. But **SPF/DKIM/DMARC, transactional-provider config,
> per-recipient send caps, and message-content sanitisation are DNS/app fixes** — SecureNow
> contains the abuser; the app/DNS change removes the weakness. Every such row pairs the
> edge-containment control **with** the app/DNS fix.
Requirements on the customer machine: `npm i -g securenow && securenow login` (admin auth +
app runtime connected). Everything else is discovered by the agent.
---
<!-- ════════════════ COPY EVERYTHING BELOW THIS LINE ════════════════ -->
# Generate a Messaging & Notifications Threat Model Report (SecureNow)
You are a senior application-security engineer specializing in outbound-communication security
and anti-abuse. Produce an **exhaustive messaging & notifications threat model for THIS
codebase**, organized along **OWASP API4:2023 (Unrestricted Resource Consumption)**,
**API6:2023 (Unrestricted Access to Sensitive Business Flows)**, and **A04:2021 (Insecure
Design)**, mapped to **SecureNow** detections and mitigations, with a ready-to-run action plan
**and** a code-level audit of every send-layer flaw you find. Every rule, flag, event name, and
SQL column you emit **must be grounded in the SecureNow SDK actually installed in this repo**
(Phase 0.5), and every detection is emitted as a **ready-to-copy** unit (SQL → save → create →
dry-run). You write **four** deliverables — **two tracks**, each as Markdown **and**
self-contained HTML — into `threat/11-messaging-notifications/` (create the folder if needed):
1. `messaging-notifications-detection-mitigation.md` — the **operational runbook**: what to run
in SecureNow (detection rules, mitigation commands, tests, runbooks).
2. `messaging-notifications-detection-mitigation.html` — the same runbook as a **self-contained**
HTML page (inline CSS + offline copy buttons, no network requests), SecureNow-branded.
3. `messaging-notifications-code-findings.md` — the **code audit**: send-layer issues found in
the codebase + recommendations (findings only, never applied).
4. `messaging-notifications-code-findings.html` — the same code audit as a **self-contained**
HTML page (inline CSS, no network requests), SecureNow-branded.
The two tracks **cross-link** each other: the gaps/instrumentation rows in the detection report
link to the relevant code finding, and each code finding/app-config fix links back to the
detection-report row it backs.
Work in the seven phases below, in order. **Never invent facts**: if something is not in the
codebase or not returned by a CLI command, say "not found" — do not guess. **Do not modify
application code.** You are auditing: every code-level fix is *described in the report*, never
applied to the repo.
**Scope discipline.** This model owns the **outbound send surface**: who/what can trigger a
paid or recipient-visible message (email / SMS / voice / push / in-app), the **authenticity** of
mail you send (SPF/DKIM/DMARC, relay), the **content** of templated messages (injection,
redirect, PII), and the **preference/unsubscribe** surface. It does **not** re-derive:
- **General volumetric rate-limiting & abuse** (raw L7 floods, per-IP/ASN request caps) →
defer to `../12-rate-limits-and-abuse/`. This model only covers floods *that map to a send*.
- **Inbound webhook mechanics** (signature schemes, replay, idempotency in general) →
defer to `../16-webhooks/`. This model only covers the **delivery-receipt / status callback**
webhook *as a trust + cost-feedback surface*.
- **DNS-record mechanics** (how SPF/DKIM/DMARC/MTA-STS records are authored & published at the
DNS layer) → defer to `../21-dns-tls-certificates/`. This model covers the **sender-spoofing
*consequence*** and names the record as the required fix, but the DNS plumbing lives there.
List those three in a **"Deferred to sibling models"** subsection (Phase 2 group L), link them
by numbered path, and only model their **send-observable** symptoms here.
---
## Phase 0 — Verify SecureNow tooling
Run and record (use `--json` where supported):
```bash
securenow doctor # connectivity must be healthy
securenow whoami # admin auth + runtime app
securenow status --json # app key(s), environment, firewall state
securenow alerts rules --json # detection rules that already exist (incl. system signature rules)
securenow automation --json # blocklist automations that already exist
securenow challenge list --json # CAPTCHA / proof-of-work challenge rules
securenow env --json # resolved SDK config (service name, endpoints)
```
If the CLI is missing or not logged in, **stop** and tell the user to run
`npm i -g securenow && securenow login`, then re-run this prompt. Capture the **app key**
(UUID) — every rule and command in the report must use it. If multiple apps exist, ask the user
which app this codebase maps to before continuing. Note the **firewall state** and any **system
signature rules** (SQLi/XSS/RCE) already present — those back the *content-injection* coverage
(CRLF/SMTP/template injection often trips XSS/SQLi signatures) and must not be duplicated.
---
## Phase 0.5 — Ground every rule & command in the INSTALLED SDK
Before writing any SQL or CLI, read the SecureNow SDK that is actually installed in this repo so
every alert rule and command is correct for THIS version — never guess flags, subcommands, event
names, or SQL columns:
```bash
cat node_modules/securenow/package.json # installed SDK version (record it in both reports)
ls node_modules/securenow # exported modules: events, sessions, register, run, …
ls node_modules/securenow/dist 2>/dev/null # built entrypoints / bundled CLI
npx securenow --help # top-level commands available in this version
npx securenow alerts rules --help # exact create flags: --name/--sql/--apps/--severity/--schedule/--nlp
npx securenow event --help # `event send` shape for synthetic tests
npx securenow ratelimit --help; npx securenow challenge --help
npx securenow blocklist --help; npx securenow automation --help; npx securenow trusted --help
```
If `node_modules/securenow` is absent, run `npm ls securenow`; if still missing, tell the user to
`npm i securenow` (or `npm i -g securenow`) and stop. EVERY command, flag, `track('…')` event
name, and SQL column you emit MUST be one the installed SDK/CLI actually exposes. If the installed
version lacks a capability this prompt references, emit the rule but annotate it
`# requires securenow >= <version>` instead of a broken command. Record the resolved version in
the appendix of BOTH reports.
In Phase 4 and Phase 5, treat `node_modules/securenow` + `--help` as the source of truth: the
`securenow/events` `track()` signatures, the `securenow alerts rules` SQL columns, and every
mitigation subcommand are discoverable there. Cross-check before emitting.
---
## Phase 1 — Inventory the send surface (codebase analysis)
Messaging security starts with knowing **every path that can emit a paid or recipient-visible
message**. Document what is **actually wired**, not what is intended. Cover at minimum:
- **Channels & providers** — email (SMTP / SendGrid / SES / Postmark / Mailgun / Resend), SMS
(Twilio / Vonage / SNS / MessageBird), voice (Twilio Voice / Plivo), push (FCM / APNs / Expo /
OneSignal / web-push), in-app/WebSocket notifications, chat (Slack/Discord/Teams webhooks).
For each: the SDK/client, the credentials source, and whether it bills per message.
- **Send endpoints catalog** — enumerate every route/handler/server-action/job that ends in a
send. Group by trigger: **public/unauthenticated** (signup, password-reset, magic-link,
contact-us, "email me a link", waitlist), **authenticated user-driven** (invite teammate,
share, referral, resend OTP, notification preferences), **system/automated** (digests,
receipts, alerts), **admin/bulk** (campaigns, broadcasts, export-and-mail). This catalog is a
report deliverable: `Trigger | Channel | Auth | Per-recipient cap? | Global cap? | Cost`.
- **Cost map (denial-of-wallet)** — for each send path, note the **unit cost** and whether a
single unauthenticated request can fan out to **N recipients** or **N messages** (loop over a
list, retries, multi-channel). Flag any path where attacker-controlled volume drives spend.
(A04 insecure-design + API4.)
- **OTP / verification / magic-link send paths** — enumerate every "send a code/link"
endpoint. For each record: is the *target* (email/phone) attacker-supplied and unauthenticated?
Is there a **per-target send cap** (cool-down), a **per-IP cap**, a **global cap**? Does the
response differ for known vs unknown accounts (**enumeration oracle**)? (API6.)
- **Recipient-targeting** — for each send, where does the recipient address come from? Caller
input (spoofable → email-bomb a victim), the authenticated user's own record (safe), or a
looked-up object (IDOR risk → mis-target). Flag any send where the caller chooses an arbitrary
destination.
- **Premium / international destination exposure (IRSF / SMS pumping)** — does any SMS/voice
path accept an **arbitrary destination number** (incl. international / premium-rate ranges)?
Is there a **destination allowlist / geo-allowlist / prefix denylist**? Carriers bill toll
fraud to *you*. (API4 / A04.)
- **Message content construction** — for each channel, how is the body/subject/header built?
Look for **user input interpolated into**: email `Subject`/`From`/`Reply-To`/arbitrary
headers (CRLF → header/SMTP injection), HTML email body (HTML/CSS injection, remote-image
tracking), SMS body, push title/body/deeplink, and **links built from `Host`/
`X-Forwarded-Host`** or unvalidated `redirect`/`next`/`return_url` params (open redirect in a
templated message). Note template engine (SSTI risk).
- **Transactional-provider & relay posture** — SMTP relay open or auth-required? Provider
configured to send only from verified domains/senders? Is a raw `nodemailer`/`smtplib`
transport pointed at an internal or third-party relay that accepts arbitrary `From`? Webhook
"send mail" admin tool reachable? (Open-relay / provider-misconfig.)
- **Sender authenticity (SPF/DKIM/DMARC)** — from config/docs/infra, what sending domains are
used? Is DKIM signing enabled in the provider? Is SPF scoped to the provider's sending hosts?
Is DMARC published at `p=reject`/`quarantine`? (Mechanics deferred to
`../21-dns-tls-certificates/`; record the **posture + gap** here.)
- **Delivery-receipt / status webhooks** — does the app ingest provider callbacks (delivered /
bounced / clicked / opened / failed / inbound-SMS)? Is the callback **signature-verified**
before its data is trusted (e.g. to mark a number valid, retry, bill, or unsubscribe)? Can a
forged callback drive cost or state? (Trust deferred-in-mechanics to `../16-webhooks/`;
the **cost/state consequence** is modeled here.)
- **Unsubscribe / preference center** — how is an unsubscribe link tokenised? Is the token
per-recipient, signed, single-purpose, and unguessable, or is it an **enumerable id / email in
the URL** (IDOR — unsubscribe or *resubscribe* anyone, read someone's prefs)? Is the
preference-update endpoint authz'd? Is there a List-Unsubscribe header / one-click endpoint?
- **PII in bodies & logs** — do message bodies, provider request payloads, error logs, or
SecureNow telemetry carry raw OTPs, reset tokens, full PII, or the recipient address
unredacted? Confirm the SDK/log pipeline redacts recipient addresses, tokens, and message
bodies before ingestion. If not, that is a high-severity finding.
- **Replay / idempotency on sends** — can a state-changing send be triggered repeatedly (retry,
double-submit, parallel requests) causing **double charge / double SMS / double email**? Is
there an idempotency key on the provider call?
- **Telemetry & SecureNow instrumentation already present** — `securenow/register` /
`securenow run` / `securenow init` (gives traffic spans automatically), any
`securenow/events` `track()` calls already emitted on send paths, and whether the firewall is
engaged. This determines what works *today* vs *after instrumentation*.
Output of this phase = the report's **Send surface & inventory** section: the send-endpoints
catalog, the **cost map**, the **OTP/verification matrix** (cap/enumeration columns), the
**recipient-targeting** table, the **premium-destination exposure** note, the **content
construction** table (per channel, what user input reaches subject/header/body/link), the
**provider/relay posture**, the **sender-authenticity (SPF/DKIM/DMARC) posture**, the
**delivery-webhook trust** note, the **unsubscribe/preference** posture, the **PII redaction
status**, and a short paragraph naming the real messaging attack surface for this stack.
---
## Phase 2 — Enumerate threats (exhaustive catalog)
Evaluate **every** threat below against the discovered surface. Each item is either **modeled**
(a row in the threat matrix) or **explicitly N/A** (one line in an "Out of scope" subsection
with the reason — e.g. "Voice items: N/A, no telephony channel"). Never silently drop an item.
Add stack-specific threats you discover that are not listed — this catalog is the floor, not the
ceiling. Tag each modeled row with its code: **API4**, **API6**, or **A04** (or "—").
**A. Denial-of-wallet & send-cost abuse (OWASP API4 / A04)**
1. SMS toll-fraud / SMS pumping (IRSF) — arbitrary/premium/international destination accepted, attacker farms paid SMS to numbers they profit from
2. Voice toll-fraud — paid outbound call/voice-OTP to premium ranges
3. Email send-cost abuse — high-volume sends through a public/auth path inflate provider spend
4. Push/notification send-cost abuse — paid push fan-out driven by attacker volume
5. Fan-out amplification — one request → N recipients/messages (list loop, multi-channel, retries) with no cap
6. Retry/idempotency cost amplification — duplicate sends from retries/double-submit/parallel requests (double charge / double SMS)
7. Provider-quota exhaustion as DoS — burning the daily send quota so legitimate transactional mail can't go out
**B. Email bombing & reflected-spam (OWASP API6 / A04)**
8. Email bombing via signup — repeatedly registering a victim's address to flood their inbox
9. Email bombing via password-reset — hammering "forgot password" for a victim address
10. Email bombing via invite/share — inviting a victim repeatedly through the invite endpoint
11. Reflected-spam via "email me a link"/contact/feedback — public send endpoint with attacker-chosen recipient
12. SMS bombing — repeated OTP/notification SMS to a victim number
13. Subscription bombing of third parties — using your app as one node in a distributed email-bomb against an external victim
**C. OTP / magic-link / verification send abuse (OWASP API6)**
14. OTP send flooding to a single target (no per-target cool-down)
15. OTP send flooding across many targets from one source (enumeration/cost)
16. Account-existence enumeration via send response (different response/timing for known vs unknown email/phone)
17. Magic-link / verification-link flooding (cost + recipient annoyance)
18. OTP/code delivery downgrade or channel-confusion abuse (force a costlier channel, e.g. voice OTP)
**D. Notification / invite / referral spam through public send endpoints (OWASP API6)**
19. Invite spam — public/low-friction invite endpoint abused to spam arbitrary addresses with your branding
20. Referral spam — referral "tell a friend" endpoint sends attacker-chosen content to arbitrary recipients
21. Comment/mention/@-notification spam — triggering notification emails to victims via app actions
22. Share/"send this to" endpoint abused to deliver attacker content under your brand
**E. Outbound content & header injection (OWASP A04, signature-detectable)**
23. CRLF header injection into email Subject/From/Reply-To/arbitrary headers (header smuggling, BCC injection)
24. SMTP command injection through unsanitised envelope/header fields
25. HTML email injection — unsanitised user input in HTML body (script in clients that render it, CSS exfil, remote-image tracking beacon)
26. Server-side template injection (SSTI) in a message template rendered with user input
27. Open redirect / link injection in a templated message (unvalidated `redirect`/`next`/`Host`-built link delivered to recipients)
28. Phishing-content injection — attacker-controlled body lets the message impersonate a trusted notice under your sending domain
**F. Sender spoofing & deliverability authenticity (OWASP A04 — DNS fix)**
29. Missing/weak SPF → anyone can send "from" your domain (brand impersonation)
30. Missing/disabled DKIM → no cryptographic origin proof; messages forgeable
31. Missing/`p=none` DMARC → no enforcement; spoofed mail still delivered
32. Subdomain / cousin-domain spoofing not covered by DMARC policy
33. Display-name / homoglyph From spoofing inside legitimately-authenticated mail
**G. Relay & provider misconfiguration (OWASP A04 / API4)**
34. Open SMTP relay — transport accepts arbitrary From/To and relays for anyone
35. Transactional provider misconfigured — sending from unverified domains / shared subdomain reputation
36. Provider/API key over-scoped or leaked enabling arbitrary sends outside the app
37. Internal "send mail" debug/admin endpoint reachable in prod with no authz
**H. Delivery-receipt / status-webhook trust (OWASP A04 — mechanics deferred to ../16-webhooks/)**
38. Unverified delivery-receipt webhook trusted to mark numbers valid / drive billing / retries
39. Forged "delivered/clicked/opened" callback poisoning engagement state or unsubscribe lists
40. Inbound-SMS / reply webhook trusted to trigger a paid auto-reply loop (cost amplification)
**I. PII leakage & mis-targeting (OWASP A04)**
41. OTP / reset token / PII in message body delivered to the wrong recipient (mis-targeting via IDOR/looked-up address)
42. PII / tokens / recipient address logged unredacted (logs, provider payloads, SecureNow telemetry)
43. Cross-recipient leak — one recipient's data rendered into another's message (template-context bleed, batch mis-merge)
44. Sensitive content (full statements, codes) sent over an insecure/low-trust channel (SMS) when policy requires otherwise
**J. Unsubscribe / preference-center abuse (OWASP API6 / A04)**
45. Unsubscribe-token IDOR — enumerable/unsigned token lets an attacker unsubscribe or *resubscribe* anyone
46. Preference-center IDOR — read/modify another user's notification preferences without authz
47. Unsubscribe-link forgery to read account existence / PII from the preference page
48. CSRF/no-confirmation one-click endpoints abused to silently change others' preferences
**K. Observable send-abuse (what telemetry actually catches — the workhorse rules)**
49. Send-flow rate spike from one IP/source (`api.sensitive.flow` flow=send count surge)
50. OTP/reset/invite request flood from one IP across the auth-send endpoints (traffic-observable POST burst)
51. Account-enumeration pattern — one IP hitting the send endpoint for many distinct targets with mixed 200/404
52. Cost-spike onset — sudden rise in send-flow events or 2xx on a send route (denial-of-wallet onset)
53. 4xx/5xx spike on a send route from one IP (probing the send endpoint / broken integration)
54. Anomalous distinct-recipient count from one source (fan-out / bombing signature)
**L. Deferred to sibling models (reference, do not re-derive)**
55. General volumetric L7 floods & raw per-IP/ASN rate limits → `../12-rate-limits-and-abuse/` (this model covers only floods that *map to a send*)
56. Inbound-webhook signature/replay/idempotency mechanics (general) → `../16-webhooks/` (this model covers the delivery-receipt callback's *cost/trust consequence* only)
57. SPF/DKIM/DMARC/MTA-STS **DNS-record authoring & publication** → `../21-dns-tls-certificates/` (this model names the record as the required fix; the DNS plumbing lives there)
> For 55–57, add **one** matrix row each marked *"deferred — see linked model"*, note the
> send-observable symptom SecureNow still catches (e.g. send-rate spike, forged-callback cost
> feedback), and link the numbered sibling. Do not duplicate their detection/mitigation here.
Every catalog item 1–57 must be either a matrix row or an explicit N/A line with a reason — do
not silently drop any item.
---
## Phase 3 — Audit the code (findings only — do not fix)
For **each** modeled threat that maps to real code, locate the responsible code and record a
**finding** for the report's "Code-level findings" section. A finding is:
- **Location** — `file:line` (clickable), the route/handler/job/template/transport name.
- **Pattern** — quote the 1–8 relevant lines. State the missing control precisely.
- **Why exploitable** — the concrete request an attacker sends and what they achieve (which
victim inbox flooded, what spend, what spoof).
- **Severity** — critical / high / medium / low (impact × reachability; weight denial-of-wallet
and victim-harm).
- **Recommended fix (described, not applied)** — the specific change. **You must not edit the
codebase.**
Look specifically for:
**Denial-of-wallet / send-cost flaws** — public/unauthenticated send paths; send loops over a
caller-supplied recipient list; SMS/voice paths accepting an arbitrary/international/premium
destination with no allowlist or geo/prefix denylist; no per-target cool-down, no per-IP cap,
no global daily cap; retries/double-submit/parallel requests with no idempotency key on the
provider call. *Recommended fixes must mention* per-target send cool-down (e.g. 1 per 60s, N per
day), per-IP and global send caps, a destination/geo allowlist or premium-prefix denylist for
SMS/voice, an idempotency key on every provider call, capping fan-out per request, and requiring
auth/proof-of-work before a paid send.
**Enumeration / send-response oracle flaws** — password-reset / OTP / magic-link / "email me a
link" handlers that return a different status, body, or timing for known vs unknown
email/phone; that reveal whether a number is reachable. *Recommended fixes must mention*
identical generic responses regardless of account existence, constant-time behaviour, and
moving the "exists?" signal out of the send response.
**Recipient-targeting / mis-target flaws** — sends where the recipient address is taken from
caller input rather than the authenticated user's own record; reset/OTP sent to an address
chosen by the request body; looked-up recipient via an IDOR-able id. *Recommended fixes must
mention* deriving the recipient from the authenticated subject (never request input for
security-sensitive sends), authz on any looked-up recipient, and confirming ownership before
sending tokens.
**Content / header / template injection flaws** — user input interpolated into email
`Subject`/`From`/`Reply-To`/custom headers without CRLF stripping (`\r`/`\n`/`%0d`/`%0a`); raw
user input concatenated into SMTP envelope; user input in HTML email body without
sanitisation/escaping; a template engine rendering user input (SSTI); links built from
`redirect`/`next`/`return_url`/`Host`/`X-Forwarded-Host` without an allowlist. *Recommended
fixes must mention* stripping/rejecting CR/LF in all header fields, using the provider's
structured API (not raw header strings), HTML-escaping/sanitising body input, never rendering
untrusted input as a template, and validating outbound links against an allowlist + a canonical
base-URL config.
**Sender-authenticity flaws** — sending domains with no DKIM signing enabled in the provider;
SMTP transport that sends `From` arbitrary domains; configuration implying SPF not scoped or
DMARC at `p=none`. *Recommended fixes must mention* enabling DKIM signing at the provider,
scoping SPF to the provider's hosts, publishing DMARC `p=quarantine`→`p=reject`, and aligning
the From domain — **note the DNS-record work is owned by `../21-dns-tls-certificates/`**.
**Relay / provider-config flaws** — `nodemailer`/`smtplib`/transport pointed at an open or
internal relay; provider configured to allow unverified senders; an internal/debug "send" route
reachable without authz; an over-scoped or hard-coded provider API key. *Recommended fixes must
mention* authenticated relays only, verified-sender enforcement, removing/guarding debug send
routes, and least-privilege scoped provider keys held in a secret store.
**Delivery-webhook trust flaws** — delivery/click/open/inbound-SMS callbacks parsed and trusted
before signature verification; a forged callback that can mark a number valid, trigger a retry,
bill, or unsubscribe; an auto-reply that the inbound webhook can loop. *Recommended fixes must
mention* verifying the provider signature on the raw body before trusting any field, idempotent
callback processing, and rate-capping any send the callback can trigger — **general webhook
mechanics owned by `../16-webhooks/`**.
**PII / logging flaws** — OTPs, reset tokens, full PII, or recipient addresses written
unredacted to logs, provider request payloads captured in telemetry, or message bodies ingested
into SecureNow without redaction. *Recommended fixes must mention* redacting recipient address +
tokens + body before logging/telemetry, never logging the secret part of an OTP/link, and
confirming the SecureNow SDK redaction config covers these fields.
**Unsubscribe / preference flaws** — unsubscribe links carrying an enumerable id or the raw
email; unsigned/guessable unsubscribe tokens; preference endpoints with no per-user authz;
one-click endpoints with no signed token / CSRF protection. *Recommended fixes must mention*
per-recipient signed single-purpose tokens (HMAC, opaque, expiring), authz on preference reads/
writes, and binding the action to the token's subject only.
If a control exists and is correct (per-target cool-down present, destination allowlist
enforced, DKIM on, callback signature verified, unsubscribe token signed), note it as a
**strength** — the posture must be honest. Absence of a control where the send surface exists is
itself a finding ("no per-target cool-down on password-reset send").
---
## Phase 4 — Map every modeled threat to SecureNow detection + mitigation
Classify each threat with exactly one coverage badge:
- 🟢 **COVERED** — detectable + mitigable with SecureNow today (existing rule, system signature
rule, or a rule you provide the SQL for, on telemetry already flowing or via a `track()` event
the app emits at the send point). Send-rate spikes, OTP/reset/invite enumeration, fan-out
bursts, and content-injection signatures land here.
- 🟡 **PARTIAL** — works after the customer adds `track('api.sensitive.flow')` /
`messaging.*` instrumentation, **or** SecureNow can only *contain the abuser at the edge*
while the real fix is **app-config** (per-target cap, destination allowlist) or **DNS**
(SPF/DKIM/DMARC). Pair the control with the app/DNS fix on every such row.
- 🔴 **GAP** — SecureNow cannot detect or mitigate this today (e.g. a forged callback emitting
no traffic SecureNow sees, a missing DKIM record). **Still include it**: give the app/DNS-level
fix, then add the line *"Requires SecureNow team — contact your SecureNow account contact (or
in-dashboard support) to request support for this threat."* Collect all gaps in the report's
"Known gaps & SecureNow feature requests" section.
> **Be honest about edge-detectable vs app/DNS fix.** SecureNow sees **traffic** and **events**;
> it contains actors via firewall / rate-limit / challenge / block / signature instant-block. It
> **cannot** see a missing SPF record, an unsigned unsubscribe token, or a forged provider
> callback that never transits the protected app. For messaging, SecureNow's native strength is
> **the send-abuse rate**: a flood of OTP/reset/invite requests or a spike in
> `api.sensitive.flow` (flow=send) cost is exactly what it catches — and it
> challenges/rate-limits/blocks the source. The **per-recipient cap, destination allowlist,
> enumeration-safe response, content sanitisation, and SPF/DKIM/DMARC** are the primary fixes;
> SecureNow is containment. A flaw that emits no traffic and no event is 🔴 until the app emits
> the event in Phase 3's recommended fix.
Use **only** the SecureNow building blocks below. Never invent CLI flags, event names, or SQL
columns.
### 4a. Instrumentation (what messaging detections feed on)
Once the app runs under `securenow run` / `securenow/register` / `securenow init`, **HTTP
traffic is captured automatically** — status codes, methods, paths, client IPs, response sizes.
That alone powers the **send-endpoint POST-burst** and **4xx/5xx-on-send** rules with no events.
But a *send* is an app-internal action traffic can't fully see (recipient, channel, cost,
whether the account existed). **Reuse the existing taxonomy first** — emit
`api.sensitive.flow` for every send (it already feeds abuse-rate detection) and
`api.ratelimit.exceeded` when the app's own send-cap rejects a request. Add `messaging.*`
events **only** where a send-specific signal genuinely adds detection value. All `track()`
calls **never throw**:
```js
const { track } = require('securenow/events');
// REUSE: every send is a sensitive business flow (API6) — feeds abuse-rate + cost-spike rules.
// flow names: otp_send | password_reset_send | magic_link_send | invite_send | referral_send |
// notification_send | sms_send | voice_send | push_send | email_send | campaign_send
track('api.sensitive.flow', { userId, ip, attributes: { flow: 'otp_send', channel: 'sms', recipient_hash: '<sha256>', cost_units: '1' } });
// REUSE: the app's own send-cap / cool-down rejected a request (per-target or per-IP cap hit).
track('api.ratelimit.exceeded', { userId, ip, attributes: { route: '/api/auth/reset', limit: '1', window: '60s', reason: 'send_cooldown' } });
// PROPOSE messaging.* ONLY where send-specific signal is needed (emit at the enforcement point):
// Enumeration probe: a send was requested for a recipient/account that does not exist.
track('messaging.enumeration.suspected', { ip, attributes: { route: '/api/auth/reset', channel: 'email', exists: 'false', recipient_hash: '<sha256>' } });
// A send to a high-cost / premium / disallowed destination was attempted or blocked (IRSF).
track('messaging.destination.blocked', { ip, attributes: { channel: 'sms', country: 'XX', prefix: '+88', reason: 'premium_range|not_allowlisted|geo_blocked' } });
// Outbound content/header injection caught at the send boundary (CRLF / SMTP / template / link).
track('messaging.content.rejected', { userId, ip, attributes: { channel: 'email', field: 'subject|header|body|link', reason: 'crlf|smtp_injection|ssti|open_redirect|html_injection' } });
// A delivery-receipt / status callback failed signature verification (forged callback).
track('messaging.callback.signature_failed', { ip, attributes: { provider: 'twilio|sendgrid|ses|fcm|custom', kind: 'delivery|click|open|inbound', reason: 'missing|invalid|expired' } });
// Unsubscribe / preference-center token abuse (IDOR / forged / enumerated token).
track('messaging.unsubscribe.abuse', { ip, attributes: { route: '/u/unsubscribe', reason: 'idor|invalid_token|enumerated|csrf' } });
```
> Hash or omit any PII before it becomes an attribute value — **never put a raw email, phone
> number, OTP, reset token, or message body in an attribute**. Use a salted `recipient_hash`,
> `country`/`prefix` (not the full number), and `exists: 'true'|'false'` (not the address). See
> the Phase 1 **PII redaction** check — attributes feed detection; they must not become a new
> leak path.
Recommended messaging event taxonomy — rules match these **exact strings**:
| Event | Emit when |
|---|---|
| `api.sensitive.flow` | **(reuse)** any send executes — `flow=*_send`, with `channel`, `recipient_hash`, `cost_units` |
| `api.ratelimit.exceeded` | **(reuse)** the app's own send cap / cool-down rejects a request (`reason=send_cooldown`) |
| `messaging.enumeration.suspected` | a send was requested for a non-existent account/recipient (`exists=false`) |
| `messaging.destination.blocked` | an SMS/voice send to a premium/disallowed/non-allowlisted destination was attempted/blocked |
| `messaging.content.rejected` | outbound content/header/link injection caught at the send boundary |
| `messaging.callback.signature_failed` | a delivery-receipt / status callback failed signature verification |
| `messaging.unsubscribe.abuse` | unsubscribe/preference token IDOR/forgery/enumeration detected |
Custom `attributes` become queryable as `attributes_string['<key>']` (e.g.
`attributes_string['flow']`, `attributes_string['channel']`, `attributes_string['country']`).
Ingest enriches every IP with **ASN/org** (`client.asn`, `client.as_org`) — enabling
botnet/datacenter-origin detection on send abuse with no extra code.
### 4b. Detection rules — SQL conventions
Two query shapes. Both **must** keep the tenant scope and **must** select an `ip` column
(per-IP aggregation is what remediation/auto-block keys on). **The tenant-scope column differs
by table** — using the wrong one fails with `UNKNOWN_IDENTIFIER`:
- **logs/events** (`signoz_logs.distributed_logs_v2`) → `resources_string['service.name'] IN (__USER_APP_KEYS__)`
- **traces/HTTP** (`signoz_traces.distributed_signoz_index_v3`) → `` `resource_string_service$$name` IN (__USER_APP_KEYS__) ``
When grouping by `ip`, add `HAVING ip != '' AND …` so rows with no client IP don't aggregate
into an empty-key bucket. Traffic columns proven available: `response_status_code`, `kind`
(server span = 2), `ts_bucket_start`, `attributes_string['http.target']`, the `client_ip`
coalesce below. Confirm any other column with a `--mode dry_run` before relying on it.
**Events-based — send-flow rate spike / denial-of-wallet onset (API4; query the logs table):**
```sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['flow'] AS flow,
attributes_string['channel'] AS channel,
count() AS sends
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] = 'api.sensitive.flow'
AND attributes_string['flow'] LIKE '%_send'
AND timestamp >= now() - INTERVAL 10 MINUTE
GROUP BY ip, flow, channel
HAVING ip != '' AND sends >= 50
```
**Events-based — fan-out / email-or-SMS bombing (one source → many distinct recipients):**
```sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['channel'] AS channel,
uniqExact(attributes_string['recipient_hash']) AS distinct_recipients,
count() AS sends
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] = 'api.sensitive.flow'
AND attributes_string['flow'] LIKE '%_send'
AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, channel
HAVING ip != '' AND distinct_recipients >= 25
```
**Events-based — OTP/reset/invite account-enumeration probe (mixed exists=false at volume):**
```sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['route'] AS route,
count() AS probes
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] = 'messaging.enumeration.suspected'
AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, route
HAVING ip != '' AND probes >= 20
```
**Events-based — IRSF / premium-destination abuse (SMS/voice toll fraud) — any hit high-signal:**
```sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['country'] AS country,
attributes_string['prefix'] AS prefix,
count() AS attempts
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] = 'messaging.destination.blocked'
AND timestamp >= now() - INTERVAL 30 MINUTE
GROUP BY ip, country, prefix
HAVING ip != '' AND attempts >= 1
```
**Traffic-based — POST burst on auth-send endpoints, no events needed (signup/reset/OTP flood):**
```sql
WITH coalesce(nullIf(attributes_string['http.client_ip'], ''), nullIf(attributes_string['net.peer.ip'], ''), nullIf(attributes_string['network.peer.address'], '')) AS client_ip
SELECT client_ip AS ip,
count() AS requests,
uniqExact(attributes_string['http.target']) AS distinct_send_paths
FROM signoz_traces.distributed_signoz_index_v3
WHERE `resource_string_service$$name` IN (__USER_APP_KEYS__)
AND timestamp >= now64(9) - INTERVAL 10 MINUTE
AND ts_bucket_start >= toUInt64(toUnixTimestamp(now() - INTERVAL 10 MINUTE)) - 1800
AND kind = 2
AND (attributes_string['http.target'] LIKE '%/reset%' OR attributes_string['http.target'] LIKE '%/forgot%' OR attributes_string['http.target'] LIKE '%/otp%' OR attributes_string['http.target'] LIKE '%/verify%' OR attributes_string['http.target'] LIKE '%/invite%' OR attributes_string['http.target'] LIKE '%/signup%')
GROUP BY ip
HAVING ip != '' AND requests >= 60
```
**Traffic-based — 4xx/5xx spike on a send route (probing the send endpoint / broken integration):**
```sql
WITH coalesce(nullIf(attributes_string['http.client_ip'], ''), nullIf(attributes_string['net.peer.ip'], ''), nullIf(attributes_string['network.peer.address'], '')) AS client_ip
SELECT client_ip AS ip,
countIf(response_status_code IN ('400','401','403','404','422','429')) AS client_errors,
countIf(response_status_code LIKE '5%') AS server_errors
FROM signoz_traces.distributed_signoz_index_v3
WHERE `resource_string_service$$name` IN (__USER_APP_KEYS__)
AND timestamp >= now64(9) - INTERVAL 10 MINUTE
AND ts_bucket_start >= toUInt64(toUnixTimestamp(now() - INTERVAL 10 MINUTE)) - 1800
AND kind = 2
AND (attributes_string['http.target'] LIKE '%/reset%' OR attributes_string['http.target'] LIKE '%/otp%' OR attributes_string['http.target'] LIKE '%/invite%' OR attributes_string['http.target'] LIKE '%/send%')
GROUP BY ip
HAVING ip != '' AND (client_errors >= 40 OR server_errors >= 25)
```
The remaining message events follow the **same logs-table shape** — swap the `event.type`
filter and the threshold: `api.ratelimit.exceeded` with `reason=send_cooldown` (≥50/15m → a
source slamming the per-target cap), `messaging.content.rejected` (≥10/15m → fuzzing the
content/header sanitiser → tie to the system signature rules below),
`messaging.callback.signature_failed` (≥5/15m → forged-callback probing, notify-only),
`messaging.unsubscribe.abuse` (≥10/15m → unsubscribe-token enumeration).
**Content/header injection (catalog E) — use the SecureNow system signature rules, don't write
SQL.** CRLF/SMTP/SSTI/HTML/XSS payloads in send fields trip the system **SQLi/XSS/RCE signature
rules** with synchronous **`instant.block`** (blocks a matching request in ~2.6s on ingest).
Confirm they're present and enabled for this app via `securenow alerts rules --json`; enable
`instant.block` rather than authoring duplicate pattern SQL. Pair with the app-side CRLF/HTML
sanitisation fix — the signature catches the obvious payloads, the app fix closes the class.
Useful attributes/columns: `event.type`, `http.client_ip`, `http.target`,
`response_status_code`, `kind`, `client.asn`, `client.as_org`, and your messaging attributes
(`flow`, `channel`, `recipient_hash`, `country`, `prefix`, `route`, `reason`).
**Emit every detection as a ready-to-copy command unit.** Each rule must be a **complete,
copyable unit** — never a fragment. For each rule emit, in order: (1) the SQL, (2) a line saving
it to `rules/<name>.sql`, (3) the full `securenow alerts rules create …` command, (4) the
dry-run test. In Markdown each is its own fenced block (so it copies cleanly). The exact flags
**must** match `securenow alerts rules --help` from Phase 0.5 — do not invent flags. Save each
rule's SQL to `rules/<name>.sql` so `--sql @rules/<name>.sql` resolves. Note pre-existing /
system rules (from Phase 0) instead of duplicating them. Example unit:
```sql
-- rules/messaging-send-spike.sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['flow'] AS flow,
attributes_string['channel'] AS channel,
count() AS sends
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] = 'api.sensitive.flow'
AND attributes_string['flow'] LIKE '%_send'
AND timestamp >= now() - INTERVAL 10 MINUTE
GROUP BY ip, flow, channel
HAVING ip != '' AND sends >= 50
```
```bash
securenow alerts rules create \
--name "Messaging: send-flow rate spike (denial-of-wallet)" \
--sql @rules/messaging-send-spike.sql \
--apps <APP_KEY> \
--severity high \
--schedule "*/5 * * * *" \
--nlp "single IP triggering 50+ sends in 10 minutes"
securenow alerts rules test <RULE_ID> --mode dry_run --wait # validate before it runs live
```
Follow the same four-block pattern for every other rule (fan-out/bombing, enumeration probe,
IRSF/premium-destination, POST-burst, 4xx/5xx spike, and the message-event variants), changing
only the SQL, the `rules/<name>.sql` path, the `--name`/`--severity`/`--schedule`/`--nlp`, and
the threshold. Injection-class rows reference the **system signature rules + `instant.block`**
rather than duplicating pattern SQL.
#### Test mode for false-positive-prone rules — ship FP-prone rules detect-only first
Alert rules have a lifecycle **mode**: `test` = **detect-only, NO mitigation** vs `prod` = full
(mitigation / auto-action armed) — plus a **status** (`Active | Disabled | Paused`). Manage with:
```bash
securenow alerts rules update <RULE_ID> --mode test # detect-only: fires notifications, takes NO action
# …observe real traffic for several days; tune the threshold; add securenow fp exclusions for any FPs…
securenow alerts rules update <RULE_ID> --mode prod # promote: arm the mitigation / auto-action
securenow alerts rules update <RULE_ID> --status Paused # or --enable / --disable / --pause shortcuts
```
**Rule of thumb:** any detection that can **false-positive** — heuristic thresholds (send-flow
flood / fan-out / enumeration counts, POST-burst, 4xx/5xx spike), broad patterns, anomaly /
volume rules, anything tuned to YOUR send traffic (a legitimate bulk-campaign burst looks like a
flood) — must ship in **`--mode test` first**. Run it detect-only for **3–7 days of real
traffic**, review what it flags, raise/lower the threshold and add `securenow fp` exclusions for
legitimate hits (e.g. your campaign/ESB sender IP), then `--mode prod` to arm mitigation. Only
**high-precision** rules (exploit-signature SQLi/XSS/RCE content-injection matches via
`instant.block`, exact-match IoCs, IRSF/premium-destination `messaging.destination.blocked` hits
where any single attempt is high-signal, known-bad ASN hits) may go straight to `prod`. In the
report, **tag EVERY rule `test-first` or `prod-ready`** and say why. (`securenow alerts rules test
<id> --mode dry_run --wait` is the separate one-off *query* validation — run it before either.)
### 4c. Mitigation commands (the only allowed remediation surface)
For send abuse, SecureNow **contains the actor at the edge**; the **app-config or DNS fix**
removes the underlying weakness. Always pair them on cost/auth/spoofing rows. Once a threat is
confirmed, **choose the narrowest effective mitigation(s) from ALL of these** and combine them
(e.g. rate-limit a `/send` route + block the worst toll-fraud IPs + challenge a NAT egress on the
reset endpoint). Re-check every command/flag against the installed SDK in Phase 0.5
(`securenow <cmd> --help`); annotate `# requires securenow >= <ver>` if absent. Scope by
**app / env / route / method / IP / duration** to avoid hitting real users.
| # | Mitigation | Command (ready-to-copy) | Use / scope |
|---|---|---|---|
| 1 | **Free firewall (network)** | `securenow firewall enable --app <APP_KEY> --env production` · `securenow run --firewall-only` · test `securenow firewall test-ip <ip> --path /x --method GET` | 500k+ known-bad IPs, hourly refresh; drop scanners before they reach a send endpoint. No app change. |
| 2 | **Exploit-signature instant block** | enable the `instant` config on the system SQLi/XSS/RCE signature rules (dashboard / MCP `securenow_alert_rule_instant_update`); custom rule → create with `--execution-mode instant` | synchronous ~2.6s block of the matching request (outbound content/header injection — catalog E). Don't duplicate pattern SQL. |
| 3 | **IP block — global** | `securenow blocklist add <ip> --app <APP_KEY> --env production --reason "..."` | confirmed-malicious source (toll-fraud farm), all routes. |
| 4 | **IP block — scoped to route (+ method)** | `securenow blocklist add <ip> --route /api/auth/* --mode prefix --method ALL --app <APP_KEY> --env production --reason "..."` (`--mode exact\|prefix\|regex`, `--method GET\|POST\|…\|ALL`) | block an IP only on the send/auth-send paths; least collateral. |
| 5 | **IP block — temporary / time-boxed** | `securenow blocklist add <ip> --duration 24h --reason "..."` (`30m`,`24h`,`7d`) · reverse `securenow blocklist unblock <id> --reason "..."` | auto-expiring containment of a send-abuse source; audit-preserving unblock. |
| 6 | **Rate limit — per IP** | `securenow ratelimit add <ip> --limit 100 --window 1m --duration 24h --reason "..."` | throttle one abusive client across all send paths. |
| 7 | **Rate limit — per route (all clients, per-IP budget)** | `securenow ratelimit add --route /api/auth/reset --mode prefix --method POST --limit 5 --window 10m --key-by ip` | cap an OTP/reset/invite/`/send` endpoint for everyone, budgeted per IP. |
| 8 | **Rate limit — per route + IP** | `securenow ratelimit add <ip> --route /api/auth/reset --mode exact --method POST --limit 5 --window 10m --duration 24h` · NL `securenow ratelimit from-text "rate limit /api/auth/reset to 5/10m for 24h" --yes` · test `securenow ratelimit test <ip> --path /api/auth/reset --method POST` | precise throttle of one client on one send route. |
| 9 | **CAPTCHA / proof-of-work challenge** | `securenow challenge add --route /api/auth/reset --difficulty 16 --clearance 30m` (route-wide) **or** `securenow challenge add <ip> --route /api/auth/reset --difficulty 18 --clearance 30m` · test `securenow challenge test <ip> --path /api/auth/reset --method POST` | bot-driven send abuse / email-or-SMS bombing from **shared / NAT / CGNAT** egress — a human passes once, a script can't keep farming sends. Prefer over a hard block when real users share the IP (resetting a password). |
| 10 | **Auto-block (risk-scored)** | `securenow automation defaults --yes` (≥95→7d, 90–94→72h, 85–89→24h) · custom `securenow automation create --conditions '[...]' --actions '[...]'` · preview `securenow automation dry-run <id>` | hands-off blocking of send-abuse by risk score; actions include block / rate_limit / requireCaptcha. |
| 11 | **Session revocation** | `securenow revoke …` (SDK `securenow/sessions` `guard()` / `isRevoked()`) | a compromised account farming sends — kill the stolen session, not the shared egress IP. |
| 12 | **Trusted IP (suppress)** | `securenow trusted add <ip> --label "Campaign/ESB sender / partner batch"` | stop false positives from your own bulk-send infra / partner batch jobs — suppresses detection **and** mitigation. NOT deny-by-default. |
| 13 | **Allowlist (deny-by-default)** | `securenow allowlist add <ip> --label "..." --reason "..."` ⚠️ once any entry exists, ONLY listed IPs reach the app | lockdown of an internal-only / admin "send mail" surface. Never for a public send endpoint. |
| 14 | **False-positive exclusion** | `securenow fp create --conditions '[...]' --rule-scope this_rule --reason "..."` · `securenow fp mark <notification-id> <ip> --rule-scope this_rule` · preview `securenow fp dry-run --conditions '[...]'` | keep a noisy send-rate / bulk-campaign rule quiet without weakening it. |
| 15 | **App / config / DNS / code fix (primary for root cause)** | *described in the Code-Findings report, never auto-applied* | the actual fix: per-target send cool-down, per-IP & global send caps, destination/geo allowlist + premium-prefix denylist, idempotency key on provider calls, enumeration-safe identical responses, recipient-from-auth-subject, signed unsubscribe tokens, CRLF/HTML sanitisation; **DNS** — publish/scope SPF, enable DKIM signing, advance DMARC `p=none`→`quarantine`→`reject` (owned by `../21-dns-tls-certificates/`). SecureNow contains; the fix removes. |
**Choosing per threat** — by **confidence**: exploit-signature/exact IoC (CRLF/SMTP/SSTI payload,
confirmed toll-fraud farm) → instant-block or block; probable send-abuse bot on shared egress
(reset/OTP/invite endpoint behind NAT/CGNAT) → **challenge**; noisy/legit-mixed send traffic
(bulk-campaign IP, flood/fan-out heuristics) → **rate-limit (test-mode first)**; compromised
account farming sends → **revoke**; known-good send infra → **trusted / fp**. By **blast radius**:
always scope to the narrowest `route`/`method`/`IP`/`duration` that stops the abuse; on
NAT/CGNAT/shared IPs prefer challenge/rate-limit over a hard block (a block nukes legitimate users
trying to reset a password). Always pair an edge mitigation with the **app/config fix** — and for
sender spoofing the **DNS fix** (`../21-dns-tls-certificates/`) — from the Code-Findings report
when SecureNow can only contain the actor. Recommend **notify-only** (no auto action) for
false-positive-prone signals (legitimate bulk-campaign burst, broken provider integration spiking
5xx, `messaging.callback.signature_failed` / `messaging.content.rejected` at low volume) — say so
with the runbook command the human runs after confirming.
### 4d. Testing every detection and mitigation
Only test against apps/environments the user owns; prefer `--env local`/staging. For synthetic
source IPs use TEST-NET ranges (`192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24`). **Never
send to a real recipient when testing** — use synthetic events and synthetic addresses only.
```bash
# Synthetic send-flow spike — exercise the denial-of-wallet rule end to end:
for i in $(seq 1 60); do
securenow event send api.sensitive.flow --ip 203.0.113.50 \
--attrs flow=otp_send,channel=sms,recipient_hash=test$i,cost_units=1,test=true
done
# Synthetic fan-out / email-bombing (many distinct recipients from one IP):
for i in $(seq 1 30); do
securenow event send api.sensitive.flow --ip 203.0.113.51 \
--attrs flow=password_reset_send,channel=email,recipient_hash=victim$i,cost_units=1,test=true
done
# Synthetic account-enumeration probe:
for i in $(seq 1 25); do
securenow event send messaging.enumeration.suspected --ip 203.0.113.52 \
--attrs route=/api/auth/reset,channel=email,exists=false,recipient_hash=probe$i,test=true
done
# Synthetic IRSF / premium-destination attempt (toll fraud):
securenow event send messaging.destination.blocked --ip 203.0.113.53 \
--attrs channel=sms,country=XX,prefix=+88,reason=premium_range,test=true
# Synthetic send-cap rejection + unsubscribe abuse:
securenow event send api.ratelimit.exceeded --ip 203.0.113.54 \
--attrs route=/api/auth/reset,limit=1,window=60s,reason=send_cooldown,test=true
securenow event send messaging.unsubscribe.abuse --ip 203.0.113.55 \
--attrs route=/u/unsubscribe,reason=idor,test=true
# Validate a rule query without waiting for the schedule:
securenow alerts rules test <RULE_ID> --mode dry_run --wait
# Traffic-based rules (POST burst on send routes) — generate spans, then check the pipeline:
securenow test-span "threat-model.messaging.smoke"
securenow forensics "requests and 4xx/5xx to /reset /otp /invite by IP in the last hour" --env production
# Content/header injection signatures — confirm a CRLF/SSTI payload trips the system rule
# + instant block (staging): send a benign-but-matching marker to a staging send field, then:
securenow firewall test-ip 203.0.113.50 --app <APP_KEY> --env production
# Mitigation verification:
securenow ratelimit test 203.0.113.50 --path /api/auth/reset --method POST
securenow challenge test 203.0.113.50 --path /api/auth/reset --method POST
# Confirm + clean up:
securenow notifications list --limit 10
securenow blocklist list # then: securenow blocklist unblock <id> --reason "threat-model test"
securenow challenge list # then: securenow challenge remove <id>
```
Every 🟢/🟡 threat row in the report must have a concrete test recipe (commands + expected
outcome: which rule fires, which notification appears, what the mitigation does).
---
## Phase 5 — Write the four reports (two tracks)
Write **four** files into `threat/11-messaging-notifications/`: a **Detection & Mitigation**
track (`messaging-notifications-detection-mitigation.md` + `.html`) and a **Code Findings &
Recommendations** track (`messaging-notifications-code-findings.md` + `.html`). The split is
honest: SecureNow-runnable detections/mitigations live in the Detection track; code/config
changes live in the Code-Findings track; nothing security-relevant is dropped. The two tracks
**cross-link** — the gaps/instrumentation rows in the Detection report link to the relevant code
finding, and each code finding / app-config fix links back to the detection-report row it backs.
### 5a. Detection & Mitigation report — sections (both .md and .html), in order
1. **Executive summary** — stats line (N threats modeled · N covered · N partial · N gaps ·
N rules to create · N mitigations), top 3 **detectable** messaging risks for this specific
stack (e.g. "unauthenticated reset-send with no per-target cool-down → email bombing",
"arbitrary SMS destination → IRSF", "DMARC `p=none` → brand-spoofable"), the installed
`securenow` version + app key + firewall state, and a one-line OWASP note (API4 + API6 owned
here; A04 sender-spoofing fix lives in DNS; volumetric/webhook/DNS deferred to siblings).
2. **SDK & environment** — installed SDK version (from `node_modules/securenow`, Phase 0.5),
app key(s), environment, firewall state, existing rules / automations / challenge rules
(from Phase 0), and the system signature rules present (SQLi/XSS/RCE) that back content
injection.
3. **Threat → Detection → Mitigation matrix** — one row per modeled threat:
`# | Threat | OWASP/CWE | Coverage 🟢/🟡/🔴 | Detection rule | Mode (test-first/prod-ready) | Signal (threshold+window) | Schedule | Sev | Mitigation`.
Severity ∈ {critical, high, medium, low}. The **Mitigation** cell must pick **specific,
scoped** mitigation(s) from the §4c toolbox (named control + scope, e.g. "challenge
`/api/auth/reset` @diff 18" + "app-fix: per-target cool-down" — never a generic "block the
IP"). The **Mode** cell tags each rule **`test-first`** (FP-prone heuristic — ships `--mode
test`) or **`prod-ready`** (high-precision — straight to `--mode prod`). Then the "Out of
scope" N/A list and the deferred sibling rows (55–57) pointing to the numbered models.
4. **Detection rules to create** — each as the **ready-to-copy unit** from Phase 4 (SQL → save
to `rules/<name>.sql` → full `securenow alerts rules create …` → dry-run test). Injection-class
rows reference the **system signature rules + `instant.block`**, not duplicate pattern SQL.
Note rules that already exist (from Phase 0) instead of duplicating them. **Mark each rule
`test-first` or `prod-ready`** (per Phase 4b): FP-prone rules ship `--mode test` and carry the
`--mode test` → observe (3–7 days) → `--mode prod` promotion step as a copyable command;
high-precision rules go straight to `prod`.
5. **Instrumentation the detections need** — only the `track('…')` events the rules above consume
(`api.sensitive.flow`, `api.ratelimit.exceeded`, and the `messaging.*` events actually used),
each as a copyable snippet; point to the **code-findings report** for *where* (file:line) to
add them. Reiterate the **no-PII-in-attributes** rule (hash recipients, never log OTP/token/body).
6. **Mitigation mechanisms** — render the **full §4c toolbox table** (all 15 rows: firewall ·
instant-block · block [global / route+method / temporary] · rate-limit [IP / route / IP+route]
· challenge · auto-block · revoke · trusted · allowlist · fp · app/config/DNS fix) + per-threat
ready-to-copy mitigation command (specific + scoped) + reversibility. Make explicit that the
app-config fix is primary for API4/API6 cost & enumeration, the DNS fix is primary for sender
spoofing, and the SecureNow control is containment.
7. **Action plan (copy-paste, ordered)** — ① engage the firewall + enable signature
instant-block, ② add the `api.sensitive.flow` + `messaging.*` instrumentation at each send
point, ③ create rules — **FP-prone (`test-first`) rules created in `--mode test`** (detect-only),
high-precision (`prod-ready`) rules straight to `--mode prod`, ④ enable automations / challenge
rules on send routes, ⑤ test, ⑥ verify in dashboard, ⑦ **promote the `test-first` rules** —
after **N days (3–7)** of observing real traffic and adding `securenow fp` exclusions for
legitimate hits, run `securenow alerts rules update <RULE_ID> --mode prod` to arm mitigation
(include this as an explicit, copyable "promote after N days" step), ⑧ schedule the app-config
work (per-target caps, destination allowlist, enumeration-safe responses, signed unsubscribe
tokens) and the DNS work (SPF/DKIM/DMARC, via `../21-dns-tls-certificates/`) — both detailed in
the **code-findings report**. Real commands only, `<APP_KEY>` already substituted.
8. **Testing & validation** — per-rule recipe from 4d (`securenow event send …` / `test-span` /
dry-run + expected outcome + cleanup, using TEST-NET IPs `192.0.2`/`198.51.100`/`203.0.113`).
9. **Response runbooks** — for each notification type (send spike, fan-out/bombing, enumeration
probe, IRSF attempt, content-injection signature, forged callback): what fired, how to confirm
a true positive → the exact respond command (copy) → the exact reverse command (copy).
10. **Known gaps & SecureNow feature requests** — every 🔴 threat: why it's not coverable today,
the interim app/DNS fix (link to the relevant code-findings row), and the "contact the
SecureNow team — contact your SecureNow account contact (or in-dashboard support)" line.
11. **Appendix** — resolved SDK/CLI version (Phase 0.5), app key, environment, firewall state,
rule IDs created, date, and a link to the code-findings report.
### 5b. Code Findings & Recommendations report — sections (both .md and .html), in order
State at the top: *"Findings only — no application code was modified."*
1. **Executive summary** — findings by severity (critical / high / medium / low), top 3 code
risks for this stack, and a one-paragraph posture verdict.
2. **Send surface & inventory** — the Phase 1 send-endpoints catalog + cost map + OTP/verification
matrix + recipient-targeting table + premium-destination note + content-construction table +
provider/relay posture + SPF/DKIM/DMARC posture + delivery-webhook trust note +
unsubscribe/preference posture + PII redaction status.
3. **Threat catalog** — the exhaustive Phase 2 catalog (groups A–L), each tagged OWASP/CWE,
modeled or explicit N/A; include the deferred-sibling rows (55–57).
4. **Code-level findings (audit)** — table
`# | Location (file:line) | Threat | OWASP/CWE | Sev | Issue | Recommended fix`, each with the
quoted 1–8 line snippet and the described fix (never applied).
5. **Strengths** — controls already present and correct (per-target cool-down present, destination
allowlist enforced, DKIM on, callback signature verified, unsubscribe token signed) — the
posture must be honest.
6. **App / config fixes (primary remediation)** — the config/code changes that remove the root
cause (per-target caps, destination/geo allowlist + premium-prefix denylist, idempotency keys,
enumeration-safe responses, recipient-from-auth-subject, signed unsubscribe tokens, CRLF/HTML
sanitisation) and the **DNS fixes** (SPF/DKIM/DMARC, owned by `../21-dns-tls-certificates/`),
described — not applied — each linked to the detection-report row it backs.
7. **Instrumentation recommendations** — the `track('…')` calls to add and the exact file:line to
add them (from Phase 1/3) so the detection rules light up; hash recipients, never log
OTP/token/body.
8. **Appendix** — files reviewed, resolved SDK version (Phase 0.5), date, and a link to the
detection-mitigation report.
### HTML reports — two self-contained skeletons (offline; inline CSS + copy JS; no network)
Both HTML files share the `<head>` below (SecureNow brand tokens + copy-button styles) and the
copy `<script>` at the end of `<body>`. Change only the `<title>`, the sidebar subtitle, the
header `<h1>`, and the section content (one body per track). **Wrap EVERY command/SQL block as a
`.cmd`** so it gets a working Copy button. The Code-Findings HTML may omit copy buttons on prose
but still wraps any example/fix command in `.cmd`.
```html
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<title><!-- "Detection & Mitigation — Messaging & Notifications — SecureNow"
OR "Code Findings — Messaging & Notifications — SecureNow" --></title>
<style>
:root{--bg:#0f1419;--panel:#161c24;--panel2:#1b2330;--border:#26303d;--txt:#dbe3ec;--muted:#8b97a7;
--accent:#3ea6ff;--accent2:#16c79a;--crit:#ff5c6c;--high:#ff9f43;--med:#f7c948;--low:#8b97a7;
--ok:#16c79a;--info:#3ea6ff;--rev:#b388ff;}
*{box-sizing:border-box}html{scroll-behavior:smooth}
body{margin:0;background:var(--bg);color:var(--txt);font:15px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
a{color:var(--accent);text-decoration:none}
code{background:#0b0f14;border:1px solid var(--border);border-radius:5px;padding:.08em .4em;font:13px/1.4 ui-monospace,"SF Mono",Menlo,Consolas,monospace;color:#9fe0c0}
.wrap{display:grid;grid-template-columns:240px 1fr;max-width:1280px;margin:0 auto}
nav{position:sticky;top:0;align-self:start;height:100vh;overflow:auto;padding:28px 18px;border-right:1px solid var(--border);background:var(--panel)}
nav .brand{font-weight:700;font-size:15px;letter-spacing:.3px}nav .brand span{color:var(--accent)}
nav .sub{color:var(--muted);font-size:12px;margin-bottom:22px}
nav a{display:block;color:var(--muted);padding:7px 10px;border-radius:7px;font-size:13.5px}
nav a:hover{background:var(--panel2);color:var(--txt)}
main{padding:36px 40px 80px;min-width:0}
header.top h1{margin:0 0 6px;font-size:26px}header.top p{margin:0;color:var(--muted)}
.pill{display:inline-block;font-size:11px;font-weight:600;padding:3px 9px;border-radius:999px;border:1px solid var(--border);color:var(--muted);background:var(--panel)}
.stats{display:grid;grid-template-columns:repeat(5,1fr);gap:14px;margin:26px 0 34px}
.stat{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:16px 18px}
.stat .n{font-size:26px;font-weight:700}.stat .l{color:var(--muted);font-size:12.5px;margin-top:2px}
section{margin:0 0 40px}
h2{font-size:18px;margin:0 0 14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
h2 .num{color:var(--accent);font-weight:700;margin-right:8px}
table{width:100%;border-collapse:collapse;font-size:13.5px;background:var(--panel);border:1px solid var(--border);border-radius:12px;overflow:hidden}
th,td{text-align:left;padding:11px 13px;border-bottom:1px solid var(--border);vertical-align:top}
th{background:var(--panel2);color:var(--muted);font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.4px}
tr:last-child td{border-bottom:none}tr:hover td{background:#19212c}
.rid{font:12px ui-monospace,Menlo,Consolas,monospace;color:#7fd1ff;white-space:nowrap}
.b{display:inline-block;font-size:11px;font-weight:700;padding:2px 8px;border-radius:6px;white-space:nowrap}
.b.crit{background:rgba(255,92,108,.15);color:var(--crit);border:1px solid rgba(255,92,108,.35)}
.b.high{background:rgba(255,159,67,.13);color:var(--high);border:1px solid rgba(255,159,67,.32)}
.b.med{background:rgba(247,201,72,.13);color:var(--med);border:1px solid rgba(247,201,72,.32)}
.b.low{background:rgba(139,151,167,.13);color:var(--low);border:1px solid rgba(139,151,167,.32)}
.c{display:inline-block;font-size:11px;font-weight:700;padding:2px 8px;border-radius:6px;white-space:nowrap}
.c.cov{background:rgba(22,199,154,.13);color:var(--ok);border:1px solid rgba(22,199,154,.35)}
.c.part{background:rgba(247,201,72,.13);color:var(--med);border:1px solid rgba(247,201,72,.32)}
.c.gap{background:rgba(255,92,108,.15);color:var(--crit);border:1px solid rgba(255,92,108,.35)}
.owasp,.cwe{display:inline-block;font:11px ui-monospace,Menlo,Consolas,monospace;color:var(--accent);border:1px solid rgba(62,166,255,.3);border-radius:6px;padding:1px 6px;white-space:nowrap}
.cwe{color:var(--rev);border-color:rgba(179,136,255,.3)}
.m{display:inline-block;font-size:11px;font-weight:600;padding:2px 8px;border-radius:6px;border:1px solid var(--border)}
.m.block{color:var(--crit);border-color:rgba(255,92,108,.35)}.m.rate{color:var(--info);border-color:rgba(62,166,255,.35)}
.m.challenge{color:var(--accent2);border-color:rgba(22,199,154,.35)}.m.firewall{color:var(--ok);border-color:rgba(22,199,154,.35)}
.m.signature{color:var(--crit);border-color:rgba(255,92,108,.35)}.m.notify{color:var(--muted)}.m.appfix{color:var(--high);border-color:rgba(255,159,67,.35)}
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:18px 20px}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
pre{background:#0b0f14;border:1px solid var(--border);border-radius:10px;padding:14px 16px;overflow:auto;font:13px ui-monospace,Menlo,Consolas,monospace;color:#cfe8da;margin:0}
.cmd{position:relative;margin:10px 0}
.copy{position:absolute;top:8px;right:8px;font:11px ui-monospace,Menlo,Consolas,monospace;color:var(--muted);background:var(--panel2);border:1px solid var(--border);border-radius:6px;padding:3px 9px;cursor:pointer}
.copy:hover{color:var(--txt);border-color:var(--accent)}.copy.done{color:var(--ok);border-color:var(--ok)}
.flow{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:6px 0 14px}
.flow .step{background:var(--panel2);border:1px solid var(--border);border-radius:9px;padding:8px 12px;font-size:13px}.flow .arr{color:var(--accent);font-weight:700}
.note{border-left:3px solid var(--high);background:rgba(255,159,67,.06);padding:10px 14px;border-radius:0 8px 8px 0;color:#e7d3bd;font-size:13.5px;margin:10px 0}
footer{color:var(--muted);font-size:12px;border-top:1px solid var(--border);padding-top:18px;margin-top:30px}
@media(max-width:880px){.wrap{grid-template-columns:1fr}nav{display:none}.stats,.grid2{grid-template-columns:1fr 1fr}main{padding:24px 18px}}
</style></head>
<body>
<div class="wrap">
<nav>
<div class="brand">Secure<span>Now</span></div>
<div class="sub"><!-- "Detection & Mitigation · Messaging & Notifications"
OR "Code Findings · Messaging & Notifications" --></div>
<!-- one <a href="#…"> per section of THIS track (5a → 11 links, or 5b → 8 links) -->
</nav>
<main>
<header class="top">
<h1><!-- report title for this track --></h1>
<p><code><!-- app name / domain --></code> · <span class="pill">securenow <!-- installed version --></span>
· <span class="pill"><!-- channels, e.g. email + SMS + push, Twilio + SES --></span></p>
</header>
<div class="stats"><!-- 5 .stat cards; numbers MUST equal this track's table/finding counts.
Detection track: threats modeled / covered / partial / gaps / rules to create.
Code-Findings track: findings critical / high / medium / low / strengths. --></div>
<!-- <section id="…"> blocks mirroring the Markdown sections of THIS track (5a or 5b) -->
<footer>Generated by the SecureNow messaging & notifications threat-model prompt ·
<!-- date --> · securenow <!-- version --> · app <code><!-- APP_KEY --></code></footer>
</main>
</div>
<script>
document.querySelectorAll('.copy').forEach(function(b){b.addEventListener('click',function(){
var pre=b.parentElement.querySelector('pre'); if(!pre)return; var t=pre.innerText;
function done(){b.textContent='Copied';b.classList.add('done');setTimeout(function(){b.textContent='Copy';b.classList.remove('done');},1500);}
function fb(){var ta=document.createElement('textarea');ta.value=t;ta.style.position='fixed';ta.style.opacity='0';document.body.appendChild(ta);ta.focus();ta.select();try{document.execCommand('copy');}catch(e){}document.body.removeChild(ta);done();}
if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(t).then(done,fb);}else{fb();}
});});
</script>
</body></html>
```
Every SQL/command block in the **Detection & Mitigation** HTML uses the copyable wrapper:
```html
<div class="cmd"><button class="copy" type="button">Copy</button><pre>securenow alerts rules create \
--name "..." --sql @rules/<name>.sql --apps <APP_KEY> --severity high \
--schedule "*/5 * * * *" --nlp "..."</pre></div>
```
Badge usage: severity → `<span class="b crit|high|med|low">`, coverage →
`<span class="c cov|part|gap">COVERED|PARTIAL|GAP</span>`, OWASP tag →
`<span class="owasp">API4</span>` / `<span class="owasp">API6</span>` /
`<span class="owasp">A04</span>`, CWE tag → `<span class="cwe">CWE-93</span>`, mitigation type →
`<span class="m firewall|signature|rate|challenge|block|notify|appfix">` (use `appfix` for both
app-config and DNS fixes), rule IDs → `<span class="rid">`. Stats numbers must equal this track's
matrix/findings row counts.
---
## Quality bar (the report is rejected if any of these fail)
- Every catalog item 1–57 is either a matrix row or an explicit N/A line; each modeled row
carries its OWASP tag (**API4**, **API6**, **A04**, or "—").
- The full domain emphasis is modeled or explicit N/A: SMS/voice toll-fraud (IRSF), email
bombing via signup/reset/invite, notification/invite/referral spam, send-as-denial-of-wallet,
OTP/magic-link send abuse + enumeration, content/header/CRLF/SMTP/SSTI injection, open
redirect in templated messages, sender spoofing (SPF/DKIM/DMARC), open relay / provider
misconfig, delivery-receipt webhook trust, PII leakage / mis-targeting, and
unsubscribe/preference IDOR.
- General volumetric rate-limiting (`../12-rate-limits-and-abuse/`), inbound-webhook mechanics
(`../16-webhooks/`), and SPF/DKIM/DMARC DNS-record plumbing (`../21-dns-tls-certificates/`) are
**deferred** by numbered path (rows present, linked, not re-derived) — this model does not
duplicate them.
- Every matrix row has a concrete signal (threshold + window), severity, and mitigation — no
"monitor for suspicious activity" filler.
- Every code finding in section 4 has a `file:line`, the quoted snippet, and a described fix —
and **no application code was modified** (this is an audit).
- Every detection SQL keeps `__USER_APP_KEYS__` scoping (correct table column: logs =
`resources_string['service.name']`, traces = `` `resource_string_service$$name` ``) and selects
an `ip` column with `HAVING ip != ''`; traffic queries keep the `ts_bucket_start` + `kind = 2`
guards and the `client_ip` coalesce.
- Instrumentation **reuses** `api.sensitive.flow` + `api.ratelimit.exceeded` and proposes
`messaging.*` events only where a send-specific signal genuinely adds value; **no PII** (raw
email/phone/OTP/token/body) appears in any attribute — recipients are hashed.
- Content-injection coverage references the **system signature rules + `instant.block`**, not
duplicate pattern SQL, paired with the app-side CRLF/HTML sanitisation fix.
- Only commands, flags, events, and SQL columns from this prompt's building blocks appear
(including `securenow challenge …`, `firewall`, and the `api.sensitive.flow` /
`api.ratelimit.exceeded` / `messaging.*` events).
- Detection vs. fix is honest: every cost/enumeration row pairs the SecureNow edge-containment
control **with** the app-config fix; every sender-spoofing row pairs it with the **DNS fix**
(owned by `../21-dns-tls-certificates/`).
- Every 🔴 gap appears in the gaps section with an interim app/DNS fix **and** the "contact the
SecureNow team" line.
- The action plan runs top-to-bottom with `<APP_KEY>` substituted in.
- **Phase 0.5 ran:** the resolved installed `securenow` version appears in **both** reports'
appendix, and no command/flag/event/column is emitted that the installed SDK/CLI does not
expose (else it is annotated `# requires securenow >= <version>`).
- **Every detection rule is a complete copyable unit** — SQL → `rules/<name>.sql` → full
`securenow alerts rules create …` → dry-run test; flags match `securenow alerts rules --help`.
- **Four** files are written to `threat/11-messaging-notifications/` — detection-mitigation
`.md` + `.html` and code-findings `.md` + `.html`; the two tracks cross-link; both HTML files
are self-contained (inline CSS/JS, no CDN/fonts/network) and **every command block in the
detection HTML has a working Copy button** (`.cmd` wrapper + copy `<script>`); stats cards
match each track's row counts.
- The split is honest: SecureNow-runnable detections/mitigations live in the Detection report;
code/config changes live in the Code-Findings report; nothing security-relevant is dropped.
- A one-line summary is printed back to the user: per-track file paths, threat counts,
rules-to-create count, code findings by severity, gaps, OWASP coverage, and the resolved SDK
version.
- The Detection report's mitigation section presents the **full toolbox** (§4c: firewall ·
instant-block · block [global / route / method / temporary] · rate-limit [IP / route / IP+route]
· challenge · auto-block · revoke · trusted · allowlist · fp · app/config/DNS fix), and **each
modeled threat's matrix row selects specific, scoped mitigation(s) from it** — never a generic
"block the IP."
- **Every false-positive-prone rule is tagged `test-first`** and carries the `--mode test` →
observe (3–7 days) → `--mode prod` promotion workflow; only high-precision rules are
`prod-ready`. The action plan creates the test-first rules in `--mode test` and has an explicit
"promote after N days" step.
<!-- ════════════════ END OF PROMPT ════════════════ -->