#08A01 · API5 · API1

Admin & Support Tools

Internal consoles, impersonation, bulk operations, and support back-doors.

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/08-admin-support-tools/ — open admin-support-tools-code-findings.html (the audit) and admin-support-tools-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

# Admin / Support Tools 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`
CLI installed and logged in. The agent will inventory the privileged internal surface (admin
consoles, support/back-office tools, impersonation, feature flags, bulk ops, exports), build an
exhaustive **admin / support-tools** threat model mapped to **OWASP A01:2021 Broken Access
Control** and the API equivalents **API5:2023 (BFLA)** / **API1:2023 (BOLA)**, audit the code
for privileged-function flaws, and emit a SecureNow-branded report in **Markdown +
self-contained HTML** — including the detection rules to create, 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.

This model owns the **operator side** of the product: the high-blast-radius internal tooling
where one compromised or over-privileged account can read, write, export, or delete across every
tenant. It is about *who may use the privileged function* and *whether the privileged action is
authorized, scoped, audited, and contained* — not the everyday end-user permission checks.

> **Honest framing.** SecureNow is a **traffic + events** security layer. The *access-control
> gate itself* — "is this caller allowed to hit `/admin/*`, impersonate, flip a flag, run a bulk
> export?" — is an **application fix**, not something SecureNow can install. What SecureNow does
> exceptionally well here is **detect the abuse and contain the operator**: admin-action spikes,
> off-hours / anomalous-geo admin access, impersonation and bulk-export `admin.*` events, and
> firewall / rate-limit / challenge / block to stop a compromised operator while you fix the
> gate. Almost every row in this model therefore **pairs an edge-containment control WITH the
> app fix** — neither alone is sufficient.

This model **defers** the general end-user access-control model, cross-tenant data boundaries,
and audit-log storage integrity to its siblings (referenced, not re-derived):
- [../02-authorization/](../02-authorization/authorization-threat-model-prompt.md) — general
  object/function-level access control (BOLA/BFLA/BOPLA) for ordinary users.
- [../03-tenant-isolation/](../03-tenant-isolation/tenant-isolation-threat-model-prompt.md) —
  cross-tenant data isolation at the storage/query layer.
- [../23-storage-and-logs/](../23-storage-and-logs/storage-and-logs-threat-model-prompt.md) —
  audit-log **storage** integrity, retention, and tamper-evidence (this model owns whether the
  log is *emitted* on a privileged action; that model owns whether the stored log is *safe*).

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 Admin / Support-Tools Threat Model Report (SecureNow)

You are a senior application-security engineer specializing in privileged-access and
insider-risk. Produce an **exhaustive admin / support-tools threat model for THIS codebase**,
organized around **OWASP A01:2021 Broken Access Control** (and its API counterparts **API5:2023
BFLA** and **API1:2023 BOLA**), mapped to **SecureNow** detections and mitigations, with a
ready-to-run action plan **and** a code-level audit of every privileged-function flaw you find.
Every rule, command, flag, event name, and SQL column is **grounded in the SecureNow SDK actually
installed in this repo** (Phase 0.5) and every detection is emitted as a **ready-to-copy command
unit** (SQL → save → create → dry-run). You write **four** deliverables — two tracks, each as
Markdown **and** self-contained HTML — into `threat/08-admin-support-tools/` (create the folder if
needed):

1. `admin-support-tools-detection-mitigation.md` — the **operational runbook**: what to run in
   SecureNow (detection rules, mitigation commands, tests, response runbooks).
