#09API6 · A04

Payment & Business Logic

Checkout, refunds, coupons, race conditions, and money-flow abuse.

Download .md

How to use this prompt

  1. 1
    Install SecureNow in your project (then optionally npx securenow login):
    $ npm install securenow
  2. 2Copy the prompt below and paste it into your AI coding agent (Claude Code, Cursor, Codex…) opened at the root of your project.
  3. 3It generates four files into threat/09-payment-business-logic/ — open payment-business-logic-code-findings.html (the audit) and payment-business-logic-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

# Payment / Business Logic 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 moves money or value
and has the `securenow` CLI installed and logged in. The agent will inventory every
money/value flow, build an exhaustive **payment & business-logic** threat model mapped to
**OWASP API6:2023 (Unrestricted Access to Sensitive Business Flows)** and **OWASP A04:2021
(Insecure Design)**, audit the code for logic flaws, and emit **two SecureNow-branded tracks**
(four files total — Detection & Mitigation + Code Findings & Recommendations, each as
**Markdown + self-contained HTML with offline copy buttons**) — including the detection rules to
create as **ready-to-copy command units**, the mitigation commands to run, how to test each one,
the code-level findings (audited, **not** fixed), and which threats still need the SecureNow team.
Every rule, flag, event name, and SQL column is **grounded in the SecureNow SDK actually
installed in the repo** (Phase 0.5), never guessed.

This model owns the **flows that move money or value, and the logic flaws that let an attacker
get an outcome the design never intended**: checkout/payment, price/quantity/currency handling,
refunds, coupons/promos, gift cards, balances/wallets/store-credit, subscriptions &
entitlements, payouts, and referral/affiliate/cashback economics. It is about *what the API
lets you do that you shouldn't be able to do* — not raw flooding and not credential abuse.

> **Read the honesty section first.** SecureNow is an **API / traffic & events** security
> layer. For payment & business-logic abuse its native strength is **rate and behaviour of a
> sensitive flow** (how fast / how often / from where / in what shape) plus three first-class
> events — **`api.race.detected`**, **`api.replay.blocked`**, **`api.idempotency.missing`** —
> that the app emits at its own enforcement points. SecureNow detects flow-rate abuse, replay,
> races, and bot-driven farming, and **contains the actor at the edge** (firewall / rate-limit /
> challenge / signature instant-block / blocklist). What it **cannot** do is be the source of
> truth for *logic correctness*: server-side price/quantity/currency validation, idempotency
> keys, database constraints/locks/transactions, and "is this user entitled" decisions are
> **app fixes**. Almost every row in this model therefore pairs **edge containment WITH the app
> fix** — the SecureNow control buys time and stops the bot; the app fix removes the flaw.

This model **does not re-derive** its siblings. It defers raw volumetric/scraping abuse to
[rate-limits & abuse](../12-rate-limits-and-abuse/), inbound payment-provider webhook
authenticity to [webhooks](../16-webhooks/), object-ownership (BOLA — "refund someone else's
order") to [authorization](../02-authorization/), and account-creation/credential abuse behind
synthetic-account farming to [authentication](../01-authentication/) and
[messaging & notifications](../11-messaging-notifications/). Run those too.

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 Payment / Business Logic Threat Model Report (SecureNow)

You are a senior application-security engineer specializing in payment systems and business-logic
abuse. Produce an **exhaustive payment & business-logic threat model for THIS codebase**,
organized along **OWASP API6:2023 (Unrestricted Access to Sensitive Business Flows)** and
**OWASP A04:2021 (Insecure Design)**, mapped to **SecureNow** detections and mitigations, with a
ready-to-run action plan **and** a code-level audit of every logic flaw you find. You write
**four** files into `threat/09-payment-business-logic/` (create the folder if needed) — **two
tracks**, each Markdown + self-contained HTML:

1. `payment-business-logic-detection-mitigation.md` — the **operational runbook**: the SecureNow
   detections to create (each a **ready-to-copy command unit**), the mitigation commands to run,
   instrumentation the detections need, the action plan, testing, and response runbooks.
2. `payment-business-logic-detection-mitigation.html` — the same runbook as a **self-contained**
   HTML page (inline CSS + JS, no network requests) with a **Copy button on every command block**,
   using the Detection & Mitigation HTML skeleton in Phase 5.
3. `payment-business-logic-code-findings.md` — the **code audit**: the money & value surface
   inventory, the full threat catalog, the code-level findings (audited, **not** fixed), the
   strengths, and the app/config + instrumentation recommendations.
4. `payment-business-logic-code-findings.html` — the same code audit as a **self-contained** HTML
   page, using the Code Findings HTML skeleton in Phase 5.

The two tracks **cross-link**: the gaps/instrumentation rows in the detection report link to the
relevant code finding, and each finding links back to the detection row it backs. Every command,
flag, `track('…')` event name, and SQL column is **grounded in the installed SecureNow SDK**
(Phase 0.5) — never guessed. `<slug>` for this domain is `payment-business-logic`.

Work in the 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 money/value flows and their logic: price/quantity/
currency tampering, coupons/promos, gift cards & balances, refunds, races on shared
balance/inventory/coupon/payout, missing idempotency on charges/orders/payouts, payment-flow
replay & step-skipping, subscription/entitlement bypass, and referral/affiliate/cashback
farming. It maps every threat to **OWASP API6** and/or **OWASP A04**. For the following, do
**not** re-derive the deep model — list them in a "Deferred to sibling models" subsection, link
those reports, and only model the **traffic/flow-observable** symptom (rate, shape, replay)
where SecureNow adds value:

- Raw volumetric / scraping / generic per-IP flooding → [rate-limits & abuse](../12-rate-limits-and-abuse/).
- Inbound payment-provider **webhook signature/replay authenticity** (Stripe/PayPal/etc.) →
  [webhooks](../16-webhooks/). (This model still covers the *business state* a forged/duplicate
  webhook corrupts — double-credit, paid-without-pay — and references the webhook model for the
  verification itself.)
- "Act on someone else's order/wallet/subscription" object-ownership (BOLA/IDOR) →
  [authorization](../02-authorization/).
- Synthetic-account creation, signup credential abuse, OTP/email farming →
  [authentication](../01-authentication/) and [messaging & notifications](../11-messaging-notifications/).

---

## 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** already present (their `instant.block` is reused later for any
payload-borne tampering markers); do not duplicate them.

---

## 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 money & value surface (codebase analysis)

Business-logic security starts with an **inventory of every flow that moves money or value, and
every place a server-side invariant is supposed to hold**. Document what the code **actually
allows**, not what the product intends. Cover at minimum:

- **Payment & checkout flow** — cart → price calculation → charge → order creation. Where is the
  **authoritative price** computed: server-side from a trusted catalog, or read from the request
  body? Map each step (add-to-cart, apply-discount, set-quantity, choose-currency, set-shipping,
  confirm, capture) and whether each step is **re-validated server-side at capture time** or
  trusted from an earlier step. Note the payment processor(s) and SDK(s).
