#07A07 · A01 · API2

OAuth / SSO / Integrations

OIDC/SAML flows, PAT/API tokens, and third-party app access & consent.

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/07-oauth-sso-integrations/ — open oauth-sso-integrations-code-findings.html (the audit) and oauth-sso-integrations-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

# OAuth / SSO / Integrations 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 federated-identity and
third-party-access surface, build an exhaustive **OAuth / SSO / Integrations** threat model
mapped to the **OWASP Top 10:2021 (A07 Identification & Authentication Failures, A01 Broken
Access Control)** and the **OWASP API Security Top 10:2023 (API2 Broken Authentication)**, audit
the code for flow-correctness 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 **federated identity and the third-party-access ecosystem**: OAuth2 / OIDC (as
both **consumer** and **provider**), SAML SSO, account linking, consent & scope, connected-app
sprawl, and long-lived machine credentials (PATs, API tokens, service accounts). It is a sibling
of three other models and **does not re-derive** them:

- Password login, MFA, and **session lifecycle** → [authentication](../01-authentication/authentication-threat-model-prompt.md) (**A07 / API2**).
- Inbound **callback / webhook authenticity** (HMAC, replay, idempotency) → [webhooks](../16-webhooks/webhooks-threat-model-prompt.md).
- Client secrets, signing keys, and machine-credential **storage / rotation in the platform** → [secrets & cloud IAM](../20-secrets-and-cloud-iam/secrets-and-cloud-iam-threat-model-prompt.md).

> **Honest coverage statement.** SecureNow is fundamentally an **API / traffic & events**
> security layer (firewall, rate-limit, challenge, exploit-signature instant-block, ASN
> enrichment, forensics). For this domain its coverage is **MEDIUM**: it natively detects the
> *observable abuse* of federated identity — token-replay and authorization-code-enumeration
> **traffic patterns**, failed-auth and error spikes on the **callback** and **token**
> endpoints, and anomalous third-party-app / token **usage** — via `auth.*` events and traffic
> spans. But **flow correctness itself is an app fix**: whether you validate `state`, enforce
> PKCE, check a SAML signature, validate `aud` / `iss` / `nonce`, or rotate refresh tokens lives
> in *your* OAuth/SAML/OIDC code, not in SecureNow. Every such row therefore pairs the **app
> fix** (primary) with the SecureNow **edge containment** of whoever is abusing the flaw.

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 OAuth / SSO / Integrations Threat Model Report (SecureNow)

You are a senior application-security engineer specializing in federated identity (OAuth2,
OIDC, SAML) and third-party access. Produce an **exhaustive OAuth / SSO / Integrations threat
model for THIS codebase**, organized along **OWASP Top 10:2021 A07 (Identification &
Authentication Failures)** and **A01 (Broken Access Control)** and the **OWASP API Security Top
10:2023 API2 (Broken Authentication)**, mapped to **SecureNow** detections and mitigations, with
a ready-to-run action plan **and** a code-level audit of every flow-correctness flaw you find.
You write **four deliverables** — two tracks, each as Markdown + a **self-contained** HTML page
(inline CSS + JS, no network requests, with **offline copy buttons** on every command) — into
`threat/07-oauth-sso-integrations/` (create the folder if needed):

1. `oauth-sso-integrations-detection-mitigation.md` — the **operational runbook**: what to run
   in SecureNow (rules to create, mitigations, tests, response runbooks).
2. `oauth-sso-integrations-detection-mitigation.html` — the same runbook, self-contained HTML
   with **copy buttons**, using the Detection & Mitigation skeleton at the end.
3. `oauth-sso-integrations-code-findings.md` — the **code audit**: flow-correctness issues found
   in the codebase + recommended fixes (described, **never applied**).
4. `oauth-sso-integrations-code-findings.html` — the same code audit, self-contained HTML.

Every alert rule and CLI command is **grounded in the installed SecureNow SDK** (Phase 0.5) and
emitted as a **ready-to-copy unit** (SQL → save to `rules/<name>.sql` → full create command →
dry-run test). The two tracks **cross-link** each other: the gaps/instrumentation rows in the
detection runbook link to the relevant code finding, and vice-versa.

Work through the phases below, in order (0 → 0.5 → 1 → 2 → 3 → 4 → 5). **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 **federated-identity flows** (OAuth2/OIDC consumer +
provider, SAML), **account linking**, **consent & scope**, **connected-app lifecycle**, and
**machine credentials** (PATs / API tokens / service accounts). It does **not** re-derive:

- **Password login, MFA, and session lifecycle** (cookie flags, server-side revocation, session
  fixation) → defer to the [authentication model](../01-authentication/authentication-threat-model-prompt.md).
- **Inbound callback / webhook signature, replay, and idempotency** → defer to the
  [webhooks model](../16-webhooks/webhooks-threat-model-prompt.md). (This model still owns the
  OAuth *redirect/callback flow correctness* — `state`, `code` handling — but not generic
  provider→you webhook HMAC.)