2. `admin-support-tools-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.
3. `admin-support-tools-code-findings.md` — the **code audit**: privileged-function flaws found in
   the codebase + recommended fixes (described, **not** applied).
4. `admin-support-tools-code-findings.html` — the same audit as a **self-contained** HTML page.

The two tracks **cross-link** each other: the gap/instrumentation rows in the detection report
link to the relevant code finding, and each code finding links back to the detection/mitigation
row it backs. `<slug>` = `admin-support-tools` (the folder name without the `08-` prefix).

Work in the seven phases below, in order. **Never invent facts**: if something is not in the
codebase or not returned by a CLI command, say "not found" — do not guess. **Do not modify
application code.** You are auditing: every code-level fix is *described in the report*, never
applied to the repo.

**Scope discipline.** This model owns the **privileged operator surface**: admin/support route
authorization (BFLA), privilege escalation into operator roles, impersonation / "log in as",
over-broad support access, audit-log *emission* on privileged actions, bulk ops & exports as
exfil paths, feature-flag / kill-switch / config authz, internet-exposed internal tools,
off-hours / anomalous-volume operator activity, break-glass / emergency access, and step-up /
second-factor on destructive admin actions. For the **general end-user access-control model**
(ordinary BOLA/BFLA/BOPLA), **cross-tenant data isolation** at the storage layer, and **audit-log
storage integrity / retention**, do **not** re-derive — list them in a "Deferred to sibling
models" subsection, link those reports, and only model the **traffic/event-observable** symptom
where SecureNow adds value.

---

## 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 existing
rules so you don't duplicate them. There are **no system "admin-abuse" signature rules** — admin
threats are about *who* and *how much*, not payload patterns; the detections in this model are
ones you author or events you instrument.

---

## 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 privileged surface (codebase analysis)

Admin security starts with knowing **what is privileged and who can reach it**. Document what is
**actually exposed and actually enforced**, not what is intended. Cover at minimum:

- **Admin / support route catalog** — enumerate every route, server action, RPC, CLI/cron, and
  background job that is **operator-only** or **support-scoped** (method + path + handler), grouped
  by router/controller. For each, record: the **authz check actually present** (middleware,
  guard, decorator, inline role check, or **none**), the **role(s)** it requires, and whether the
  check is centrally enforced or duplicated per-handler (duplication = drift risk). Common
  prefixes: `/admin`, `/internal`, `/support`, `/backoffice`, `/ops`, `/staff`, `/console`,
  `/manage`, `/superuser`, plus admin GraphQL fields/mutations and admin-only server actions.
- **Role & permission model** — list the roles (user / support / admin / superadmin / billing /
  read-only-support / …), how role is **assigned** (DB column, JWT claim, SSO group, env list of
  emails), how it is **read** on each request, and whether role can be influenced by client input
  (mass assignment on signup/profile-update, a `role` field accepted in a body, a self-serve
  "request admin" path).
- **Impersonation / "log in as" / sudo** — find every place an operator can act **as another
  user or tenant**: support "view as", "login as", admin token mint, masquerade middleware,
  `sudo`/`assume-role`, debug `?as_user=`. Record: who may invoke it, whether it requires
  **target/owner consent**, whether it is **time-boxed**, whether it is **scoped** (read-only vs
  full write), whether the impersonated session is **clearly marked**, and whether **every action
  during impersonation is audited with both real and effective identity**.
- **Support / cross-tenant access** — anywhere support staff can read/write **any tenant's** data
  (lookup-by-email, customer search, ticket tooling, refund/credit, data export). Record whether
  access requires a **reason/justification/ticket reference**, whether it is **scoped to the
  customer under support** (vs "all customers"), and whether each access is logged.
- **Audit logging of privileged actions** — for each privileged action (role change,
  impersonation start/stop, bulk op, export, flag flip, kill-switch, config change, delete),
  determine whether an audit record is **emitted** and whether it captures **who (real +
  effective), what, when, why (justification), and from where (IP/source)**. Note actions with
  **no audit**, audit written **after** the action (so a failure loses it), or audit the actor
  can **edit/delete** themselves. (Storage/tamper-evidence is deferred to ../23-storage-and-logs/;
  this model owns whether the log is *emitted at all* on the privileged path.)
- **Bulk operations & exports** — enumerate every endpoint that operates on **many records at
  once** or returns a **large dataset**: bulk delete/update/email, CSV/JSON/Parquet export,
  "download all", reporting dumps, DB snapshot tools, admin search returning unbounded results.
  Record row/size ceilings, async-job vs synchronous, whether exports are scoped to one tenant,
  and whether the action is rate-limited and audited.
- **Feature flags / kill switches / config endpoints** — find flag-management, kill-switch,
  maintenance-mode, and runtime-config routes/UIs. Record the authz on **read** vs **write**,
  whether a flag flip can disable security controls (auth, rate-limit, the SecureNow firewall
  itself), and whether changes are audited.
- **Network / SSO exposure of internal tools** — determine whether admin/internal tooling is
  **reachable from the public internet** or gated behind VPN / IP allowlist / mTLS / SSO. Check
  for separate hostnames (`admin.`, `internal.`), whether they share the public app's ingress,
  and whether the only thing between the internet and `/admin` is an application login.
- **Break-glass / emergency access** — find any "emergency", "incident", "god-mode",
  "maintenance bypass", or temporary-elevation path. Record whether it **expires**, requires
  **two-person / approval**, is **alerted on use**, and is **reviewed after**.
- **Step-up / second-factor on destructive actions** — for destructive or high-impact admin
  actions (delete account/tenant, mass delete, payout, role grant, export PII, disable security),
  determine whether a **re-authentication / step-up / MFA / typed-confirmation** is required, or
  whether a single session cookie is sufficient.
- **Telemetry privacy & redaction** — confirm the SecureNow SDK / log pipeline redacts
  `Authorization`, `Cookie`, `Set-Cookie`, admin session tokens, impersonation tokens, exported
  PII, and customer emails before ingestion. Admin events will carry actor/target identifiers —
  ensure they are **hashed or non-PII** so events don't become a new leak path. If redaction is
  not found, create a high-severity finding.
- **SecureNow instrumentation already present** — `securenow/register` / `securenow run` /
  `securenow init` (gives traffic spans automatically), any existing `securenow/events`
  `track('admin.*')` calls, and whether the firewall is engaged. This determines what works
  *today* (traffic to `/admin/*`) vs *after instrumentation* (the rich `admin.*` events).

Output of this phase = the report's **Privileged surface & inventory** section: the admin/support
route catalog (method/path/required-role/authz-check-present), the role/permission model, the
impersonation map (who/consent/scope/time-box/audit), the cross-tenant-support posture, the
audit-emission map per privileged action, the bulk-ops/export catalog with ceilings, the
flag/kill-switch authz table, the network/SSO exposure posture, the break-glass inventory, the
step-up posture, the telemetry redaction status, and a short paragraph naming the real
operator-side 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. "Impersonation items: N/A, no 'log in as' feature exists"). Never silently
drop an item. Add stack-specific threats you discover that are not listed — this catalog is the
floor, not the ceiling. Tag each modeled row with its framework code: **A01** (always, since the
whole domain is Broken Access Control) plus the API mapping **API5** (BFLA — function reachable
by the wrong role) or **API1** (BOLA — operator reaching the wrong object/tenant), or "—".

**A. Unauthorized access to admin / support routes (BFLA — function-level authz gaps)**
1. Admin route with **no** authz check (direct hit to `/admin/*` as an ordinary user) — A01/API5
2. Authz enforced only in the **UI** (hidden button) but the API/route is reachable directly — A01/API5
3. Authz check present but **wrong** (checks logged-in, not role; checks `role != 'user'` allowing any non-user) — A01/API5
4. Per-handler authz **drift** — most admin routes guarded, one new route shipped unguarded — A01/API5
5. Admin GraphQL field / mutation / server action reachable without the gateway middleware — A01/API5
6. Admin functionality behind a guessable/forced parameter or method (`?admin=1`, verb tunneling to a protected verb) — A01/API5
7. Static-asset / framework route bypass landing inside an admin handler (path normalization) — A01/API5

**B. Privilege escalation user → support → admin (role tampering, mass assignment)**
8. Mass assignment of a `role`/`isAdmin`/`permissions` field on signup/profile/update (BOPLA-adjacent) — A01/API5
9. Role stored in a client-controllable place (JWT claim not re-verified, cookie, localStorage echoed back) — A01/API5
10. Self-serve elevation path (a support tool that can grant the caller's own role) — A01/API5
11. Horizontal→vertical: editing another user's role via an under-scoped support "edit user" endpoint — A01/API5
12. Stale/over-broad role grant never revoked (ex-employee, temporary elevation that stuck) — A01
13. Role check trusts an SSO group/claim that the user can influence or that isn't validated — A01/API5

**C. Impersonation / masquerade / sudo abuse ("login as")**
14. Impersonation reachable by a non-admin or by support beyond their tier — A01/API5
15. Impersonation without **target/owner consent** where consent is required (privacy/regulatory) — A01
16. Impersonation **not time-boxed** (session never auto-ends; operator stays "as user" indefinitely) — A01
17. Impersonation **not scoped** (read-only intended, but full write/delete/payout possible) — A01/API1
18. Actions during impersonation audited only as the **effective** user (real operator hidden) — A01
19. Impersonation token mintable directly (the mint endpoint itself lacks authz / is forgeable) — A01/API5
20. Privilege carry-over: an admin impersonating a user retains admin abilities (admin-as-user superuser) — A01/API5

**D. Over-broad support access (read/write any tenant without justification — BOLA at operator scale)**
21. Support can read **any** tenant's data with no scoping to the customer under support — A01/API1
22. Support can write/modify/delete **any** tenant's data (refund, credit, reset) unscoped — A01/API1
23. No **reason/justification/ticket** required to access a customer record — A01
24. Customer lookup/search returns cross-tenant results in bulk (enumeration of all customers) — A01/API1
25. Support export pulls a whole tenant (or all tenants) rather than the record in question — A01/API1

**E. Missing / tamperable audit logs on privileged actions**
26. Privileged action with **no** audit record emitted (role change, delete, export, flag flip) — A01
27. Audit captures **effective** identity only, not the **real** operator during impersonation — A01
28. Audit written **after** the side-effect (action succeeds, log fails → no trace) — A01
29. Audit record the actor can **edit or delete** through the same admin tooling — A01
30. Justification/reason field optional or free-text-unvalidated (no real accountability) — A01

**F. Bulk operations & exports as exfiltration paths**
31. Bulk export with **no row/size ceiling** (one click pulls the entire dataset) — A01/API1
32. Bulk delete/update with no confirmation, no scope cap, no rate limit (mass destruction) — A01
33. Export reachable by a role that shouldn't have data-export rights (support → full PII dump) — A01/API5
34. Export not scoped to one tenant (cross-tenant dump) — A01/API1
35. Repeated/automated export by one operator (slow exfil under the radar) — A01
36. Async-export job whose result URL is then accessible without re-authorization — A01/API1

**G. Feature-flag / kill-switch / config endpoints without admin authz**
37. Flag/config **write** endpoint reachable without admin authz — A01/API5
38. A flag flip can disable a **security control** (auth, rate-limit, the SecureNow firewall) — A01
39. Kill-switch / maintenance-mode toggle without authz (DoS via config) — A01/API5
40. Runtime config change (limits, allowlists, secrets references) without authz or audit — A01/API5

**H. Internal tools internet-exposed (no SSO / network gate)**
41. Admin tooling reachable from the public internet with only an app login in front — A01
42. Internal/debug/ops endpoints on the same public ingress, not VPN/IP-allowlist/mTLS gated — A01
43. Separate admin hostname that still resolves & serves to the public internet — A01
44. SSO/IdP gate intended but bypassable (direct origin hit, missing on a subset of routes) — A01/API5

**I. Off-hours / anomalous-volume admin activity (compromised operator)**
45. Operator account compromised → high-volume privileged actions in a short window — A01 *(observable)*
46. Admin access from an **anomalous geo / ASN / new device** (credential theft) — A01 *(observable)*
47. **Off-hours** admin activity (privileged actions outside the operator's normal window) — A01 *(observable)*
48. Impersonation **fan-out** (one operator impersonating many distinct users quickly) — A01 *(observable)*
49. Bulk-export **burst** (one operator exporting at a rate/volume no human workflow needs) — A01/API1 *(observable)*

**J. Break-glass / emergency-access without expiry or review**
50. Emergency-elevation path with **no expiry** (god-mode that never auto-revokes) — A01
51. Break-glass without **two-person / approval** control — A01
52. Break-glass **use not alerted** in real time — A01 *(observable)*
53. Break-glass **never reviewed** after the incident (no post-hoc audit requirement) — A01

**K. Second-factor / step-up missing on destructive admin actions**
54. Destructive admin action (delete tenant, mass delete, payout, disable security) with **no step-up / re-auth / MFA** — A01
55. Single long-lived admin session sufficient for all destructive actions (no per-action confirmation) — A01
56. Step-up present in UI but the API accepts the destructive call without the step-up token — A01/API5

**L. Negative-space & evasion (operator-side)**
57. Path/encoding normalization bypass of the admin authz middleware (`%2e`, trailing slash, case, matrix params) — A01/API5
58. HTTP method override (`X-HTTP-Method-Override`, verb tunneling) reaching a protected admin verb — A01/API5
59. Direct-origin access bypassing the SSO/WAF gate in front of admin tooling — A01
60. Client-IP / forwarded-header spoofing to defeat an IP-allowlist gate on admin routes — A01
61. Forced browsing / ID enumeration of admin objects (sequential admin resource IDs) — A01/API1

**M. Observable abuse (what telemetry actually catches — the workhorse rules)**
62. Spike in privileged-action volume from one operator/IP (compromise or runaway script) — A01 *(observable)*
63. Admin-route 403/401 spike from one source (a non-admin probing the admin surface — BFLA recon) — A01/API5 *(observable)*
64. One source touching an anomalous count of **distinct** admin routes (admin-surface mapping) — A01/API5 *(observable)*
65. Off-hours / anomalous-geo admin authentication or admin-route access — A01 *(observable)*
66. Impersonation-event rate / fan-out from one operator — A01 *(observable)*
67. Bulk-export event rate / total volume from one operator — A01/API1 *(observable)*

**N. Deferred — modeled in sibling models (reference, do not re-derive)**
68. General end-user object/function-level access control (ordinary BOLA/BFLA/BOPLA) → [../02-authorization/](../02-authorization/authorization-threat-model-prompt.md) — A01/API1/API5
69. Cross-tenant **data isolation** at the storage/query layer (DB row scoping, query guards) → [../03-tenant-isolation/](../03-tenant-isolation/tenant-isolation-threat-model-prompt.md) — A01/API1
70. Audit-log **storage** integrity, retention, immutability, and tamper-evidence → [../23-storage-and-logs/](../23-storage-and-logs/storage-and-logs-threat-model-prompt.md) — A01

> For 68–70, add **one** matrix row each marked *"deferred — see linked model"*, and only note
> the SecureNow traffic/event-observable symptom here (e.g. cross-tenant access shows up as an
> operator reaching many distinct tenants; audit-storage tamper is a storage-model concern). The
> full detection/mitigation lives in the other reports. This model owns whether the audit is
> *emitted on the privileged path*; ../23-storage-and-logs/ owns whether the stored record is
> safe.

Every catalog item must be either a matrix row or an explicit N/A line with a reason — do not
silently drop any item.

---

## Phase 3 — Audit the code (findings only — do not fix)

For **each** modeled threat that maps to real code, locate the responsible code and record a
**finding** for the report's "Code-level findings" section. A finding is:

- **Location** — `file:line` (clickable), the route/handler/middleware/guard/resolver name.
- **Pattern** — quote the 1–8 relevant lines. State the missing control precisely (e.g.
  "`router.use('/admin', adminRouter)` with no `requireRole('admin')` → any session reaches
  admin"; "`if (user) {…}` instead of `if (user.role === 'admin')` → authenticated-but-not-admin
  passes"; "`User.update(req.body)` accepts a `role` key → mass-assignment escalation";
  "`/impersonate/:userId` mints a session with no caller role check and no audit"; "support
  `getCustomer(email)` runs unscoped across all tenants with no justification arg";
  "`deleteAccount()` has no re-auth/step-up"; "feature-flag PUT has no admin guard").
- **Why exploitable** — the concrete request an attacker (external or insider) sends and what
  they achieve (reach admin function, escalate role, impersonate, export every tenant, flip a
  flag that disables security, etc.).
- **Severity** — critical / high / medium / low (impact × reachability). An unguarded admin route
  or a working privilege-escalation is **critical**; a missing audit on a privileged action is
  typically **high**.
- **Recommended fix (described, not applied)** — the specific change: e.g. "centralize an
  `requireRole('admin')` guard mounted on the whole `/admin` router and assert it in a test that
  fails the build if any admin route lacks it"; "strip `role`/`permissions` from accepted body
  fields, set role only via a dedicated audited admin endpoint"; "gate impersonation behind admin
  role + target consent + a time-boxed, read-only-by-default, clearly-marked session, and audit
  every action with both real and effective identity"; "require a `justification` and scope
  support reads to the customer under the active ticket"; "emit an immutable audit record
  **before** the side-effect, server-side, that the actor cannot edit"; "cap export rows and
  scope to one tenant; move large exports to an audited async job"; "require admin authz on
  flag/kill-switch writes and never let a flag disable a security control"; "put admin tooling
  behind SSO + an IP allowlist / VPN, not just the app login"; "add re-auth / step-up / MFA on
  destructive actions and verify the step-up token server-side". Reference the secure pattern,
  not a code diff. **You must not edit the codebase.**

If a control exists and is correct (central admin guard present and tested, role un-spoofable,
impersonation consented + time-boxed + fully audited, exports capped & scoped, step-up enforced
server-side), note it as a **strength** — the posture must be honest. Absence of a control where
the privileged surface exists is itself a finding ("no audit record on any role change";
"`/admin` reachable from the public internet with only a session cookie").

---

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

Classify each threat with exactly one coverage badge:

- 🟢 **COVERED** — detectable + mitigable with SecureNow today (an `admin.*` event taxonomy you
  instrument, or admin-route traffic that is already flowing, plus a rule you provide the SQL
  for). SecureNow's home turf here is **observable abuse**: admin-action spikes, off-hours /
  anomalous-geo admin access, impersonation & bulk-export rate/fan-out, admin-surface
  probing/403-spike, and **containing a compromised operator** at the edge.
- 🟡 **PARTIAL** — works after the customer adds `admin.*` instrumentation (most rich admin
  detections are event-based — emit the event in Phase 3's recommended fix), **or** SecureNow can
  only *contain the operator at the edge* while the real fix is the **access-control gate / app
  config**. Note clearly: the access-control gate itself is **always an app fix**; SecureNow
  detects the abuse and contains the source. Almost every BFLA/escalation/impersonation/over-broad
  row is 🟡 — paired (edge-containment + app fix).
- 🔴 **GAP** — SecureNow cannot detect or mitigate this today (a flaw that emits no traffic and no
  event — e.g. a missing audit record on a path nobody is currently abusing, an un-revoked stale
  grant, a break-glass with no expiry). **Still include it**: give the app/config-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. (Note: many of these become 🟡 the moment the app emits the
  corresponding `admin.*` event — say so explicitly and pair the row with that instrumentation.)

> **Be honest about what is edge-detectable vs an app fix.** SecureNow sees **traffic** and
> **events**; it contains actors via firewall / rate-limit / challenge / block. It **cannot**
> install your access-control gate, scope a support query, add an audit record, or time-box an
> impersonation session — those are **app fixes**. What it *can* do is see the **abuse** (the
> admin-action spike, the off-hours access, the impersonation fan-out, the export burst, the
> 403-spike of a non-admin probing `/admin`) and **contain the operator/source**. Pair the
> control with the app fix on every access-control row. A privileged action that emits no traffic
> and no event is a 🔴 until the app emits the `admin.*` event from Phase 3's recommended fix.

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

### 4a. Instrumentation (what admin detections feed on)

Two layers feed this model:

1. **Traffic (automatic)** — once the app runs under `securenow run` / `securenow/register` /
   `securenow init`, HTTP traffic to admin/support routes is captured automatically: status codes
   (incl. **401/403** for BFLA-recon spikes), methods, paths (`http.target` — your admin route
   prefixes), client IPs, response sizes (large = export). This powers the **observable-abuse**
   rules (catalog I/M) with **no instrumentation**.
2. **Events (you add)** — the rich admin signals (who did what, impersonation, exports, flag
   flips, break-glass) are **not visible in raw traffic** — they're app-internal decisions. Emit
   `securenow/events` `track('admin.*')` at the enforcement point. **`track()` never throws** —
   it must never block or break a privileged action.

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

// A privileged admin action executed (the workhorse — feeds spike/off-hours/volume rules):
track('admin.action', { userId: operatorId, ip, attributes: { action: 'role_change|delete|config_change|refund|credit', target: '<hashed_user_or_tenant_id>', result: 'success|denied', justification_present: 'true|false' } });

// An admin/support route was reached by someone NOT authorized (BFLA — high-signal):
track('admin.access.denied', { userId: callerId, ip, attributes: { route: '/admin/users', required_role: 'admin', actual_role: 'user' } });

// A role / permission was granted or changed (escalation surface):
track('admin.role.changed', { userId: operatorId, ip, attributes: { target: '<hashed_user_id>', from_role: 'user', to_role: 'admin', tenant: '<hashed_tenant_id>' } });

// Impersonation lifecycle — both identities, scope, time-box:
track('admin.impersonate.start', { userId: operatorId, ip, attributes: { target: '<hashed_user_id>', scope: 'read_only|full', consent: 'true|false', ttl_minutes: '30' } });
track('admin.impersonate.action',{ userId: operatorId, ip, attributes: { target: '<hashed_user_id>', action: '<what_was_done>' } });
track('admin.impersonate.stop',  { userId: operatorId, ip, attributes: { target: '<hashed_user_id>', duration_minutes: '12' } });

// Cross-tenant / over-broad support access (BOLA at operator scale):
track('admin.support.access', { userId: operatorId, ip, attributes: { tenant: '<hashed_tenant_id>', scope: 'single_record|tenant_wide|all_tenants', justification_present: 'true|false' } });

// Bulk operation / export (exfil path):
track('admin.bulk.export', { userId: operatorId, ip, attributes: { dataset: 'users|orders|pii', rows: '<number_as_string>', scope: 'single_tenant|all_tenants', format: 'csv|json' } });
track('admin.bulk.mutate', { userId: operatorId, ip, attributes: { action: 'delete|update', rows: '<number_as_string>', resource: 'users|orders' } });

// Feature-flag / kill-switch / config change (can disable security):
track('admin.flag.changed', { userId: operatorId, ip, attributes: { flag: '<flag_name>', value: 'on|off', security_relevant: 'true|false' } });

// Break-glass / emergency elevation (always alert):
track('admin.breakglass.used', { userId: operatorId, ip, attributes: { reason: '<short_reason>', approved_by: '<hashed_id_or_none>', ttl_minutes: '60' } });

// Step-up / re-auth required for a destructive action (and whether it was satisfied):
track('admin.stepup.required', { userId: operatorId, ip, attributes: { action: 'delete_tenant|payout|disable_security', satisfied: 'true|false' } });
```

> Hash or omit any PII before it becomes an attribute value (operator email, target user/tenant
> id, exported data, justification free-text) — see the Phase 1 **telemetry privacy & redaction**
> check. Attributes feed detection; they must not become a new leak path. Always send the
> **real operator identity** as `userId` even during impersonation, and the impersonated target as
> a hashed attribute, so a compromised operator is attributable.

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

| Event | Emit when |
|---|---|
| `admin.action` | any privileged admin action executes (role/delete/config/refund/credit) |
| `admin.access.denied` | a non-authorized caller reaches an admin/support route (BFLA recon) |
| `admin.role.changed` | a role/permission is granted or changed |
| `admin.impersonate.start` | an operator begins impersonating a user/tenant |
| `admin.impersonate.action` | an action is taken inside an impersonated session |
| `admin.impersonate.stop` | an impersonation session ends |
| `admin.support.access` | support reads/writes a customer record (scope + justification) |
| `admin.bulk.export` | a bulk export / "download all" runs (rows + scope) |
| `admin.bulk.mutate` | a bulk delete/update runs (rows + resource) |
| `admin.flag.changed` | a feature flag / kill-switch / runtime config is changed |
| `admin.breakglass.used` | an emergency-elevation / break-glass path is used |
| `admin.stepup.required` | a destructive action required (and did/didn't satisfy) step-up |

Custom `attributes` become queryable as `attributes_string['<key>']` (e.g.
`attributes_string['scope']`, `attributes_string['action']`, `attributes_string['rows']`).
Ingest enriches every IP with **ASN/org** (`client.asn`, `client.as_org`) and geo — enabling
anomalous-geo / datacenter-origin detection on operator access 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`). **Admin events are richest** — most of this model's
detections query the **logs** table for `admin.*` events.

**Traffic-based — admin-surface probing / BFLA recon (403/401 spike on admin routes), 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_admin_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 '/support/%' OR attributes_string['http.target'] LIKE '/backoffice/%')
  AND response_status_code IN ('401','403')
GROUP BY ip
HAVING ip != '' AND denied >= 10
```

**Traffic-based — large/anomalous response size from one source on an admin/export route (bulk extraction):**

```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 export_hits
FROM signoz_traces.distributed_signoz_index_v3
WHERE `resource_string_service$$name` IN (__USER_APP_KEYS__)
  AND timestamp >= now64(9) - INTERVAL 30 MINUTE
  AND ts_bucket_start >= toUInt64(toUnixTimestamp(now() - INTERVAL 30 MINUTE)) - 1800
  AND kind = 2
  AND (attributes_string['http.target'] LIKE '%/export%' OR attributes_string['http.target'] LIKE '%/download%' OR attributes_string['http.target'] LIKE '/admin/%')
  AND response_status_code = '200'
GROUP BY ip
HAVING ip != '' AND export_hits >= 20
```

**Events-based — admin-action spike from one operator (compromise / runaway — catalog I/M):**

```sql
SELECT
  attributes_string['http.client_ip'] AS ip,
  attributes_string['user.id']        AS operator,
  count() AS actions
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] = 'admin.action'
  AND timestamp >= now() - INTERVAL 10 MINUTE
GROUP BY ip, operator
HAVING ip != '' AND actions >= 50
```

**Events-based — non-admin reaching admin routes / BFLA (any sustained hit is high-signal):**

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

**Events-based — impersonation fan-out (one operator impersonating many distinct users):**

```sql
SELECT
  attributes_string['http.client_ip'] AS ip,
  attributes_string['user.id']        AS operator,
  uniqExact(attributes_string['target']) AS distinct_targets,
  count() AS impersonations
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] = 'admin.impersonate.start'
  AND timestamp >= now() - INTERVAL 30 MINUTE
GROUP BY ip, operator
HAVING ip != '' AND distinct_targets >= 5
```

**Events-based — bulk-export burst / volume (exfil — catalog F/M):**

```sql
SELECT
  attributes_string['http.client_ip'] AS ip,
  attributes_string['user.id']        AS operator,
  count() AS exports,
  sum(toUInt64OrZero(attributes_string['rows'])) AS total_rows
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] = 'admin.bulk.export'
  AND timestamp >= now() - INTERVAL 60 MINUTE
GROUP BY ip, operator
HAVING ip != '' AND (exports >= 5 OR total_rows >= 50000)
```

**Events-based — off-hours / anomalous-geo admin access (compromised operator):**

```sql
SELECT
  attributes_string['http.client_ip'] AS ip,
  attributes_string['user.id']        AS operator,
  attributes_string['client.asn']     AS asn,
  attributes_string['client.as_org']  AS as_org,
  count() AS actions
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] = 'admin.action'
  AND (toHour(timestamp) >= 22 OR toHour(timestamp) < 6)   -- adjust to the operator team's window/timezone
  AND timestamp >= now() - INTERVAL 60 MINUTE
GROUP BY ip, operator, asn, as_org
HAVING ip != '' AND actions >= 1
```

**Events-based — break-glass used (always alert; any hit) & security-relevant flag flipped:**

```sql
SELECT
  attributes_string['http.client_ip'] AS ip,
  attributes_string['user.id']        AS operator,
  attributes_string['event.type']     AS signal,
  count() AS hits
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] IN ('admin.breakglass.used','admin.flag.changed')
  AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, operator, signal
HAVING ip != '' AND hits >= 1
```

The other catalog events follow the **same shape** — swap the `event.type` filter and the
threshold: `admin.role.changed` (≥3/15m → escalation burst, or ≥1 to-admin → notify-only),
`admin.support.access` with `scope='all_tenants'` or `justification_present='false'` (≥1 →
over-broad access, notify-only/runbook), `admin.bulk.mutate` (≥1 with high `rows` → mass
destruction), `admin.stepup.required` with `satisfied='false'` (≥1 → destructive action without
step-up, high-signal). Where a row is a *config/posture* gap (no expiry on break-glass, no audit
emitted, internet-exposed admin), there is **no SQL** — it is an **app/config fix**; SecureNow
detects the *use/abuse* once the event flows.

Useful attributes/columns: `event.type`, `user.id`, `http.client_ip`, `http.target`,
`response_status_code`, `kind`, `client.asn`, `client.as_org`, and your admin attributes
(`action`, `route`, `actual_role`, `target`, `scope`, `rows`, `justification_present`, `flag`,
`satisfied`).

**Emit every detection as a ready-to-copy command unit.** A detection is never a SQL fragment — it
is a **complete, copyable unit** so the user can paste it straight into a terminal. For each rule
emit, in this order, each as its own fenced block (so each copies cleanly):

1. the **SQL**, with a leading `-- rules/<name>.sql` comment line,
2. a line **saving it** to `rules/<name>.sql`,
3. the full **`securenow alerts rules create …`** command referencing `--sql @rules/<name>.sql`,
4. the **dry-run test** that validates it before it runs live.

The exact flags must match `securenow alerts rules --help` from Phase 0.5 — never invent a flag.
Note any pre-existing/system rule (from Phase 0) instead of duplicating its SQL. Example unit:

```sql
-- rules/admin-action-spike.sql
SELECT
  attributes_string['http.client_ip'] AS ip,
  attributes_string['user.id']        AS operator,
  count() AS actions
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] = 'admin.action'
  AND timestamp >= now() - INTERVAL 10 MINUTE
GROUP BY ip, operator
HAVING ip != '' AND actions >= 50
```

```bash
# save the SQL, then create the rule from the file:
cat > rules/admin-action-spike.sql <<'SQL'
SELECT attributes_string['http.client_ip'] AS ip, attributes_string['user.id'] AS operator, count() AS actions
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] = 'admin.action' AND timestamp >= now() - INTERVAL 10 MINUTE
GROUP BY ip, operator HAVING ip != '' AND actions >= 50
SQL
```

```bash
securenow alerts rules create \
  --name "Admin: action spike from one operator" \
  --sql @rules/admin-action-spike.sql \
  --apps <APP_KEY> \
  --severity high \
  --schedule "*/5 * * * *" \
  --nlp "single operator performing 50+ privileged admin actions in 10 minutes"
