#22A08 · A06 · API9

Supply Chain & CI/CD

Dependency integrity, pipeline injection, and artifact signing & provenance.

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/22-supply-chain-cicd/ — open supply-chain-cicd-code-findings.html (the audit) and supply-chain-cicd-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

# Supply Chain & CI/CD 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 dependency + build + release surface,
build an exhaustive **supply chain & CI/CD** threat model mapped to **OWASP A08:2021 (Software
& Data Integrity Failures)**, **A06:2021 (Vulnerable & Outdated Components)**, and **API9:2023
(Improper Inventory Management)**, audit the manifests / lockfiles / CI workflows / Dockerfiles
for integrity flaws, and emit a **two-track, four-file** SecureNow-branded deliverable
(**Markdown + self-contained HTML for each track**) — a Detection & Mitigation runbook (the few
detection rules to create, the mitigation commands to run, how to test each one) and a Code
Findings & Recommendations audit (the repo/CI/cloud-config flaws, audited and described, **not**
fixed), plus which threats need the SecureNow team. Every rule and command is **grounded in the
SecureNow SDK actually installed in this repo** and emitted as a **ready-to-copy block**.

> **Where the content lives for THIS domain.** Supply chain & CI/CD is SecureNow's **lowest
> native-coverage** model (LOW–MEDIUM): the Detection & Mitigation report carries only the single
> `api.dependency.vulnerable` **notify-only** rule plus the downstream-exploit edge references —
> keep that honesty. The **bulk of the substance is in the Code Findings report** (repo / CI /
> cloud-config: pin, sign, scan, scope tokens, isolate runners), which the agent **audits and
> describes, never applies.**

This model owns **the path from third-party code and the build pipeline into production**:
dependency integrity (CVEs, malicious packages, lockfile/hashes), CI/CD pipeline security
(injection, runner trust, token scope, secret exfiltration), and artifact/release integrity
(signing, SBOM, provenance, base images, release channels). It is **not** primarily a traffic
model: SecureNow's runtime telemetry catches only the *observable tail* of this domain — a
vulnerable component **serving traffic** (one `api.dependency.vulnerable` event class + a
notify-only rule). **The bulk of this model is repo / CI / cloud-config findings** — pin, sign,
scan, scope tokens, isolate runners — which the agent **audits and describes, never applies.**

> SecureNow is fundamentally an **API / traffic** security layer (firewall, rate-limit,
> challenge, exploit-signature instant-block, ASN enrichment, forensics). For supply chain &
> CI/CD it has the **lowest native coverage** of any model: LOW–MEDIUM. It contributes one
> runtime event class (`api.dependency.vulnerable`) and notify-only rules; everything else is a
> config-audit finding whose **primary fix is in the repo / CI / cloud**, not at the edge. Be
> honest about this in every row.

This model **defers** three neighbours and does not re-derive them:

- **CI secret storage** (where pipeline secrets live, KMS, OIDC-vs-static creds, secret
  rotation) → [../20-secrets-and-cloud-iam/](../20-secrets-and-cloud-iam/). This model models
  secret **exfiltration through the build** (echoed env, cache/artifact leakage), not secret
  *storage*.
- **Runtime dependency-CVE exposure serving live traffic** (a vulnerable framework / parser
  actively reachable on the wire) → [../14-api-security/](../14-api-security/). This model models
  the dependency's **presence, provenance, and integrity** in the build; API security models its
  *exploitation at the edge*.
- **Browser-side / client supply chain** (front-end `<script>` deps, Magecart, sub-resource
  integrity, npm in the browser bundle) → [../05-client-side-supply-chain/](../05-client-side-supply-chain/).
  This model owns server/CI build-time deps; the client model owns what ships to the browser.

Requirements on the customer machine: `npm i -g securenow && securenow login` (admin auth + app
runtime connected). Everything else is discovered by the agent from the repo.

---

<!-- ════════════════ COPY EVERYTHING BELOW THIS LINE ════════════════ -->

# Generate a Supply Chain & CI/CD Threat Model Report (SecureNow)

You are a senior application-security engineer specializing in software supply chain and CI/CD
security. Produce an **exhaustive supply chain & CI/CD threat model for THIS codebase**,
organized along **OWASP A08:2021 (Software & Data Integrity Failures)**, **A06:2021 (Vulnerable
& Outdated Components)**, and **API9:2023 (Improper Inventory Management)**, mapped to
**SecureNow** detections and mitigations where they exist, with a ready-to-run action plan
**and** a config-level audit of every integrity flaw you find. You write **FOUR deliverables**
(two tracks, each Markdown + self-contained HTML) into `threat/22-supply-chain-cicd/` (create the
folder if needed):

1. `supply-chain-cicd-detection-mitigation.md` — the **operational runbook**: what to run in
   SecureNow (the one `api.dependency.vulnerable` notify-only rule, mitigation commands, tests).
2. `supply-chain-cicd-detection-mitigation.html` — the same runbook as a **self-contained** HTML
   page (inline CSS + offline **copy buttons**, no network requests), SecureNow branding.
3. `supply-chain-cicd-code-findings.md` — the **code audit**: every repo/CI/cloud-config integrity
   flaw found, with `file:line`, quoted snippet, and a described fix (**never applied**).
4. `supply-chain-cicd-code-findings.html` — the same audit as a **self-contained** HTML page.

Every alert rule and CLI command MUST be **grounded in the SecureNow SDK actually installed in
this repo** (Phase 0.5) and emitted as a **ready-to-copy block** (Phase 4). Because this is a
LOW–MEDIUM native domain, the **code-findings report carries most of the content** (repo / CI /
cloud-config); the detection report honestly covers only `api.dependency.vulnerable` notify-only
plus the downstream-exploit edge references. The two tracks **cross-link** each other.

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, manifests, lockfiles, CI workflows, or Dockerfiles. Do NOT run destructive
dependency updates** (`npm update`, `npm audit fix`, `yarn upgrade`, `pip install -U`, bumping a
lockfile, etc.). You are auditing: every fix is *described in the report*, never applied to the
repo. Reading manifests and running **read-only** inspection (e.g. `npm ls`, `cat package.json`)
is fine; any command that mutates a manifest, lockfile, or installed tree is forbidden.

**Scope discipline.** This model owns dependency integrity (CVEs, malicious packages, lockfile
hashes, install scripts), CI/CD pipeline security (workflow injection, runner trust, token
scope, secret exfiltration through the build), and artifact/release integrity (signing, SBOM,
provenance/SLSA, base images, release channels). Do **not** re-derive: **CI secret storage**
(→ [../20-secrets-and-cloud-iam/](../20-secrets-and-cloud-iam/)), **runtime dependency-CVE
exposure on live traffic** (→ [../14-api-security/](../14-api-security/)), **browser-side / client
deps & Magecart** (→ [../05-client-side-supply-chain/](../05-client-side-supply-chain/)). List
those in a "Deferred to sibling models" subsection, link them, and only model the
**build-/integrity-side** of each here.

---

## Phase 0 — Verify SecureNow tooling

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

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

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

> **Reality check for this domain:** most supply-chain/CI-CD threats produce **no SecureNow
> telemetry at all** — they live in the repo, in GitHub/GitLab/CI config, in registries, and on
> build runners that SecureNow does not observe. SecureNow contributes one runtime signal class
> (`api.dependency.vulnerable`) plus notify-only rules. Do not pretend otherwise: the value of
> this report is the **config audit** (Phase 3) and the **described repo/CI/cloud fixes**
> (Phase 4c, layer "repo/CI/cloud-config fix"). State this in the executive summary.

---

## 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 — especially the single
`api.dependency.vulnerable` event name and the `alerts rules create` flags this domain depends on.

---

## Phase 1 — Inventory the supply-chain & build surface (codebase analysis)