- **Storage and rotation of client secrets / signing keys / machine credentials inside the
  platform** (vaulting, KMS, CI exposure) → defer to the
  [secrets & cloud IAM model](../20-secrets-and-cloud-iam/secrets-and-cloud-iam-threat-model-prompt.md).
  (This model owns whether tokens are *over-scoped, unrotated, leaked in your own logs*, and
  whether your provider authenticates clients — the secret's lifecycle in the vault is theirs.)

For deferred items, add **one** matrix row each marked *"deferred — see linked model"*, note only
the SecureNow traffic-observable symptom, and link the report. Do not author detection SQL for them.

---

## Phase 0 — Verify SecureNow tooling

Run and record (use `--json` where supported):

```bash
securenow doctor                # connectivity must be healthy
securenow whoami                # admin auth + runtime app
securenow status --json         # app key(s), environment, firewall state
securenow alerts rules --json   # detection rules that already exist (incl. system signature rules)
securenow automation --json     # blocklist automations that already exist
securenow challenge list --json # CAPTCHA / proof-of-work challenge rules
securenow env --json            # resolved SDK config (service name, endpoints)
```

If the CLI is missing or not logged in, **stop** and tell the user to run
`npm i -g securenow && securenow login`, then re-run this prompt. Capture the **app key**
(UUID) — every rule and command in the report must use it. If multiple apps exist, ask the
user which app this codebase maps to before continuing. Note the **firewall state** and any
**system signature rules** already present (they are reused for any payload-borne abuse on the
callback/token endpoints — do not duplicate them).

---

## Phase 0.5 — Ground every rule & command in the INSTALLED SDK

Before writing any SQL or CLI, read the SecureNow SDK that is actually installed in this repo so
every alert rule and command is correct for THIS version — never guess flags, subcommands, event
names, or SQL columns:

```bash
cat node_modules/securenow/package.json    # installed SDK version (record it in both reports)
ls node_modules/securenow                  # exported modules: events, sessions, register, run, …
ls node_modules/securenow/dist 2>/dev/null # built entrypoints / bundled CLI
npx securenow --help                       # top-level commands available in this version
npx securenow alerts rules --help          # exact create flags: --name/--sql/--apps/--severity/--schedule/--nlp
npx securenow event --help                 # `event send` shape for synthetic tests
npx securenow ratelimit --help; npx securenow challenge --help
npx securenow blocklist --help; npx securenow automation --help; npx securenow trusted --help
```

If `node_modules/securenow` is absent, run `npm ls securenow`; if still missing, tell the user to
`npm i securenow` (or `npm i -g securenow`) and stop. EVERY command, flag, `track('…')` event
name, and SQL column you emit MUST be one the installed SDK/CLI actually exposes. If the installed
version lacks a capability this prompt references, emit the rule but annotate it
`# requires securenow >= <version>` instead of a broken command. Record the resolved version in
the appendix of BOTH reports.

In Phase 4 and Phase 5, treat `node_modules/securenow` + `--help` as the source of truth: the
`securenow/events` `track()` signatures, the `securenow alerts rules` SQL columns, and every
mitigation subcommand are discoverable there. Cross-check before emitting.

---

## Phase 1 — Inventory the identity & integration surface (codebase analysis)

A federated-identity model starts with an **inventory of every trust edge**: every provider you
trust, every client you authenticate, every token you mint or accept, every third party you have
granted access to. Document what is **actually wired**, not what the docs claim. Cover at minimum:

- **Role(s) you play** — are you an OAuth/OIDC **consumer** (relying party / client logging
  users in via Google, GitHub, Microsoft, Okta, Auth0, …), an OAuth/OIDC **provider**
  (you-as-IdP minting access/refresh/id tokens for your own API or third-party apps), a SAML
  **service provider** (SP), a SAML **identity provider** (IdP), or several at once? Each role
  has its own threats — enumerate per role.
- **Providers & IdPs trusted** — list every external IdP/social provider. For each: protocol
  (OAuth2 / OIDC / SAML), the discovery/metadata source (`.well-known/openid-configuration`,
  SAML metadata URL/XML), how its signing keys (JWKS / x509 cert) are fetched, pinned, cached,
  and rotated, and whether `iss` / entityID is validated against an allowlist.
- **OAuth/OIDC flows in use** — authorization-code, **authorization-code + PKCE**, implicit
  (legacy — flag it), hybrid, client-credentials (M2M), device-code, refresh-token, ROPC
  (resource-owner password — flag it). For each: is it used by a **public** client (SPA/mobile,
  no secret) or a **confidential** client (server, has secret)?
- **Endpoint catalog** — enumerate every identity route (method + path): authorization
  redirect/start, **callback / redirect_uri handler(s)**, **token endpoint**, userinfo,
  introspection, revocation, JWKS, OIDC discovery, SAML ACS (Assertion Consumer Service), SAML
  SLO (single logout), SAML metadata, consent screen, account-linking/connect, connected-apps
  management, PAT/API-token issuance & list & revoke, and any IdP-initiated entry point. Mark
  each public / internal / admin. This catalog is a report deliverable.
- **redirect_uri / ACS handling** — how is `redirect_uri` (OAuth) or the SAML ACS URL
  validated? Exact-match allowlist, prefix match, wildcard subdomain, or reflected from the
  request? Any **open-redirect** sink (a `returnTo` / `next` / `RelayState` parameter the
  callback honors after auth)? Where is the post-login landing URL constructed from?
- **state / nonce / PKCE** — is an unguessable `state` generated, bound to the user's session,
  and verified on callback (anti-CSRF)? Is a `nonce` generated and verified inside the id_token
  (OIDC replay protection)? Is **PKCE** (`code_challenge`/`code_verifier`, S256) enforced — and
  is it enforced for **public** clients specifically? Are `state`/`nonce`/`code` single-use and
  TTL-bounded?
- **Token validation (you-as-consumer)** — for every JWT/id_token/access_token you *accept*:
  signature algorithm allowlist (is `alg=none` or HS/RS confusion possible?), key source (JWKS
  vs static), and validation of `iss`, `aud`, `exp`, `nbf`, `iat`, `nonce`, and `azp`. Is the
  **id_token** ever used where an **access_token** is required (or vice-versa) — token
  confusion? Is the `aud` of an access token checked against *your* resource identifier?
- **Token issuance (you-as-provider)** — how do you authenticate the **client** at the token
  endpoint (client_secret_basic/post, private_key_jwt, none)? Are authorization codes single-use
  and short-lived and bound to the `redirect_uri` + PKCE verifier? Do you issue refresh tokens —
  and do you **rotate** them and **detect reuse** of a rotated token? What are token lifetimes
  and scopes? Is the consent/scope set enforced at the resource server?
- **SAML specifics (if SP/IdP present)** — is the assertion **signature** verified (and is the
  *assertion* signed, not just the response)? Is the signing happening before any XML
  canonicalization / parsing that could enable **signature wrapping (XSW)**? Are **unsigned**
  assertions rejected? Is `NameID` parsed in a way vulnerable to **comment injection**
  (`a<!---->@b.com`)? Are `Conditions` / `NotOnOrAfter` / `Audience` / `Recipient` /
  `InResponseTo` validated? Is **IdP-initiated** SSO allowed (no `InResponseTo` to bind) — and
  if so how is it constrained?
- **Account linking** — when a federated identity arrives, how is it matched to an existing
  account? By **verified** email only, or by any email the provider returns? Can an attacker
  register an account at a sloppy IdP with the victim's email and **auto-link** to take over?
  Is provider+subject (`iss`+`sub`) the join key (correct) or just email (risky)? Is re-auth
  required to link a new provider to a logged-in account?
- **Consent & scope** — what scopes can a third-party app or social login request? Is consent
  recorded, displayed, and revocable? Can scope be **upgraded** silently on a later request
  (incremental consent without re-prompt)? Is there **dangling consent** — grants that survive
  user offboarding, app de-listing, or secret rotation?
- **Connected-app / integration registry** — list every third-party app, marketplace
  integration, or OAuth client that has been granted access to user/tenant data. For each:
  granted scopes, who can install, whether installs are audited, whether an admin can see and
  **revoke** them, and whether least-privilege is enforced. Note **sprawl** (many over-permissioned
  apps, orphaned installs).
- **PAT / API-token issuance** — personal access tokens / API keys / service tokens minted by
  the app. For each: are **scopes** required, is **expiry** mandatory, is the token shown once
  and stored hashed, can it be **rotated/revoked**, and is its use audited? Search for token
  leakage paths — tokens in **logs, CI output, repo history, error bodies, URLs**.
- **Machine-to-machine / service accounts** — client-credentials clients and service-account
  keys. Are they **shared across environments** (one key in dev+staging+prod)? Over-scoped?
  Long-lived with no rotation? Embedded in client bundles or mobile apps?
- **Telemetry privacy & redaction** — confirm the SecureNow SDK / log pipeline redacts
  `Authorization`, `Cookie`, bearer/access/refresh/id tokens, authorization `code`, `client_secret`,
  SAML assertions/`RelayState`, PATs, and emails before ingestion. If federated-identity secrets
  can reach telemetry unredacted, that is a high-severity finding (and a self-inflicted leak path).
- **SecureNow instrumentation already present** — `securenow/register` / `securenow run` /
  `securenow init` (gives traffic spans on the callback/token endpoints automatically), any
  `securenow/events` `track('auth.*')` calls already wired into the OAuth/SAML handlers, and
  whether the firewall is engaged. This determines what works *today* vs *after instrumentation*.

Output of this phase = the report's **Identity & integration surface** section: the role
matrix (consumer/provider/SP/IdP), the trusted-IdP list (protocol / key source / `iss`
validation), the OAuth-flow table (flow / client type / PKCE / state / nonce), the endpoint
catalog (auth/callback/token/ACS/SLO/JWKS/consent/PAT, visibility), the **redirect_uri / ACS
validation posture**, the **token-validation posture** (consumer) and **token-issuance posture**
(provider), the **account-linking rule**, the **consent/scope map**, the **connected-app
registry**, the **PAT / machine-credential inventory**, the **telemetry redaction status**, and a
short paragraph naming the real federated-identity 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. "SAML items: N/A, OAuth/OIDC-only stack"; "you-as-provider items: N/A,
consumer-only relying party"). 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: **A07**, **A01**, or **API2** (OWASP API Security Top 10:2023), or a
combination, or "—".

**A. redirect_uri / callback manipulation (A01 / A07)**
1. `redirect_uri` not allowlisted (open redirect) → authorization `code` / token sent to
   attacker-controlled host
2. Loose `redirect_uri` matching (prefix/substring/wildcard subdomain, path traversal,
   userinfo `@`, encoded host) bypassing the allowlist
3. Open redirect in the post-login `returnTo` / `next` / `RelayState` honored *after* auth →
   token/session leakage via the final hop
4. `redirect_uri` swapped between the authorize and token requests (downgrade / mismatch)

**B. CSRF & flow-binding on the callback (A07 / API2)**
5. Missing or unverified `state` → login-CSRF / forced-login / code-injection on the callback
6. `state` not bound to the initiating session (accepted from any session)
7. Missing **PKCE** on a **public** client → authorization-code interception is replayable
8. Authorization-code interception or **replay** (code not single-use, not bound to PKCE
   verifier / `redirect_uri` / client)
9. Implicit-flow access/id token leakage in the URL fragment (Referer, history, logs, proxies)

**C. Token confusion & forgery (you-as-consumer) (A07 / API2)**
10. JWT `alg=none` accepted (signature stripped)
11. Algorithm confusion — RS256 verified with the public key as an HS256 secret (key confusion)
12. `aud` not validated → a token minted for another client/resource is accepted (audience confusion)
13. `iss` not validated → a token from the wrong/attacker IdP is accepted
14. `exp` / `nbf` / `iat` not validated → expired or not-yet-valid tokens accepted
15. **access-token vs id-token confusion** — id_token used as an access token (or vice-versa) at
    a resource/userinfo endpoint
16. Signature not verified against the *current* JWKS (stale/rotated key, `kid` not pinned, JWKS
    fetched from an attacker-influenced URL)

**D. OIDC protocol correctness (A07 / API2)**
17. `nonce` not generated or not validated → id_token replay
18. OIDC **mix-up attack** — with multiple IdPs, the response is not bound to the IdP it was
    requested from (`iss` in the authorization response not checked)
19. Code/token substitution across IdPs (cut-and-paste / IdP-confusion)
20. Userinfo / claims trusted without re-validating the subject binding

**E. SAML assertion attacks (A07 / API2)** *(model only if SAML present, else N/A)*
21. Unsigned assertion accepted (only the response signed, or nothing signed)
22. **Signature wrapping (XSW)** — attacker injects a forged assertion alongside a signed one;
    parser trusts the wrong element
23. **NameID comment injection** (`victim<!---->@evil.com`) → identity confusion / takeover
24. `Conditions` / `NotOnOrAfter` / `Audience` / `Recipient` / `InResponseTo` not validated
    (assertion replay, audience confusion)
25. **IdP-initiated** SSO abuse (no `InResponseTo` binding → forged/stolen assertion injected)
26. XML external entity (XXE) / entity expansion in the SAML XML parser

**F. Account linking & social takeover (A07 / A01)**
27. Auto-link by **unverified** provider email → attacker registers victim's email at a sloppy
    IdP and inherits the victim's account
28. Linking keyed on email instead of `iss`+`sub` → provider re-use / email-change collisions
29. New provider linked to a logged-in account **without re-authentication** (CSRF-linkable)
30. JIT-provisioning / role-mapping over-trust — IdP-supplied groups/roles grant elevated access
    without server-side mapping control

**G. Consent & scope (A01)**
31. Over-scoped consent — app/login requests far more than it needs and the user grants it
32. **Scope upgrade** — additional scopes added on a later request without re-prompting consent
33. **Dangling consent** — grants survive offboarding / app de-listing / secret rotation; no TTL
34. Consent not recorded / not displayable / not revocable by the user or an admin

**H. You-as-provider weaknesses (A07 / API2)** *(model only if you mint tokens, else N/A)*
35. Weak / missing **client authentication** at the token endpoint (accepts `none` for a
    confidential client; guessable/weak `client_secret`)
36. Authorization code not single-use / not short-lived / not bound to client+`redirect_uri`+PKCE
37. **Refresh-token rotation absent** — a stolen refresh token works indefinitely; no reuse
    detection / family revocation
38. Tokens over-scoped or long-lived by default; scope not enforced at the resource server
39. No token **revocation** / introspection, or revocation not honored

**I. Connected-app sprawl & third-party access (A01)**
40. Third-party connected-app **sprawl** — many over-permissioned or orphaned integrations
41. Excessive granted permissions (an integration with org-wide / write scope it doesn't need)
42. No **revocation or audit** of connected apps (admin can't see/kill them; installs not logged)
43. Anomalous third-party-app **usage** — a connected app suddenly pulling data at scale or from
    a new network (traffic-observable)

**J. PAT / API-token & machine credentials (A07 / API2)**
44. PAT / API token issued without **scope** or without **expiry** (long-lived god tokens)
45. Token **leakage** in logs, CI output, repo history, error bodies, URLs, Referer
46. No PAT **rotation / revocation**, or revocation not enforced at the API
47. M2M / service-account credentials **over-shared** across environments (one key dev+staging+prod)
48. Service-account / client-credentials token used from an anomalous network/ASN (traffic-observable)
49. Invalid-token / key **spray** against the token / API endpoints (enumeration of valid tokens)

**K. Negative-space & evasion (A07 / A01)**
50. Forced-browsing the callback / token / JWKS / metadata endpoints to map the IdP integration
51. `client_id` / `redirect_uri` enumeration to find a registered loose URI
52. Encoding/normalization tricks on `redirect_uri` / ACS / `state` to bypass validation
53. Direct-origin access bypassing the CDN/WAF on the callback or token endpoint
54. Client-IP header spoofing (`X-Forwarded-For`) to evade per-IP limits on the token/callback flow

**L. Observable abuse — the workhorse SecureNow rules (A07 / API2)**
55. Failed-auth / error spike on the **callback** endpoint from one IP/ASN (state/PKCE/code
    tampering, broken or attacking integration)
56. Failed-auth / error spike on the **token** endpoint (client-auth failures, code-replay,
    invalid-grant probing)
57. Authorization-**code / state enumeration** traffic — one IP hitting the callback with many
    distinct `code`/`state` values (replay / interception probing) via the `auth.oauth.*` events
58. **Token-replay** pattern — the same token/`code` presented repeatedly or from multiple IPs
    (via `auth.token.*` events)
59. SAML ACS error / signature-failure spike (XSW / unsigned-assertion / replay attempts) via
    the `auth.saml.*` events
60. Anomalous connected-app / PAT usage — sudden volume or new-ASN origin for a known app/token
    (via `auth.integration.*` / `auth.token.*` events)
61. Invalid-token / client-auth **spray** — many distinct failing tokens/clients from one source

**M. Deferred — modeled in sibling models (reference, do not re-derive)**
62. Password login, MFA, **session lifecycle**, cookie flags, server-side session revocation,
    session fixation (**A07 / API2**) → [authentication model](../01-authentication/authentication-threat-model-prompt.md)
63. Inbound **callback / webhook authenticity** — HMAC signature, timestamp tolerance, replay
    dedupe, idempotency for provider→you webhooks → [webhooks model](../16-webhooks/webhooks-threat-model-prompt.md)
64. **Storage / rotation of client secrets, signing keys, and machine credentials** in the
    platform (vaulting, KMS, CI secret exposure) → [secrets & cloud IAM model](../20-secrets-and-cloud-iam/secrets-and-cloud-iam-threat-model-prompt.md)

> For 62–64, add **one** matrix row each marked *"deferred — see linked model"*, and only note
> the SecureNow traffic-observable symptom (e.g. failed-login spike, webhook-signature-fail
> spike, secret-in-log finding) here. The full detection/mitigation lives in the other reports.

---

## 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/strategy/resolver name
  (e.g. the Passport/`next-auth`/`openid-client`/`@node-saml`/`oidc-provider` strategy or the
  callback handler).
- **Pattern** — quote the 1–8 relevant lines. State the missing control precisely (e.g.
  "`redirect_uri` compared with `startsWith()` → subdomain/path bypass"; "`jwt.verify(token,
  key)` with no `algorithms` allowlist → `alg=none`/RS↔HS confusion"; "callback handler never
  reads/compares `state` → login-CSRF"; "`samlify`/`@node-saml` configured with
  `wantAssertionsSigned: false`"; "account matched by `email` not `iss`+`sub`"; "refresh token
  reissued without invalidating the old one → no rotation").
- **Why exploitable** — the concrete request/flow an attacker drives and what they achieve
  (account takeover, token theft, silent privilege grant).
- **Severity** — critical / high / medium / low (impact × reachability). Federated-identity
  flaws that yield account takeover are typically **critical/high**.
- **Recommended fix (described, not applied)** — the specific change, referencing the secure
  pattern, not a code diff: e.g. "exact-match `redirect_uri` against a per-client allowlist;
  reject anything else"; "pass `{ algorithms: ['RS256'] }` and verify against pinned JWKS by
  `kid`; never accept `none`"; "generate a CSPRNG `state`, store it in the session, require
  equality on callback, expire after one use"; "enforce PKCE S256 for public clients and bind
  the code to the verifier"; "validate `iss`, `aud`, `exp`, `nonce` on every id_token"; "require
  `WantAssertionsSigned`, verify the *assertion* signature before parsing, reject unsigned and
  duplicate assertions (XSW), and canonicalize NameID to defeat comment injection"; "link only
  on `iss`+`sub` with a verified email, require step-up re-auth to link a new provider"; "rotate
  refresh tokens on every use and revoke the whole family on reuse detection"; "require scope +
  expiry on every PAT, store hashed, expose a revoke + audit view". **You must not edit the
  codebase.**

If a control exists and is correct (PKCE enforced, `state` verified, algorithm allowlist
pinned, SAML assertion signature required, account-linking keyed on `iss`+`sub` with verified
email, refresh-token rotation with reuse detection), note it as a **strength** — the posture
must be honest. Absence of a control where the flow exists is itself a finding ("no `state`
check on the OAuth callback").

Look specifically for these high-frequency federated-identity flaws:

**redirect/callback flaws** — `redirect_uri`/ACS compared with `startsWith`/`includes`/regex
instead of exact allowlist; `returnTo`/`next`/`RelayState` reflected into a redirect after auth;
post-login URL built from a request parameter. *Fix mentions* exact-match per-client allowlists,
a canonical base-URL config, and rejecting non-allowlisted redirects.

**state / PKCE / code flaws** — callback that never reads `state`, or compares it to a constant;
`state` not session-bound; no PKCE for public clients; authorization code reusable. *Fix
mentions* CSPRNG single-use session-bound `state`, mandatory PKCE S256, single-use TTL-bounded codes.

**token-validation flaws (consumer)** — `jwt.verify`/`jose` calls with no `algorithms` list;
`aud`/`iss`/`exp`/`nonce` unchecked; id_token used as access token; JWKS fetched from a
response-supplied URL or not pinned by `kid`. *Fix mentions* algorithm allowlist, full claim
validation, pinned JWKS, and not cross-using id/access tokens.

**SAML flaws** — `wantAssertionsSigned`/`wantAssertionSigned` false or unset; signature checked
after parsing; unsigned/duplicate assertions accepted; NameID parsed without canonicalization;
XML parser with external entities enabled; IdP-initiated allowed without constraint. *Fix
mentions* required assertion signing, sign-then-parse order, XSW-safe libraries, disabling XXE,
NameID canonicalization, and constraining IdP-initiated.

**account-linking flaws** — match by `email` not `iss`+`sub`; auto-link on unverified email; no
re-auth to link. *Fix mentions* `iss`+`sub` join key, verified-email requirement, step-up re-auth.

**provider flaws (you-as-IdP)** — token endpoint accepting `none` client auth for confidential
clients; weak `client_secret`; codes not single-use; refresh tokens not rotated / no reuse
detection; scope not enforced at the resource server. *Fix mentions* enforced client auth,
single-use codes, refresh-token rotation + family revocation, resource-server scope checks.

**PAT / machine-credential flaws** — tokens minted without scope/expiry; stored plaintext;
tokens in logs/CI/repo; shared across environments; no revocation. *Fix mentions* mandatory
scope+expiry, hashed-at-rest, per-environment credentials, rotation, revocation, and redaction.

> Note: the *storage/vaulting* of those secrets is the [secrets & cloud IAM model](../20-secrets-and-cloud-iam/secrets-and-cloud-iam-threat-model-prompt.md);
> here you audit the **issuance, scope, expiry, leakage, and rotation** behavior of your own code.

---

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

Classify each threat with exactly one coverage badge:

- 🟢 **COVERED** — detectable + mitigable with SecureNow today (existing rule, system signature
  rule, or a rule you provide the SQL for, on telemetry that is already flowing). For this domain
  this is the **observable-abuse** family: failed-auth/error spikes on callback/token endpoints,
  code/state enumeration traffic, token-replay patterns, SAML ACS error spikes, anomalous
  connected-app/PAT usage, and invalid-token spray — once the `auth.*` events are emitted.
- 🟡 **PARTIAL** — works after the customer adds instrumentation (`track('auth.oauth.*' /
  'auth.saml.*' / 'auth.token.*' / 'auth.integration.*')`), **or** SecureNow can only *contain
  the abuser at the edge* while the real fix is **flow-correctness in the app** (PKCE, `state`,
  `aud`/`iss`/`nonce`, SAML signature, refresh rotation). **Most rows in this domain are 🟡** —
  pair the edge containment with the app fix on every one.
- 🔴 **GAP** — SecureNow cannot detect or mitigate this today (a silent flow flaw that emits no
  traffic and no event — e.g. an `aud`-not-validated bug that simply *accepts* a forged token
  with a 200, or `alg=none` acceptance, until the app emits an event for it). **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.

> **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 / signature
> instant-block. It **cannot** see the *correctness* of a flow it isn't told about: a missing
> `state` check, an unvalidated `aud`, an unsigned SAML assertion accepted with a 200, a
> refresh token that never rotates — these are **app fixes**. SecureNow detects the *abuse* (the
> error spike, the code/state enumeration, the token replayed from a new ASN, the invalid-token
> spray) and contains the source, but the missing validation is the primary fix. **A successful
> attack that returns 200 and emits no event is a 🔴 until the app emits the event in Phase 3's
> recommended fix.** Pair the control with the app fix on every 🟡 row.

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

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

Once the app runs under `securenow run` / `securenow/register` / `securenow init`, **HTTP
traffic on the callback / token / ACS endpoints is captured automatically** — status codes
(incl. **4xx** and **5xx**), methods, paths, client IPs. Spike/enumeration rules on those
endpoints need **no events**.

The flow-internal signals (which code/token failed and why) traffic can't see — add
`securenow/events` `track()` at the OAuth/SAML/token enforcement points. `track()` is
fire-and-forget and **never throws**, so it is safe inside an auth handler:

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

// OAuth/OIDC callback outcomes (you-as-consumer):
track('auth.oauth.callback.failure', { userId, sessionId, ip, attributes: { provider: 'google', reason: 'state_mismatch|pkce_failed|code_replay|redirect_uri_mismatch|nonce_mismatch' } });
track('auth.oauth.callback.success', { userId, sessionId, ip, attributes: { provider: 'google' } });

// Token validation / issuance:
track('auth.token.rejected', { userId, ip, attributes: { reason: 'alg_none|alg_confusion|bad_audience|bad_issuer|expired|signature|token_confusion', token_type: 'access|id|refresh|pat' } });
track('auth.token.replay_detected', { userId, ip, attributes: { token_type: 'refresh|access|code|pat', token_ref: '<hash>' } });
track('auth.refresh.reuse_detected', { userId, ip, attributes: { family: '<hash>' } });

// You-as-provider client auth:
track('auth.client.auth_failed', { ip, attributes: { client_id: '<hash_or_id>', reason: 'bad_secret|none_for_confidential|unknown_client' } });

// SAML:
track('auth.saml.assertion.rejected', { userId, ip, attributes: { idp: '<entityID_hash>', reason: 'unsigned|signature_invalid|xsw|nameid_comment|expired|bad_audience|replay' } });

// Account linking:
track('auth.account.link', { userId, ip, attributes: { provider: 'google', verified_email: 'true|false', join_key: 'iss_sub|email' } });

// Consent / scope:
track('auth.consent.granted', { userId, ip, attributes: { client_id: '<hash_or_id>', scopes: 'read,write,admin' } });
track('auth.scope.upgraded', { userId, ip, attributes: { client_id: '<hash_or_id>', added_scopes: 'admin' } });

// Connected-app / PAT usage and lifecycle:
track('auth.integration.used', { ip, attributes: { client_id: '<hash_or_id>', scope: 'read|write|admin' } });
track('auth.pat.issued', { userId, ip, attributes: { scope: 'repo,admin', has_expiry: 'true|false' } });
track('auth.pat.rejected', { ip, attributes: { reason: 'expired|revoked|out_of_scope|unknown' } });
```

> Hash or omit any PII/secret before it becomes an attribute value (emails, raw access/refresh/id
> tokens, authorization `code`, `client_secret`, SAML assertions, PATs, entityIDs) — see the
> Phase 1 **telemetry privacy & redaction** check. Attributes feed detection; they must not
> become a new leak path. Use `token_ref`/`family`/`token_type` (a hash + a category), never the
> token itself.

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

| Event | Emit when |
|---|---|
| `auth.oauth.callback.success` / `auth.oauth.callback.failure` | an OAuth/OIDC callback resolves (with `reason` on failure) |
| `auth.token.rejected` | an accepted JWT/access/id/refresh/PAT fails validation (alg/aud/iss/exp/confusion) |
| `auth.token.replay_detected` | the same token/`code` is presented again or from a new IP |
| `auth.refresh.reuse_detected` | a rotated refresh token is re-used (family compromise) |
| `auth.client.auth_failed` | client authentication at the token endpoint fails (you-as-provider) |
| `auth.saml.assertion.rejected` | a SAML assertion fails signature/XSW/NameID/replay/audience checks |
| `auth.account.link` | a federated identity is linked to an account (records verified-email + join key) |
| `auth.consent.granted` | a user grants consent/scopes to a client |
| `auth.scope.upgraded` | additional scopes are added to an existing grant |
| `auth.integration.used` | a connected app / integration makes an authorized call (feeds usage anomaly) |
| `auth.pat.issued` | a PAT/API token is minted (records scope + whether it has an expiry) |
| `auth.pat.rejected` | a PAT/API token is refused (expired/revoked/out-of-scope/unknown) |

Custom `attributes` become queryable as `attributes_string['<key>']` (e.g.
`attributes_string['provider']`, `attributes_string['reason']`). Ingest enriches every IP with
**ASN/org** (`client.asn`, `client.as_org`) — enabling new-ASN / datacenter-origin detection on
token replay and connected-app usage 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`).

**Traffic-based — failed-auth / error spike on the callback endpoint (state/PKCE/code tampering),
no events needed** (adjust the `http.target` LIKE to your real callback path):

```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,
       countIf(response_status_code IN ('400','401','403','422')) AS auth_errors
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 '%/callback%' OR attributes_string['http.target'] LIKE '%/oauth%' OR attributes_string['http.target'] LIKE '%/saml/acs%')
GROUP BY ip
HAVING ip != '' AND auth_errors >= 15
```

**Traffic-based — error spike on the token endpoint (client-auth failures, invalid-grant probing):**

```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,
       countIf(response_status_code IN ('400','401','403')) AS token_errors
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 '%/token%' OR attributes_string['http.target'] LIKE '%/oauth/token%')
GROUP BY ip
HAVING ip != '' AND token_errors >= 20
```

**Events-based — OAuth callback failures by reason (state/PKCE/code-replay tampering; logs table):**

```sql
SELECT
  attributes_string['http.client_ip'] AS ip,
  attributes_string['provider']       AS provider,
  attributes_string['reason']         AS reason,
  count() AS failures
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] = 'auth.oauth.callback.failure'
  AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, provider, reason