```

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

Injection-class or system-signature-backed rows are the exception: reference the **system
signature rules + `instant.block`** instead of re-emitting SQL (admin threats are who/how-much, so
this is rare here — applies only where a sibling signature already covers a payload pattern).

### 4b′. Test mode for false-positive-prone rules — tag every rule `test-first` or `prod-ready`

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 (admin-action
spike, BFLA-recon 403-spike, distinct-admin-route, impersonation fan-out, export burst, off-hours
counts), broad patterns, anomaly / volume rules, anything tuned to YOUR operator team's traffic and
working window — 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 operator hits (the ops batch host, the maintenance window), then `--mode prod` to arm the
mitigation. Only **high-precision** rules (an exploit-signature match, exact-match IoCs, the
**always-alert single-hit** signals that are notify-only by design — break-glass use, a
role-change-to-admin, a security-relevant flag flip, a destructive action without step-up) may go
straight to `prod`/`prod-ready`. 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. Mitigation commands (the only allowed remediation surface)

For admin abuse, SecureNow **contains the operator/source at the edge**; the **access-control gate
/ app fix** removes the underlying weakness. **The gate itself is always an app fix** — on every
access-control, escalation, impersonation, over-broad-access, audit, flag, exposure, break-glass,
and step-up row, pair the SecureNow containment WITH the app fix.

Replace the old "mitigation layers table" with this complete toolbox. Once a threat is confirmed,
**choose the narrowest effective mitigation(s) from ALL of these** and combine them (e.g. rate-limit
the `/admin` surface + block the worst operator IP + 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 operators. For admin abuse the **access-control gate is always an app fix** (row
15); SecureNow's job is to **contain the compromised/abusing operator** at the edge — pair the two.

| # | 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 /admin --method GET` | 500k+ known-bad IPs, hourly refresh; drop scanners probing the admin surface 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). Rare here — admin threats are who/how-much, not payload patterns; applies only where a sibling signature covers a pattern. |
| 3 | **IP block — global** | `securenow blocklist add <ip> --app <APP_KEY> --env production --reason "..."` | confirmed-compromised operator IP, all routes — cut them off while you revoke session/role in-app. |
| 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 operator/attacker only on the admin surface; 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 probing source; audit-preserving unblock. |
| 6 | **Rate limit — per IP** | `securenow ratelimit add <ip> --limit 100 --window 1m --duration 24h --reason "..."` | throttle one runaway operator/script across the app. |
| 7 | **Rate limit — per route (all clients, per-IP budget)** | `securenow ratelimit add --route /admin/export --mode prefix --method GET --limit 30 --window 1m --key-by ip` | cap an expensive/abusable admin or export endpoint for everyone, budgeted per IP. |
| 8 | **Rate limit — per route + IP** | `securenow ratelimit add <ip> --route /admin --mode prefix --method ALL --limit 30 --window 1m --duration 24h` · NL `securenow ratelimit from-text "rate limit /admin to 30/min for 24h" --yes` · test `securenow ratelimit test <ip> --path /admin --method GET` | precise throttle of one operator on the admin surface. |
| 9 | **CAPTCHA / proof-of-work challenge** | `securenow challenge add --route /admin --difficulty 16 --clearance 30m` (route-wide) **or** `securenow challenge add <ip> --route /admin --difficulty 18 --clearance 30m` · test `securenow challenge test <ip> --path /admin --method GET` | suspected automated abuse of admin/support tooling from **shared / NAT / CGNAT** egress — a human passes once, a script can't. Prefer over a hard block when real operators 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 for high-confidence admin-abuse signals; actions include block / rate_limit / requireCaptcha. |
| 11 | **Session revocation** | `securenow revoke …` (SDK `securenow/sessions` `guard()` / `isRevoked()`) | compromised operator / stolen admin session — kill the session, not just the IP (the in-app response to a confirmed operator compromise). |
| 12 | **Trusted IP (suppress)** | `securenow trusted add <ip> --label "Ops bastion / VPN egress / monitor"` | stop false positives from the legitimate operator bastion / VPN egress — 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 the internal/admin-only surface** — the strongest network gate for admin tooling (only the ops bastion / VPN / office egress reaches `/admin`). Never for a public app surface. |
| 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 operator-activity rule quiet without weakening it (e.g. a known batch/ops host). |
| 15 | **App / config / code fix (primary for root cause — the access-control gate)** | *described in the Code-Findings report, never auto-applied* | the actual fix: add/centralize the admin authz guard, strip mass-assigned role fields, consent+time-box+scope+audit impersonation, require justification & tenant-scope on support reads, emit immutable audit before the side-effect, cap & scope exports, gate flags/kill-switches behind admin authz, put internal tools behind SSO + IP-allowlist/VPN, expire & two-person break-glass, server-side step-up on destructive actions. SecureNow contains; the fix removes. |

