#02A01 · API1 BOLA · API5 BFLA
Authorization
BOLA/IDOR, broken function-level auth, mass assignment, privilege escalation, multi-tenant.
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/02-authorization/— openauthorization-code-findings.html(the audit) andauthorization-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
# Authorization 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 has the
`securenow` SDK/CLI installed and logged in. The agent will analyze the codebase, build an
exhaustive **authorization** (access-control) threat model, audit the code for access-control
flaws, and emit a SecureNow-branded **two-track** deliverable set in **Markdown + self-contained
HTML** — a **Detection & Mitigation** runbook (the rules to create, the mitigation commands to
run, how to test each one) and a **Code Findings & Recommendations** audit (the access-control
flaws in the code, audited and **not** fixed) — plus which threats still need the SecureNow team.
Every alert rule and CLI command the agent emits is **grounded in the `securenow` SDK actually
installed in `node_modules/securenow`** (Phase 0.5) — never a guessed flag, subcommand, event
name, or SQL column — and every detection is written as a **ready-to-copy unit**
(SQL → save to `rules/<name>.sql` → full `securenow alerts rules create …` → dry-run test).
Authorization ≠ authentication. Authentication proves *who you are*; authorization decides
*what you are allowed to do with it*. This model assumes the caller is already
authenticated and asks: can they reach data or actions that are not theirs? This is OWASP
**A01:2021 Broken Access Control** (the #1 web risk) and OWASP API Security **API1 (BOLA)**,
**API3 (BOPLA)**, **API5 (BFLA)**. Run the [authentication threat model](../01-authentication/authentication-threat-model-prompt.md)
first if you have not — the two are complementary.
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 an Authorization Threat Model Report (SecureNow)
You are a senior application-security engineer specializing in access control. Produce an
**exhaustive authorization threat model for THIS codebase**, mapped to **SecureNow**
detections and mitigations, with a ready-to-run action plan **and** a code-level audit of
every access-control flaw you find. Every rule and command you emit is grounded in the
**installed** `securenow` SDK (Phase 0.5), and every detection is a **ready-to-copy unit**
(SQL → `rules/<name>.sql` → full `securenow alerts rules create …` → dry-run test).
You write **FOUR** files into `threat/02-authorization/` (create the folder if needed) — two
tracks, each as Markdown **and** a self-contained HTML page (inline CSS + JS, no network
requests, with offline **copy buttons** on every command block), using the SecureNow branding
skeletons at the end:
**Track A — Detection & Mitigation (the operational runbook: what to run in SecureNow)**
1. `authorization-detection-mitigation.md` — the runbook in Markdown.
2. `authorization-detection-mitigation.html` — the same, self-contained HTML with copy buttons.
**Track B — Code Findings & Recommendations (the code audit + recommendations)**
3. `authorization-code-findings.md` — the code-level findings in Markdown.
4. `authorization-code-findings.html` — the same, self-contained HTML.
The two tracks **cross-link** each other: every gap/instrumentation row in the Detection report
links to the relevant code finding, and each code finding links back to the detection row it
backs.
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 reports*, never
applied to the repo.
---
## 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
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.
---
## 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 — Map the authorization model (codebase analysis)
Document what access control is **actually implemented**, not what is intended. Cover at
minimum:
- **Model** — RBAC, ABAC, ACLs, ownership-based, scopes/claims, or ad-hoc. Where is the
policy defined (middleware, decorators, per-route guards, a policy engine, inline `if`s)?
- **Principals & roles** — the set of roles/permissions, how a request's identity and role
are resolved (session, JWT claim, DB lookup), and whether the role is **re-verified
server-side** or trusted from the token/client.
- **Tenancy** — is the app multi-tenant? How is `tenantId`/`orgId`/`accountId` derived — from
the server-side session, or from a client-supplied header/param/body? Is **every** data
query scoped by tenant?
- **Object ownership** — for each resource type, how is "does this principal own / may access
this object" enforced? Is it a per-record check, or only a "is logged in" check?
- **Enforcement points** — list every entry surface and whether each enforces authz: REST
routes, GraphQL resolvers, gRPC, WebSocket handlers, background jobs/cron, webhooks,
server actions, file/object-storage access, admin tooling, internal/service endpoints.
- **Sensitive functions & objects** — admin endpoints, role/permission management, billing,
exports/reports, bulk/batch endpoints, impersonation, data deletion, integration tokens.
- **Method & path handling** — is authz enforced per HTTP method (not just GET)? Are paths
normalized (encoding, `..`, trailing slash, case) **before** the authz decision?
- **Field-level control** — can clients set protected fields (mass assignment: `role`,
`isAdmin`, `tenantId`, `ownerId`, `balance`, `verified`)? Do responses over-return fields a
role shouldn't see?
- **SecureNow instrumentation already present** — `securenow/register` or `securenow run`,
`securenow/sessions` `guard()`, and any `securenow/events` `track('authz.*' / 'admin.*')`
calls. This determines which detections work *today* vs *after instrumentation*.
Output of this phase = the report's **Scope & authorization model** section: a component
table (Model / Roles / Tenancy / Ownership checks / Enforcement points / Sensitive surfaces /
Field control) plus a short paragraph naming the real access-control attack surface for this
stack.
---
## Phase 2 — Enumerate threats (exhaustive catalog)
Evaluate **every** threat below against the discovered model. 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. "cross-tenant: N/A, single-tenant app"). Never silently drop an item.
Add stack-specific threats you discover that are not listed — this catalog is the floor, not
the ceiling.
**A. Object-level authorization — BOLA / IDOR (OWASP API1)**
1. IDOR read: fetch another user's object by manipulating an ID (path/query/body)
2. IDOR write/delete: modify or delete another user's object
3. BOLA enumeration at scale: one principal walking many object IDs
4. Predictable identifiers (sequential ints, UUIDv1, base64'd PKs) that enable enumeration
5. Nested/indirect reference not re-checked (`/orders/{mine}/items/{not-mine}`)
6. List/search/export endpoints that return objects across owners
**B. Function-level authorization — BFLA (OWASP API5)**
7. Missing authz on admin/privileged endpoints (force-browse to `/admin`, `/internal`)
8. Method-based BFLA: read is checked but `POST`/`PUT`/`PATCH`/`DELETE` is not
9. Hidden/undocumented endpoints reachable without a role check (debug, internal API)
10. Role enforced in the UI only, not server-side (client-side authorization)
11. New or changed endpoint ships **without** an authz check (missing-by-default)
**C. Object property-level authorization — BOPLA / mass assignment (OWASP API3)**
12. Mass assignment: client sets protected fields (`role`, `isAdmin`, `tenantId`, `balance`)
13. Excessive data exposure: response returns fields the role/owner shouldn't see (PII, flags)
14. Per-field write without per-field authz (changing `price`, `ownerId`, `status` via PATCH)
**D. Privilege escalation**
15. Vertical escalation: user → admin (forced role param, privileged default, role tampering)
16. Horizontal escalation: act as another same-level user across an account/tenant boundary
17. Escalation via token/claim tampering (role/scope/tenant in JWT trusted, not re-verified)
18. Stale privilege: role downgrade/removal not reflected until token expiry
19. Self-service privilege grant (a user can assign themselves a role/permission)
**E. Multi-tenancy & data isolation**
20. Cross-tenant object access (query not scoped by the server-side tenant)
21. Client-supplied tenant trusted (tenant from header/param/body overrides the session)
22. Shared-cache / shared-connection / shared-key cross-tenant bleed
23. Cross-tenant via list/search/aggregate endpoints that omit the tenant filter
24. Cross-tenant via background jobs / exports / webhooks where authz context is dropped
**F. Policy-model flaws (RBAC / ABAC)**
25. Default-allow: access denied only on an explicit rule; new resources are open
26. Over-broad roles / wildcard permissions / role explosion
27. Wrong-subject check: caller's role checked against the wrong resource/owner
28. Confused deputy: a privileged service performs an action for an unprivileged caller with no re-check
29. Inconsistent enforcement across entry points (REST vs GraphQL vs gRPC vs cron vs webhook)
30. Time-of-check / time-of-use: authz decision is stale by the time the action executes
**G. Token / session / claim authorization**
31. Scope/audience not enforced (token valid but issued for a different scope/app/tenant)
32. Over-privileged API keys (no scopes, no per-resource or per-tenant limits)
33. Key/token reuse across tenants or environments (prod key works in another tenant)
34. No step-up / re-authorization on sensitive actions (delete account, change role, payout)
35. Authorization cached past a permission revocation (revoked grant still works)
**H. Workflow / business-logic authorization**
36. Force-browsing a multi-step flow (skip approval/payment/verification step)
37. Parameter tampering changing ownership/price/quantity/recipient (business-logic authz)
38. Replay of an authorized action beyond its allowed count/window
39. Race / TOCTOU double-use of a single-use or limited authorization (double-spend)
**I. Indirect & inherited access surfaces**
40. GraphQL: nested resolver authz bypass; introspection exposing privileged mutations
41. Batch / bulk endpoints bypassing per-item authz
42. File / object storage: signed-URL abuse, predictable blob keys, path traversal to others' files
43. SSRF leveraging the server's identity to reach internal authorized services
44. Server-side export/render running with the server's privileges over user-controlled refs
45. Webhook / callback / integration endpoints acting with elevated, unscoped trust
**J. Negative-space & bypass techniques**
46. Path normalization/encoding bypass (`/admin/..;/`, `%2e%2e`, double-encoding, trailing slash, case)
47. HTTP method override (`X-HTTP-Method-Override`, verb tunneling) bypassing method-scoped authz
48. Reaching the same handler via an alternate host/route that skips the gateway authz (direct origin)
49. HTTP parameter pollution changing the authorized subject or resource
**K. Cross-origin & response handling**
50. CORS misconfiguration enabling authenticated cross-origin reads (reflected origin + credentials)
51. Per-user authorized responses cached on a shared cache/CDN/proxy → cross-user leak
52. Authorization-relevant data leaked in errors/responses enabling targeted access
**L. Administrative & infrastructure surfaces**
53. Unprotected admin panels / internal tools / dashboards reachable from the internet
54. Debug/diagnostic endpoints exposed (actuator, `/metrics`, `/__debug`, GraphQL playground)
55. Cloud metadata / internal service endpoints reachable via the app's identity (IMDS, k8s API)
**M. Observable abuse (what telemetry can actually catch)**
56. 401/403 spike from one IP or principal (authz probing / forced browsing)
57. One principal touching an anomalous count of **distinct** object IDs (BOLA enumeration)
58. Access to admin/sensitive paths by a non-admin role
59. Cross-tenant access attempts (tenant mismatch at the authz layer)
60. Privilege-elevation events at an anomalous rate, off-hours, or self-granted
---
## 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/resolver name.
- **Pattern** — quote the 1–8 vulnerable lines. State the access-control rule that is missing
or wrong (e.g. "query scoped by `id` only, never by `req.user.tenantId`").
- **Why exploitable** — the concrete request an attacker sends and what they get.
- **Severity** — critical / high / medium / low (impact × reachability).
- **Recommended fix (described, not applied)** — the specific change: e.g. "scope the query
`where: { id, ownerId: req.user.id }` and 404 on miss"; "replace `Object.assign(user, body)`
with an explicit allow-list of writable fields"; "derive `tenantId` from the session, never
from `req.body.tenantId`"; "add a server-side role guard on the `DELETE` handler";
"normalize and decode the path before the authz decision". Reference the secure pattern, not
a code diff. **You must not edit the codebase.**
If a check exists and is correct, note it as a **strength** (it belongs in the report so the
posture is honest). Absence of a finding for a catalog item where the surface exists is itself
a finding ("no ownership check on …").
---
## 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, or a rule you
provide the SQL for, on telemetry that is already flowing).
- 🟡 **PARTIAL** — works after the customer adds instrumentation (`track('authz.*')` events),
or the detection is inherently pattern-based / false-positive-prone (notify-only), or
SecureNow can only *contain the abuser* while the real fix is app-level.
- 🔴 **GAP** — SecureNow cannot detect or mitigate this today. **Still include it**: give the
app-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 authorization specifically.** Unlike authentication, a *successful*
> access-control bypass often looks like a normal `200` and emits no failure signal. SecureNow
> detects **patterns of abuse** (enumeration, probing, cross-tenant attempts, admin-path
> access by the wrong role) and **contains the actor** (rate-limit / challenge / block /
> revoke). It does **not** replace the missing server-side check — that is always an **app
> fix**, listed alongside the detection. A single quiet IDOR with no `authz.*` event is a 🔴
> 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 authz detections feed on)
Authorization decisions are mostly invisible to traffic alone, so the high-value detections
need the app to emit an **authorization-decision event** at each enforcement point. Add these
with `securenow/events` — fire-and-forget, never throws:
```js
const { track } = require('securenow/events');
// Emit at every authorization decision (especially DENY, and GRANT on sensitive resources):
track('authz.denied', {
userId, sessionId, ip,
attributes: {
decision: 'deny',
role: 'member',
tenant: tenantId, // the principal's tenant
resource_type: 'invoice',
object_id: requestedId,
owner_id: record?.ownerId, // the object's real owner (reveals cross-owner attempts)
action: 'read',
route: '/api/invoices/:id',
reason: 'not_owner',
},
});
// Privilege / role change — actor and target:
track('authz.elevation', { userId: actorId, ip, attributes: { target: targetUserId, from_role: 'member', to_role: 'admin', self: String(actorId === targetUserId) } });
// Sensitive admin function invoked:
track('admin.action', { userId, ip, attributes: { role, action: 'export_all_users', route: '/admin/export' } });
// Sessions: one line — emits `session.seen` and enforces revocations (needed to kill a
// session/user you catch escalating or crossing tenants):
const securenow = require('securenow/sessions');
app.use(securenow.guard());
```
Recommended authorization event taxonomy — rules match these **exact strings**:
| Event | Emit when |
|---|---|
| `authz.denied` | any access decision resolves to DENY |
| `authz.granted` | a **sensitive** access is granted (admin/billing/export/cross-owner-ok) |
| `authz.elevation` | a role/permission is granted or changed |
| `authz.tenant.mismatch` | the request's tenant ≠ the resource's tenant at the authz layer |
| `admin.action` | a privileged/admin function executes |
| `session.seen` | automatic via `guard()` |
Custom `attributes` become queryable as `attributes_string['<key>']` (e.g.
`attributes_string['object_id']`). Ingest enriches every event IP with **ASN/org**
(`client.asn`, `client.as_org`). HTTP traffic (status codes incl. **401/403**, paths, IPs) is
captured automatically once the app runs under `securenow run` / `securenow/register` /
`securenow init` — traffic-based rules (probing, admin-path access, force-browsing) need **no
events at all**.
### 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** (`signoz_logs.distributed_logs_v2`) → `resources_string['service.name'] IN (__USER_APP_KEYS__)`
- **traces** (`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.
**Events-based — BOLA enumeration** (one principal hitting many distinct objects; query the
**logs** table):
```sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['enduser.id'] AS principal,
uniqExact(attributes_string['object_id']) AS distinct_objects,
count() AS attempts
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] IN ('authz.denied','authz.granted')
AND attributes_string['resource_type'] != ''
AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, principal
HAVING ip != '' AND distinct_objects >= 30
```
**Events-based — cross-tenant attempts / not-owner denials**:
```sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['enduser.id'] AS principal,
count() AS cross_tenant_attempts
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] IN ('authz.tenant.mismatch','authz.denied')
AND attributes_string['reason'] IN ('cross_tenant','not_owner','wrong_tenant')
AND timestamp >= now() - INTERVAL 30 MINUTE
GROUP BY ip, principal
HAVING ip != '' AND cross_tenant_attempts >= 5
```
**Events-based — self-service / anomalous privilege elevation**:
```sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['enduser.id'] AS actor,
count() AS elevations
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] = 'authz.elevation'
AND (attributes_string['self'] = 'true' OR attributes_string['to_role'] IN ('admin','owner','superuser'))
AND timestamp >= now() - INTERVAL 60 MINUTE
GROUP BY ip, actor
HAVING ip != '' AND elevations >= 1
```
**Traffic-based — forced-browsing / authz probing** (401/403 spike, no events needed):
```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 denied,
uniqExact(attributes_string['http.target']) AS distinct_paths
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 response_status_code IN ('401','403')
GROUP BY ip
HAVING denied >= 25 AND distinct_paths >= 8
```
**Traffic-based — sensitive/admin-path access** (alert on any access to privileged prefixes;
combine with role events to catch the wrong principal):
```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 hits, uniqExact(attributes_string['http.target']) AS paths
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 '/admin%' OR attributes_string['http.target'] LIKE '/internal%' OR attributes_string['http.target'] LIKE '%/actuator%')
GROUP BY ip
HAVING hits >= 1
```
Useful attributes: `event.type`, `enduser.id`, `session.id`, `http.client_ip`, `http.target`,
`response_status_code`, `client.asn`, `client.as_org`, and your authz attributes
(`decision`, `role`, `tenant`, `resource_type`, `object_id`, `owner_id`, `action`, `reason`).
#### Emit every detection as a ready-to-copy command unit
Each detection becomes a **complete, copyable unit** — never a fragment. For every 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; in the Detection HTML each is wrapped in a `.cmd` with a Copy
button. The exact flags **must match `securenow alerts rules --help` from Phase 0.5** — never a
guessed flag. 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/authz-bola-enum.sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['enduser.id'] AS principal,
uniqExact(attributes_string['object_id']) AS distinct_objects,
count() AS attempts
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] IN ('authz.denied','authz.granted')
AND attributes_string['resource_type'] != ''
AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, principal
HAVING ip != '' AND distinct_objects >= 30
```
```bash
securenow alerts rules create \
--name "Authz: BOLA object enumeration (single principal)" \
--sql @rules/authz-bola-enum.sql \
--apps <APP_KEY> \
--severity high \
--schedule "*/15 * * * *" \
--nlp "one principal accessing 30+ distinct object IDs in 15 minutes"
securenow alerts rules test <RULE_ID> --mode dry_run --wait # validate before it runs live
```
### 4b-test. Test mode for false-positive-prone rules
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 (BOLA
enumeration / 401-403 probe / cross-tenant-attempt 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 (shared-NAT 403 bursts, bulk-export users, internal
scanners), then `--mode prod` to arm mitigation. Only **high-precision** rules (exploit-signature
SQLi/XSS/RCE matches, exact-match IoCs, self-service `to_role=admin` elevation with `self=true`,
known-bad ASN hits) may go straight to `prod`. In the report, **tag each 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. The full SecureNow mitigation toolbox (select per threat)
For authorization abuse, the SecureNow control plane **contains the actor**; the **app fix**
restores the missing check. Always pair them. Once a threat is confirmed, **choose the narrowest
effective mitigation(s) from ALL of these** and combine them (e.g. rate-limit the `/api/invoices`
enumeration 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 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 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 injection). Don't duplicate pattern SQL. |
| 3 | **IP block — global** | `securenow blocklist add <ip> --app <APP_KEY> --env production --reason "..."` | confirmed-malicious source, all routes. |
| 4 | **IP block — scoped to route (+ method)** | `securenow blocklist add <ip> --route /admin* --mode prefix --method ALL --app <APP_KEY> --env production --reason "..."` (`--mode exact\|prefix\|regex`, `--method GET\|POST\|…\|ALL`) | block an IP only on sensitive paths (e.g. `/admin*`, `/internal*`); 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; 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/invoices --mode prefix --method GET --limit 30 --window 5m --key-by ip` | cap an expensive/enumerable endpoint for everyone, budgeted per IP. |
| 8 | **Rate limit — per route + IP** | `securenow ratelimit add <ip> --route /api/invoices --mode exact --method GET --limit 30 --window 5m --duration 24h` · NL `securenow ratelimit from-text "rate limit /api/invoices to 30/5m for 24h" --yes` · test `securenow ratelimit test <ip> --path /api/invoices --method GET` | precise throttle of one client on one route. |
| 9 | **CAPTCHA / proof-of-work challenge** | `securenow challenge add --route /api/invoices --difficulty 16 --clearance 30m` (route-wide) **or** `securenow challenge add <ip> --route /api/invoices --difficulty 18 --clearance 30m` · test `securenow challenge test <ip> --path /api/invoices --method GET` | BOLA enumeration / scraping of authorized endpoints from **shared / NAT / CGNAT** egress — a human passes once, a script can't. Prefer over a hard block when real users 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 by risk score; actions include block / rate_limit / requireCaptcha. |
| 11 | **Session revocation** | `securenow revoke session <id> --reason "..."` / `securenow revoke user <id>` / `revoke list` / `revoke restore <id>` (SDK `securenow/sessions` `guard()` / `isRevoked()`) | a principal caught escalating privilege or crossing tenants — kill the stolen/abusing session, not the IP. Needs `guard()` in the app. |
| 12 | **Trusted IP (suppress)** | `securenow trusted add <ip> --label "Office VPN / partner / pentest box / monitor"` | stop false positives from known-good infra (internal scanners, QA) — 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 surface. Never for a public app. |
| 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 rule quiet without weakening it. |
| 15 | **App / config / code fix (primary for root cause)** | *described in the Code-Findings report, never auto-applied* | the actual missing check: ownership scope, tenant scope, server-side role guard, field allow-list, path normalization, step-up re-auth, retire route. SecureNow contains; the fix removes. |
**Choosing per threat** — by **confidence**: exploit-signature/exact IoC → instant-block or
block; self-service `to_role=admin` elevation with `self=true` → block + `revoke`; probable BOLA
bot on shared egress → **challenge**; noisy/legit-mixed traffic (403 spikes from shared NAT,
bulk-export users) → **rate-limit (test-mode first)**; session/tenant compromise → **revoke**;
known-good 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. Always pair an edge mitigation with the **app/config fix**
(Code-Findings report) — SecureNow can only contain the actor; the app fix restores the missing
authorization check. All commands here are verified against the installed SDK in Phase 0.5.
### 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`).
```bash
# Synthetic authz events — exercise an events-based rule end to end (BOLA enumeration):
for i in $(seq 1 35); do
securenow event send authz.denied --user attacker@example.com \
--session test-$i --ip 203.0.113.77 \
--attrs decision=deny,resource_type=invoice,object_id=inv-$i,owner_id=victim,action=read,reason=not_owner,test=true
done
# Synthetic privilege elevation:
securenow event send authz.elevation --user attacker@example.com --ip 203.0.113.77 \
--attrs target=attacker@example.com,from_role=member,to_role=admin,self=true,test=true
# Validate a rule query without waiting for the schedule:
securenow alerts rules test <RULE_ID> --mode dry_run --wait
# Traffic-based rules (forced browsing / admin paths) — simulate, then check the pipeline:
securenow test-span "threat-model.authz.smoke"
securenow forensics "401 and 403 responses by IP in the last hour" --env production
# Mitigation verification:
securenow challenge test 203.0.113.77 --path /api/invoices --method GET
securenow ratelimit test 203.0.113.77 --path /api/invoices --method GET
securenow firewall test-ip 203.0.113.77 --app <APP_KEY> --env production
securenow revoke list
# 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 deliverables (two tracks)
Write **two tracks**, each as Markdown **and** self-contained HTML, into `threat/02-authorization/`:
- **Track A — Detection & Mitigation** (the operational runbook: what to run in SecureNow):
`authorization-detection-mitigation.md` + `authorization-detection-mitigation.html`.
- **Track B — Code Findings & Recommendations** (the code audit + recommendations):
`authorization-code-findings.md` + `authorization-code-findings.html`.
The two tracks **cross-link**: the gaps/instrumentation rows in the Detection report link to the
relevant code finding, and each code finding links back to the detection row it backs. Both HTML
files are self-contained (inline CSS + JS, no network) with **copy buttons** on every command
block. Stats cards must equal the table/finding row counts in each report.
### 5a. Track A — Detection & Mitigation report (sections, in order, both .md and .html)
1. **Executive summary** — stats line (threats modeled · covered · partial · gaps · rules to
create · mitigations), the top 3 *detectable* access-control risks, the installed `securenow`
version + app key + firewall state.
2. **SDK & environment** — installed SDK version (from `node_modules/securenow` via Phase 0.5),
app key(s), environment, firewall state, existing rules / automations / challenge rules (from
Phase 0), and any system signature rules present.
3. **Threat → Detection → Mitigation matrix** — one row per modeled threat:
`# | Threat | OWASP/CWE | Coverage 🟢/🟡/🔴 | Detection rule | Signal (threshold+window) | Schedule | Sev | Mode | Mitigation`.
Severity ∈ {critical, high, medium, low}. Each row's **Mitigation** cell must pick
**specific, scoped mitigation(s)** from the Phase 4c toolbox (by number/name, scoped to the
route/method/IP/duration that applies) — never a generic "block the IP." The **Mode** cell
tags the rule **`test-first`** (false-positive-prone — heuristic thresholds, broad patterns,
anomaly/volume) or **`prod-ready`** (high-precision — exact IoC, signature, self-service
`to_role=admin`+`self=true`); test-first rules carry the `--mode test` → observe (3–7 days) →
`--mode prod` promotion workflow. Then the "Out of scope" N/A list and any rows deferred to a
sibling threat model.
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 …` → `alerts rules test … --mode
dry_run`). Tag each rule **`test-first`** or **`prod-ready`** (per Phase 4b-test); for every
`test-first` rule include the `--mode test` → observe (3–7 days) → `--mode prod` promotion step
in its unit. Note rules that already exist (from Phase 0) instead of duplicating them; injection-
class rows reference the system signature rules + `instant.block` rather than duplicate SQL.
5. **Instrumentation the detections need** — only the `track('authz.*')` / `guard()` events the
rules above consume, each as a copyable snippet; point to the **code-findings report** for
*where* (file:line) to add them.
6. **Mitigation mechanisms** — render the **full Phase 4c mitigation toolbox table** (all 15 rows:
free firewall · exploit-signature `instant.block` · IP block [global / route+method / temporary]
· rate-limit [per-IP / per-route / route+IP] · challenge · auto-block · revoke · trusted ·
allowlist · fp · **app-config fix**) plus, per threat, the **specific, scoped** ready-to-copy
mitigation command(s) selected from it and their reversibility. Make explicit that the app fix is
primary and the SecureNow control is containment.
7. **Action plan (copy-paste, ordered)** — ① firewall + signature instant-block, ② add
instrumentation, ③ create rules — **create every false-positive-prone (`test-first`) rule in
`--mode test`** (detect-only), prod-ready rules armed, ④ enable automations / challenge, ⑤ test,
⑥ verify, ⑦ **promote to prod after N days** — an explicit step that, after 3–7 days of observed
real traffic + threshold tuning + `securenow fp` exclusions, runs
`securenow alerts rules update <RULE_ID> --mode prod` on each test-first rule, ⑧ schedule the
app/config fixes (from the code report). Real commands only, `<APP_KEY>` already substituted.
8. **Testing & validation** — per-rule recipe (`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** — per notification type: confirm TP → respond command (copy) → reverse
command (copy).
10. **Known gaps & SecureNow feature requests** — each 🔴: why it is not coverable today, the
interim fix (link to the code report), and the "contact the SecureNow team" line.
11. **Appendix** — resolved SDK/CLI version, app key, environment, rule IDs created, date.
### 5b. Track B — Code Findings & Recommendations report (sections, in order, both .md and .html)
State at the top: *"Findings only — no application code was modified."*
1. **Executive summary** — findings by severity (critical / high / medium / low), the top 3 code
risks, one-paragraph posture verdict.
2. **Surface & inventory** — the Phase 1 inventory for this domain (model / roles / tenancy /
ownership checks / enforcement points / sensitive surfaces / field control).
3. **Threat catalog** — the exhaustive Phase 2 catalog (grouped A–M, each tagged OWASP/CWE,
modeled or explicit N/A). Drop nothing.
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** — access controls already present and correct (honest posture).
6. **App / config fixes (primary remediation)** — the config/code changes that remove the root
cause (described, not applied): ownership scope, tenant scope, server-side role guard, field
allow-list, path normalization, step-up re-auth — each linked to the detection-report row it
backs.
7. **Instrumentation recommendations** — the `track('authz.*')` / `guard()` calls to add and the
exact file:line to add them, so the detection rules light up.
8. **Appendix** — files reviewed, resolved SDK version, 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>` below (brand tokens + copy-button styles) and the copy
`<script>` at the end of `<body>`. Produce **two** files from it: change only the `<title>`, the
sidebar subtitle, the header, the stats cards, and the section content. **Wrap EVERY
command/SQL/code block as a `.cmd`** (so it gets a Copy button). Stats numbers must equal the
table/finding row counts.
```html
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<title><!-- "Detection & Mitigation — Authorization — SecureNow" OR "Code Findings — Authorization — 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 · Authorization" OR "Code Findings · Authorization" --></div>
<!-- one <a href="#…"> per section of THIS track -->
</nav>
<main>
<header class="top"><h1><!-- report title --></h1>
<p><code><!-- app name / domain --></code> · <span class="pill">securenow <!-- installed version --></span></p></header>
<div class="stats"><!-- 5 .stat cards; numbers MUST equal the table/finding counts --></div>
<!-- <section id="…"> blocks mirroring the Markdown sections of THIS track (5a or 5b) -->
<footer>Generated by the SecureNow authorization 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 "*/15 * * * *" --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 `<span class="owasp">API1</span>`;
CWE `<span class="cwe">CWE-639</span>`; mitigation
`<span class="m firewall|signature|rate|challenge|block|notify|appfix">`; rule IDs
`<span class="rid">`. 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`.
---
## Quality bar (the report is rejected if any of these fail)
- Every catalog item A1–M60 is either a matrix row or an explicit N/A line.
- Every matrix row has a concrete signal (threshold + window), severity, and mitigation — no
"monitor for suspicious activity" filler.
- Every code finding 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 and selects an `ip` column.
- Only commands, flags, events, and SQL columns from this prompt's building blocks appear
(including `securenow challenge …`).
- Detection vs. fix is honest: where SecureNow can only contain the actor, the row says so and
pairs the control with the **app fix**.
- Every 🔴 gap appears in the gaps section with an interim app 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/02-authorization/` (detection-mitigation .md + .html,
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.
- The Detection report's mitigation section presents the **full toolbox** (Phase 4c §6: 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.
- A one-line summary is printed back: per-track file paths, threat counts, rules-to-create count,
code findings by severity, gaps, and the resolved SDK version.
<!-- ════════════════ END OF PROMPT ════════════════ -->