HAVING ip != '' AND failures >= 10
```

**Events-based — token rejection / replay & refresh reuse (token confusion, code/token replay):**

```sql
SELECT
  attributes_string['http.client_ip'] AS ip,
  attributes_string['event.type']     AS signal,
  attributes_string['reason']         AS reason,
  count() AS attempts
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] IN ('auth.token.rejected','auth.token.replay_detected','auth.refresh.reuse_detected')
  AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, signal, reason
HAVING ip != '' AND attempts >= 5
```

**Events-based — SAML assertion attacks (XSW / unsigned / NameID / replay) — any hit is high-signal:**

```sql
SELECT
  attributes_string['http.client_ip'] AS ip,
  attributes_string['reason']         AS reason,
  count() AS attempts
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] = 'auth.saml.assertion.rejected'
  AND timestamp >= now() - INTERVAL 30 MINUTE
GROUP BY ip, reason
HAVING ip != '' AND attempts >= 1
```

**Events-based — anomalous connected-app / PAT usage (new-ASN or volume; enrichment-aware):**

```sql
SELECT
  attributes_string['client_id']  AS client_id,
  client.as_org                   AS as_org,
  attributes_string['http.client_ip'] AS ip,
  count() AS calls
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] = 'auth.integration.used'
  AND timestamp >= now() - INTERVAL 60 MINUTE