**Choosing per threat** — by **confidence**: exploit-signature/exact IoC → instant-block or block;
confirmed-compromised operator → **hard-block the IP + revoke the session/role in-app**; probable
automated abuse on shared egress → **challenge**; noisy/legit-mixed operator traffic → **rate-limit
(test-mode first)**; known-good ops infra → **trusted / fp**; an internal/admin-only surface that
should never face the public internet → **allowlist (deny-by-default, row 13)** is especially
relevant for locking it down. By **blast radius**: always scope to the narrowest
`route`/`method`/`IP`/`duration` that stops the abuse; on NAT/CGNAT/shared operator egress prefer
challenge/rate-limit over a hard block. **Always pair an edge mitigation with the app/config fix**
(row 15, the access-control gate, in the Code-Findings report) — on admin rows SecureNow can only
contain the actor; the gate is what removes the weakness.

Tier guidance: SecureNow **cannot install the access-control gate** — that is the app fix and it
is **primary** on every row here. SecureNow's role is **containment of a compromised or abusing
operator**: rate-limit/challenge a probing source, **hard-block** a confirmed-compromised
operator IP at the edge while you revoke the session and role in-app, and trust the legitimate
bastion/VPN egress so operator work isn't disrupted. Reserve hard block for confirmed-malicious /
confirmed-compromised. Recommend **notify-only** (no auto action) for high-signal-but-not-block
events that need a human to confirm and respond in-app: a single role-change-to-admin, a
break-glass use, a security-relevant flag flip, an over-broad support access, a destructive action
without step-up — say so explicitly with the runbook command the human runs after confirming.