Supply-chain security starts with an **inventory of what enters the build and what leaves it**.
Document what is **actually present and configured**, not what is intended. Cite the file for
every item. Cover at minimum:

- **Package managers & ecosystems** — npm/yarn/pnpm (`package.json`, `package-lock.json`,
  `yarn.lock`, `pnpm-lock.yaml`), pip/Poetry/uv (`requirements*.txt`, `pyproject.toml`,
  `poetry.lock`, `uv.lock`), Go (`go.mod`, `go.sum`), Cargo (`Cargo.toml`, `Cargo.lock`), Maven/
  Gradle, Bundler, Composer, NuGet. List every ecosystem in the repo and whether each has a
  committed lockfile.
- **Dependency counts & depth** — direct vs transitive counts per ecosystem (from the lockfile),
  whether transitive versions are pinned/hashed, and any `*`/`latest`/range specifiers
  (`^`, `~`, `>=`, `git+`, `http(s)` tarball URLs, local `file:`/`link:` deps).
- **Registry & source config** — `.npmrc`, `.yarnrc(.yml)`, `pip.conf`, `~/.config/pip`,
  `cargo` config, Maven `settings.xml`: which registries are configured, scoped registries
  (`@scope:registry=`), whether the public registry is used for internal scopes (dependency-
  confusion risk), private mirror/proxy presence, `always-auth`, and any auth tokens committed.
- **Install / build / lifecycle scripts** — `scripts` in `package.json` (`preinstall`,
  `install`, `postinstall`, `prepare`, `prepublish`), `setup.py`/`build` hooks, `build.rs`,
  Makefile/justfile targets, and whether dependency install runs scripts (`npm ci` runs them;
  `--ignore-scripts` / `enable-pre-post-scripts=false` disables them). Note packages known to
  ship `postinstall` scripts.
- **CI/CD platforms & workflows** — enumerate every pipeline file: GitHub Actions
  (`.github/workflows/*.yml`), GitLab CI (`.gitlab-ci.yml`), CircleCI (`.circleci/config.yml`),
  Jenkins (`Jenkinsfile`), Azure Pipelines, Bitbucket, Buildkite, Travis, Drone, Tekton. For
  each: triggers (`on:` / rules), jobs, and what each job has access to.
- **CI trigger model & untrusted input** — for each workflow flag: `pull_request_target`,
  `workflow_run`, `issue_comment`, `pull_request` from forks, `schedule`, `workflow_dispatch`.
  Identify every place untrusted input (PR title/body, branch name, issue comment, fork code,
  `github.event.*`) flows into a `run:` block, a `script`, an env var, or an action input
  (script-injection sink).
- **CI/CD action & step pinning** — are third-party GitHub Actions / orbs / images pinned by
  **commit SHA** or by mutable tag (`@v4`, `@main`)? List every external action and its
  pin form.
- **Runner model & permissions** — GitHub-hosted vs **self-hosted** runners; runner labels;
  whether self-hosted runners are reachable from fork PRs; `permissions:` blocks (`GITHUB_TOKEN`
  default scope — `contents: write`, `id-token: write`, `packages: write`); GitLab CI job
  token scope; long-lived static cloud creds vs **OIDC**.
- **Secrets in the pipeline** — how secrets reach jobs (encrypted secrets, OIDC, env vars),
  whether any secret is `echo`'d / printed / passed to untrusted steps / written to logs or
  artifacts, masking config, and whether secrets are exposed to fork PRs. (Storage itself →
  defer to ../20-secrets-and-cloud-iam/.)
- **Build cache & artifacts** — `actions/cache`, dependency caches, Docker layer cache,
  artifact upload/download (`upload-artifact`), package publish steps, and whether cache keys
  are attacker-influenceable (cache poisoning) or artifacts leak secrets/source.
- **Container images** — every `Dockerfile` / `Containerfile` / `docker-compose*.yml` /
  k8s manifest: `FROM` base images and whether they are pinned by **digest** (`@sha256:…`) or
  mutable tag (`:latest`, `:18`); registry source (Docker Hub, GHCR, ECR, internal); multi-stage
  build hygiene; whether the build pulls third-party tarballs/scripts via `curl | sh`.
- **Artifact & release integrity** — is anything **signed** (cosign/Sigstore, GPG, npm
  provenance, Maven signing)? Is an **SBOM** produced (CycloneDX/SPDX)? Is **provenance /
  attestation** generated (SLSA, `actions/attest-build-provenance`)? How are releases cut
  (tags, GitHub Releases, registry publish) and what verifies them downstream?
- **Release channels & deploy triggers** — what auto-deploys, from which branches/tags, and
  whether deploys can be triggered from **forks or unreviewed branches**; branch protection /
  required reviews / required status checks on the deploy-source branch; environment protection
  rules / required reviewers on the deploy job.
- **Mutable third-party fetches** — any build/runtime step that fetches a mutable remote
  (`curl … | sh`, `wget`, unpinned CDN script, `go install pkg@latest`, `pip install` from a
  URL) — supply-chain ingestion points with no integrity check.
- **Dependency scanning in place** — Dependabot/Renovate config, `npm audit`/`pip-audit`/
  `osv-scanner`/Snyk/Trivy/Grype steps, `CODEOWNERS`, signed-commit enforcement, and whether
  any of it **blocks** the build or is advisory-only.
- **SecureNow runtime instrumentation present** — `securenow/register` / `securenow run` /
  `securenow init` (gives traffic spans automatically) and any `securenow/events` `track()`
  calls already emitting `api.dependency.vulnerable`. This determines whether the one runtime
  signal class works *today* vs *after instrumentation*.

Output of this phase = the report's **Supply-chain & build surface** section: the ecosystem +
lockfile table, the unpinned-version list, the registry/source config, the install-script
inventory, the CI/CD workflow catalog (file / triggers / runner / token scope), the
untrusted-input → `run:` sink list, the action/image pinning table, the secrets-in-pipeline
posture, the artifact/SBOM/provenance status, the release-channel/deploy-trigger map, the
mutable-fetch list, the existing-scanning status, and a short paragraph naming the real
supply-chain 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. "Container items: N/A, no Dockerfile / no container build"). 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
(**A08:2021**, **A06:2021**, **API9:2023**, or "—").

**A. Vulnerable & outdated dependencies (OWASP A06:2021)**
1. Known-CVE direct dependency pinned/ranged to a vulnerable version
2. Known-CVE **transitive** dependency pulled by a direct dep (deep tree)
3. Outdated/end-of-life runtime or framework with unpatched CVEs (Node/Python/Java/Go base)
4. Abandoned/unmaintained package (no updates, no maintainer) carrying latent risk
5. Vulnerable build-time-only dependency (devDependency / build tool) reachable on CI runners
6. Vulnerable component **serving live traffic** (deferred symptom — see ../14-api-security/)

**B. Malicious packages (OWASP A08:2021)**
7. Typosquatting — a near-name package substituted for the intended one
8. Dependency confusion — internal package name resolvable from the **public** registry at a higher version
9. Hijacked / compromised maintainer account pushing a malicious version
10. Malicious **install / lifecycle script** (`preinstall`/`postinstall`/`prepare`) executing on install
11. Trojanized update of a previously-good package (protestware / supply-chain implant)
12. Starjacking / fake-provenance package masquerading as a trusted repo
13. Direct `git+`/`http(s)` tarball / `file:` dependency from an untrusted or mutable source

**C. Lockfile & version-pinning integrity (OWASP A08:2021)**
14. Missing lockfile — versions resolved at install time (non-reproducible build)
15. Lockfile present but **ignored** in CI (`npm install` instead of `npm ci`; lockfile not committed)
16. Unpinned version ranges (`^`/`~`/`*`/`latest`) allowing silent drift to a malicious version
17. Tampered lockfile hashes / integrity field stripped or mismatched (`integrity`/`go.sum`)
18. Lockfile not enforced (`--frozen-lockfile`/`npm ci`/`--locked` not used) → resolver can deviate
19. Mixed/duplicate lockfiles across managers causing resolution ambiguity