GROUP BY client_id, as_org, ip
HAVING ip != '' AND calls >= 500
```

The other events follow the **same shape** — swap the `event.type` filter and the threshold:
`auth.client.auth_failed` (≥20/15m → client-auth spray, you-as-provider), `auth.pat.rejected`
(≥15/15m → invalid-token / PAT spray), `auth.scope.upgraded` (≥1 → notify-only, route to the
runbook), `auth.pat.issued` with `has_expiry=false` (≥1 → notify-only governance signal).

If any payload-borne abuse appears on these endpoints (e.g. an XXE/SQLi marker on the SAML ACS
or a callback parameter), rely on the **SecureNow system signature rules + `instant.block`** —
confirm they're enabled for this app via `securenow alerts rules --json`; do not author duplicate
pattern SQL.

Useful attributes/columns: `event.type`, `http.client_ip`, `http.target`, `response_status_code`,
`kind`, `client.asn`, `client.as_org`, and your auth attributes (`provider`, `reason`,
`token_type`, `client_id`, `idp`, `scopes`).

**Emit every detection as a ready-to-copy command unit.** Never a fragment — for each rule emit,
in order, as separate fenced blocks (so each copies cleanly): (1) the SQL, with a leading
`-- rules/<name>.sql` comment, (2) a line saving it to `rules/<name>.sql`, (3) the full
`securenow alerts rules create …` command, (4) the dry-run test. Save each rule's SQL to
`rules/<name>.sql` so `--sql @rules/<name>.sql` resolves. The exact flags **must match
`securenow alerts rules --help`** from Phase 0.5 — do not guess. Note pre-existing / system rules
(from Phase 0) instead of duplicating them.

```sql
-- rules/oauth-callback-failures.sql
SELECT
  attributes_string['http.client_ip'] AS ip,
  attributes_string['provider']       AS provider,
  attributes_string['reason']         AS reason,
  count() AS failures
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] = 'auth.oauth.callback.failure'
  AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, provider, reason