### 4d. Testing every detection and mitigation

Only test against apps/environments the user owns; prefer `--env local`/staging. For synthetic
source IPs use TEST-NET ranges (`192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24`).

```bash
# Synthetic admin-action spike — exercise the operator-spike rule end to end:
for i in $(seq 1 55); do
  securenow event send admin.action --ip 203.0.113.50 \
    --attrs action=role_change,target=hashed_u1,result=success,justification_present=true,test=true
done

# Synthetic BFLA recon (non-admin reaching admin routes):
for i in $(seq 1 6); do
  securenow event send admin.access.denied --ip 203.0.113.51 \
    --attrs route=/admin/users,required_role=admin,actual_role=user,test=true
done

# Synthetic impersonation fan-out (5 distinct targets from one operator):
for t in 1 2 3 4 5; do
  securenow event send admin.impersonate.start --ip 203.0.113.52 \
    --attrs target=hashed_u$t,scope=full,consent=false,ttl_minutes=30,test=true
done

# Synthetic bulk-export burst:
securenow event send admin.bulk.export --ip 203.0.113.53 \
  --attrs dataset=pii,rows=80000,scope=all_tenants,format=csv,test=true

# Synthetic break-glass use (always-alert rule):
securenow event send admin.breakglass.used --ip 203.0.113.54 \
  --attrs reason=incident_4821,approved_by=none,ttl_minutes=60,test=true

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

# Traffic-based rules (admin 403-spike / large-export response) — generate spans, then check pipeline:
securenow test-span "threat-model.admin.smoke"
securenow forensics "admin-route requests and 401/403 by IP in the last hour" --env production

# Mitigation verification:
securenow firewall test-ip 203.0.113.50 --app <APP_KEY> --env production
securenow ratelimit test 203.0.113.50 --path /admin --method GET
securenow challenge test 203.0.113.50 --path /admin --method GET

# 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: Detection & Mitigation + Code Findings)

You write **four** files into `threat/08-admin-support-tools/` — two tracks, each as Markdown
**and** self-contained HTML:

1. `admin-support-tools-detection-mitigation.md` — the **operational runbook**: what to run in
   SecureNow.
2. `admin-support-tools-detection-mitigation.html` — same, self-contained HTML with **copy
   buttons**.
3. `admin-support-tools-code-findings.md` — the **code audit**: privileged-function flaws in the
   codebase + recommendations.
4. `admin-support-tools-code-findings.html` — same, self-contained 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/mitigation row it backs.

### 5a. Detection & Mitigation report — sections (both `.md` and `.html`), in order

1. **Executive summary** — stats line (threats modeled · covered · partial · gaps · rules to
   create · mitigations), the top 3 *detectable* operator-side risks for this stack, the installed
   `securenow` version (from Phase 0.5) + app key + firewall state, and a one-line OWASP note (A01
   Broken Access Control owned here; API5/API1 mapped; what is deferred to the authorization /
   tenant-isolation / storage-and-logs models).
2. **SDK & environment** — installed SDK version (from `node_modules/securenow`), app key(s),
   environment, firewall state, existing rules / automations / challenge rules (from Phase 0).
   There are **no system "admin-abuse" signature rules** — note that explicitly.
3. **Threat → Detection → Mitigation matrix** — one row per modeled threat:
   `# | Threat | OWASP (A01 + API5/API1) | Coverage 🟢/🟡/🔴 | Detection rule | Signal (threshold+window) | Schedule | Sev | Mode | Mitigation`.
   Severity ∈ {critical, high, medium, low}. The **Mode** cell tags each rule `test-first` or
   `prod-ready` (per Phase 4b′). The **Mitigation** cell must pick **specific, scoped**
   mitigation(s) from the Phase 4c toolbox (by #/name — e.g. "rate-limit `/admin` per-IP (row 8) +
   block confirmed IP scoped to `/admin*` (row 4) + app fix: central authz guard (row 15)"), never
   a generic "block the IP." Pair edge-containment WITH the app fix on every access-control row
   (link the app fix to its code-findings row). Then the "Out of scope" N/A list and the
   **deferred-to-sibling rows** (68–70) pointing to ../02-authorization/, ../03-tenant-isolation/,
   ../23-storage-and-logs/.
4. **Detection rules to create** — each as the **ready-to-copy command unit** from Phase 4
   (SQL with `-- rules/<name>.sql` → save to `rules/<name>.sql` → full `securenow alerts rules
   create …` → `securenow alerts rules test <RULE_ID> --mode dry_run --wait`). **Mark each rule
   `test-first` or `prod-ready`** (per Phase 4b′): every FP-prone rule is `test-first` and carries
   the `securenow alerts rules update <RULE_ID> --mode test` → observe 3–7 days → `--mode prod`
   promotion step inline; only high-precision / always-alert-notify-only rules are `prod-ready`.
   Note any pre-existing rule (from Phase 0) instead of duplicating it. Where a row is a
   config/posture gap with no SQL, point to the code-findings report instead.
5. **Instrumentation the detections need** — only the `track('admin.*')` events the rules above
   consume, each as a copyable snippet; point to the **code-findings report** for *where* (file +
   line) to add them. Stress: always send the **real operator identity** as `userId` even during
   impersonation, and hash target/tenant identifiers.
6. **Mitigation mechanisms** — render the **full Phase 4c 15-row mitigation toolbox table**
   (firewall · instant-block · block [global / route+method / temporary] · rate-limit [IP / route /
   IP+route] · challenge · auto-block · session-revoke · trusted · **allowlist / deny-by-default** ·
   fp · **app-config fix = the access-control gate**) + the "Choosing per threat" guidance +
   per-threat **ready-to-copy** mitigation command + reversibility. Make explicit that the
   **access-control gate is the primary app fix on every access-control row** (it lives in the
   code-findings report) and the SecureNow control is **containment of a compromised/abusing
   operator** at the edge; highlight **allowlist (row 13, deny-by-default)** as the strongest
   network lockdown for an internal/admin-only surface.
7. **Action plan (copy-paste, ordered)** — ① engage the firewall + trust the ops bastion/VPN
   egress (and, where the admin surface is internal-only, consider the **allowlist / deny-by-default
   lockdown**, row 13), ② add the `admin.*` event instrumentation at every privileged enforcement
   point, ③ create the rules — **FP-prone rules in `--mode test`** (each followed by a
   `securenow alerts rules update <RULE_ID> --mode test` line) and an explicit **"promote after N
   days (3–7) once tuned: `securenow alerts rules update <RULE_ID> --mode prod`"** step;
   high-precision / always-alert rules go straight to `prod`, ④ enable automations / challenge rules
   for high-confidence abuse, ⑤ test, ⑥ verify in dashboard, ⑦ schedule the app/config-fix work
   from the code-findings report (the access-control gates — the primary remediation). Real commands
   only, `<APP_KEY>` already substituted.
8. **Testing & validation** — per-rule recipe (4d): `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`).
9. **Response runbooks** — per notification type (admin-action spike, BFLA recon, impersonation
   fan-out, export burst, off-hours/anomalous-geo access, break-glass use, security-flag flip,
   role-change-to-admin, destructive-without-step-up): confirm TP → respond command (copy) +
   in-app action (revoke session, revoke role, end impersonation) → reverse command (copy).