**D. Install / build-time code execution (OWASP A08:2021)**
20. Post-install / lifecycle script runs untrusted code on the CI runner
21. Post-install / lifecycle script runs untrusted code on **developer machines** (local install)
22. Build step executes a fetched remote script (`curl … | sh`, `go install …@latest`) with no integrity check
23. Compiler/plugin/loader (e.g. esbuild plugin, webpack loader, `build.rs`) executing attacker-controlled code

**E. CI/CD pipeline injection (OWASP A08:2021)**
24. GitHub Actions `pull_request_target` running fork code **with** repo secrets / write token
25. Script injection — untrusted `github.event.*` (PR title/body, branch, comment) interpolated into `run:`
26. `workflow_run` / `issue_comment` triggers granting elevated context to untrusted input
27. Checkout of untrusted PR head in a privileged workflow then building/running it
28. Reusable-workflow / composite-action / orb injection from an untrusted source
29. Cache key derived from attacker-controlled input → poisoned restore in a trusted job

**F. Runner & pipeline-token over-privilege (OWASP A08:2021)**
30. Untrusted / self-hosted runner reachable from fork PRs (code-exec on your infra)
31. Self-hosted runner not ephemeral — persistent state reused across jobs (implant persistence)
32. Over-privileged `GITHUB_TOKEN` (default `write`/broad scope where read-only suffices)
33. Over-broad GitLab CI job token / pipeline credentials usable beyond the repo
34. Long-lived static cloud credentials in CI instead of short-lived **OIDC**
35. `id-token: write` / cloud-deploy permissions granted to jobs that don't need them

**G. Secret exfiltration through the build (OWASP A08:2021)**
36. Secret echoed / printed / leaked to job logs (`echo $SECRET`, `set -x`, debug output)
37. Secret exposed to fork PR / untrusted step (PR-triggered job with secret access)
38. Secret written into a build **artifact** (uploaded artifact contains `.env`/keys/source maps)
39. Secret poisoned into / read from the **build cache** (cache as an exfil or implant channel)
40. Secret committed to the repo / present in build context (`.env`, key file, token in config)
41. Secret passed to a third-party action that can forward it (exfil via dependency action)