HAVING ip != '' AND failures >= 10
```

```bash
# save the SQL so --sql @rules/<name>.sql resolves
mkdir -p rules && $EDITOR rules/oauth-callback-failures.sql   # paste the SQL block above
```

```bash
securenow alerts rules create \
  --name "OAuth: callback failure spike (state/PKCE/code tampering)" \
  --sql @rules/oauth-callback-failures.sql \
  --apps <APP_KEY> \
  --severity high \
  --schedule "*/15 * * * *" \
  --nlp "single IP with 10+ OAuth callback failures in 15 minutes"
```

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

### 4b-bis. 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 (callback/token
error spikes, code/state enumeration counts, connected-app/PAT volume, client-auth/PAT spray),
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 (a busy SSO IdP egress, a high-volume
partner integration), then `--mode prod` to arm mitigation. Only **high-precision** rules
(exploit-signature SQLi/XSS/RCE matches on callback/ACS/token params, exact-match IoCs, single-hit
SAML-assertion-rejected / refresh-reuse / token-replay signals, known-bad ASN hits) may go straight
to `prod`. **Tag every rule `test-first` or `prod-ready`** in the report 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 federated-identity abuse, SecureNow **contains the actor at the edge**; the **app fix**
removes the underlying flow weakness. **The app fix is primary** for every flow-correctness row
(PKCE, `state`, `aud`/`iss`/`nonce`, SAML signature, account-linking key, refresh rotation,
PAT scope/expiry) — always pair it with the containment control.

Once a threat is confirmed, **choose the narrowest effective mitigation(s) from ALL of these** and
combine them (e.g. rate-limit the `/oauth/token` route + block the worst IPs + challenge a NAT IdP
egress + revoke the stolen session). 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 SSO 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 /oauth/token --method POST` | 500k+ known-bad IPs, hourly refresh; drop scanners hitting the callback/token endpoints 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 payload-borne abuse on the callback/ACS/token params (XXE/SQLi/XSS markers). 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 /oauth* --mode prefix --method ALL --app <APP_KEY> --env production --reason "..."` (`--mode exact\|prefix\|regex`, `--method GET\|POST\|…\|ALL`) | block an IP only on the OAuth/SAML paths; least collateral. |
| 5 | **IP block — temporary / time-boxed** | `securenow blocklist add <ip> --duration 24h --reason "..."` (`30m`,`24h`,`7d`) · reverse `securenow blocklist unblock <id> --reason "..."` | auto-expiring containment of a token-spray scanner; 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 /oauth/token --mode prefix --method POST --limit 30 --window 15m --key-by ip` | cap the token/callback endpoint for everyone, budgeted per IP (invalid-grant probing, client-auth spray). |
| 8 | **Rate limit — per route + IP** | `securenow ratelimit add <ip> --route /oauth/token --mode exact --method POST --limit 5 --window 1m --duration 24h` · NL `securenow ratelimit from-text "rate limit /oauth/token to 5/min for 24h" --yes` · test `securenow ratelimit test <ip> --path /oauth/token --method POST` | precise throttle of one client on one identity route. |
| 9 | **CAPTCHA / proof-of-work challenge** | `securenow challenge add --route /login --difficulty 16 --clearance 30m` (route-wide) **or** `securenow challenge add <ip> --route /login --difficulty 18 --clearance 30m` · test `securenow challenge test <ip> --path /login --method GET` | forced-login / callback abuse from **shared / NAT / CGNAT / IdP** egress — a human passes once, a script can't keep replaying. Prefer over a hard block when real SSO 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()`) | **token theft / account-link takeover** — kill the stolen session/family, not the IP. The decisive cleanup after an OAuth/SAML takeover (needs `guard()` from the auth model). |
| 12 | **Trusted IP (suppress)** | `securenow trusted add <ip> --label "Okta IdP egress / partner integration / monitor"` | stop false positives from known-good IdP/partner infra — suppresses detection **and** mitigation. NOT deny-by-default. |
| 13 | **Allowlist (deny-by-default)** | `securenow allowlist add <ip> --label "..." --reason "..."` ⚠️ once any entry exists, ONLY listed IPs reach the app | lockdown of an internal/admin-only surface (e.g. an admin connected-apps console). Never for a public login flow. |
| 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 (busy SSO IdP egress, high-volume partner integration) without weakening it. |
| 15 | **App / config / code fix (PRIMARY for root cause)** | *described in the Code-Findings report, never auto-applied* | the actual flow-correctness fix: exact-match `redirect_uri` allowlist, mandatory PKCE, session-bound `state`, `aud`/`iss`/`nonce`/`exp` validation, algorithm allowlist, SAML assertion-signing + XSW-safe parsing, `iss`+`sub` linking with verified email, refresh-token rotation + reuse detection, PAT scope/expiry/rotation, per-environment service creds. SecureNow contains; the fix removes. |