10. **Known gaps & SecureNow feature requests** — every 🔴: why it's not coverable today, interim
    fix (link to the code-findings row), and the "contact the SecureNow team" line. Call out which
    gaps become 🟡 once the matching `admin.*` event is emitted.
11. **Appendix** — resolved SDK/CLI version (from Phase 0.5), app key, environment, firewall
    state, rule IDs created, date, and a link to the **code-findings** report.

### 5b. Code Findings & Recommendations report — sections (both `.md` and `.html`), in order

State at the top: *"Findings only — no application code was modified."*

1. **Executive summary** — findings by severity (critical / high / medium / low), the top 3 code
   risks, and a one-paragraph operator-side posture verdict.
2. **Privileged surface & inventory** — the full Phase 1 inventory: the admin/support route
   catalog (method/path/required-role/authz-present) + role model + impersonation map +
   cross-tenant support posture + audit-emission map + bulk-ops/export catalog + flag/kill-switch
   authz table + network/SSO exposure posture + break-glass inventory + step-up posture +
   telemetry-redaction status + tenancy note.
3. **Threat catalog** — the exhaustive Phase 2 catalog (groups A–N), each item tagged
   OWASP/CWE (A01 always + API5/API1), modeled or explicit N/A. Include the deferred rows (68–70)
   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 finding links to the
   detection/mitigation row it backs.