**H. Artifact, SBOM & provenance integrity (OWASP A08:2021)**
42. Unsigned / unverified build artifacts (no cosign/GPG/npm-provenance; consumers can't verify)
43. No SBOM produced — unknown component inventory; CVE blast radius unmeasurable
44. No provenance / build attestation (SLSA) — origin & build steps not attestable
45. Published artifact not verified against its source (no signature check at consume/deploy time)
46. Release published from a build whose inputs were never integrity-checked (untrusted → prod)

**I. Container & base-image integrity (OWASP A08:2021)**
47. Compromised / untrusted **base image** (`FROM` an unverified third-party image)
48. Unpinned image tag (`:latest`/`:18`) — mutable base drifts to a malicious/vulnerable layer
49. Base image with known CVEs (outdated distro/runtime layer)
50. Build pulls third-party binaries/tarballs/scripts into the image with no checksum/signature
51. Image published to a registry without signing / digest verification on pull

**J. Build-cache & mutable-dependency poisoning (OWASP A08:2021)**
52. Build-cache poisoning — a tampered cache entry restored into a trusted build
53. Mutable third-party CDN / `<script src>` / remote include without integrity (build/runtime)
54. Floating action/tool version (`@main`, `@latest`) silently upgraded to a malicious revision
55. Proxy/mirror cache serving a substituted package (registry MITM / poisoned mirror)

**K. Release-channel & deploy-trigger tampering (OWASP A08:2021 / API9:2023)**
56. Auto-deploy from a **fork** or unreviewed branch reaching production
57. Missing branch protection / required reviews on the deploy-source branch
58. Missing environment protection / required reviewers on the deploy job
59. Tag/release tampering — mutable tags re-pointed; release assets replaceable
60. Unverified downstream consumption of the release (no signature/digest gate at deploy)
61. Shadow/zombie pipelines or release channels still live (inventory drift — **API9**)

**L. Negative-space & evasion**
62. Scanner blind spots — vulnerable dep present but excluded from the scanned manifest (e.g. vendored/`git` dep, lockfile not scanned)
63. Dependency present in lockfile but absent from the manifest (or vice-versa) → audit gap
64. Advisory-only scanning that never blocks the build (findings ignored at merge)
65. Suppressed/ignored advisories (`.nsprc`, `audit-ci` allowlist, `# nosec`) hiding live risk
66. Vendored / committed `node_modules` / `vendor/` bypassing registry integrity entirely

**M. Observable abuse (what SecureNow telemetry can actually catch at runtime)**
67. A vulnerable component is **live and serving traffic** — emitted as `api.dependency.vulnerable` (notify-only; the install/upgrade fix is primary). This is the **only** native runtime signal for this domain; everything else is a config-audit finding.

**N. Deferred — modeled in sibling models (reference, do not re-derive)**
68. CI **secret storage** (vault/KMS, OIDC vs static creds, rotation) → [../20-secrets-and-cloud-iam/](../20-secrets-and-cloud-iam/)
69. Runtime dependency-CVE **exploitation on live traffic** (vulnerable framework on the wire) → [../14-api-security/](../14-api-security/)
70. **Browser-side / client** supply chain (front-end deps, Magecart, SRI on shipped `<script>`) → [../05-client-side-supply-chain/](../05-client-side-supply-chain/)

> For 68–70, add **one** matrix row each marked *"deferred — see linked model"* and only note
> the build-/integrity-side seam this model owns (e.g. "we model secret *exfiltration through the
> build*, storage is deferred"). The full model lives in the linked report — do not duplicate it.

> Items A1–L66 are mostly OWASP **A06:2021** (vulnerable components: A-group) and **A08:2021**
> (everything integrity-related: B–L groups), with **API9:2023** on inventory-drift rows
> (zombie pipelines/channels, K61). Tag each row accordingly. Each 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 & config (findings only — do not fix)

For **each** modeled threat that maps to a real file, locate the responsible manifest / lockfile
/ workflow / Dockerfile / config and record a **finding** for the report's "Config-level
findings" section. A finding is:

- **Location** — `file:line` (clickable): the manifest, lockfile, workflow file, Dockerfile, or
  config, and the specific dependency / job / step / `FROM` line.
- **Pattern** — quote the 1–8 relevant lines. State the missing control precisely (e.g.
  `"\"lodash\": \"^4.17.4\"` → unpinned range, CVE-2021-23337 reachable"; `"uses: actions/checkout@main"`
  → mutable action pin; `"on: pull_request_target"` + `actions/checkout` of `head.sha` →
  fork-code with secrets; `"FROM node:latest"` → unpinned base image; lockfile absent → no
  integrity; `"npm install"` in CI → lockfile not enforced).
- **Why exploitable** — the concrete supply-chain path: e.g. "attacker publishes a higher
  version of the internal scope to the public registry → resolver pulls it → `postinstall` runs
  on the runner with the deploy token in env."
- **Severity** — critical / high / medium / low (impact × reachability; a malicious-install path
  on a runner with cloud-write OIDC is critical; an advisory-only scanner is medium).
- **Recommended fix (described, not applied)** — the specific change: e.g. "pin `lodash` to
  `4.17.21` and commit the lockfile"; "pin every third-party action to a full commit SHA and
  enable Dependabot for actions"; "split the `pull_request_target` job so secret/write steps
  never touch fork code, and never check out `head.ref` in a privileged job"; "set
  `permissions: { contents: read }` at workflow top and grant `id-token: write` only on the
  publish job"; "pin `FROM` to `@sha256:…` and rebuild on base-image advisories"; "switch CI to
  `npm ci` / `--frozen-lockfile`"; "add `--ignore-scripts` for untrusted installs"; "adopt OIDC
  short-lived cloud creds and delete static keys"; "generate an SBOM + cosign signature +
  `actions/attest-build-provenance` and verify at deploy". **Reference the secure pattern, never
  a code diff, and never run a dependency update.**

If a control exists and is correct (lockfile committed + `npm ci` enforced; actions pinned by
SHA; base images digest-pinned; OIDC in use; signing + SBOM + provenance present), note it as a
**strength** — the posture must be honest. Absence of a control where the surface exists is
itself a finding ("no lockfile committed for the Python service"; "no dependency scanning in
CI"; "every action pinned by mutable tag").

Audit specifically for:

**Dependency / version flaws** — unpinned ranges (`^`/`~`/`*`/`latest`/`git+`/URL tarball/`file:`)
in any manifest; missing or uncommitted lockfile; CI installing without lockfile enforcement
(`npm install` vs `npm ci`, no `--frozen-lockfile`/`--locked`/`poetry install --no-update`);
stripped/mismatched integrity hashes (`integrity`/`go.sum`/`*.lock` hashes); known-CVE versions
visible in the lockfile (cross-reference the version strings — do **not** run a network scanner
or an update); abandoned/EOL runtimes. *Fixes must mention* exact-version pinning, committing &
enforcing lockfiles, hash/integrity verification, and scheduled non-blocking advisory review.

**Malicious-package & install-script flaws** — internal scope names resolvable from the public
registry (dependency confusion) without a scoped private registry / `always-auth`; near-name /
typosquat-prone deps; `preinstall`/`install`/`postinstall`/`prepare` scripts in the dependency
set; install that runs scripts on CI and dev machines; `git+`/URL/`file:` deps from untrusted
sources. *Fixes must mention* scoped private registry + lockfile pinning, `--ignore-scripts` /
`enable-pre-post-scripts=false` for untrusted installs, allow-listing scripts, and a private
proxy/mirror.

**CI/CD pipeline-injection flaws** — `pull_request_target` / `workflow_run` / `issue_comment`
workflows that grant secrets/write to untrusted input; `github.event.*` (PR title/body, branch,
comment) interpolated directly into `run:`/`script`/env (script injection); checkout of fork
`head` in a privileged job; reusable workflow / composite action / orb from an untrusted source;
cache keys derived from attacker-controlled input. *Fixes must mention* avoiding
`pull_request_target` for fork code, passing untrusted input through `env:` then referencing it
(not inline interpolation), splitting privileged vs untrusted jobs, pinning reusable workflows by
SHA, and hardening cache keys.

**Runner & token-scope flaws** — self-hosted runners reachable from fork PRs; non-ephemeral
runners; default/broad `GITHUB_TOKEN` (`contents: write`, `packages: write`, `id-token: write`)
where read-only suffices; over-broad GitLab job tokens; long-lived static cloud creds instead of
OIDC. *Fixes must mention* `permissions:` least-privilege at workflow + job level, ephemeral /
isolated runners, denying fork PRs on self-hosted runners, and OIDC short-lived credentials.

**Secret-exfiltration flaws** — secrets echoed/printed (`echo $X`, `set -x`); secrets exposed to
fork-triggered jobs; secrets written into uploaded artifacts (`.env`, keys, source maps);
secrets in cache; secrets committed to the repo or build context; secrets passed to third-party
actions that could forward them. *Fixes must mention* masking, never echoing, blocking secret
access on untrusted triggers, scanning artifacts before upload, scrubbing build context, and
minimizing which steps see each secret. (Secret *storage/rotation* → defer to
../20-secrets-and-cloud-iam/.)

**Artifact / SBOM / provenance flaws** — no signing (cosign/GPG/npm provenance); no SBOM
(CycloneDX/SPDX); no provenance/attestation (SLSA); published artifacts not verified at
consume/deploy. *Fixes must mention* signing artifacts, generating an SBOM in CI, emitting build
provenance (`actions/attest-build-provenance`), and verifying signature/digest at deploy time.

**Container / base-image flaws** — `FROM` pinned by mutable tag (`:latest`/`:18`) not digest;
untrusted base image source; base with known CVEs; `curl|sh` / unverified tarball pulls in the
Dockerfile; images pushed without signing or pulled without digest. *Fixes must mention*
digest-pinned `FROM @sha256:…`, trusted/minimal base images, checksum-verified downloads, and
signed images verified on pull.

**Release-channel / deploy-trigger flaws** — auto-deploy from forks or unreviewed branches;
missing branch protection / required reviews / required checks on the deploy-source branch;
missing environment protection / required reviewers on the deploy job; mutable tags;
unverified downstream consumption; live shadow/zombie pipelines (API9). *Fixes must mention*
branch protection + required reviews/checks, environment protection rules, immutable tags,
deploy-time signature/digest gates, and retiring dead pipelines/channels.

**Scanning / negative-space gaps** — advisory-only scanning that never blocks merge; suppressed
advisories (`.nsprc`/audit allowlist/`# nosec`); vendored `node_modules`/`vendor/` bypassing
registry integrity; deps in lockfile-but-not-manifest (or vice-versa); scanners that miss
`git`/vendored deps. *Fixes must mention* gating the build on scan results, periodic review of
suppressions, scanning vendored trees, and reconciling manifest vs lockfile. (Inventory from
manifests/lockfiles/workflows — **do not run destructive updates or a live network scan**.)

---

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

Classify each threat with exactly one coverage badge. **Be honest: this domain is LOW–MEDIUM
native coverage.** Almost every row is 🟡 or 🔴 with the **primary fix in the repo/CI/cloud**;
the *only* 🟢-ish native runtime signal is the live-vulnerable-component event.

- 🟢 **COVERED** — detectable + actionable with SecureNow today on telemetry already flowing.
  For this domain that is essentially **only** the live-vulnerable-component runtime signal
  (`api.dependency.vulnerable` → notify-only rule) once the app emits it. Use 🟢 sparingly and
  honestly.
- 🟡 **PARTIAL** — works only after the customer adds instrumentation (`track('api.dependency.vulnerable')`
  at app start), or SecureNow can only *contain an exploit attempt at the edge* (a known-bad CI
  egress IP, an exploit-signature instant-block) while the **real fix is repo/CI/cloud config**.
  Pair the edge control with the repo/CI fix on every such row.
- 🔴 **GAP** — SecureNow cannot detect or mitigate this today (it never reaches SecureNow's
  telemetry — it's in the registry, the lockfile, the workflow, the runner, the registry MITM).
  **Still include it**: give the repo/CI/cloud-config 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. **The majority of this model's rows are 🔴 or 🟡-with-config-primary — say so plainly.**

> **What SecureNow can and cannot see here.** SecureNow sees **runtime traffic** and **events**
> from the running app, and contains actors at the edge (firewall / rate-limit / challenge /
> block / signature instant-block). It does **not** see your registry resolution, your lockfile,
> your GitHub Actions runner, your `postinstall` script, your base-image digest, or your release
> pipeline. So a typosquat, a `pull_request_target` injection, an unsigned artifact, or a
> `:latest` base image are **repo/CI/cloud-config fixes** — SecureNow's only contribution is:
> (a) emitting `api.dependency.vulnerable` when a vulnerable component is **live and serving
> traffic**, as a notify-only signal that the upgrade is overdue; and (b) containing the
> *downstream exploit attempt* if a compromised dependency leads to attacker traffic (exploit
> signatures, floods — which are really the API-security model's job). Pair every row's edge
> control with its primary repo/CI/cloud fix. A flaw that emits no traffic and no event is 🔴
> until the app emits the event in Phase 3's recommended fix.

Use **only** the SecureNow building blocks below. Never invent CLI flags, event names, or SQL
columns — they must be ones the **installed SDK/CLI from Phase 0.5** actually exposes.

> **Ready-to-copy command unit (required for the one rule this domain creates).** The single
> events-based detection becomes a **complete, copyable unit** — never a fragment. Emit, in order:
> (1) the SQL; (2) a line saving it to `rules/<name>.sql`; (3) the full
> `securenow alerts rules create …` command (flags exactly as `securenow alerts rules --help`
> reports them in Phase 0.5); (4) the dry-run test. In Markdown each is its own fenced block so it
> copies cleanly; in the Detection HTML each is wrapped in a `.cmd` with a Copy button. Save the
> rule's SQL to `rules/<name>.sql` so `--sql @rules/<name>.sql` resolves. For this domain there is
> exactly **one** such unit (the `api.dependency.vulnerable` notify-only rule); the downstream
> injection/flood tail **references the pre-existing system signature rules + `instant.block`**
> instead of duplicating SQL, and the repo/CI/cloud-config fix remains the **primary** remediation
> on essentially every row.

### 4a. Instrumentation (the one runtime signal this domain feeds on)

There is **no automatic** supply-chain telemetry — the build and registry are invisible to
SecureNow. The single useful runtime signal is emitted by the **running app** when it detects a
vulnerable component it is actually running. Add `securenow/events` `track()` at app start
(never throws):

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

// A vulnerable / known-CVE component is LIVE in this running service (A06:2021).
// Emit at boot from a dependency-audit/SBOM check the app runs against itself, or from a
// startup probe of critical package versions. Notify-only: the upgrade is the primary fix.
track('api.dependency.vulnerable', {
  ip,                                  // server/instance IP (or omit)
  attributes: {
    package:  'lodash',
    version:  '4.17.4',
    severity: 'critical|high|medium|low',
    surface:  'framework|parser|multipart|graphql|grpc|swagger|xml|yaml|media|runtime',
    advisory: 'CVE-2021-23337',        // optional; hash/omit anything sensitive
  },
});
```

> This is the **same** `api.dependency.vulnerable` event the API-security model uses for runtime
> exposure — **reuse it verbatim, do not invent a new event name.** This model uses it as a
> notify-only "the component shipped to prod is overdue for upgrade" signal; the API-security
> model uses it for the *exploitation-on-traffic* angle. One event, two lenses.
>
> Everything else in this domain (typosquats, lockfile drift, CI injection, runner trust,
> unsigned artifacts, base-image pinning) has **no SecureNow event** and **no SQL** — it is a
> **config-audit finding** with a repo/CI/cloud fix. Do not fabricate events for these.

Hash or omit any sensitive value before it becomes an attribute (internal package paths,
advisory details that reveal infra) — see the Phase 1 telemetry-redaction posture. Attributes
feed detection; they must not become a new leak path.

Recommended event taxonomy for this model — the rule matches this **exact string**:

| Event | Emit when |
|---|---|
| `api.dependency.vulnerable` | a known-vulnerable / outdated component is **live in the running service** (notify-only; install/upgrade is the primary fix) |

Custom `attributes` become queryable as `attributes_string['<key>']` (e.g.
`attributes_string['package']`, `attributes_string['severity']`). Ingest enriches every IP with
**ASN/org** (`client.asn`, `client.as_org`) — useful only for the downstream-exploit/CI-egress
angle, not for the config findings.

### 4b. Detection rules — SQL conventions

Almost all of this model needs **no SQL** — the findings are config-level. There is exactly one
events-based rule (the live-vulnerable-component notify-only rule). Plus, if a compromised
dependency leads to *attacker traffic*, the **system exploit-signature rules** (SQLi/XSS/RCE
with `instant.block`) and the API-security model's traffic rules apply — reference them, do not
re-author pattern SQL here.

Conventions for the one rule we do write: keep the tenant scope and select an `ip` column. **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. For traffic queries (only relevant to the deferred downstream-exploit
angle) keep the `ts_bucket_start` + `kind = 2` guards and the `client_ip` coalesce. Confirm any
column with `--mode dry_run` before relying on it.

**Events-based — live vulnerable component shipped to prod (A06:2021), notify-only.** Emit it as
the ready-to-copy unit: SQL saved to `rules/supply-chain-vuln-component.sql`, then create, then
dry-run.

```sql
-- rules/supply-chain-vuln-component.sql
SELECT
  attributes_string['package']  AS package,
  attributes_string['version']  AS version,
  attributes_string['severity'] AS severity,
  attributes_string['surface']  AS surface,
  attributes_string['http.client_ip'] AS ip,
  count() AS occurrences
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
  AND attributes_string['event.type'] = 'api.dependency.vulnerable'
  AND attributes_string['severity'] IN ('critical','high')
  AND timestamp >= now() - INTERVAL 24 HOUR
GROUP BY package, version, severity, surface, ip
HAVING occurrences >= 1
```

> This rule is **notify-only**: it surfaces "a critical/high component is live and overdue for
> upgrade" — it must **not** auto-block (the abuser here is your own service, not an attacker).
> Route it to the dependency-upgrade runbook. Drop the `severity IN (...)` filter to also track
> medium/low.

**Downstream-exploit traffic (only if a compromised dep is being exploited):** do **not** write
new pattern SQL. Confirm the system **SQLi/XSS/RCE signature rules** are present and enabled via
`securenow alerts rules --json`, and enable `instant.block` on them. For volumetric/fuzzing tails
of a compromise, reuse the API-security model's traffic rules
(→ [../14-api-security/](../14-api-security/)) rather than duplicating them here.

Create + validate the one rule (flags must match `securenow alerts rules --help` from Phase 0.5):

```bash
securenow alerts rules create \
  --name "Supply chain: live vulnerable component (critical/high)" \
  --sql @rules/supply-chain-vuln-component.sql \
  --apps <APP_KEY> \
  --severity high \
  --schedule "0 */6 * * *" \
  --nlp "a critical or high severity vulnerable dependency is live in the running service"

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

Useful attributes/columns: `event.type`, `package`, `version`, `severity`, `surface`,
`advisory`, `http.client_ip`, `client.asn`, `client.as_org`.

#### 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 (flood / scrape /
enumeration counts), broad patterns, anomaly / volume rules, anything tuned to YOUR traffic — must
ship in **`--mode test` first**. Run it detect-only for **3–7 days of real traffic**, review what it
flags, raise/lower the threshold and add `securenow fp` exclusions for legitimate hits, then
`--mode prod` to arm mitigation. Only **high-precision** rules (exploit-signature SQLi/XSS/RCE
matches, exact-match IoCs, 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.)

> For this domain the one runtime rule, `api.dependency.vulnerable`, is **notify-only** and emitted
> by your own service — it can be **`prod-ready`** (it never auto-blocks; promoting to `prod` only
> routes it to the upgrade runbook). The downstream-exploit references to the system signature rules
> are high-precision exploit matches → **`prod-ready`**. Any heuristic/volumetric downstream rule you
> borrow from the API-security model is **`test-first`**: ship it `--mode test`, observe 3–7 days,
> tune, then `--mode prod`.

### 4c. Mitigation commands — the full SecureNow toolbox (select per threat)

For this domain the **repo / CI / cloud-config fix (row 15) is the PRIMARY remediation** on almost
every row. SecureNow's edge controls (rows 1–14) only help with the *downstream-exploit* tail (a
compromised dep producing attacker traffic) or with containing a known-bad CI-egress / scanner IP;
and the one runtime signal, `api.dependency.vulnerable`, stays **notify-only** (it routes to the
upgrade runbook, never to auto-block). Once a threat is confirmed, **choose the narrowest effective
mitigation(s) from ALL of these** and combine them (e.g. rate-limit a route + block the worst IPs +
challenge a NAT egress) — but on most supply-chain rows the only real lever is the config fix. Re-check
every command/flag against the installed SDK in Phase 0.5 (`securenow <cmd> --help`); annotate
`# requires securenow >= <ver>` if absent. Scope by **app / env / route / method / IP / duration**
to avoid hitting real users.

| # | Mitigation | Command (ready-to-copy) | Use / scope |
|---|---|---|---|
| 1 | **Free firewall (network)** | `securenow firewall enable --app <APP_KEY> --env production` · `securenow run --firewall-only` · test `securenow firewall test-ip <ip> --path /x --method GET` | 500k+ known-bad IPs, hourly refresh; drop scanners before the app. No app change. Only relevant to the *downstream-exploit* tail, not the supply-chain root cause. |
| 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 a request when a compromised dependency produces injection-shaped attacker traffic. Don't duplicate pattern SQL. Downstream tail only. |
| 3 | **IP block — global** | `securenow blocklist add <ip> --app <APP_KEY> --env production --reason "..."` | confirmed-malicious source exploiting a compromised dependency, all routes. Downstream tail only. |
| 4 | **IP block — scoped to route (+ method)** | `securenow blocklist add <ip> --route /admin* --mode prefix --method ALL --app <APP_KEY> --env production --reason "..."` (`--mode exact\|prefix\|regex`, `--method GET\|POST\|…\|ALL`) | block an IP only on sensitive paths; least collateral. Downstream tail only. |
| 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 compromise-driven source; audit-preserving unblock. Downstream tail only. |
| 6 | **Rate limit — per IP** | `securenow ratelimit add <ip> --limit 100 --window 1m --duration 24h --reason "..."` | throttle a compromised-dep beacon / exploit flood from one client across the app. Downstream tail only. |
| 7 | **Rate limit — per route (all clients, per-IP budget)** | `securenow ratelimit add --route /api/search --mode prefix --method GET --limit 60 --window 1m --key-by ip` | cap an endpoint being abused via a compromise for everyone, budgeted per IP. Downstream tail only. |
| 8 | **Rate limit — per route + IP** | `securenow ratelimit add <ip> --route /api/login --mode exact --method POST --limit 5 --window 1m --duration 24h` · NL `securenow ratelimit from-text "rate limit /api/login to 5/min for 24h" --yes` · test `securenow ratelimit test <ip> --path /api/login --method POST` | precise throttle of one client on one route. Downstream tail only. |
| 9 | **CAPTCHA / proof-of-work challenge** | `securenow challenge add --route /login --difficulty 16 --clearance 30m` (route-wide) **or** `securenow challenge add <ip> --route /api/search --difficulty 18 --clearance 30m` · test `securenow challenge test <ip> --path /login --method GET` | bot-shaped abuse riding a compromise from **shared / NAT / CGNAT** egress — a human passes once, a script can't. Prefer over a hard block when real users share the IP. Downstream tail only. |
| 10 | **Auto-block (risk-scored)** | `securenow automation defaults --yes` (≥95→7d, 90–94→72h, 85–89→24h) · custom `securenow automation create --conditions '[...]' --actions '[...]'` · preview `securenow automation dry-run <id>` | hands-off blocking of downstream-exploit traffic by risk score; actions include block / rate_limit / requireCaptcha. Downstream tail only. |
| 11 | **Session revocation** | `securenow revoke …` (SDK `securenow/sessions` `guard()` / `isRevoked()`) | if a compromised dependency leads to session theft / account takeover — kill the stolen session, not the IP. Downstream tail only. |
| 12 | **Trusted IP (suppress)** | `securenow trusted add <ip> --label "CI runner / build egress / monitor"` | stop false positives from your own CI runners, internal scanners, partner build jobs — suppresses detection **and** mitigation. NOT deny-by-default. |
| 13 | **Allowlist (deny-by-default)** | `securenow allowlist add <ip> --label "..." --reason "..."` ⚠️ once any entry exists, ONLY listed IPs reach the app | lockdown of an internal/admin-only surface. Never for a public app. |
| 14 | **False-positive exclusion** | `securenow fp create --conditions '[...]' --rule-scope this_rule --reason "..."` · `securenow fp mark <notification-id> <ip> --rule-scope this_rule` · preview `securenow fp dry-run --conditions '[...]'` | keep the vuln-component / downstream rules quiet without weakening them. |
| 15 | **App / config / code fix (PRIMARY for this whole model)** | *manifest / lockfile / workflow / Dockerfile / branch-protection / registry change, described in the Code-Findings report — never auto-applied; never run a destructive dependency update* | the actual fix: pin exact versions + commit & enforce lockfiles, scoped private registry (dependency confusion), `--ignore-scripts` for untrusted installs, pin actions/base-images by SHA/digest, split `pull_request_target` jobs, least-privilege `GITHUB_TOKEN`/OIDC, isolate/ephemeralize runners, sign artifacts + SBOM + provenance, branch/environment protection, retire zombie pipelines. SecureNow contains; the fix removes. |

**Choosing per threat** — for the supply-chain root causes (A1–L66) the SecureNow column is almost
always **"config fix only — row 15, no edge control applies"**; say that explicitly rather than
padding rows with an irrelevant block command. Where a compromise produces attacker traffic, choose
by **confidence**: exploit-signature/exact IoC → instant-block (row 2) or block (rows 3–5); probable
bot on shared egress → **challenge** (row 9); noisy/legit-mixed traffic → **rate-limit (test-mode
first)** (rows 6–8); session compromise → **revoke** (row 11); known-good CI/scanner noise →
**trusted / fp** (rows 12, 14). By **blast radius**: always scope to the narrowest
`route`/`method`/`IP`/`duration` that stops the abuse; on NAT/CGNAT/shared IPs prefer challenge or
rate-limit over a hard block. Always pair an edge mitigation with the **app/config fix (row 15, the
Code-Findings report)** — for this model the config fix is the primary on essentially every row and
SecureNow can only ever *contain* the downstream tail. The `api.dependency.vulnerable` rule is
**notify-only** — it routes to the upgrade runbook, never to auto-block.

### 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 live-vulnerable-component event — exercise the notify-only rule end to end:
securenow event send api.dependency.vulnerable --ip 203.0.113.50 \
  --attrs package=lodash,version=4.17.4,severity=critical,surface=parser,advisory=CVE-2021-23337,test=true

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

# Downstream-exploit tail (only if testing a compromise-produces-traffic scenario):
#   send a request carrying a benign-but-matching marker (e.g. ?q=' OR '1'='1) to a staging URL,
#   then verify the system signature rule + instant block fired:
securenow firewall test-ip 203.0.113.50 --app <APP_KEY> --env production
securenow test-span "threat-model.supply-chain.smoke"
securenow forensics "requests and 4xx/5xx by IP in the last hour" --env production

# Mitigation verification (downstream tail only):
securenow ratelimit test 203.0.113.50 --path /path --method GET
securenow challenge test 203.0.113.50 --path /path --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>
```

> The **config-audit findings (A1–L66) have no SecureNow test** — they are verified by
> re-reading the manifest / workflow / Dockerfile after the described fix is applied by a human.
> Each such row's "test" in the report is: *"re-audit `<file>` — confirm the version is pinned /
> the action is SHA-pinned / the lockfile is enforced / the base image is digest-pinned."* State
> that honestly; do not invent a `securenow` command that cannot exercise a config finding.

Every 🟢/🟡 *runtime* row must have a concrete `securenow` test recipe (commands + expected
outcome). Every config-audit row must have a concrete **re-audit** verification step.

---

## Phase 5 — Write the FOUR reports (two tracks)

Write **two tracks**, each as Markdown + a self-contained HTML twin, into
`threat/22-supply-chain-cicd/`:

- **Track A — Detection & Mitigation** (`supply-chain-cicd-detection-mitigation.md` / `.html`):
  the operational runbook — what to run in SecureNow. For this LOW–MEDIUM domain this is the
  *short* track: the one `api.dependency.vulnerable` notify-only rule, the mitigation layers (with
  **repo/CI/cloud-config fix = primary** on almost every row), and the tests.
- **Track B — Code Findings & Recommendations** (`supply-chain-cicd-code-findings.md` / `.html`):
  the code/config audit — every repo/CI/cloud-config integrity flaw, **described not applied**.
  For this domain this is the *long* track and carries most of the substance.

The two tracks **cross-link**: each detection/instrumentation row that depends on a code change
links to the relevant code finding, and each code finding that a SecureNow control can partially
contain links back to its detection/mitigation 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* supply-chain/CI-CD risks, the resolved installed
   `securenow` version (from Phase 0.5) + app key + firewall state, and **an explicit statement
   that this domain is mostly repo/CI/cloud-config fixes with LOW–MEDIUM SecureNow native
   coverage** (point the reader to the code-findings report for the bulk), plus the one-line
   framework note (A08:2021 / A06:2021 / API9:2023 — owned vs deferred).
2. **SDK & environment** — installed SDK version (from `node_modules/securenow`), app key(s),
   environment, firewall state, existing rules/automations/challenge rules (Phase 0), and any
   system signature rules present (used for the downstream-exploit tail).
3. **Threat → Detection → Mitigation matrix** — one row per modeled threat:
   `# | Threat | Framework | Coverage 🟢/🟡/🔴 | Detection (rule name + test-first/prod-ready tag, or "—") | Signal (event/config-audit finding) | Schedule/window | Sev | Mitigation (repo/CI/cloud fix primary)`.
   Severity ∈ {critical, high, medium, low}. Every row's "Signal" is either the
   `api.dependency.vulnerable` event **or** the explicit text "config-audit finding" naming the
   file **and linking to that finding in the code-findings report**. Each row's **Mitigation cell
   must pick specific, scoped mitigation(s) by number from the §4c 15-row toolbox** (e.g.
   "row 15 config fix (pin `FROM @sha256:…`) + row 2 instant-block on downstream injection"),
   scoped to app/env/route/method/IP/duration — never a generic "block the IP." Each detection
   rule carries a **`test-first` or `prod-ready`** tag (config-audit-only rows that create no rule
   show "—"). Include the deferred rows (68–70) pointing to the sibling models. Follow with the
   "Out of scope" N/A list.
4. **Detection rules to create** — the one `api.dependency.vulnerable` rule as the **ready-to-copy
   unit** (SQL → save to `rules/supply-chain-vuln-component.sql` → full
   `securenow alerts rules create …` → dry-run test). Note that it is **notify-only** and tag it
   `prod-ready` (it never auto-blocks). **Tag every rule `test-first` or `prod-ready`** and, for any
   `test-first` rule, show the `--mode test` → observe (3–7 days) → tune → `--mode prod` promotion
   step inline. Reference (do not duplicate) the **system signature rules + `instant.block`**
   (`prod-ready`, high-precision) and the API-security traffic rules for the downstream-exploit tail
   (any heuristic/volumetric borrow is `test-first`).
5. **Instrumentation the detections need** — only the one `track('api.dependency.vulnerable')`
   call the rule consumes, as a copyable snippet; point to the code-findings report's
   *Instrumentation recommendations* for the exact `file:line` to add it.
6. **Mitigation mechanisms** — render the **full §4c 15-row toolbox table** verbatim (1 firewall ·
   2 exploit-signature instant-block · 3–5 IP block [global / route+method / temporary] · 6–8
   rate-limit [per-IP / per-route / per-route+IP] · 9 challenge · 10 auto-block · 11 session
   revocation · 12 trusted · 13 allowlist · 14 fp · **15 app/config fix = PRIMARY for this model**)
   + the "Choosing per threat" paragraph + per-threat ready-to-copy mitigation command +
   reversibility. Make explicit that the config fix (row 15) is primary on essentially every row and
   the SecureNow edge controls (rows 1–14) only address the downstream-exploit tail.
7. **Action plan (copy-paste, ordered)** — ① firewall + signature instant-block for the
   downstream tail (`prod-ready`), ② add the `api.dependency.vulnerable` instrumentation, ③ create
   the detection rules — **any false-positive-prone (heuristic/volumetric) rule is created in
   `--mode test`** with an explicit **"promote to `--mode prod` after N days (3–7)"** step once it is
   tuned; high-precision rules (the notify-only vuln-component rule, signature matches) go straight
   to `prod-ready`, ④ enable automations/challenge where the downstream tail warrants, ⑤ test,
   ⑥ verify in dashboard, ⑦ promote the tuned `test-first` rules `--mode test` → `--mode prod`, then
   schedule the repo/CI/cloud-config fixes (ranked by severity, from the code-findings report). Real
   commands only; `<APP_KEY>` substituted; config fixes referenced, never applied here.
8. **Testing & validation** — per-rule recipe (4d): `securenow event send …` / dry-run + expected
   outcome + cleanup (TEST-NET IPs 192.0.2/198.51.100/203.0.113). State that the config-audit rows
   have **no SecureNow test** — their verification is a **re-audit** of the file, owned by the
   code-findings report.
9. **Response runbooks** — per notification type: confirm TP → response command (copy) → reverse
   command (copy). For the notify-only rule: confirm + plan the upgrade (route to the code-findings
   fix), never auto-block.
10. **Known gaps & SecureNow feature requests** — every 🔴: why it's not coverable today (it never
    reaches SecureNow's telemetry), the interim repo/CI/cloud-config fix (**link to the code
    report**), and the "contact the SecureNow team" line. This is large for this model.
11. **Appendix** — resolved SDK/CLI version, app key, environment, rule ID created, firewall
    state, date, and a link to the code-findings report.

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

State at top: *"Findings only — no application code, manifest, lockfile, workflow, or Dockerfile
was modified, and no dependency update was run."*

1. **Executive summary** — config findings by severity (critical/high/med/low), top 3 code/config
   risks for this specific stack, one-paragraph posture verdict, and a pointer to the
   detection-mitigation report for what SecureNow can actually run.
2. **Surface & inventory** — the Phase 1 inventory: ecosystem + lockfile table, unpinned-version
   list, registry/source config, install-script inventory, CI/CD workflow catalog (file /
   triggers / runner / token scope), untrusted-input→`run:` sink list, action/image pinning
   table, secrets-in-pipeline posture, artifact/SBOM/provenance status,
   release-channel/deploy-trigger map, mutable-fetch list, existing-scanning status.
3. **Threat catalog** — the exhaustive Phase 2 catalog (groups A–N), each tagged
   **A08:2021 / A06:2021 / API9:2023 / "—"**, modeled or explicit N/A. Drop nothing.
4. **Code-level findings (audit — not applied)** — table
   `# | Location (file:line) | Threat | Framework | Sev | Issue | Recommended fix`, each with the
   quoted 1–8 line manifest/workflow/Dockerfile snippet and the described fix (**never applied,
   never a dependency update run**).
5. **Strengths** — controls already present and correct (lockfile committed + `npm ci` enforced;
   actions SHA-pinned; base images digest-pinned; OIDC in use; signing + SBOM + provenance
   present) — honest posture.
6. **App / config fixes (primary remediation)** — the repo/CI/cloud-config changes that remove the
   root cause (pin + commit/enforce lockfiles, scoped private registry, `--ignore-scripts`, SHA-pin
   actions, digest-pin base images, split `pull_request_target`, least-privilege token/OIDC,
   isolate/ephemeralize runners, sign + SBOM + provenance, branch/environment protection, retire
   zombie pipelines), described not applied, each **linked to the detection-report row it backs**.
7. **Instrumentation recommendations** — the one `track('api.dependency.vulnerable')` call to add
   and the exact `file:line` to add it (app boot / startup dependency-audit probe), so the
   notify-only detection rule lights up. Link to the detection report's rule.
8. **Appendix** — files reviewed (manifests/lockfiles/workflows/Dockerfiles), resolved SDK
   version, CI platform(s), ecosystems/lockfiles audited, 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 **same `<head>`** (brand tokens + copy-button styles) and the copy
`<script>` at the end of `<body>`. Change only the `<title>`, the sidebar subtitle, the header,
and the section content (one body per track). **Wrap EVERY command/SQL block as a `.cmd`** so it
gets a Copy button (in the Detection HTML especially; the Code-Findings HTML may omit Copy on
prose but still wraps any example/fix command in `.cmd`). Stats numbers must equal the
matrix/findings row counts.

#### Shared skeleton (both tracks)

```html
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<title><!-- "Detection & Mitigation — Supply Chain & CI/CD — SecureNow" OR "Code Findings — Supply Chain & CI/CD — 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 · Supply Chain & CI/CD" OR "Code Findings · Supply Chain & CI/CD" --></div>
    <!-- one <a href="#…"> per section of THIS track -->
  </nav>
  <main>
    <header class="top"><h1><!-- report title --></h1>
      <p><code><!-- app name / repo --></code> · <span class="pill">securenow <!-- installed version --></span> · <span class="pill"><!-- ecosystems + CI, e.g. npm + pip · GitHub Actions · Docker --></span></p></header>
    <div class="stats"><!-- 5 .stat cards; numbers MUST equal the table/finding counts of THIS track --></div>
    <!-- <section id="…"> blocks mirroring the Markdown sections of THIS track (5a or 5b) -->
    <footer>Generated by the SecureNow supply chain &amp; CI/CD 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 A body — Detection & Mitigation (`supply-chain-cicd-detection-mitigation.html`)

Title `Detection & Mitigation — Supply Chain & CI/CD — SecureNow`; sidebar subtitle
`Detection & Mitigation · Supply Chain & CI/CD`; `<h1>` `Supply Chain &amp; CI/CD — Detection &amp; Mitigation`.
Five stat cards: **threats modeled · covered · partial · gaps — SecureNow team · rules to create**.
`<section>` blocks mirror 5a (1–11). **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 "Supply chain: live vulnerable component (critical/high)" \
  --sql @rules/supply-chain-vuln-component.sql --apps &lt;APP_KEY&gt; --severity high \
  --schedule "0 */6 * * *" --nlp "..."</pre></div>
```

#### Track B body — Code Findings (`supply-chain-cicd-code-findings.html`)

Title `Code Findings — Supply Chain & CI/CD — SecureNow`; sidebar subtitle
`Code Findings · Supply Chain & CI/CD`; `<h1>` `Supply Chain &amp; CI/CD — Code Findings &amp; Recommendations`.
Five stat cards: **critical · high · medium · low · total findings** (numbers equal the
findings-table row counts). `<section>` blocks mirror 5b (1–8). Prose may omit Copy buttons, but
any example/fix command is still wrapped in `.cmd`.

#### Badge usage (both tracks)

Severity → `<span class="b crit|high|med|low">`; coverage → `<span class="c cov|part|gap">COVERED|PARTIAL|GAP</span>`;
framework tag → `<span class="owasp">A08:2021</span>` / `<span class="owasp">A06:2021</span>` /
`<span class="owasp">API9</span>` (CWE if used → `<span class="cwe">CWE-…</span>`); mitigation type →
`<span class="m appfix|firewall|signature|rate|challenge|block|notify">` (use `appfix` for the
repo/CI/cloud-config fix — the primary on almost every row); rule ID → `<span class="rid">`. 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 framework tag (**A08:2021** / **A06:2021** / **API9:2023** / "—").
- Coverage badges are **honest about LOW–MEDIUM native coverage**: the only 🟢-ish native
  runtime signal is `api.dependency.vulnerable` (notify-only); the bulk of rows are 🟡 (config
  primary, optional edge containment for the downstream tail) or 🔴 (config fix only,
  invisible to SecureNow). No row claims SecureNow detects a registry/lockfile/runner condition
  it cannot see.
- Every matrix row's "Signal" is a **concrete** signal: the `api.dependency.vulnerable` event,
  **or** the explicit text "config-audit finding" naming the file — never "monitor for
  suspicious activity" filler. Every row has a severity and a fix.
- Every modeled row pairs the **repo/CI/cloud-config fix as PRIMARY** with any edge containment;
  no row offers an edge control as if it fixed the supply-chain root cause.
- Every config finding in section 4 has a `file:line`, the quoted manifest/workflow/Dockerfile
  snippet, and a described fix — and **no application code, manifest, lockfile, workflow, or
  Dockerfile was modified, and no dependency update was run** (this is an audit).
- The single detection SQL keeps `__USER_APP_KEYS__` scoping with the correct table column
  (`resources_string['service.name']` for the logs/events table) and selects an `ip` column; it
  is marked **notify-only**.
- Injection / downstream-exploit coverage references the **system signature rules + `instant.block`**
  and the API-security traffic rules — it does **not** re-author pattern SQL.
- Only commands, flags, events, and SQL columns from this prompt's building blocks appear
  (including `securenow challenge …`, `firewall`, `trusted`, and the single
  `api.dependency.vulnerable` event — no invented supply-chain events).
- The three sibling models are **deferred** (rows present, linked by numbered path,
  not re-derived): ../20-secrets-and-cloud-iam/ (CI secret storage), ../14-api-security/ (runtime
  dependency-CVE exposure on traffic), ../05-client-side-supply-chain/ (browser-side deps /
  Magecart).
- Every 🔴 gap appears in the gaps section with an interim repo/CI/cloud-config fix **and** the
  "contact the SecureNow team" line.
- The action plan runs top-to-bottom with `<APP_KEY>` substituted in; config fixes are described,
  never applied.
- Config-audit rows have a concrete **re-audit** verification step (not a fake `securenow`
  command); runtime rows have a real `securenow` test recipe.
- **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>`).
- The one detection rule is a **complete copyable unit** (SQL → `rules/supply-chain-vuln-component.sql`
  → full `securenow alerts rules create …` → dry-run test); flags match `alerts rules --help`.
- **Four** files are written to `threat/22-supply-chain-cicd/` (`supply-chain-cicd-detection-mitigation`
  .md + .html, `supply-chain-cicd-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** (each wrapped in `.cmd`).
- The split is honest: SecureNow-runnable detections/mitigations live in the Detection report;
  repo/CI/cloud-config changes live in the Code-Findings report; nothing security-relevant is
  dropped, and the Code-Findings report carries the bulk for this LOW–MEDIUM domain.
- Each HTML's stats cards match its own track's counts (Detection: threats/covered/partial/gaps/
  rules; Code-Findings: critical/high/med/low/total findings).
- 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, framework coverage, and the resolved
  SDK version.
- The Detection report's mitigation section presents the **full toolbox** (§4c: firewall ·
  instant-block · block [global / route / method / temporary] · rate-limit [IP / route / IP+route] ·
  challenge · auto-block · revoke · trusted · allowlist · fp · **app/config fix = primary**), and
  **each modeled threat's matrix row selects specific, scoped mitigation(s) from it by number** —
  never a generic "block the IP." For the supply-chain root causes the selection is "row 15 config
  fix only" on almost every row; edge controls appear only on the downstream-exploit tail.
- **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 (the notify-only
  `api.dependency.vulnerable` rule, exploit-signature matches, exact IoCs) 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 ════════════════ -->