**Choosing per threat** — by **confidence**: exploit-signature/exact IoC on the callback/ACS/token
params → instant-block or block; probable bot / forced-login on a shared IdP/NAT egress →
**challenge**; noisy/legit-mixed traffic (callback/token error spikes, connected-app/PAT volume) →
**rate-limit (test-mode first)**; **session/token compromise (theft, account-link takeover, refresh
reuse) → revoke (row 11 — especially relevant for this domain's token/account-takeover threats)**;
known-good IdP/partner noise → **trusted / fp**. By **blast radius**: always scope to the narrowest
`route`/`method`/`IP`/`duration` that stops the abuse; on NAT/CGNAT/shared IdP egress prefer
challenge/rate-limit over a hard block (a block nukes legitimate SSO logins). **Always pair an edge
mitigation with the app/config fix (Code-Findings report) — the app fix is PRIMARY for every
flow-correctness row; SecureNow can only contain the actor.**

Tier guidance: **challenge** sits between rate-limit and block — prefer it over a hard block
when the abusing IP may be a **shared IdP/NAT/CGNAT egress** carrying real SSO users (a block
nukes legitimate logins; a challenge lets humans through and stops the script). Reserve hard
block for confirmed-malicious or scanner traffic. Recommend **notify-only** (no auto action) for
false-positive-prone or governance signals: SAML ACS error spikes from a broken IdP integration,
a partner connected-app's batch job, `auth.scope.upgraded`, `auth.pat.issued` with no expiry —
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 OAuth callback failures — exercise the callback-failure rule end to end:
for i in $(seq 1 12); do
  securenow event send auth.oauth.callback.failure --ip 203.0.113.50 \
    --attrs provider=google,reason=state_mismatch,test=true
done

# Synthetic token replay / refresh reuse (token confusion family):
securenow event send auth.token.replay_detected --ip 203.0.113.50 \
  --attrs token_type=refresh,token_ref=abc123hash,test=true
securenow event send auth.refresh.reuse_detected --ip 203.0.113.50 \
  --attrs family=fam-hash,test=true

# Synthetic SAML assertion attack (any hit is high-signal):
securenow event send auth.saml.assertion.rejected --ip 203.0.113.51 \
  --attrs idp=idp-hash,reason=xsw,test=true

# Synthetic client-auth spray (you-as-provider) and PAT spray:
for i in $(seq 1 25); do
  securenow event send auth.client.auth_failed --ip 203.0.113.52 \
    --attrs client_id=app-1,reason=bad_secret,test=true
done

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

# Traffic-based rules (callback/token error spikes) — generate spans, then check pipeline:
securenow test-span "threat-model.oauth.smoke"
securenow forensics "requests and 4xx by IP on /oauth and /callback 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 /oauth/token --method POST
securenow challenge test 203.0.113.50 --path /login --method GET
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 reports (two tracks)

Write **four** files into `threat/07-oauth-sso-integrations/`: the Detection & Mitigation track
(`oauth-sso-integrations-detection-mitigation.md` + `.html`) and the Code Findings &
Recommendations track (`oauth-sso-integrations-code-findings.md` + `.html`). The
SecureNow-runnable detections/mitigations live in the **Detection** report; the code/config
changes live in the **Code-Findings** report; nothing security-relevant is dropped. The two
tracks **cross-link**: each gap/instrumentation row in the detection report links to the relevant
code finding, and each code finding's edge-containment note links back to the detection row.

### 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), top 3 detectable federated-identity risks, installed `securenow`
   version + app key + firewall state, and a one-line OWASP note (A07/A01/API2 owned here vs the
   session/webhook/secrets concerns deferred to siblings).
2. **SDK & environment** — installed SDK version (from `node_modules/securenow`, Phase 0.5),
   app key(s), environment, firewall state, existing rules / automations / challenge rules (from
   Phase 0), system signature rules present.
3. **Threat → Detection → Mitigation matrix** — one row per modeled threat:
   `# | Threat | OWASP (A07/A01/API2) | Coverage 🟢/🟡/🔴 | Detection rule | Signal (threshold+window) | Schedule | Sev | Mode (test-first/prod-ready) | Mitigation`.
   Severity ∈ {critical, high, medium, low}. The **Mitigation** cell must pick **specific, scoped**
   mitigation(s) from the Phase 4c toolbox (the numbered row(s), scoped by route/method/IP/duration)
   — never a generic "block the IP"; pair the app fix (primary) with the edge containment on every
   flow-correctness row. The **Mode** cell tags each rule `test-first` (FP-prone heuristic/volume/
   anomaly rule → ships `--mode test`) or `prod-ready` (high-precision signature/exact-IoC/single-hit
   rule → straight to `--mode prod`). Then the "Out of scope" N/A list (e.g. SAML N/A if OAuth-only,
   you-as-provider N/A if consumer-only) and the deferred-to-sibling rows (62–64, linked). Each
   gap/instrumentation row links to its code-findings row.
4. **Detection rules to create** — each as the **ready-to-copy 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-bis): a `test-first` rule's unit includes the
   `securenow alerts rules update <RULE_ID> --mode test` → observe (3–7 days) → tune + `securenow fp`
   → `securenow alerts rules update <RULE_ID> --mode prod` promotion step; a `prod-ready` rule arms
   immediately. Injection-class / payload-borne rows reference the **system signature rules +
   `instant.block`**, not duplicate SQL. Note rules that already exist (from Phase 0) instead of
   duplicating them.