5. **Strengths** — controls already present and correct (central admin guard tested, role
   un-spoofable, impersonation consented + time-boxed + fully audited, exports capped & scoped,
   step-up enforced server-side) — the posture must be honest.
6. **App / config fixes (primary remediation)** — the access-control-gate changes that remove the
   root cause (described, not applied): centralize the admin authz guard, strip mass-assigned role
   fields, consent + time-box + scope + audit impersonation, require justification & tenant-scope
   on support reads, emit immutable audit before the side-effect, cap & scope exports, gate
   flags/kill-switches behind admin authz, put internal tools behind SSO + IP-allowlist/VPN,
   expire & two-person break-glass, require server-side step-up on destructive actions. Each linked
   to the detection-report row it backs.
7. **Instrumentation recommendations** — the `track('admin.*')` calls to add and the exact
   file:line to add them (at each privileged enforcement point), so the detection rules light up.
8. **Appendix** — files reviewed, resolved SDK version (from Phase 0.5), date, and a link to the
   **detection-mitigation** report.

### 5c. Self-contained HTML skeletons (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. The
**Detection & Mitigation** HTML wraps EVERY command/SQL block as a `.cmd` (so it gets a Copy
button); the **Code Findings** HTML may omit copy buttons on prose but still wraps any
example/fix command in `.cmd`.