- **Amount / quantity / currency handling** — every field an attacker controls that influences
  what they pay: `price`, `amount`, `qty`, `currency`, `discount`, `tax`, `shipping`, `total`,
  line-item arrays. Check for: negative values accepted, zero accepted where it shouldn't be,
  client-supplied totals trusted, currency mismatch (pay in a weak currency, credited in a
  strong one), integer overflow / float rounding (sub-cent, banker's-rounding, `0.1+0.2`),
  unit confusion (cents vs dollars), and per-line-item vs order-level recomputation.
- **Coupon / promo / discount system** — code format & guessability (sequential, short,
  predictable), per-code use cap, per-user cap, stacking rules (can two codes combine? code +
  store-credit?), expiry enforcement, scope (which SKUs), and whether validation happens
  **atomically at redemption** or can be raced. Is there an endpoint that reveals "valid/invalid"
  (enumeration oracle)?
- **Gift card / balance / wallet / store-credit** — issuance, redemption, balance-check, and
  transfer flows. Card-number format & entropy (enumerable?), whether balance-check is
  unauthenticated or rate-unlimited (drain-by-enumeration), whether redemption is atomic against
  the stored balance, and whether balance changes use a ledger with constraints vs a mutable
  numeric field.
- **Refund / chargeback flow** — who can trigger a refund, against what, to which instrument.
  Check: double-refund (refund the same order twice), refund > original amount, refund-then-keep
  (refund + still ship / keep entitlement), refund-to-different-instrument (refund to an attacker
  card), refund-then-chargeback (refund and also dispute = paid twice back), and partial-refund
  arithmetic that can exceed the original.
- **Concurrency / shared mutable state** — every counter or balance multiple parallel requests
  can hit: account balance, inventory/stock count, coupon remaining-uses, gift-card balance,
  payout-available amount, seat count, rate quota. For each, note the **atomicity mechanism**:
  DB transaction, `SELECT … FOR UPDATE` / row lock, optimistic version column, unique constraint,
  atomic decrement (`UPDATE … SET n = n - 1 WHERE n >= 1`), or **none** (read-modify-write =
  race). "None" is a finding.
- **Idempotency** — every charge / order-create / payout / credit / email-with-cost call. Is
  there an **idempotency key** (client-supplied or derived) enforced with a unique constraint or
  a dedupe store, so a retry/double-click/replay can't create two charges/orders/payouts? Note
  the provider idempotency support (Stripe `Idempotency-Key`, etc.) and whether the app uses it.
- **Payment-flow integrity / step-skipping** — can a client POST directly to the
  "mark-as-paid" / "fulfill" / "grant-entitlement" endpoint without the charge succeeding? Is
  order state a server-side state machine (pending → authorized → captured → fulfilled) with
  enforced transitions, or can any state be set from the request? Is the "payment succeeded"
  decision made from a **trusted server-to-server signal** (verified webhook / server-side
  capture confirmation) or from a **client redirect/callback parameter** (`?status=success`)?
- **Subscription & entitlement** — plan changes (upgrade/downgrade), trial start, cancellation,
  and how **access is gated**. Check: downgrade-keep-access (cancel/downgrade but entitlement not
  revoked), trial farming (new trial per email/card/device), seat/quantity abuse (add seats,
  do work, remove seats), proration abuse, and whether entitlement is re-checked on each use or
  cached client-side / in a long-lived token.
- **Payout / withdrawal / transfer** (marketplaces, wallets, creator payouts) — who can withdraw,
  validation of destination, minimum/maximum, double-payout race, payout before funds clear,
  and idempotency on the payout call.
- **Referral / affiliate / cashback economics** — reward trigger (signup? first purchase?),
  self-referral prevention, synthetic-account farming (one human, many fake accounts each earning
  a reward), reward stacking, and whether the reward is granted **before** the qualifying event
  is irreversibly confirmed (refund-after-reward).
- **Free-tier / metered-resource farming** — anything given away on signup or per-account
  (free credits, free compute/LLM calls, free storage, free trial of a paid downstream) that a
  bot can farm by creating accounts or replaying the grant flow.
- **Webhook-driven state changes** — every inbound webhook that **changes money/value state**
  (payment succeeded, subscription renewed, payout paid). Note the provider, whether signature is
  verified on the **raw body before trusting any field**, replay/event-id dedupe, and idempotent
  processing. (Authenticity itself → [webhooks](../16-webhooks/); the *state corruption* a
  forged/duplicate webhook causes is modeled here.)
- **Authorization & ownership on money flows** — for each money endpoint, is ownership
  enforced (can I refund/withdraw/redeem against *another* user's order/wallet)? Note the gap and
  **defer the deep BOLA model** to [authorization](../02-authorization/); model only the
  flow-rate / enumeration symptom here.
- **Telemetry privacy & redaction** — confirm the SecureNow SDK / log pipeline redacts payment
  tokens, PANs/card data, gift-card numbers, coupon codes, `Authorization`/`Cookie`, webhook
  secrets, emails, and amounts-tied-to-identity before ingestion. Attributes feed detection —
  they must not become a new leak path. If not found, create a high-severity finding.
- **SecureNow instrumentation already present** — `securenow/register` / `securenow run` /
  `securenow init` (traffic spans automatically), any existing `securenow/events` `track()`
  calls (especially `api.race.detected` / `api.replay.blocked` / `api.idempotency.missing` /
  `api.sensitive.flow`), and whether the firewall is engaged. This determines what works *today*
  vs *after instrumentation*.

Output of this phase = the report's **Money & value surface** section: the **flow catalog**
(flow → endpoints → server-side-validated? → atomicity → idempotency), the **amount-field
trust table** (field / source-of-truth / validated?), the **coupon/gift-card/balance posture**,
the **refund posture**, the **concurrency map** (shared counter → locking mechanism / none), the
**idempotency map**, the **subscription/entitlement gating**, the **referral/payout economics**,
the **webhook-driven-state list**, the **telemetry redaction status**, and a short paragraph
naming the real money-abuse 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. "Gift-card items: N/A, no gift-card / store-credit feature"). 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 **API6** and/or **A04** (use both where
both apply; a logic flaw with no flow-rate signal is usually **A04** primarily).

**A. Amount / quantity / currency tampering (A04 · API6)**
1. Client-supplied price / total trusted at capture (pay an attacker-chosen amount)
2. Negative price / quantity / amount accepted (credit the buyer / reduce total below zero)
3. Quantity tampering (fractional, zero, huge, or array-of-line-items mismatch vs price)
4. Currency substitution (pay in a weak currency, credited at a strong-currency rate)
5. Integer overflow / float rounding / sub-cent abuse (`0.1+0.2`, banker's rounding, truncation)
6. Unit confusion (cents-vs-dollars / minor-unit mismatch between client, server, processor)
7. Discount / tax / shipping field tampering pushing total to zero or negative
8. Line-item recomputation gap (per-item price trusted; order total not re-summed server-side)

**B. Coupon / promo / discount abuse (API6 · A04)**
9. Coupon code enumeration (guessable/sequential codes; valid/invalid oracle)
10. Coupon reuse beyond per-code or per-user limit (replay the redeem call)
11. Coupon stacking (two codes, or code + store-credit, combined against intent)
12. Expired / out-of-scope coupon accepted (expiry/SKU scope not enforced at redemption)
13. Race on coupon remaining-uses (parallel redeems exceed the cap) — pairs with E20

**C. Gift card / balance / store-credit abuse (API6 · A04)**
14. Gift-card / card-number enumeration (low-entropy numbers; unauthenticated balance-check)
15. Balance-check used as a draining oracle (unlimited probes find funded cards)
16. Gift-card / balance double-spend via race (parallel redeem against one balance) — pairs with E20
17. Store-credit / wallet applied beyond available balance (read-modify-write, no atomic debit)
18. Gift-card / credit transfer abuse (transfer to attacker, transfer more than held)

**D. Refund / chargeback abuse (API6 · A04)**
19a. Double-refund (refund the same order/transaction twice)
19b. Refund exceeding original amount (partial-refund arithmetic > original; refund > paid)
19c. Refund-then-keep (refund issued but goods shipped / entitlement retained)
19d. Refund-to-wrong-instrument (refund routed to an attacker-chosen card/account)
19e. Refund-then-chargeback (refund + dispute = paid back twice)

**E. Race conditions / parallel-request abuse (API6 · A04)**
20. Generic shared-counter race — parallel requests against balance / inventory / coupon /
    payout / seat count exceed the limit (TOCTOU read-modify-write with no lock/transaction)
21. Inventory oversell / overbook (more sold than in stock via concurrent checkout)
22. Payout / withdrawal double-spend race (two parallel withdrawals drain past available)

**F. Idempotency / duplicate state-change (API6 · A04)**
23. Duplicate charge (retry/double-click/replay creates two charges; no idempotency key)
24. Duplicate order / fulfillment (one payment → two orders, or two fulfillments)
25. Duplicate payout / credit / reward (one trigger → paid/credited twice)
26. Missing idempotency on paid downstream (one action → two SMS/email/LLM/charge costs)

**G. Payment-flow replay & step-skipping (A04 · API6)**
27. Step-skip to "paid"/"fulfilled" (POST directly to a state the charge should gate)
28. Trust a client-side success signal (`?status=success` / client callback) instead of a
    verified server-side / webhook confirmation
29. Order state not a server-enforced state machine (any state settable from the request)
30. Replay of a state-changing request (no nonce/timestamp/idempotency on the capture/grant call)

**H. Subscription / entitlement bypass (A04 · API6)**
31. Downgrade/cancel-keep-access (plan changed but entitlement not revoked / token still valid)
32. Trial farming (new free trial per email/card/device; no dedupe across identities)
33. Seat / quantity abuse (add seats → do work → remove seats; proration gaming)
34. Entitlement cached client-side or in a long-lived token, not re-checked per use

**I. Referral / affiliate / cashback farming (API6 · A04)**
35. Synthetic-account referral farming (one human, many fake accounts each earning a reward)
36. Self-referral (refer yourself / circular referral rings)
37. Reward granted before the qualifying event is irreversibly confirmed (refund-after-reward)
38. Reward stacking / multi-program combination beyond intent

**J. Free-tier / metered-resource farming (API6)**
39. Free-credit / free-compute / free-trial-of-paid-downstream farming via repeated signup/buy
40. Metered-resource grant replayed (replay the "grant free credits" call)

**K. Webhook-driven state corruption (API6 · A04 — authenticity deferred to ../16-webhooks/)**
41. Forged/unverified payment webhook flips state to "paid" without a real charge
42. Duplicate/replayed payment webhook double-credits / double-fulfills (no event-id dedupe)

**L. Negative-space, evasion & enumeration (API6)**
43. Distributed flow abuse — the same business-flow abuse spread across many IPs/accounts to stay
    under per-IP/per-account thresholds (botnet / synthetic-account fleet)
44. Slow-and-low flow abuse — abuse paced under volumetric thresholds (1 redeem/min for hours)
45. Header/IP spoofing to evade per-IP flow limits (`X-Forwarded-For`) — XFF is **not** trusted;
    detection keys on the trusted-ingress `client_ip`
46. Sensitive-flow execution at a rate/shape no human would produce (50 checkouts/min, every
    coupon code tried in order) — the workhorse **observable-abuse** signal

**M. Deferred — modeled in sibling models (reference, do not re-derive)**
47. Raw volumetric flood / generic scraping on money endpoints → [rate-limits & abuse](../12-rate-limits-and-abuse/) (**API4**)
48. Inbound payment-webhook signature/replay **authenticity** → [webhooks](../16-webhooks/) (**API8**)
49. Object-ownership on a money flow (refund/withdraw/redeem another user's resource — BOLA) → [authorization](../02-authorization/) (**API1**)
50. Synthetic-account *creation* / signup credential & OTP abuse behind farming → [authentication](../01-authentication/) + [messaging & notifications](../11-messaging-notifications/) (**API2**)

> For 47–50, add **one** matrix row each marked *"deferred — see linked model"*, and only note
> the SecureNow flow-observable symptom here (e.g. redeem-rate spike, refund-rate spike, distinct
> accounts per IP). The full detection/mitigation lives in the other reports.

> Every catalog item A1–L46 must be either a matrix row or an explicit N/A line with a reason —
> do not silently drop any item. Each modeled row carries **API6** and/or **A04** (or, for the
> deferred rows, the sibling's owning code with a pointer).

---

## 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/service/resolver name.
- **Pattern** — quote the 1–8 relevant lines. State the missing invariant precisely (e.g.
  "`const total = req.body.total` → client-supplied total trusted"; "`balance -= amount` after a
  separate read → read-modify-write race, no `SELECT … FOR UPDATE`/atomic decrement"; "no unique
  constraint / idempotency key on `charges` → retry creates a second charge"; "order status set
  from `req.body.status` → no server-side state machine"; "refund handler never checks
  `order.refundedAmount` → double-refund").
- **Why exploitable** — the concrete request(s) an attacker sends (incl. the *parallel* request
  for races) and what they achieve (free goods, drained card, double payout).
- **Severity** — critical / high / medium / low (impact × reachability; direct money loss = high/
  critical).
- **Recommended fix (described, not applied)** — the specific change: e.g. "compute the
  authoritative price server-side from the catalog; ignore client `price`/`total`"; "reject
  negative/zero/non-integer quantities and amounts; validate currency against an allowlist";
  "wrap balance/inventory/coupon mutation in a transaction with `SELECT … FOR UPDATE` or an
  atomic conditional `UPDATE`; add a DB unique constraint / CHECK"; "require and enforce an
  idempotency key with a unique index on charge/order/payout"; "make order state a server-side
  state machine; confirm payment only from a verified webhook / server-side capture, never a
  client callback param"; "track `refundedAmount` and reject refunds that would exceed the
  captured amount; lock the order row"; "revoke entitlement synchronously on
  downgrade/cancel/refund and re-check entitlement per use, not from a cached token"; "dedupe
  trials/referrals across email+card+device fingerprints". Reference the secure pattern, not a
  code diff. **You must not edit the codebase.**

If a control exists and is correct (server-side price authority, atomic decrement with a unique
constraint, idempotency keys enforced, server-side state machine, refund-cap check), note it as a
**strength** — the posture must be honest. Absence of a control where the money flow exists is
itself a finding ("no idempotency key anywhere in the charge path").

Look specifically for, and write findings on:

**Amount/price flaws** — totals/prices/discounts read from the request and trusted; no
server-side recompute from a trusted catalog; missing sign/range/type validation on amount &
quantity; currency not allowlisted; float arithmetic on money (use minor-unit integers /
decimal); per-line vs order-total mismatch. *Fixes must mention* server-side price authority,
strict amount/quantity/currency validation, and integer/decimal money handling.

**Coupon/gift-card/balance flaws** — guessable codes; unauthenticated or unthrottled
balance-check / validate endpoints (enumeration oracle); redemption that reads-then-writes a
balance or use-count without a lock/atomic update/unique constraint; stacking not enforced;
expiry/scope checked at apply-time but not at capture-time. *Fixes must mention* high-entropy
codes, authenticated+rate-limited validation, atomic redemption with constraints, and
re-validation at capture.

**Refund flaws** — refund handler that doesn't check already-refunded amount; refund amount not
capped to captured amount; refund instrument taken from the request rather than the original
payment method; no lock on the order during refund; entitlement/shipment not reversed. *Fixes
must mention* refunded-amount tracking + cap, original-instrument-only refunds, order-row locking,
and reversing the granted value.

**Race / concurrency flaws** — read-modify-write on balance/inventory/coupon/payout/seat with no
transaction, row lock, optimistic version, or unique constraint; "check then act" gaps; provider
calls not wrapped so two parallel requests both succeed. *Fixes must mention* DB transactions,
`SELECT … FOR UPDATE`/row locks, atomic conditional updates, unique constraints, and optimistic
locking.

**Idempotency flaws** — charge/order/payout/credit creation with no idempotency key or unique
constraint; retries/double-submits creating duplicates; provider idempotency support unused;
paid downstream (SMS/email/LLM) re-fired on retry. *Fixes must mention* idempotency keys with a
unique index, provider idempotency headers, and dedupe stores.

**Payment-flow integrity flaws** — endpoints that set order/payment state from request input;
fulfillment/entitlement gated on a client success param rather than a verified server signal;
no server-side state machine; state-changing calls with no replay protection. *Fixes must mention*
server-side state machines, payment confirmation only from verified webhook / server-side
capture, and nonce/idempotency on capture/grant.

**Subscription/entitlement flaws** — entitlement not revoked on downgrade/cancel/refund;
entitlement cached in a long-lived token and not re-checked; trial/seat dedupe missing. *Fixes
must mention* synchronous revocation, per-use entitlement checks, and cross-identity trial dedupe.

**Referral/affiliate/farming flaws** — reward granted before the qualifying event is final;
no self-referral / synthetic-account guard; reward stacking. *Fixes must mention* granting only
after irreversible confirmation, self-referral and fingerprint dedupe, and reward-stacking caps.

---

## Phase 4 — Map every modeled threat to SecureNow detection + mitigation

Classify each threat with exactly one coverage badge:

- 🟢 **COVERED** — detectable + mitigable with SecureNow today, on telemetry already flowing or
  via the three first-class app events. For this domain the genuinely-green cases are
  **flow-rate / shape abuse** (too many checkouts/redeems/refunds/payouts per IP/account/window),
  **distributed flow abuse** (per-ASN / many-accounts), **bot-driven farming** (challenge/block),
  and the **`api.race.detected` / `api.replay.blocked` / `api.idempotency.missing`** events once
  the app emits them.
- 🟡 **PARTIAL** — works only after the customer adds instrumentation (the `api.*` events), or
  SecureNow can only *contain the abuser at the edge* (rate-limit / challenge / block) while the
  **logic correctness itself is an app fix**. **This is the default for this model**: a single
  parallel race, a price-tampered request, a missing idempotency key — SecureNow can catch the
  *abusive pattern or the app-emitted event* and stop the actor, but the server-side
  validation / lock / constraint is the real fix. Pair the SecureNow control WITH the app fix on
  every such row.
- 🔴 **GAP** — SecureNow cannot detect or mitigate this today (a one-shot logic flaw that emits
  no abnormal traffic and no event — e.g. a single price-tampered checkout that "looks normal").
  **Still include it**: give the app-level fix, an interim containment if any, 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-containment vs app fix.** SecureNow sees **traffic** (rates, status
> codes, paths, client IP, response sizes, ASN/org) and **events** the app emits at its own
> enforcement points. It contains actors via firewall / rate-limit / challenge / block /
> signature instant-block. It **cannot** know that a *single* checkout used a tampered price, or
> that a balance debit raced, unless the app **emits the event** (`api.race.detected`,
> `api.replay.blocked`, `api.idempotency.missing`, `api.sensitive.flow`) — and even then, the
> fix that stops the loss is the **server-side validation / lock / constraint / state machine**.
> So: model flow-rate & farming as 🟢 where the signal is real; model the per-flow logic flaws as
> 🟡 (event + edge containment) **paired with the app fix**; mark the truly invisible one-shot
> logic flaws 🔴 with the app fix and the contact line.

Use **only** the SecureNow building blocks below. Never invent CLI flags, event names, or SQL
columns.

### 4a. Instrumentation (what payment/business-logic detections feed on)

Once the app runs under `securenow run` / `securenow/register` / `securenow init`, **HTTP
traffic is captured automatically** — status codes (incl. **429**/**5xx**), methods, paths,
client IPs, response sizes — so **flow-rate** rules on the *paths* of money endpoints
(`/api/checkout`, `/api/refund`, `/api/coupon/redeem`, `/api/payout`) need **no events**. But the
high-value business-logic signals — a race, a blocked replay, a missing idempotency key, a
sensitive flow firing — are **app-internal** and must be emitted with `securenow/events`
`track()` at the enforcement point. **`track()` never throws** (a telemetry failure must never
break a payment).

```js
const { track } = require('securenow/events');

// A sensitive business flow executed (API6) — feeds flow-rate/abuse detection.
// Emit on EVERY checkout / refund / redeem / payout / reward-grant / withdrawal:
track('api.sensitive.flow', { userId, ip, attributes: { flow: 'checkout|refund|coupon_redeem|gift_card_redeem|payout|withdrawal|reward_grant|subscription_change', amount_bucket: 'lt10|10_100|100_1k|gt1k', items: '50' } });

// The app detected concurrent/parallel abuse of a single-use or limited flow (races):
track('api.race.detected', { userId, ip, attributes: { route: '/api/coupon/redeem', resource_id: '<hash_or_id>', flow: 'checkout|coupon|gift_card|balance|inventory|payout' } });

// The app rejected a state-changing request as a replay / duplicate (replay & step-skip):
track('api.replay.blocked', { userId, ip, attributes: { route: '/api/checkout/capture', reason: 'duplicate_nonce|expired_timestamp|duplicate_idempotency_key', flow: 'checkout|coupon|gift_card|payout|refund' } });

// A paid / state-changing call ran WITHOUT an idempotency key (instrument the gap itself):
track('api.idempotency.missing', { userId, ip, attributes: { route: '/api/order', flow: 'checkout|payment|payout|refund|reward|email|sms|llm' } });
```

Supporting events for the coupon/gift-card/refund/farming and webhook-state rows (all `api.*`;
emit at the enforcement point; never throw). Reuse the canonical names — do not invent new ones:

```js
const { track } = require('securenow/events');

// App's own quota/limit rejected a flow request (per-user coupon cap, per-account refund cap):
track('api.ratelimit.exceeded', { userId, ip, attributes: { route: '/api/coupon/redeem', limit: '1', window: 'per_user' } });

// A request body failed amount/quantity/currency validation (price/qty/negative tampering):
track('api.payload.rejected', { ip, attributes: { route: '/api/checkout', reason: 'negative_amount|client_total|bad_currency|qty_out_of_range|rounding' } });

// Inbound payment webhook authenticity (authenticity model lives in ../16-webhooks/, but emit
// here so the business-state rules can correlate a forged/duplicate money webhook):
track('api.webhook.signature_failed', { ip, attributes: { route: '/webhooks/stripe', provider: 'stripe|paypal|braintree|adyen|custom', reason: 'missing|invalid|expired_timestamp|bad_format' } });
track('api.webhook.duplicate_ignored', { ip, attributes: { route: '/webhooks/stripe', provider: 'stripe|paypal|braintree|adyen|custom', event_id: '<hash_or_id>' } });
```

> Hash or omit any PII / money-sensitive value before it becomes an attribute (card numbers,
> gift-card numbers, coupon codes, raw amounts tied to identity, payment tokens, emails) — see the
> Phase 1 **telemetry privacy & redaction** check. Use coarse `amount_bucket` ranges, never raw
> amounts. Attributes feed detection; they must not become a new leak path.

Recommended event taxonomy — rules match these **exact strings**:

| Event | Emit when |
|---|---|
| `api.sensitive.flow` | a money/value flow executes (checkout/refund/redeem/payout/reward/subscription change) |
| `api.race.detected` | concurrent/parallel abuse of a single-use or limited flow is detected |
| `api.replay.blocked` | a state-changing request is rejected (dup nonce / expired ts / dup idempotency key) |
| `api.idempotency.missing` | a paid/state-changing call ran without an idempotency key |
| `api.ratelimit.exceeded` | the app's own per-user/per-flow quota rejects a request |
| `api.payload.rejected` | an amount/quantity/currency/body validation gate rejects a request |
| `api.webhook.signature_failed` | an inbound payment webhook signature is missing/invalid/expired/malformed |
| `api.webhook.duplicate_ignored` | an already-processed payment webhook event-id is ignored (idempotency) |

Custom `attributes` become queryable as `attributes_string['<key>']` (e.g.
`attributes_string['flow']`, `attributes_string['reason']`). Ingest enriches every IP with
**ASN/org** (`client.asn`, `client.as_org`) — enabling botnet/datacenter-origin and
distributed-farming detection 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; OTEL SDK
versions vary (`http.method` vs `http.request.method`). **`X-Forwarded-For` is not trusted** —
always key on the coalesced trusted-ingress `client_ip`.

**Traffic-based — sensitive-flow rate abuse on a money endpoint (single IP), no events needed:**
Counts requests to the money path; tune the path and threshold per flow (checkout, refund,
redeem, payout).

```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 flow_calls
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 '/api/coupon/redeem%' OR attributes_string['http.target'] LIKE '/api/gift-card/%' OR attributes_string['http.target'] LIKE '/api/refund%')
GROUP BY ip
HAVING ip != '' AND flow_calls >= 30
```

**Traffic-based — coupon / gift-card enumeration (many calls + many client errors = guessing):**
A draining/enumeration oracle shows as a high request count with a high invalid/4xx ratio.

```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 attempts,
       countIf(response_status_code IN ('400','402','404','409','422')) AS invalid
FROM signoz_traces.distributed_signoz_index_v3
WHERE `resource_string_service$$name` IN (__USER_APP_KEYS__)
  AND timestamp >= now64(9) - INTERVAL 15 MINUTE
  AND ts_bucket_start >= toUInt64(toUnixTimestamp(now() - INTERVAL 15 MINUTE)) - 1800
  AND kind = 2
  AND (attributes_string['http.target'] LIKE '/api/coupon/%' OR attributes_string['http.target'] LIKE '/api/gift-card/%')
GROUP BY ip
HAVING ip != '' AND attempts >= 50 AND invalid >= 30
```

**Traffic-based — distributed flow abuse by ASN (botnet / synthetic-account fleet, catalog L43):**
Spreads the same abuse across many IPs in one ASN to stay under per-IP limits. Notify-only — an
ASN can carry real users; confirm before acting.

```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 attributes_string['client.as_org'] AS as_org,
       any(client_ip) AS ip,
       uniqExact(client_ip) AS distinct_ips,
       count() AS flow_calls
FROM signoz_traces.distributed_signoz_index_v3
WHERE `resource_string_service$$name` IN (__USER_APP_KEYS__)
  AND timestamp >= now64(9) - INTERVAL 15 MINUTE
  AND ts_bucket_start >= toUInt64(toUnixTimestamp(now() - INTERVAL 15 MINUTE)) - 1800
  AND kind = 2
  AND attributes_string['http.target'] LIKE '/api/checkout%'
GROUP BY as_org
HAVING ip != '' AND distinct_ips >= 20 AND flow_calls >= 200
```

**Events-based — race detected on a limited flow (API6/A04; query the logs table):**
Any `api.race.detected` is high-signal; a cluster from one IP confirms scripted parallelism.

```sql
SELECT
  attributes_string['http.client_ip'] AS ip,
  attributes_string['flow']           AS flow,
  attributes_string['route']          AS route,
  count() AS races
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] = 'api.race.detected'
  AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, flow, route
HAVING ip != '' AND races >= 1
```

**Events-based — replay / step-skip / missing-idempotency on a money flow:**

```sql
SELECT
  attributes_string['http.client_ip'] AS ip,
  attributes_string['event.type']     AS signal,
  attributes_string['flow']           AS flow,
  attributes_string['route']          AS route,
  count() AS hits
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] IN ('api.replay.blocked','api.idempotency.missing')
  AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, signal, flow, route
HAVING ip != '' AND hits >= 1
```

**Events-based — amount/qty/currency tampering rejected by the app (price tampering, catalog A):**

```sql
SELECT
  attributes_string['http.client_ip'] AS ip,
  attributes_string['route']          AS route,
  attributes_string['reason']         AS reason,
  count() AS rejections
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] = 'api.payload.rejected'
  AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, route, reason
HAVING ip != '' AND rejections >= 5
```

**Events-based — forged / duplicate payment webhook (business-state corruption, catalog K):**
Authenticity itself is the [webhooks](../16-webhooks/) model's job; this rule surfaces the money
impact.

```sql
SELECT
  attributes_string['http.client_ip'] AS ip,
  attributes_string['event.type']     AS signal,
  attributes_string['provider']       AS provider,
  count() AS attempts
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] IN ('api.webhook.signature_failed','api.webhook.duplicate_ignored')
  AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, signal, provider
HAVING ip != '' AND attempts >= 3
```

The other catalog events follow the **same shape** — swap the `event.type` filter and the
threshold: `api.sensitive.flow` grouped by `userId` (≥30 flows/10m from one account → farming),
`api.ratelimit.exceeded` (≥50/15m → ignoring the per-user cap). Where a row's only signal is a
single invisible logic flaw (one tampered price that the app *accepted*), there is **no SQL** —
it is 🔴 until the app emits `api.payload.rejected`/`api.replay.blocked` per Phase 3's fix.

**Payload-borne tampering markers — reuse the system signature rules, don't write SQL.** If price/
qty tampering arrives as an injection-shaped payload (operator smuggling, SQLi in an amount
field), the SecureNow **system signature rules** with synchronous **`instant.block`** (blocks a
matching request in ~2.6s on ingest) already cover the injection; confirm they're present and
enabled via `securenow alerts rules --json` and enable `instant.block` rather than authoring
duplicate pattern SQL. Pure logic tampering (a valid-looking negative number) is **not** a
signature case — that's the app-fix row.

Useful attributes/columns: `event.type`, `http.client_ip`, `http.target`,
`response_status_code`, `kind`, `client.asn`, `client.as_org`, and your money attributes
(`flow`, `reason`, `route`, `amount_bucket`, `provider`, `resource_id`).

**Emit each detection as a ready-to-copy command unit.** Never emit a fragment. For each rule,
emit — in order, each as its own fenced block so it copies cleanly:

1. the **SQL**, with a `-- rules/<name>.sql` header comment;
2. a line **saving** it to `rules/<name>.sql` so `--sql @rules/<name>.sql` works;
3. the full **`securenow alerts rules create …`** command (flags exactly as
   `securenow alerts rules --help` from Phase 0.5 lists them — `--name/--sql/--apps/--severity/`
   `--schedule/--nlp`);
4. the **dry-run test** (`securenow alerts rules test <RULE_ID> --mode dry_run --wait`) to
   validate the query before it runs live.

Note pre-existing / system rules (from Phase 0) instead of duplicating them. Example unit:

```sql
-- rules/payment-refund-rate.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 flow_calls
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 '/api/refund%'
GROUP BY ip
HAVING ip != '' AND flow_calls >= 30
```

```bash
mkdir -p rules
cat > rules/payment-refund-rate.sql <<'SQL'
-- (paste the SQL block above)
SQL
```

```bash
securenow alerts rules create \
  --name "Payment: refund-rate abuse (single IP)" \
  --sql @rules/payment-refund-rate.sql \
  --apps <APP_KEY> \
  --severity high \
  --schedule "*/5 * * * *" \
  --nlp "single IP calling the refund flow 30+ times in 10 minutes"
```

```bash
securenow alerts rules test <RULE_ID> --mode dry_run --wait   # validate before it runs live
```

#### 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 (flow-rate,
redeem/refund/checkout counts, enumeration counts), broad patterns, anomaly / volume rules,
anything tuned to YOUR traffic — 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 (bulk buyers, partner batch refunds, seasonal coupon spikes), then
`--mode prod` to arm mitigation. Only **high-precision** rules (exploit-signature SQLi/XSS/RCE
matches, exact-match IoCs, known-bad ASN hits, and the high-signal `api.race.detected` /
`api.replay.blocked` events) may go straight to `prod`. (`securenow alerts rules test <id> --mode
dry_run --wait` is the separate one-off *query* validation — run it before either.)

**Tag every rule `test-first` or `prod-ready`** in the report, and say why. For this domain almost
every traffic-based flow-rate / enumeration / farming / ASN-distributed heuristic is **`test-first`**
(business-logic thresholds are tuned to your traffic); the small set of high-precision rows
(signature instant-block, single high-signal `api.race.detected` / `api.replay.blocked` hits) are
**`prod-ready`**.

### 4c. Mitigation commands (the only allowed remediation surface)

For payment & business-logic abuse, SecureNow **contains the actor at the edge**; the
**app/config fix** removes the underlying logic weakness. **Pair them on every per-flow logic
row** — the SecureNow control buys time and stops the bot/loop; the server-side
validation/lock/constraint is the primary fix.

Once a threat is confirmed, **choose the narrowest effective mitigation(s) from ALL of these** and
combine them (e.g. rate-limit the coupon/checkout route + block the worst IPs + challenge a NAT
egress). 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 buyers.

| # | 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 /api/checkout --method POST` | 500k+ known-bad IPs, hourly refresh; drop scanners/known-bad sources hitting money flows before the app. 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 (payload-borne tampering markers in amount/coupon/qty fields). Don't duplicate pattern SQL. |
| 3 | **IP block — global** | `securenow blocklist add <ip> --app <APP_KEY> --env production --reason "..."` | confirmed-malicious source / aggressive enumerator, all routes. |
| 4 | **IP block — scoped to route (+ method)** | `securenow blocklist add <ip> --route /api/payout* --mode prefix --method ALL --app <APP_KEY> --env production --reason "..."` (`--mode exact\|prefix\|regex`, `--method GET\|POST\|…\|ALL`) | block an IP only on sensitive money paths (payout/refund/redeem); 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 payout/enumeration abuser; audit-preserving unblock. |
| 6 | **Rate limit — per IP** | `securenow ratelimit add <ip> --limit 100 --window 1m --duration 24h --reason "..."` | throttle one abusive client across the app. |
| 7 | **Rate limit — per route (all clients, per-IP budget)** | `securenow ratelimit add --route /api/coupon/redeem --mode prefix --method POST --limit 5 --window 1m --key-by ip` | cap an expensive/abusable money endpoint (coupon redeem, checkout, refund) for everyone, budgeted per IP. |
| 8 | **Rate limit — per route + IP** | `securenow ratelimit add <ip> --route /api/coupon/redeem --mode exact --method POST --limit 5 --window 1m --duration 24h` · NL `securenow ratelimit from-text "rate limit /api/coupon/redeem to 5/min for 24h" --yes` · test `securenow ratelimit test <ip> --path /api/coupon/redeem --method POST` | precise throttle of one client on one money route — coupon/gift-card enumeration, refund/checkout-rate abuse. |
| 9 | **CAPTCHA / proof-of-work challenge** | `securenow challenge add --route /api/checkout --difficulty 16 --clearance 30m` (route-wide) **or** `securenow challenge add <ip> --route /api/coupon/redeem --difficulty 18 --clearance 30m` · test `securenow challenge test <ip> --path /api/checkout --method POST` | bot-driven farming / business-flow abuse from **shared / NAT / CGNAT** egress — a human passes once, a script can't keep farming. Prefer over a hard block when real buyers share the IP. |
| 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 money-flow abusers by risk score; actions include block / rate_limit / requireCaptcha. |
| 11 | **Session revocation** | `securenow revoke …` (SDK `securenow/sessions` `guard()` / `isRevoked()`) | account takeover on a money flow — kill the stolen session driving the fraudulent checkout/payout, not the IP. |
| 12 | **Trusted IP (suppress)** | `securenow trusted add <ip> --label "Partner billing integration / reconciliation batch"` | stop false positives from known-good billing infra — 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/admin-only payout/billing surface. Never for a public storefront. |
| 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 flow-rate rule quiet for legitimate bulk buyers / partner batch refunds without weakening it. |
| 15 | **App / config / code fix (primary for root cause)** | *described in the Code-Findings report, never auto-applied* | the actual fix: server-side price authority, amount/qty/currency validation, atomic redemption + DB constraints/locks/transactions, idempotency keys, refund-amount cap, server-side state machine, verified-webhook-only confirmation, synchronous entitlement revocation, trial/referral dedupe. SecureNow contains; the fix removes. |

**Choosing per threat** — by **confidence**: exploit-signature/exact IoC → instant-block or block;
probable bot/farming on shared egress → **challenge**; noisy/legit-mixed flow traffic (coupon
spikes, bulk refunds, the ASN-distributed rule) → **rate-limit (test-mode first)**; account
takeover on a money flow → **revoke**; known-good billing noise → **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 paying
customers). **Always pair an edge mitigation with the app/config fix** (Code-Findings report) —
SecureNow can only contain the actor; the server-side validation/lock/constraint/idempotency/state
machine is the primary fix on every logic row.

Tier guidance: **challenge** sits between rate-limit and block — prefer it over a hard block
when the abusing IP may be a shared/NAT/CGNAT egress carrying real buyers (a block nukes
paying customers; a challenge lets humans check out and stops the farming script). Reserve hard
block for confirmed-malicious enumerators / payout abusers. Recommend **notify-only** (no auto
action) for false-positive-prone signals: legitimate bulk purchasers, partner batch refunds,
seasonal coupon spikes, and the ASN-distributed rule — say so explicitly with the runbook
command the human runs after confirming. **And on every logic row, name the app fix as primary.**

### 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 real card / gift-card / coupon data** — use `test=true` and synthetic values.

```bash
# Synthetic sensitive-flow farming — exercise the flow-rate rule end to end:
for i in $(seq 1 40); do
  securenow event send api.sensitive.flow --ip 203.0.113.50 \
    --attrs flow=coupon_redeem,amount_bucket=lt10,test=true
done

# Synthetic race detection (coupon double-redeem):
securenow event send api.race.detected --ip 203.0.113.51 \
  --attrs route=/api/coupon/redeem,flow=coupon,resource_id=test-coupon,test=true

# Synthetic replay / missing-idempotency on checkout:
securenow event send api.replay.blocked --ip 203.0.113.51 \
  --attrs route=/api/checkout/capture,reason=duplicate_idempotency_key,flow=checkout,test=true
securenow event send api.idempotency.missing --ip 203.0.113.52 \
  --attrs route=/api/order,flow=payment,test=true

# Synthetic amount/currency tampering rejected:
for i in $(seq 1 6); do
  securenow event send api.payload.rejected --ip 203.0.113.52 \
    --attrs route=/api/checkout,reason=negative_amount,test=true
done

# Synthetic forged/duplicate payment webhook (business-state):
securenow event send api.webhook.signature_failed --ip 203.0.113.53 \
  --attrs route=/webhooks/stripe,provider=stripe,reason=invalid,test=true

# Validate a rule query without waiting for the schedule:
securenow alerts rules test <RULE_ID> --mode dry_run --wait

# Traffic-based rules (redeem/refund/checkout-rate, enumeration) — generate spans, then check:
securenow test-span "threat-model.payment.smoke"
securenow forensics "requests and 4xx by IP to /api/coupon and /api/refund in the last hour" --env production

# Mitigation verification:
securenow ratelimit test 203.0.113.50 --path /api/coupon/redeem --method POST
securenow challenge test 203.0.113.50 --path /api/checkout --method POST
securenow firewall test-ip 203.0.113.50 --app <APP_KEY> --env production

# 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 two tracks (FOUR files)

Write **four** files into `threat/09-payment-business-logic/` — two tracks, each Markdown +
self-contained HTML. The two tracks **cross-link**: detection rows that need an event or that are
🔴 link to the relevant code finding; each code finding links back to the detection row it backs.

### 5a. Detection & Mitigation report — `payment-business-logic-detection-mitigation.{md,html}`

The **operational runbook** — what to run in SecureNow. Required sections, in order:

1. **Executive summary** — stats line (N threats modeled · N covered · N partial · N gaps ·
   N rules to create · N mitigations), the top 3 *detectable* money-abuse risks for this stack,
   the installed `securenow` version + app key + firewall state, and a one-line mapping note: this
   model owns **OWASP API6 (Sensitive Business Flows) + A04 (Insecure Design)**; volumetric,
   webhook-authenticity, BOLA, and synthetic-account *creation* are deferred to the linked sibling
   models.
2. **SDK & environment** — the 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 which **system signature rules** are present.
3. **Threat → Detection → Mitigation matrix** — one row per modeled threat:
   `# | Threat | OWASP (API6/A04) | Coverage 🟢/🟡/🔴 | Detection (rule name or "—") | Mode (test-first/prod-ready) | Signal (threshold+window) | Schedule | Sev | Mitigation (edge control + app fix)`.
   Severity ∈ {critical, high, medium, low}. Each row's **Mitigation** cell must pick **specific,
   scoped** mitigation(s) from the §4c toolbox (named layer + scope: route/method/IP/duration) —
   never a generic "block the IP" — paired with the app fix on every logic row. Each row carries a
   **`test-first` / `prod-ready`** tag for its detection rule (business-logic heuristics are usually
   `test-first`). Include the deferred rows (47–50) pointing to the sibling models. Follow with the
   "Out of scope" N/A list and the "Deferred to sibling models" subsection. Rows whose fix is in
   code link to the matching **code finding**.
4. **Detection rules to create** — each as the **ready-to-copy command unit** from Phase 4
   (SQL → save to `rules/<name>.sql` → full `securenow alerts rules create …` → dry-run test).
   **Tag each rule `test-first` or `prod-ready`** (per the Phase 4b test-mode block); for every
   `test-first` rule, include the `--mode test` → observe (3–7 days) → `--mode prod` promotion step
   inline. Note rules that already exist (from Phase 0) instead of duplicating them. Injection-class
   / payload-borne tampering rows reference the **system signature rules + `instant.block`**, not
   duplicate SQL.
5. **Instrumentation the detections need** — only the `track('…')` events the rules above consume
   (`api.sensitive.flow` / `api.race.detected` / `api.replay.blocked` / `api.idempotency.missing`
   / `api.payload.rejected` / `api.webhook.*`), each as a copyable snippet; point to the
   **code-findings report** for *where* (file:line) to add them.
6. **Mitigation mechanisms** — render the **full §4c 15-row mitigation toolbox table** (firewall ·
   exploit-signature instant-block · IP block [global / route+method / temporary] · rate-limit
   [per-IP / per-route / route+IP] · challenge · auto-block · session revocation · trusted ·
   allowlist · fp · **app-config fix**) followed by the "Choosing per threat" paragraph, then the
   per-threat ready-to-copy mitigation command + reversibility. Each threat selects **specific,
   scoped** mitigation(s) from the toolbox (named layer + route/method/IP/duration). Make explicit
   that the **app/config fix is primary on every logic row** (it lives in the code-findings report)
   and the SecureNow control is containment — it stops the bot/loop while the server-side
   validation/lock/constraint/idempotency removes the flaw.
7. **Action plan (copy-paste, ordered)** — ① engage the firewall + enable signature instant-block,
   ② add the money-flow event instrumentation at the enforcement points where traffic is blind,
   ③ create the flow-rate + event rules — **create every FP-prone (`test-first`) rule in
   `--mode test`** (detect-only), and only the high-precision `prod-ready` rules straight to
   `--mode prod`, ④ enable automations / challenge rules on the money paths, ⑤ test, ⑥ verify in
   dashboard, ⑦ **promote** the `test-first` rules with `securenow alerts rules update <RULE_ID>
   --mode prod` **after N days** (3–7) of observing real traffic + adding `securenow fp` exclusions
   for legitimate hits, ⑧ schedule the app/config fixes (from the code-findings report:
   server-side price authority, atomic redemption + constraints, idempotency keys, refund cap,
   state machine, entitlement revocation, trial/referral dedupe). Real commands only, `<APP_KEY>`
   substituted.
8. **Testing & validation** — per 🟢/🟡 row: a concrete recipe (`securenow event send …` /
   `test-span` / dry-run) + expected outcome (which rule fires, which notification appears, what
   the mitigation does) + cleanup. Use TEST-NET IPs (`192.0.2.0/24`, `198.51.100.0/24`,
   `203.0.113.0/24`); never send real card/gift-card/coupon data.
9. **Response runbooks** — for each notification type (flow-rate abuse, enumeration, race,
   replay/missing-idempotency, tampering-rejected, forged/duplicate webhook): what fired, how to
   confirm a true positive, the exact command to respond (copy), the exact command to reverse
   (copy), **and** the app-fix ticket to file (link to the code finding).
10. **Known gaps & SecureNow feature requests** — every 🔴 threat (the invisible one-shot logic
    flaws): why it's not coverable today, the interim fix (link to the code finding) + any edge
    containment, and the "contact the SecureNow team" line.
11. **Appendix** — resolved SDK/CLI version (from Phase 0.5), app key, environment, firewall
    state, rule IDs created, date, link to the code-findings report.

### 5b. Code Findings & Recommendations report — `payment-business-logic-code-findings.{md,html}`

State at the top: *"Findings only — no application code was modified."* Required sections, in
order:

1. **Executive summary** — findings by severity (critical / high / medium / low), the top 3 code
   risks for this stack, and a one-paragraph posture verdict.
2. **Money & value surface** — the Phase 1 inventory: the flow catalog + amount-field trust table
   + coupon/gift-card/balance posture + refund posture + concurrency map + idempotency map +
   subscription/entitlement gating + referral/payout economics + webhook-driven-state list +
   telemetry redaction status.
3. **Threat catalog** — the exhaustive Phase 2 catalog (grouped A–M, each tagged API6/A04,
   modeled or explicit N/A), including the deferred rows (47–50) pointing to the sibling models.
4. **Code-level findings (audit — not applied)** — a table
   `# | Location (file:line) | Threat | OWASP | Sev | Issue | Recommended fix`, each with the
   quoted 1–8 line snippet and the described fix (never applied). Each row links to the detection
   row it backs.
5. **Strengths** — controls already present and correct (server-side price authority, atomic
   decrement with a unique constraint, idempotency keys enforced, server-side state machine,
   refund-cap check) — the posture must be honest.
6. **App / config fixes (primary remediation)** — the config/code changes that remove the root
   cause (described, not applied): server-side price authority, amount/qty/currency validation,
   atomic redemption + DB constraints/locks/transactions, idempotency keys, refund-amount cap,
   server-side state machine, verified-webhook-only confirmation, synchronous entitlement
   revocation, trial/referral dedupe. 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, so the detection rules light up.
8. **Appendix** — files reviewed, resolved SDK version (from Phase 0.5), date, link to the
   detection-mitigation report.

### 5c. HTML skeletons — two self-contained files (offline; inline CSS + copy JS; no network)

Both HTML files share the `<head>` (brand tokens + copy-button styles) and the copy `<script>` at
the end of `<body>`. Change only the `<title>`, the sidebar subtitle, and the section content.
Wrap **every** command/SQL block as a `.cmd` so it gets a Copy button. Stats numbers must equal
the matrix / findings row counts. The Code-Findings HTML may omit copy buttons on prose, but still
wraps any example/fix command in `.cmd`.

**Detection & Mitigation HTML** — `payment-business-logic-detection-mitigation.html`:

```html
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Detection &amp; Mitigation — Payment / Business Logic — 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 &amp; Mitigation · Payment / Business Logic</div>
    <!-- one <a href="#…"> per section (1–11 of 5a) -->
  </nav>
  <main>
    <header class="top"><h1>Detection &amp; Mitigation — Payment / Business Logic</h1>
      <p><code><!-- app name / domain --></code> · <span class="pill">securenow <!-- installed version --></span></p></header>
    <div class="stats">
      <div class="stat"><div class="n"><!-- N --></div><div class="l">threats modeled</div></div>
      <div class="stat"><div class="n" style="color:var(--ok)"><!-- N --></div><div class="l">covered</div></div>
      <div class="stat"><div class="n" style="color:var(--med)"><!-- N --></div><div class="l">partial</div></div>
      <div class="stat"><div class="n" style="color:var(--crit)"><!-- N --></div><div class="l">gaps — SecureNow team</div></div>
      <div class="stat"><div class="n" style="color:var(--info)"><!-- N --></div><div class="l">rules to create</div></div>
    </div>
    <!-- <section id="…"> blocks mirroring sections 1–11 of 5a; wrap EVERY command/SQL in .cmd -->
    <footer>Generated by the SecureNow payment / business-logic 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>
```

**Code Findings HTML** — `payment-business-logic-code-findings.html`: identical `<head>` and copy
`<script>` as above; change only the `<title>` to
`Code Findings — Payment / Business Logic — SecureNow`, the sidebar subtitle to
`Code Findings · Payment / Business Logic`, the `<h1>` to
`Code Findings — Payment / Business Logic`, and the body to mirror sections 1–8 of 5b. Its five
stat cards are by severity: `critical` (crit) · `high` (high) · `medium` (med) · `low` (low) ·
`strengths` (ok). Wrap any example/fix command in `.cmd`.

**Copyable command wrapper** (Detection & Mitigation HTML — every SQL/command block):

```html
<div class="cmd"><button class="copy" type="button">Copy</button><pre>securenow alerts rules create \
  --name "..." --sql @rules/&lt;name&gt;.sql --apps &lt;APP_KEY&gt; --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">API6</span>` / `<span class="owasp">A04</span>`, CWE →
`<span class="cwe">CWE-840</span>`, mitigation type →
`<span class="m firewall|signature|rate|challenge|block|notify|appfix">`, rule IDs →
`<span class="rid">`. Stats numbers must equal the matrix / findings row counts. Both HTML files
are self-contained — inline CSS + JS, no CDN/fonts/network.

---

## Quality bar (the report is rejected if any of these fail)

- Every catalog item A1–L46 is either a matrix row or an explicit N/A line; each modeled row
  carries its **OWASP API6 and/or A04** tag, and the deferred rows (47–50) point to the sibling
  model that owns them.
- The core money-abuse threats — price/qty/currency tampering & negatives, coupon stacking/
  enumeration/reuse, gift-card/balance enumeration & draining, refund abuse (double/over/
  wrong-instrument/then-chargeback), races on balance/inventory/coupon/payout, missing
  idempotency (duplicate charge/order/payout), payment-flow replay & step-skipping,
  subscription/entitlement bypass, referral/affiliate/cashback farming, free-tier/metered
  farming, and webhook-driven state corruption — are each modeled or explicit N/A, with the
  matching `api.*` event where detection needs instrumentation.
- Volumetric/scraping, webhook-authenticity, BOLA object-ownership, and synthetic-account
  *creation* are **deferred** to the sibling models by **numbered path**
  (`../12-rate-limits-and-abuse/`, `../16-webhooks/`, `../02-authorization/`,
  `../01-authentication/`) — rows present, linked, not re-derived.
- 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 —
  `resources_string['service.name']` for logs vs `` `resource_string_service$$name` `` for
  traces) and selects an `ip` column; traffic queries keep the `ts_bucket_start` + `kind = 2`
  guards and the `client_ip` coalesce; grouped queries keep `HAVING ip != ''`; rules run with
  `--mode dry_run` first. `X-Forwarded-For` is not trusted.
- Coverage is honest: native MED is flow-rate abuse + `api.race.detected` / `api.replay.blocked`
  / `api.idempotency.missing` events + challenge/block on bots; the **logic correctness itself**
  (server-side price/qty validation, idempotency keys, DB constraints/locks/transactions,
  entitlement revocation) is an **app fix** — every per-flow logic row pairs edge containment
  WITH the app fix.
- 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-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.
- Payload-borne tampering markers reference the **system signature rules + `instant.block`**,
  not duplicate pattern SQL.
- Only commands, flags, events, and SQL columns from this prompt's building blocks appear
  (including `securenow challenge …`, `firewall`, and the `api.sensitive.flow` /
  `api.race.detected` / `api.replay.blocked` / `api.idempotency.missing` / `api.payload.rejected`
  / `api.webhook.*` events).
- Every 🔴 gap appears in the gaps section with an interim app/config 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 `alerts rules --help`.
- **Four** files are written to `threat/09-payment-business-logic/`
  (`payment-business-logic-detection-mitigation.md` + `.html`,
  `payment-business-logic-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**; the stats cards match the table/finding 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: **per-track file paths**, threat counts, rules-to-create
  count, code findings by severity, gaps, OWASP API6/A04 coverage, and the resolved SDK version.

<!-- ════════════════ END OF PROMPT ════════════════ -->