5. **Instrumentation the detections need** — only the `track('auth.*')` 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 toolbox table verbatim** (all 15 rows:
   firewall · exploit-signature instant-block · IP block [global / route+method / temporary] ·
   rate-limit [per-IP / per-route / route+IP] · challenge · auto-block · **session revocation** ·
   trusted · allowlist · fp · app-config fix) plus the "Choosing per threat" paragraph, each row's
   trigger and reversibility, and the per-threat ready-to-copy mitigation command. Make explicit that
   the **app fix is primary** for every flow-correctness row and the SecureNow control is containment
   of the abuser, and that session revocation (row 11) is the decisive cleanup for token-theft /
   account-link takeover.
7. **Action plan (copy-paste, ordered)** — ① engage the firewall + enable signature instant-block,
   ② add the `auth.*` event instrumentation in the OAuth/SAML/token handlers (link to the code
   report for the file:line), ③ create rules — **every FP-prone (`test-first`) rule is created in
   `--mode test`** (detect-only) and carries an explicit **"promote after N days (3–7) of clean
   observation"** step (`securenow alerts rules update <RULE_ID> --mode prod` once tuned), while
   `prod-ready` rules arm immediately, ④ enable automations / challenge rules, ⑤ test, ⑥ verify in
   dashboard, ⑦ schedule the app-fix flow-correctness work (from the code report). Real commands
   only, `<APP_KEY>` already substituted.
8. **Testing & validation** — per-threat test recipes (Phase 4d): `securenow event send …` /
   `test-span` / dry-run + expected outcome + cleanup (TEST-NET IPs 192.0.2 / 198.51.100 /
   203.0.113).
9. **Response runbooks** — for each notification type: what fired, how to confirm a true positive,
   the exact command to respond (rate-limit / challenge / block / revoke) [copy], the exact
   command to reverse [copy] — and, for flow-correctness rows, the app-fix reminder (link to the
   code report).
10. **Known gaps & SecureNow feature requests** — every 🔴 threat (the silent flow flaws that
    return 200 and emit nothing): why it's not coverable today, interim app/config fix (link to
    the code report), and the "contact the SecureNow team" line.
11. **Appendix** — resolved SDK/CLI version (Phase 0.5), app key, environment, firewall state,
    rule IDs created, date, link to the code-findings report.

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

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

1. **Executive summary** — findings by severity (critical / high / med / low), top 3 code-level
   federated-identity risks for this stack, one-paragraph posture verdict.
2. **Surface & inventory** — the Phase 1 inventory for this domain: the role matrix
   (consumer/provider/SP/IdP), trusted-IdP list (protocol / key source / `iss` validation), the
   OAuth-flow table (flow / client type / PKCE / state / nonce), the endpoint catalog
   (auth/callback/token/ACS/SLO/JWKS/consent/PAT, visibility), the redirect_uri/ACS posture, the
   token validation/issuance posture, the account-linking rule, the consent/scope map, the
   connected-app registry, the PAT/machine-credential inventory, the telemetry redaction status.
3. **Threat catalog** — the exhaustive Phase 2 catalog (A1–M64, grouped), each tagged
   OWASP/CWE, modeled or explicit N/A. No silent drops; the deferred rows (62–64) link to the
   sibling models.
4. **Code-level findings (audit)** — a 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**). This is the section the user
   reviews to plan their own fixes.
5. **Strengths** — controls already present and correct (PKCE enforced, `state` verified,
   algorithm allowlist pinned, SAML assertion signing required, `iss`+`sub` linking with verified
   email, refresh-token rotation with reuse detection) — the posture must be honest.
6. **App / config fixes (primary remediation)** — the config/code changes that remove the root
   cause (exact-match `redirect_uri` allowlist, mandatory PKCE, session-bound `state`,
   `aud`/`iss`/`nonce`/`exp` validation, algorithm allowlist, SAML assertion-signing + XSW-safe
   parsing, `iss`+`sub` linking, refresh-token rotation + reuse detection, PAT scope/expiry/
   rotation, per-environment service creds) — described, not applied, each linked to the
   detection-report row it backs.
7. **Instrumentation recommendations** — the `track('auth.*')` calls to add and the exact
   file:line to add them (in the OAuth/SAML/token enforcement points), so the detection rules
   light up. Hash/categorize all PII/secret attributes.
8. **Appendix** — files reviewed, resolved SDK version (Phase 0.5), date, link to the
   detection-mitigation report.

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

Both HTML files share the **same `<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 — one body per track (5a or 5b). Self-contained: inline CSS + JS, no external
fonts/scripts/network. Wrap **every** command/SQL/code block as a `.cmd` (so it gets a working
Copy button); the Code-Findings HTML may omit copy buttons on prose, but still wraps any
example/fix command in `.cmd`. Keep this domain's title/subtitle.

**Shared `<head>` + copy CSS (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 per track:
     Detection:     "Detection & Mitigation — OAuth / SSO / Integrations — SecureNow"
     Code Findings: "Code Findings — OAuth / SSO / Integrations — SecureNow" -->
<title>Detection & Mitigation — OAuth / SSO / Integrations — 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.revoke{color:var(--rev);border-color:rgba(179,136,255,.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>
<!-- ▼▼▼ BODY A — Detection & Mitigation track (file: oauth-sso-integrations-detection-mitigation.html) ▼▼▼ -->
<div class="wrap">
  <nav>
    <div class="brand">Secure<span>Now</span></div>
    <div class="sub">Detection &amp; Mitigation · OAuth / SSO / Integrations</div>
    <!-- one <a href="#…"> per section of 5a (1–11) -->
  </nav>
  <main>
    <header class="top">
      <h1>OAuth / SSO / Integrations — Detection &amp; Mitigation</h1>
      <p><code><!-- app name / domain --></code> · <span class="pill">securenow <!-- installed version --></span> · <span class="pill"><!-- role, e.g. OIDC relying party + SAML SP --></span></p>
    </header>
    <div class="stats">
      <div class="stat"><div class="n"><!-- N --></div><div class="l">threats modeled</div></div>
      <div class="stat"><div class="n" style="color:var(--ok)"><!-- N --></div><div class="l">covered</div></div>
      <div class="stat"><div class="n" style="color:var(--med)"><!-- N --></div><div class="l">partial</div></div>
      <div class="stat"><div class="n" style="color:var(--crit)"><!-- N --></div><div class="l">gaps — SecureNow team</div></div>
      <div class="stat"><div class="n" style="color:var(--accent)"><!-- N --></div><div class="l">rules to create</div></div>
    </div>
    <!-- <section id="…"> blocks mirroring the 5a Markdown sections (Exec summary → SDK & env →
         matrix → rules to create → instrumentation → mitigation → action plan → testing →
         runbooks → known gaps → appendix). Wrap EVERY command/SQL block in a .cmd (see below). -->
    <footer>Generated by the SecureNow OAuth / SSO / Integrations threat-model prompt · <!-- date --> · securenow <!-- version --> · app <code><!-- APP_KEY --></code></footer>
  </main>
</div>
<!-- ▲▲▲ END BODY A ▲▲▲ -->
<script>
document.querySelectorAll('.copy').forEach(function(b){b.addEventListener('click',function(){
  var pre=b.parentElement.querySelector('pre'); if(!pre)return; var t=pre.innerText;
  function done(){b.textContent='Copied';b.classList.add('done');setTimeout(function(){b.textContent='Copy';b.classList.remove('done');},1500);}
  function fb(){var ta=document.createElement('textarea');ta.value=t;ta.style.position='fixed';ta.style.opacity='0';document.body.appendChild(ta);ta.focus();ta.select();try{document.execCommand('copy');}catch(e){}document.body.removeChild(ta);done();}
  if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(t).then(done,fb);}else{fb();}
});});
</script>
</body>
</html>
```