Shared `<head>` + copy CSS + copy `<script>` (identical in both files):

```html
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<title><!-- "Detection & Mitigation — Admin / Support Tools — SecureNow" OR "Code Findings — Admin / Support Tools — 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 · Admin / Support Tools" OR "Code Findings · Admin / Support Tools" --></div>
    <!-- one <a href="#…"> per section -->
  </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 admin/support-tools 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>
```

**Track 1 — Detection & Mitigation HTML `<body>`.** Title `Detection & Mitigation — Admin /
Support Tools`, sidebar subtitle `Detection & Mitigation · Admin / Support Tools`, one
`<section id="…">` per 5a section (executive summary, SDK & environment, matrix, detection rules,
instrumentation, mitigation mechanisms, action plan, testing, response runbooks, known gaps,
appendix). Stats cards: threats modeled · covered · partial · gaps · rules to create. **Every**
SQL/command block uses the copyable wrapper:

```html
<div class="cmd"><button class="copy" type="button">Copy</button><pre>securenow alerts rules create \
  --name "Admin: action spike from one operator" --sql @rules/admin-action-spike.sql \
  --apps &lt;APP_KEY&gt; --severity high --schedule "*/5 * * * *" \
  --nlp "single operator performing 50+ privileged admin actions in 10 minutes"</pre></div>
```

**Track 2 — Code Findings HTML `<body>`.** Title `Code Findings — Admin / Support Tools`, sidebar
subtitle `Code Findings · Admin / Support Tools`, one `<section id="…">` per 5b section (executive
summary, privileged surface & inventory, threat catalog, code-level findings, strengths, app/config
fixes, instrumentation recommendations, appendix). Stats cards: critical · high · medium · low ·
total findings. Prose needs no Copy button, but any example/fix command is still wrapped in `.cmd`.

Badge usage (both files): severity `<span class="b crit|high|med|low">`; coverage
`<span class="c cov|part|gap">COVERED|PARTIAL|GAP</span>`; OWASP `<span class="owasp">A01</span>` /
`<span class="owasp">API5</span>`; CWE `<span class="cwe">CWE-862</span>`; mitigation type
`<span class="m firewall|signature|rate|challenge|block|notify|appfix">` (use `appfix` for the
access-control-gate fix, primary on most rows); rule IDs `<span class="rid">`. Both HTML files are
fully self-contained (inline CSS + JS, no network). Stats numbers must equal the matrix/findings
row counts.

---

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

- Every catalog item A1–N70 is either a matrix row or an explicit N/A line; each modeled row
  carries its OWASP tag (**A01** always, plus **API5** for BFLA / **API1** for BOLA, or "—").
- The full domain emphasis is covered: unauthorized admin/support route access (BFLA), privilege
  escalation user→support→admin (role tampering / mass assignment), impersonation / masquerade /
  sudo ("login as") with scope/consent/audit, over-broad cross-tenant support access, missing /
  tamperable audit on privileged actions, bulk ops & exports as exfil, feature-flag / kill-switch
  / config authz, internet-exposed internal tools, off-hours / anomalous-volume operator activity,
  break-glass without expiry/review, and missing step-up / second-factor on destructive actions —
  each modeled or explicit N/A.
- The access-control gate itself is named as the **app fix**, and every access-control /
  escalation / impersonation / over-broad / flag / exposure / break-glass / step-up row **pairs
  edge-containment WITH the app fix**.
- Coverage badges are **honest**: native MED coverage is admin-action spikes, off-hours /
  anomalous-geo admin access, impersonation & bulk-export `admin.*` events, BFLA-recon 403-spikes,
  and **containing a compromised operator** — these are 🟢; gate/scope/audit/expiry fixes are 🟡
  (paired) or 🔴 (until the `admin.*` event is emitted).
- The sibling models are **deferred** by **numbered path** (rows present, linked, not re-derived):
  general access control → ../02-authorization/, cross-tenant data → ../03-tenant-isolation/,
  audit-log **storage** integrity → ../23-storage-and-logs/. This model owns audit *emission* on
  the privileged path; ../23-storage-and-logs/ owns stored-log safety.
- Every matrix row has a concrete signal (threshold + window), severity, and mitigation — no
  "monitor for suspicious activity" filler.
- Every code finding in the code-findings report has a `file:line`, the quoted snippet, and a
  described fix — and **no application code was modified** (this is an audit).
- Every detection SQL keeps `__USER_APP_KEYS__` scoping (correct table column: logs →
  `resources_string['service.name']`, traces → `` `resource_string_service$$name` ``) and selects
  an `ip` column; traffic queries keep the `ts_bucket_start` + `kind = 2` guards and the
  `client_ip` coalesce; grouped rows keep `HAVING ip != ''`; new rules run with `--mode dry_run`
  before relying on unconfirmed columns.
- Only commands, flags, events, and SQL columns from this prompt's building blocks appear
  (including `securenow challenge …`, `firewall`, `trusted`, and the `admin.*` 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 / SQL 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 (with `-- rules/<name>.sql`) →
  save to `rules/<name>.sql` → full `securenow alerts rules create …` → dry-run test; flags match
  `securenow alerts rules --help`.
- **Four** files are written to `threat/08-admin-support-tools/`
  (`admin-support-tools-detection-mitigation.md` + `.html`, `admin-support-tools-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 matrix / findings row counts.
- The split is **honest**: SecureNow-runnable detections/mitigations live in the
  detection-mitigation report; code/config changes (the access-control gate) live in the
  code-findings report; nothing security-relevant is dropped.
- The Detection report's mitigation section presents the **full toolbox** (Phase 4c: firewall ·
  instant-block · block [global / route / method / temporary] · rate-limit [IP / route / IP+route] ·
  challenge · auto-block · revoke · trusted · **allowlist / deny-by-default** · fp · app-fix), and
  **each modeled threat's matrix row selects specific, scoped mitigation(s) from it** (by #/name) —
  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 to the user: per-track file paths, threat counts,
  rules-to-create count, code findings by severity, gaps, and the resolved SDK version.

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