**Code-Findings HTML — same `<head>` + same `<script>`; only the `<title>`, sidebar subtitle, and
body change** (file: `oauth-sso-integrations-code-findings.html`):

```html
<!-- <head> is byte-identical to Body A's <head>, except:
       <title>Code Findings — OAuth / SSO / Integrations — SecureNow</title> -->
<body>
<!-- ▼▼▼ BODY B — Code Findings & Recommendations track ▼▼▼ -->
<div class="wrap">
  <nav>
    <div class="brand">Secure<span>Now</span></div>
    <div class="sub">Code Findings · OAuth / SSO / Integrations</div>
    <!-- one <a href="#…"> per section of 5b (1–8) -->
  </nav>
  <main>
    <header class="top">
      <h1>OAuth / SSO / Integrations — Code Findings &amp; Recommendations</h1>
      <p><code><!-- app name / domain --></code> · <span class="pill">securenow <!-- installed version --></span> · Findings only — no application code was modified.</p>
    </header>
    <div class="stats">
      <div class="stat"><div class="n" style="color:var(--crit)"><!-- N --></div><div class="l">critical</div></div>
      <div class="stat"><div class="n" style="color:var(--high)"><!-- N --></div><div class="l">high</div></div>
      <div class="stat"><div class="n" style="color:var(--med)"><!-- N --></div><div class="l">medium</div></div>
      <div class="stat"><div class="n" style="color:var(--low)"><!-- N --></div><div class="l">low</div></div>
      <div class="stat"><div class="n" style="color:var(--ok)"><!-- N --></div><div class="l">strengths</div></div>
    </div>
    <!-- <section id="…"> blocks mirroring the 5b Markdown sections (Exec summary → surface &
         inventory → threat catalog → code-level findings → strengths → app/config fixes →
         instrumentation recommendations → appendix). Prose may skip copy buttons, but any
         example/fix command is still wrapped in a .cmd. -->
    <footer>Generated by the SecureNow OAuth / SSO / Integrations threat-model prompt · <!-- date --> · securenow <!-- version --> · app <code><!-- APP_KEY --></code></footer>
  </main>
</div>
<!-- ▲▲▲ END BODY B ▲▲▲ -->
<!-- same copy <script> as Body A -->
</body>
</html>
```

**Copyable command wrapper** — every SQL/command block in the **Detection & Mitigation** HTML
uses the `.cmd` wrapper so the copy `<script>` attaches a Copy button:

```html
<div class="cmd"><button class="copy" type="button">Copy</button><pre>securenow alerts rules create \
  --name "..." --sql @rules/&lt;name&gt;.sql --apps &lt;APP_KEY&gt; --severity high \
  --schedule "*/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 tag →
`<span class="owasp">A07</span>` / `<span class="owasp">A01</span>` / `<span class="owasp">API2</span>`,
CWE tag → `<span class="cwe">CWE-287</span>`, mitigation type →
`<span class="m firewall|signature|rate|challenge|block|revoke|notify|appfix">`, rule IDs →
`<span class="rid">`. Wrap every SQL/command/code block in `.cmd` (with a Copy button in the
detection HTML). Stats numbers must equal the matrix/findings row counts.

---

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

- Every catalog item A1–M64 is either a matrix row or an explicit N/A line; each modeled row
  carries its OWASP tag (A07 / A01 / API2, or "—").
- The role-specific families are honestly scoped: SAML items (E21–E26) are modeled **or** N/A
  ("OAuth/OIDC-only"); you-as-provider items (H35–H39) are modeled **or** N/A ("consumer-only
  relying party"). No silent drops.
- **Session lifecycle (62), webhook authenticity (63), and secret storage/rotation (64) are
  deferred** to the [authentication](../01-authentication/authentication-threat-model-prompt.md),
  [webhooks](../16-webhooks/webhooks-threat-model-prompt.md), and
  [secrets & cloud IAM](../20-secrets-and-cloud-iam/secrets-and-cloud-iam-threat-model-prompt.md)
  models (rows present, linked, not re-derived) — this model does not duplicate them.
- Every matrix row has a concrete signal (threshold + window), severity, and mitigation — no
  "monitor for suspicious activity" filler.
- Detection vs. fix is honest: every flow-correctness row (PKCE, `state`, `aud`/`iss`/`nonce`,
  SAML signature, account-linking key, refresh rotation, PAT scope/expiry) pairs the SecureNow
  edge-containment control **with the app fix**, and the app fix is marked primary. A successful
  attack that returns 200 and emits no event is a 🔴, not a 🟢.
- Every code finding in section 4 has a `file:line`, the quoted snippet, and a described fix —
  and **no application code was modified** (this is an audit).
- Every detection SQL keeps `__USER_APP_KEYS__` scoping (correct table column: `resources_string
  ['service.name']` for logs/events vs `` `resource_string_service$$name` `` for traces) and
  selects an `ip` column; traffic queries keep the `ts_bucket_start` + `kind = 2` guards and the
  `client_ip` coalesce; grouped queries keep `HAVING ip != ''`.
- Payload-borne abuse on the callback/ACS/token endpoints references the **system signature
  rules + `instant.block`**, not duplicate pattern SQL.
- Only commands, flags, events, and SQL columns from this prompt's building blocks appear
  (including `securenow challenge …`, `firewall`, `revoke`, and the `auth.*` events). No invented
  CLI flags, event names, or SQL columns.
- No secret/PII (tokens, `code`, `client_secret`, SAML assertions, emails) is emitted as an
  unredacted event attribute — hashes/categories only.
- Every 🔴 gap appears in the gaps section with an interim app/config fix **and** the "contact
  the SecureNow team" line.
- The action plan runs top-to-bottom with `<APP_KEY>` substituted in.
- **Phase 0.5 ran**: the resolved installed `securenow` version appears in **both** reports'
  appendix, and no command/flag/event/column is emitted that the installed SDK/CLI does not expose
  (else it is annotated `# requires securenow >= <version>`).
- Every detection rule is a **complete copyable unit** (SQL → `rules/<name>.sql` → full
  `securenow alerts rules create …` → `--mode dry_run` test); flags match `alerts rules --help`.
- **Four** files are written to `threat/07-oauth-sso-integrations/` — the Detection & Mitigation
  track (`oauth-sso-integrations-detection-mitigation.md` + `.html`) and the Code Findings track
  (`oauth-sso-integrations-code-findings.md` + `.html`); the two tracks **cross-link** each other;
  both HTML files are self-contained (inline CSS/JS, no CDN/fonts/scripts/network) and **every
  command block in the detection HTML has a working Copy button**; the stats cards match the
  table/finding counts.
- The split is honest: SecureNow-runnable detections/mitigations live in the **Detection** report;
  code/config changes live in the **Code-Findings** report; nothing security-relevant is dropped.
- A one-line summary is printed back: per-track file paths, threat counts, rules-to-create count,
  code findings by severity, gaps, OWASP coverage, and the resolved SDK version.
- The Detection report's mitigation section presents the **full toolbox** (§6 / Phase 4c: firewall ·
  instant-block · block [global / route / method / temporary] · rate-limit [IP / route / IP+route] ·
  challenge · auto-block · **revoke** · trusted · allowlist · fp · app-fix), and **each modeled
  threat's matrix row selects specific, scoped mitigation(s) from it** — never a generic "block the
  IP."
- **Every false-positive-prone rule is tagged `test-first`** and carries the `--mode test` → observe
  (3–7 days) → `--mode prod` promotion workflow; only high-precision rules are `prod-ready`. The
  action plan creates the test-first rules in `--mode test` and has an explicit "promote after N
  days" step.

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