#06A03 · CWE-89/78/94
Injection (SQLi / RCE / SSTI)
SQL/NoSQL injection, command injection, SSTI, and path traversal — server-side sinks.
How to use this prompt
- 1Install SecureNow in your project (then optionally
npx securenow login):$ npm install securenow - 2Copy the prompt below and paste it into your AI coding agent (Claude Code, Cursor, Codex…) opened at the root of your project.
- 3It generates four files into
threat/06-injection/— openinjection-code-findings.html(the audit) andinjection-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
# Injection (Server-Side) 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 every place untrusted input
reaches a server-side interpreter/sink, build an exhaustive **server-side injection** threat
model mapped to **OWASP A03:2021 Injection** and the relevant **CWEs**, audit the code for
injection flaws, and emit **four** SecureNow-branded deliverables across **two tracks**
(Detection & Mitigation + Code Findings & Recommendations, each as **Markdown + self-contained
HTML** with offline **copy buttons**) — every rule and command **grounded in the installed SDK**
and shipped as a **ready-to-copy unit** — including the system signature rules to confirm/enable,
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 is the **server-side injection** deep model — the classic interpreter-sink exploit class
that the [API security model](../14-api-security/api-security-threat-model-prompt.md) touches
as a single catalog (its catalog **C**). It owns **every server-side sink**: SQL, NoSQL,
OS/command, code/`eval`, deserialization, template (SSTI), LDAP/XPath/XQuery, path
traversal/LFI, header/CRLF/log injection, ORM/query-builder raw fragments, XXE, and search-DSL
injection — plus the encoding/normalization filter-bypass tricks that defeat naïve input
filters. It **defers** (does not re-derive): **client-side** XSS (reflected/stored/DOM in the
browser) → [../04-xss-csrf-cors/](../04-xss-csrf-cors/); **GraphQL** injection/query abuse →
[../15-graphql-security/](../15-graphql-security/); **LLM prompt** injection →
[../25-ai-llm-features/](../25-ai-llm-features/); and the broader API surface (resource
consumption, SSRF, misconfig, inventory) → [../14-api-security/](../14-api-security/).
> **This is SecureNow's strongest play.** Payload-borne injection (SQLi / XSS / RCE patterns)
> is covered by **system signature rules** that ship with synchronous **`instant.block`** —
> they block a matching request **in ~2.6s on ingest**, before it reaches your handler, with
> **no instrumentation and no rule authoring**. Your job here is to **confirm those signatures
> are present and enabled** for this app, classify what is signature-covered vs what needs an
> **app fix** (parameterization, allow-listing, safe APIs), and **pair edge-containment with
> the app fix** — the signature stops the payload that matches; only parameterized/safe-API code
> closes the sink. SecureNow does not "see" a string-built query or an `exec()` call; it sees
> the **request** carrying the payload.
Requirements on the customer machine: `npm i -g securenow && securenow login` (admin auth +
app runtime connected). Everything else is discovered by the agent.
---
<!-- ════════════════ COPY EVERYTHING BELOW THIS LINE ════════════════ -->
# Generate a Server-Side Injection Threat Model Report (SecureNow)
You are a senior application-security engineer specializing in injection vulnerabilities.
Produce an **exhaustive server-side injection threat model for THIS codebase**, organized along
**OWASP A03:2021 Injection** and mapped to the relevant **CWEs**
(**CWE-89** SQLi, **CWE-78** OS command, **CWE-94** code injection, **CWE-79** XSS,
**CWE-90** LDAP, **CWE-643** XPath, **CWE-22** path traversal, **CWE-611** XXE,
**CWE-502** deserialization, **CWE-117** log injection), mapped to **SecureNow** detections and
mitigations, with a ready-to-run action plan **and** a code-level audit of every injection sink
you find. You write **four** deliverables across **two tracks** into `threat/06-injection/`
(create the folder if needed) — every rule and command **grounded in the SDK actually installed
in this repo** (Phase 0.5) and shipped as a **ready-to-copy unit** (Phase 4):
**Track 1 — Detection & Mitigation (the operational runbook: what to run in SecureNow):**
1. `injection-detection-mitigation.md` — the detection/mitigation runbook in Markdown.
2. `injection-detection-mitigation.html` — the same, as a **self-contained** HTML page (inline
CSS + JS, no network requests) with a **Copy button** on every command/SQL block.
**Track 2 — Code Findings & Recommendations (the code audit: issues + recommendations):**
3. `injection-code-findings.md` — the code audit in Markdown (findings only, **never** applied).
4. `injection-code-findings.html` — the same, as a **self-contained** HTML page.
The two tracks **cross-link** each other: the gaps/instrumentation rows in the detection report
link to the relevant code finding, and each code finding links back to the detection row it backs.
Work in the phases below, in order. **Never invent facts**: if something is not in the
codebase or not returned by a CLI command, say "not found" — do not guess. **Do not modify
application code.** You are auditing: every code-level fix is *described in the report*, never
applied to the repo.
**Scope discipline.** This model owns **server-side interpreter sinks** (SQL, NoSQL, OS/command,
code/`eval`, deserialization, SSTI, LDAP/XPath/XQuery, path traversal/LFI, header/CRLF/log
injection, ORM raw fragments, XXE, search-DSL) and the encoding/normalization bypasses that
defeat input filters. For the following, do **not** re-derive the deep model — list them in a
"Deferred to sibling models" subsection, link the report, and only model their
server-side/traffic-observable overlap where SecureNow adds value:
- **Client-side XSS** (reflected/stored/DOM executing in the browser) → [client-side XSS / CSRF / CORS model](../04-xss-csrf-cors/). *(Server-side **stored**-payload sinks and reflected-payload signature detection are in-scope here; the DOM-execution model is there.)*
- **GraphQL injection & query abuse** → [GraphQL security model](../15-graphql-security/).
- **LLM prompt injection** → [AI / LLM features model](../25-ai-llm-features/).
- **SSRF, resource consumption, misconfig, API inventory** → [API security model](../14-api-security/) (parent; injection is its catalog C — here it is the whole model).
---
## 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 (CRITICAL: find the system SQLi/XSS/RCE 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.
**The most important Phase 0 output for this model:** from `securenow alerts rules --json`,
identify the **system signature / exploit rules** (SQLi / XSS / RCE pattern matchers) and record
for each: its rule ID/name, whether it is **enabled** for this app, and whether
**`instant.block`** is armed on it. These signatures are the backbone of injection coverage —
**do not author duplicate pattern SQL for them.** Note the **firewall state** too (the free
firewall tier drops known-bad sources before the app sees them). If the signature rules are
present but **disabled** or **not instant-blocking**, that is the single highest-leverage action
in the whole report — call it out 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.
---
## Phase 1 — Inventory the injection surface (codebase analysis)
Injection defense starts with a **sink inventory**: enumerate every place untrusted input
reaches an interpreter, and for each sink record whether the input is **parameterized / escaped /
allow-listed** (safe) or **concatenated / interpolated / passed raw** (a finding). Document what
is **actually in the code**, not what is intended. Cover at minimum:
- **Untrusted input sources** — request body/query/params/headers, cookies, path segments,
uploaded file names/contents, webhook payloads, message-queue/job payloads, imported
CSV/XML/JSON, third-party/upstream API responses (second-order source), and DB-stored values
re-used in a later query (**second-order injection**). Note every source that flows to a sink.
- **SQL sinks (CWE-89)** — raw driver queries, string-concatenated/template-literal SQL,
dynamic `ORDER BY` / `LIMIT` / column / table names (not parameterizable — must be
allow-listed), `IN (...)` list building, `LIKE` with user wildcards, stored
procedures called with concatenation, and **second-order** SQL (value stored then later
concatenated). Record the driver/ORM and whether placeholders (`?`, `$1`, `:name`) are used.
- **NoSQL / operator-injection sinks** — MongoDB/Mongoose/`$where`/`$expr`, query objects built
directly from `req.body` so an attacker can smuggle operators (`{ "$gt": "" }`, `{ "$ne": null }`,
`{ "$where": "..." }`), JSON-operator smuggling through body parsers that keep object values,
Redis/CouchDB/Elasticsearch DSL built from input.
- **OS / command injection sinks (CWE-78, RCE)** — `child_process.exec`/`execSync`, shell
`spawn`/`system`, backticks, `os.system`/`subprocess` with `shell=True`, `Runtime.exec`,
`popen`, ImageMagick/ffmpeg/git/tar shell-outs, and any command string built from input.
- **Code injection sinks (CWE-94)** — `eval`, `Function(...)`, `vm.runIn*`, `setTimeout(string)`,
dynamic `require()`/`import()` with a user-influenced specifier, `pickle`/`marshal`, dynamic
class/method dispatch from input.
- **Insecure deserialization sinks (CWE-502)** — `node-serialize`, Java `ObjectInputStream`,
PHP `unserialize`, Python `pickle`/`yaml.load` (unsafe), .NET `BinaryFormatter`/`JSON.NET`
`TypeNameHandling`, polymorphic/typed JSON, any gadget-chain-reachable deserializer fed
attacker bytes.
- **Server-side template injection sinks (SSTI)** — user input compiled **as a template** (not
just passed as data) in Jinja2/Django, Handlebars/Mustache, Twig, EJS/Pug, Freemarker/Velocity,
ERB/Liquid, or string-built template sources. Distinguish *data into a template* (safe) from
*input becoming the template* (SSTI → often RCE).
- **LDAP / XPath / XQuery sinks (CWE-90, CWE-643)** — directory search filters, XPath/XQuery
expressions built by concatenating input.
- **Path traversal / LFI sinks (CWE-22)** — `fs.readFile`/`sendFile`/`require`/`include`/static
serving with a path built from input; `../`, encoded (`%2e%2e`, `..%2f`, double-encoded),
absolute paths, null-byte, and Windows `..\` / UNC. Note archive extraction (zip-slip) as a
path-traversal sink.
- **Header / CRLF / response-splitting / open-path sinks** — `res.setHeader`/redirect
`Location`/`Set-Cookie` built from input (CRLF → response splitting / header injection).
- **Log injection sinks (CWE-117)** — user input written to logs without newline/CRLF
neutralization (log forging, log-pipeline injection, downstream SIEM poisoning). Especially
dangerous when logs are parsed by another system.
- **ORM / query-builder raw fragments** — Sequelize `literal`/`where: { [Op]: sequelize.literal(...) }`,
TypeORM `query()`/raw `where`, Knex `whereRaw`/`raw`, Prisma `$queryRawUnsafe`,
Django `.extra()`/`.raw()`, Hibernate HQL string-built, ActiveRecord string `where`. Any raw
SQL fragment fed input.
- **XXE / entity-expansion sinks (CWE-611)** — XML parsers (libxml2, DOM/SAX, `lxml`, JAXP,
.NET `XmlReader`) with external-entity resolution / DTD processing enabled; SVG/Office/SOAP
payloads; billion-laughs entity expansion.
- **Search-DSL sinks** — Elasticsearch/OpenSearch/Lucene/Solr query strings, SQL-like search
DSLs, filter-expression languages built from input.
- **Input normalization & filter layer** — any homegrown blacklist/regex "sanitizer", WAF-style
filter, decode order, charset handling. Note where the same input is decoded/normalized
**after** the filter check (double-decode bypass), or where mixed case / unicode / overlong
UTF-8 / null bytes defeat the filter.
- **SecureNow instrumentation already present** — `securenow/register` / `securenow run` /
`securenow init` (gives traffic spans automatically so the signatures can match), any
`securenow/events` `track()` calls, and whether the firewall + signature rules are engaged.
- **Telemetry privacy & redaction** — confirm the SDK/log pipeline redacts secrets, tokens,
and PII before ingestion. **Injection-specific:** ensure raw injection payloads captured as
attributes do not themselves leak data (a successful injection's *response* must not be logged
verbatim). If redaction is missing, raise a high-severity finding.
Output of this phase = the report's **Injection surface & sink inventory** section: a
**sink table** (`Sink | File:line | Interpreter | Input source | Parameterized? | 2nd-order? | Risk`),
the input-source → sink flow map, the normalization/filter posture, the redaction status, and a
short paragraph naming the real injection attack surface for this stack (which interpreters are
in play, which sinks are concatenation-built, what an attacker reaches if one falls).
---
## 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. "LDAP items: N/A, no directory backend"). Never silently drop an item.
Add stack-specific sinks you discover that are not listed — this catalog is the **floor**, not
the ceiling. Tag each modeled row with **A03:2021** and its specific **CWE**.
**A. SQL injection (CWE-89)**
1. Classic SQLi via string-concatenated / template-literal query from request input
2. Dynamic `ORDER BY` / `GROUP BY` / column / table name from input (not parameterizable — needs allow-list)
3. Dynamic `LIMIT` / `OFFSET` / pagination value injected into the query
4. `IN (...)` list / bulk-id query built by joining input values
5. `LIKE` clause with attacker-controlled wildcards / escape bypass
6. Second-order SQLi — value stored safely, later concatenated into a query
7. Blind / boolean-based SQLi (response-difference oracle)
8. Time-based blind SQLi (`SLEEP`/`pg_sleep`/`WAITFOR` — response-timing oracle)
9. Stacked queries / batch statement injection (`;` second statement)
10. Stored-procedure / function call built by concatenation
**B. NoSQL / operator injection (CWE-89-class)**
11. Mongo operator smuggling via body — `{ "$gt": "" }`, `{ "$ne": null }` to bypass auth/filter
12. `$where` / `$expr` / `$function` server-side JS injection (RCE-class on the DB)
13. JSON operator smuggling through a body parser that preserves object/array values
14. Query built directly from `req.body` (whole-object trust → operator injection)
15. Other document/KV store DSL injection (CouchDB Mango, Redis, Elasticsearch query body)
**C. OS / command injection — RCE (CWE-78)**
16. Shell command string concatenated from input (`exec`/`execSync`/`system`/backticks)
17. Argument injection / flag injection into an otherwise-safe spawned binary (`--`-bypass)
18. `shell=True` / shell metacharacters reaching the shell (`;`, `|`, `$()`, `` ` ``, `&&`)
19. Shell-out to image/media/git/tar/pdf tooling with input in the command line
**D. Code injection & deserialization (CWE-94, CWE-502)**
20. `eval` / `Function(...)` / `vm.runIn*` / `setTimeout(string)` on input
21. Dynamic `require()` / `import()` / module-path from input
22. Insecure deserialization — gadget chain via `unserialize`/`pickle`/`ObjectInputStream`/`node-serialize`
23. Polymorphic / typed JSON deserialization (`TypeNameHandling`, `yaml.load` unsafe, typed binders)
**E. Server-side template injection (SSTI)**
24. User input compiled as a template (Jinja2/Twig/Freemarker/Velocity → RCE)
25. User input as a template in a JS engine (Handlebars/EJS/Pug/Mustache helpers)
26. Sandbox-escape / context-object access from a partially-trusted template
**F. LDAP / XPath / XQuery injection (CWE-90, CWE-643)**
27. LDAP search-filter injection (`*`, `)(uid=*`, filter break-out)
28. XPath / XQuery injection (authentication/data-extraction filter break-out)
**G. Path traversal / LFI (CWE-22)**
29. `../` / encoded / double-encoded path traversal into file read/serve
30. Absolute-path / drive-letter / UNC injection into a path-from-input
31. Null-byte / extension-confusion traversal (`%00`, `file.php%00.png`)
32. Archive extraction path traversal (zip-slip — `../` inside an uploaded archive)
33. Local file inclusion / dynamic include from a user-influenced path
**H. Header / CRLF / log injection (CWE-117 + response splitting)**
34. CRLF header injection / HTTP response splitting via a reflected header/redirect value
35. `Set-Cookie` / `Location` / cache-key injection from input
36. Log forging / log injection — CRLF or control chars into log lines (CWE-117)
37. Log-pipeline / SIEM injection — crafted log line that downstream parsers mis-handle
**I. ORM / query-builder injection**
38. Raw fragment helpers fed input (`whereRaw`, `$queryRawUnsafe`, `.raw()`, `literal`, `.extra()`)
39. ORM mass-assignment into query filters / sort that reaches raw SQL
40. HQL / JPQL / ActiveRecord string-`where` injection
**J. XML / entity & DSL injection (CWE-611)**
41. XXE — external entity → file read / SSRF / OOB exfiltration
42. Entity-expansion / billion-laughs DoS
43. Search-DSL injection (Elasticsearch/Lucene/Solr/OpenSearch query-string built from input)
44. XInclude / DTD parameter-entity OOB
**K. Encoding, normalization & filter-bypass (negative-space / evasion)**
45. Double / triple URL-encoding bypass of a naïve filter (`%252e`, `%2527`)
46. Unicode / overlong-UTF-8 / homoglyph / full-width bypass of a keyword blacklist
47. Mixed-case / comment-insertion SQL evasion (`SeLeCt`, `/**/`, `--`)
48. Decode-after-filter order bug (input checked, then decoded into the payload)
49. Null-byte / control-char / whitespace-variant evasion of a sanitizer
**L. Observable abuse (what telemetry + signatures actually catch — the workhorse coverage)**
50. Exploit-signature match in a request (SQLi / XSS / RCE patterns) → **system signature rule + `instant.block`**
51. Injection-fuzzing burst — one IP firing many payload variants → 4xx/5xx + distinct-path spike
52. Time-based-blind probing — repeated slow/erroring requests to one endpoint (5xx / latency onset)
53. App-emitted "input rejected by my own validator/parameterizer" signal (high-signal, needs instrumentation)
54. Second-order detection — app emits when a stored value fails a later safe-query gate
**M. Deferred — modeled in sibling models (reference, do not re-derive)**
55. Client-side reflected / stored / DOM **XSS execution in the browser** → [../04-xss-csrf-cors/](../04-xss-csrf-cors/) *(server-side payload signature detection stays here; browser execution is there)*
56. **GraphQL** injection / query-language abuse → [../15-graphql-security/](../15-graphql-security/)
57. **LLM prompt injection** → [../25-ai-llm-features/](../25-ai-llm-features/)
58. **SSRF** (incl. XXE-driven SSRF mitigation), API resource consumption, misconfig & inventory → [../14-api-security/](../14-api-security/)
> For 55–58, add **one** matrix row each marked *"deferred — see linked model"*, and only note
> the SecureNow-observable overlap (e.g. for XSS: the **reflected payload signature** fires here
> on ingest; the browser-execution model lives in 04). The full detection/mitigation lives in
> the linked report. Do not duplicate their catalogs.
> **Coverage-badge honesty (read before tagging).** Use 🟢/🟡/🔴 strictly:
> - 🟢 **COVERED** — the **system signature rule + `instant.block`** matches this payload class
> on ingest **and** there is a clear path to closing the sink. Catalog **A/B-partial/C** SQLi
> and RCE pattern-bearing payloads, and reflected-XSS-shaped payloads (L50), land here for the
> *edge containment* half. **Always pair with the app fix** — the signature blocks the matching
> payload; only parameterization/safe-API closes the sink against novel/encoded payloads.
> - 🟡 **PARTIAL** — signature coverage is incomplete or evasion-prone (encoded/blind/second-order
> payloads slip a pattern matcher), **or** detection needs app instrumentation (`track()` the
> rejection), **or** SecureNow can only contain the abusing IP while the **app fix is primary**.
> Blind/time-based SQLi, NoSQL operator smuggling, SSTI, deserialization, path traversal,
> header/log injection, and all filter-bypass items are typically 🟡.
> - 🔴 **GAP** — SecureNow cannot detect or mitigate this today (e.g. a second-order sink that
> emits no distinctive request and no event, a DB-internal `$where` JS execution). **Still
> include it**, give the app fix, and add the *"contact the SecureNow team"* line.
---
## Phase 3 — Audit the code (findings only — do not fix)
For **each** modeled threat that maps to a real sink, 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/repository/DAO/resolver name and
the **sink** (e.g. `db.query`, `child_process.exec`, `fs.readFile`, `template.render`).
- **Pattern** — quote the 1–8 relevant lines. State the missing control precisely (e.g.
"`db.query(\`SELECT * FROM u WHERE id=${req.params.id}\`)` — string-built SQL, no placeholder";
"`User.find(req.body)` — whole-body trust → Mongo operator injection";
"`exec('convert ' + req.file.path)` — input in a shell string → RCE";
"`ejs.render(req.body.tpl)` — input compiled as a template → SSTI";
"`fs.readFile(path.join(dir, req.query.name))` — no traversal guard").
- **Why exploitable** — the concrete request an attacker sends and what they achieve
(auth bypass, data exfiltration, RCE, file read, log poisoning) — include a sample payload.
- **Severity** — critical / high / medium / low (impact × reachability; RCE/SQLi data-exfil = critical).
- **Recommended fix (described, not applied)** — the **app fix** is always primary:
- SQL → **parameterized queries / prepared statements / placeholders**; for dynamic
`ORDER BY`/column/table use an **allow-list mapping**, never interpolation.
- NoSQL → reject/cast operator objects, validate that filter values are scalars, use schema
validation, disable `$where`/server-side JS.
- OS command → avoid the shell; use `execFile`/`spawn` with an **argument array** (no shell),
allow-list the binary and args, never interpolate.
- Code/eval → remove `eval`/dynamic-`require`; use a safe parser/dispatch table.
- Deserialization → use safe formats (JSON without type binding), disable polymorphic typing,
`yaml.safeLoad`, never deserialize attacker bytes into typed objects.
- SSTI → never compile user input as a template; pass input as **data**; sandbox/limit the engine.
- LDAP/XPath → use parameterized/escaped APIs, allow-list filter inputs.
- Path traversal → resolve + `realpath` and assert the result stays under a base dir;
allow-list filenames; reject `..`, encoded variants, absolute/UNC; safe archive extraction.
- Header/CRLF/log → strip/encode CR/LF; use framework header APIs; encode log fields (CWE-117).
- ORM raw → replace raw fragments with bound parameters / the query builder's safe API.
- XXE → disable external entities + DTD (`noent=false`, `FEATURE_SECURE_PROCESSING`, etc.).
- Filter-bypass → **don't rely on blacklists**; canonicalize/decode once **before** validation,
validate against an allow-list, and back it with the safe sink API (defense in depth).
Reference the secure pattern, not a code diff. **You must not edit the codebase.**
If a sink is **safe** (parameterized, `execFile` with args array, allow-listed path, entities
disabled), note it as a **strength** — the posture must be honest. A concatenation-built sink
where input reaches the interpreter is itself a finding even if "no known payload works today".
For L53/L54 (app-emitted signals), the *recommended fix* is to **add the `track()` call** at the
rejection point (see Phase 4a) so the otherwise-blind sink becomes detectable.
---
## Phase 4 — Map every modeled threat to SecureNow detection + mitigation
Classify each threat with exactly one coverage badge (🟢 / 🟡 / 🔴 — per the honesty rules in
Phase 2). The defining fact of this model:
> **Payload-borne injection is covered by SecureNow's system signature rules with synchronous
> `instant.block`.** SQLi / XSS / RCE pattern matches block the request **in ~2.6s on ingest**,
> before it reaches the handler — **no instrumentation, no rule authoring.** Your job is to
> **confirm + enable** those signatures (Phase 0) and **not** write duplicate pattern SQL.
> Everything a signature can't see — **blind/time-based** SQLi, **second-order** payloads,
> **NoSQL operator** smuggling, **SSTI**, **deserialization gadgets**, **encoded/normalized
> evasions** — is where you add app instrumentation (🟡) or accept a gap (🔴), and where the
> **app fix is the real remediation**.
Use **only** the SecureNow building blocks below. Never invent CLI flags, event names, or SQL
columns — and use **only** flags the installed SDK exposes (Phase 0.5: `securenow alerts rules
--help`).
> **Ready-to-copy command unit (required for every custom rule).** Each *new* detection becomes a
> **complete, copyable unit** — never a fragment. For each rule emit, in order: (1) the **SQL**,
> (2) a line **saving it to `rules/<name>.sql`** (e.g. the `-- rules/<name>.sql` header comment so
> `--sql @rules/<name>.sql` resolves), (3) the **full `securenow alerts rules create …` command**,
> (4) the **dry-run test** (`securenow alerts rules test <RULE_ID> --mode dry_run --wait`). In
> Markdown each is its own fenced block so it copies cleanly; the exact flags must match
> `securenow alerts rules --help` from Phase 0.5. **For the injection payload classes (catalog C /
> L50) the strongest play is the system signature rules + `instant.block` — do NOT duplicate their
> pattern SQL;** note/reference the existing system rule instead of emitting a create command. The
> ready-to-copy unit applies to the custom observable-abuse and app-event rules (L51–L54) only.
### 4a. Instrumentation (what injection detections feed on)
Once the app runs under `securenow run` / `securenow/register` / `securenow init`, **HTTP
traffic is captured automatically** — so the **system signature rules can match payloads in the
request with no code changes**. That covers the pattern-bearing half (catalog C / L50).
For the sinks a signature **can't** see — blind/time-based, NoSQL operators, SSTI,
deserialization, path traversal, second-order, filter-bypass — add `securenow/events` `track()`
at the **rejection / safe-gate point** (it never throws):
```js
const { track } = require('securenow/events');
// Your own validator / parameterizer / safe-sink rejected a suspicious input:
track('injection.attempt.blocked', { userId, ip, attributes: { sink: 'sql|nosql|command|code|template|ldap|xpath|path|header|log|orm|xxe|search_dsl', route: '/api/search', reason: 'operator_object|metachar|traversal|template_marker|deser_type|encoded_payload' } });
// A NoSQL query rejected because a body field was an operator object instead of a scalar:
track('injection.nosql.operator', { userId, ip, attributes: { route: '/api/login', field: 'username', operator: '$ne|$gt|$where|$expr' } });
// A path resolved OUTSIDE the allowed base dir (traversal caught by your realpath guard):
track('injection.path.traversal', { ip, attributes: { route: '/api/file', reason: 'escapes_base|absolute|encoded|null_byte' } });
// A template/deser/eval guard tripped (high-signal — these are usually pre-RCE):
track('injection.template.blocked', { ip, attributes: { route: '/api/render', engine: 'ejs|handlebars|jinja|twig', reason: 'input_as_template' } });
track('injection.deser.blocked', { ip, attributes: { route: '/api/import', format: 'pickle|java|php|node|yaml|typed_json', reason: 'untrusted_type' } });
// SECOND-ORDER: a stored value failed a later safe-query gate (closes the blind spot):
track('injection.secondorder.detected', { userId, ip, attributes: { sink: 'sql|command|template', source: 'db|import|upstream', route: '/api/report' } });
// XXE / entity-expansion blocked by your hardened XML parser:
track('injection.xxe.blocked', { ip, attributes: { route: '/api/xml', reason: 'external_entity|dtd|entity_expansion' } });
```
> Hash or omit any PII before it becomes an attribute value, and **never put the raw injection
> payload or the injected response into an attribute** — see the Phase 1 redaction check.
> Attributes feed detection; they must not become a new leak path.
Recommended injection event taxonomy — rules match these **exact strings**:
| Event | Emit when |
|---|---|
| `injection.attempt.blocked` | your own validator/safe-sink rejects a suspicious input at any sink |
| `injection.nosql.operator` | a NoSQL filter field arrived as an operator object instead of a scalar |
| `injection.path.traversal` | a resolved path escaped the allowed base dir (traversal caught) |
| `injection.template.blocked` | user input was about to be compiled as a template (SSTI guard) |
| `injection.deser.blocked` | an untrusted/typed object was refused by a deserialization guard |
| `injection.secondorder.detected` | a stored/imported/upstream value failed a later safe-query gate |
| `injection.xxe.blocked` | an XML parser refused external entities / DTD / entity expansion |
Custom `attributes` become queryable as `attributes_string['<key>']` (e.g.
`attributes_string['sink']`). Ingest enriches every IP with **ASN/org** (`client.asn`,
`client.as_org`) — enabling botnet/datacenter-origin detection with no extra code.
### 4b. Detection rules — SQL conventions
> **First and foremost: the injection payload classes (catalog C / L50) use the SecureNow
> system signature rules — do NOT write SQL for them.** SQLi / XSS / RCE detection ships as
> **system signature rules** with synchronous **`instant.block`** (blocks a matching request in
> ~2.6s on ingest). Confirm they're present and enabled for this app via
> `securenow alerts rules --json` (look for the signature/exploit rules), and **enable
> `instant.block`** rather than authoring duplicate pattern SQL. Authoring your own SQLi-pattern
> SQL is redundant, slower (scheduled, not synchronous), and noisier.
The custom SQL rules below cover what signatures **don't**: the **observable abuse** of injection
fuzzing/probing (L51/L52) and the **app-emitted** events (L53/L54). 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.
**Traffic-based — injection-fuzzing burst (L51): one IP, many distinct paths + client errors
(payload variants being sprayed across endpoints):**
```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,
uniqExact(attributes_string['http.target']) AS distinct_paths,
countIf(response_status_code IN ('400','403','422','500')) AS suspicious_responses,
count() AS total
FROM signoz_traces.distributed_signoz_index_v3
WHERE `resource_string_service$$name` IN (__USER_APP_KEYS__)
AND timestamp >= now64(9) - INTERVAL 10 MINUTE
AND ts_bucket_start >= toUInt64(toUnixTimestamp(now() - INTERVAL 10 MINUTE)) - 1800
AND kind = 2
GROUP BY ip
HAVING ip != '' AND distinct_paths >= 25 AND suspicious_responses >= 40
```
**Traffic-based — time-based-blind / expensive-payload probing (L52): one IP driving 5xx or
error spikes on a small set of endpoints (the app choking on `SLEEP()`-style or malformed
payloads):**
```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,
countIf(response_status_code LIKE '5%') AS server_errors,
uniqExact(attributes_string['http.target']) AS paths,
count() AS total
FROM signoz_traces.distributed_signoz_index_v3
WHERE `resource_string_service$$name` IN (__USER_APP_KEYS__)
AND timestamp >= now64(9) - INTERVAL 10 MINUTE
AND ts_bucket_start >= toUInt64(toUnixTimestamp(now() - INTERVAL 10 MINUTE)) - 1800
AND kind = 2
GROUP BY ip
HAVING ip != '' AND server_errors >= 30 AND paths <= 5
```
**Events-based — app-rejected injection attempts (L53): your validator/safe-sink fired
`injection.*` (query the logs table). Any sustained burst from one IP is a real attacker who is
being stopped at the app boundary — block them at the edge so they stop spending your CPU:**
```sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['event.type'] AS signal,
attributes_string['sink'] AS sink,
attributes_string['route'] AS route,
count() AS attempts
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] IN ('injection.attempt.blocked','injection.nosql.operator','injection.path.traversal','injection.template.blocked','injection.deser.blocked','injection.xxe.blocked')
AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, signal, sink, route
HAVING ip != '' AND attempts >= 5
```
**Events-based — second-order / high-signal single-hit (L54 + SSTI/deser/XXE): these are
pre-RCE or blind-spot signals — alert on a single occurrence (notify, then contain):**
```sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['event.type'] AS signal,
attributes_string['route'] AS route,
count() AS hits
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] IN ('injection.secondorder.detected','injection.template.blocked','injection.deser.blocked','injection.xxe.blocked')
AND timestamp >= now() - INTERVAL 30 MINUTE
GROUP BY ip, signal, route
HAVING ip != '' AND hits >= 1
```
Useful attributes/columns: `event.type`, `http.client_ip`, `http.target`,
`response_status_code`, `kind`, `client.asn`, `client.as_org`, and your injection attributes
(`sink`, `reason`, `route`, `field`, `operator`, `engine`, `format`, `source`).
Create + validate each rule as a **ready-to-copy unit** — save the SQL to `rules/<name>.sql`
(give every block a `-- rules/<name>.sql` header), then create, then dry-run test. Each block is
separately copyable:
```sql
-- rules/injection-app-rejected.sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['event.type'] AS signal,
attributes_string['sink'] AS sink,
attributes_string['route'] AS route,
count() AS attempts
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] IN ('injection.attempt.blocked','injection.nosql.operator','injection.path.traversal','injection.template.blocked','injection.deser.blocked','injection.xxe.blocked')
AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, signal, sink, route
HAVING ip != '' AND attempts >= 5
```
```bash
securenow alerts rules create \
--name "Injection: app-rejected attempts (single IP)" \
--sql @rules/injection-app-rejected.sql \
--apps <APP_KEY> \
--severity high \
--schedule "*/5 * * * *" \
--nlp "single IP triggering 5+ injection.* rejections in 15 minutes"
securenow alerts rules test <RULE_ID> --mode dry_run --wait # validate before it runs live
```
#### 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 (fuzzing-burst /
blind-probing counts, distinct-path / 5xx spikes), 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`** and say why — note that the
**exploit-signature / SQLi-XSS-RCE matches (catalog C / L50) are typically `prod-ready`**, while
the custom traffic-threshold rules (fuzzing burst L51, blind probing L52, app-rejected bursts L53)
are typically `test-first`. (`securenow alerts rules test <id> --mode dry_run --wait` is the
separate one-off *query* validation — run it before either.)
### 4c. The full SecureNow mitigation toolbox (select per threat)
For injection, the **app fix closes the sink** and the SecureNow control **contains the actor at
the edge / blocks the matching payload**. On **every** injection row, pair the signature/edge
control with the app fix — the signature stops the payload that matches its pattern; only
parameterized/safe-API code stops the novel, encoded, blind, or second-order payload.
Once a threat is confirmed, **choose the narrowest effective mitigation(s) from ALL of these** and
combine them (e.g. rate-limit `/api/search` + block the worst fuzzing IPs + challenge a NAT
egress). **For injection's payload classes the strongest play remains the exploit-signature
`instant.block` (row 2)** — enable it first. 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 injection scanners before the app. No app change. |
| 2 | **Exploit-signature instant block** | enable the `instant` config on the system SQLi/XSS/RCE signature rules (dashboard / MCP `securenow_alert_rule_instant_update`); custom rule → create with `--execution-mode instant` | synchronous ~2.6s block of the matching request (payload injection — catalog C / L50). **This is the primary edge control for this model.** Don't duplicate pattern SQL. |
| 3 | **IP block — global** | `securenow blocklist add <ip> --app <APP_KEY> --env production --reason "..."` | confirmed-malicious injection source, all routes. |
| 4 | **IP block — scoped to route (+ method)** | `securenow blocklist add <ip> --route /api/search* --mode prefix --method ALL --app <APP_KEY> --env production --reason "..."` (`--mode exact\|prefix\|regex`, `--method GET\|POST\|…\|ALL`) | block an IP only on the injection-targeted 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; audit-preserving unblock. |
| 6 | **Rate limit — per IP** | `securenow ratelimit add <ip> --limit 100 --window 1m --duration 24h --reason "..."` | throttle one fuzzing client across the app. |
| 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 injection-targeted endpoint for everyone, budgeted per IP. |
| 8 | **Rate limit — per route + IP** | `securenow ratelimit add <ip> --route /api/search --mode exact --method GET --limit 5 --window 1m --duration 24h` · NL `securenow ratelimit from-text "rate limit /api/search to 5/min for 24h" --yes` · test `securenow ratelimit test <ip> --path /api/search --method GET` | precise throttle of one fuzzing client on one route. |
| 9 | **CAPTCHA / proof-of-work challenge** | `securenow challenge add --route /api/search --difficulty 16 --clearance 30m` (route-wide) **or** `securenow challenge add <ip> --route /api/search --difficulty 18 --clearance 30m` · test `securenow challenge test <ip> --path /api/search --method GET` | automated fuzzers from **shared / NAT / CGNAT** egress — a human passes once, a script can't keep spraying payloads. Prefer over a hard block when real users share the IP. |
| 10 | **Auto-block (risk-scored)** | `securenow automation defaults --yes` (≥95→7d, 90–94→72h, 85–89→24h) · custom `securenow automation create --conditions '[...]' --actions '[...]'` · preview `securenow automation dry-run <id>` | hands-off blocking by risk score; signature hits score high. Actions include block / rate_limit / requireCaptcha. |
| 11 | **Session revocation** | `securenow revoke …` (SDK `securenow/sessions` `guard()` / `isRevoked()`) | if an injection led to a hijacked/forged session — kill the session, not the IP. |
| 12 | **Trusted IP (suppress)** | `securenow trusted add <ip> --label "Internal SAST/DAST scanner / monitor"` | stop false positives from your own pentest/scanner 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. Never for a public app. |
| 14 | **False-positive exclusion** | `securenow fp create --conditions '[...]' --rule-scope this_rule --reason "..."` · `securenow fp mark <notification-id> <ip> --rule-scope this_rule` · preview `securenow fp dry-run --conditions '[...]'` | keep a noisy fuzzing/5xx rule quiet without weakening the signatures. |
| 15 | **App / config / code fix (PRIMARY — closes the sink)** | *described in the Code-Findings report, never auto-applied* | the real fix: **parameterized queries**, **allow-list dynamic identifiers**, **`execFile`/arg-array (no shell)**, remove `eval`/dynamic-require, **safe deserialization**, never compile input as a template, **`realpath`+base-dir assertion** for paths, **disable XML external entities**, strip CRLF from headers/logs, replace ORM raw fragments with bound params. SecureNow contains; the fix removes. |
**Choosing per threat** — by **confidence**: exploit-signature SQLi/XSS/RCE payload / exact IoC →
**instant-block (row 2)** or block; probable fuzzer on shared egress → **challenge**; noisy/
legit-mixed traffic (fuzzing-burst / blind-probing thresholds) → **rate-limit (test-mode first)**;
session compromise from an injection → **revoke**; your own scanner noise → **trusted / fp**. By
**blast radius**: always scope to the narrowest `route`/`method`/`IP`/`duration` that stops the
abuse; on NAT/CGNAT/shared IPs prefer challenge/rate-limit over a hard block. For the high-signal
pre-RCE events (SSTI/deser/XXE/second-order) recommend **notify + immediate human review**, then
block. Always pair an edge mitigation with the **app/config fix** (Code-Findings report) when
SecureNow can only contain the actor — a row that has only the signature and no parameterization
plan is incomplete.
For every 🔴 gap, give the **interim app/config fix** (e.g. "disable `$where` at the DB; cast all
NoSQL filter values to scalars in a shared middleware") and add:
*"Requires SecureNow team — contact your SecureNow account contact (or in-dashboard support) to
request support for this threat."*
### 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 app-rejected injection events — exercise the events-based rule end to end:
for i in $(seq 1 6); do
securenow event send injection.attempt.blocked --ip 203.0.113.60 \
--attrs sink=sql,route=/api/search,reason=metachar,test=true
done
# Synthetic NoSQL operator injection + path traversal + high-signal SSTI:
securenow event send injection.nosql.operator --ip 203.0.113.60 \
--attrs route=/api/login,field=username,operator='$ne',test=true
securenow event send injection.path.traversal --ip 203.0.113.61 \
--attrs route=/api/file,reason=escapes_base,test=true
securenow event send injection.template.blocked --ip 203.0.113.61 \
--attrs route=/api/render,engine=ejs,reason=input_as_template,test=true
securenow event send injection.secondorder.detected --ip 203.0.113.62 \
--attrs sink=sql,source=db,route=/api/report,test=true
# Validate a rule query without waiting for the schedule:
securenow alerts rules test <RULE_ID> --mode dry_run --wait
# Traffic-based rules (fuzzing burst / blind-probing) — generate spans, then check pipeline:
securenow test-span "threat-model.injection.smoke"
securenow forensics "requests and 4xx/5xx by IP in the last hour" --env production
# SIGNATURE + instant-block (the core of this model) — confirm a payload triggers the system rule
# and is blocked synchronously on a STAGING URL you own. Send a request carrying a benign-but-
# matching marker (e.g. ?q=' OR '1'='1 or ?q=<script>alert(1)</script> or ;id) and verify
# the block fired and the request was stopped (~2.6s on ingest):
securenow firewall test-ip 203.0.113.60 --app <APP_KEY> --env production
# Mitigation verification:
securenow ratelimit test 203.0.113.60 --path /api/search --method GET
securenow challenge test 203.0.113.60 --path /api/search --method GET
# Confirm + clean up:
securenow notifications list --limit 10
securenow blocklist list # then: securenow blocklist unblock <id> --reason "threat-model test"
securenow challenge list # then: securenow challenge remove <id>
```
Every 🟢/🟡 threat row in the report must have a concrete test recipe (commands + expected
outcome: which rule or **signature** fires, which notification appears, what the mitigation does).
For catalog C / L50 the recipe is the **signature + instant-block** confirmation above, not a
custom-rule test.
---
## Phase 5 — Write the FOUR reports (two tracks)
Write **four** files into `threat/06-injection/` — two per track, each MD + HTML. The two tracks
**cross-link**: detection-report gap/instrumentation rows link to the matching code finding, and
each code finding links back to the detection row it backs. SecureNow-runnable detections and
mitigations live in **Track 1**; code/config changes live in **Track 2** — nothing
security-relevant is dropped, and nothing is duplicated across the two.
### 5a. Track 1 — Detection & Mitigation report (`injection-detection-mitigation.md` + `.html`)
The **operational runbook**: what to run in SecureNow. Sections, in order:
1. **Executive summary** — stats line (N threats modeled · N covered · N partial · N gaps ·
N rules to create · N mitigations), top 3 detectable injection risks for this stack, the
installed `securenow` version + app key + firewall state, and — critically — **the state of the
system SQLi/XSS/RCE signature rules + `instant.block`** for this app (enabled? armed?), since
that is the single biggest lever.
2. **SDK & environment** — installed SDK version (from `node_modules/securenow`, Phase 0.5),
app key(s), environment, firewall state, the existing rules / automations / challenge rules
(from Phase 0), and **which system signature rules are present** (SQLi/XSS/RCE) with their
`instant.block` state.
3. **Threat → Detection → Mitigation matrix** — one row per modeled threat:
`# | Threat | A03/CWE | Coverage 🟢/🟡/🔴 | Detection (signature / rule name / "—") | Signal (threshold + window or "signature match on ingest") | Schedule | Mode (test-first / prod-ready) | Sev | Mitigation (edge + app fix)`.
Severity ∈ {critical, high, medium, low}. Each row's **Mitigation cell must pick specific,
scoped mitigation(s) from the §4c toolbox** (by number/name, scoped to route/method/IP/duration)
paired with the app fix — **never a generic "block the IP."** Each rule also carries a
**`test-first` or `prod-ready`** tag (exploit-signature SQLi/XSS/RCE matches are typically
`prod-ready`; custom traffic-threshold rules are typically `test-first`). Include the deferred
rows (55–58) pointing to the sibling models. Follow with the "Out of scope" N/A list and the
"Deferred to sibling models" subsection. Rows whose remediation is an app/config fix **link to
the matching code finding** in the Track-2 report.
4. **Detection rules to create** — first, the **system signature rules to confirm/enable** (rule
IDs from Phase 0, the `instant.block` state, the command/dashboard step to arm them — do **not**
duplicate them in SQL); these are **`prod-ready`**. Then, for each *new* custom rule (fuzzing
burst, blind probing, app-rejected events, second-order), the **ready-to-copy command unit** from
Phase 4: SQL → save to `rules/<name>.sql` → full `securenow alerts rules create …` → `… rules
test … --mode dry_run --wait`. **Mark each rule `test-first` or `prod-ready`**; for every
`test-first` rule include the `--mode test` → observe (3–7 days) → tune/`fp` → `--mode prod`
promotion step (per §4b). Injection-class rows reference the **system signature rules +
`instant.block`**, not duplicate pattern SQL.
5. **Instrumentation the detections need** — only the `injection.*` `track('…')` events the rules
above consume, each as a copyable snippet; point to the Track-2 code-findings report for
*where* (file:line) to add them.
6. **Mitigation mechanisms** — render the **full §4c toolbox table** (all 15 rows: free firewall ·
**exploit-signature `instant.block`** · block [global / route+method / temporary] · rate-limit
[IP / route / IP+route] · challenge · auto-block · session revocation · trusted · allowlist · fp ·
**app-config fix**) + the "Choosing per threat" guidance + a per-threat ready-to-copy mitigation
command + reversibility. Make explicit that the **app fix is primary** (it closes the sink) and
the signature/edge control is **containment** of the matching payload + the actor.
7. **Action plan (copy-paste, ordered)** — ① engage the firewall + **confirm and enable
`instant.block` on the system SQLi/XSS/RCE signature rules** (highest leverage; `prod-ready`),
② add the `injection.*` instrumentation at the sinks the signature can't see, ③ create the custom
rules — **every false-positive-prone rule created in `--mode test`** (detect-only) with an
explicit **"promote to `--mode prod` after N days (3–7) of clean observation"** step
(`securenow alerts rules update <RULE_ID> --mode prod`); only `prod-ready` rules arm immediately,
④ enable automations / challenge rules, ⑤ test (signature confirmation + rule dry-runs),
⑥ verify in dashboard, ⑦ schedule the **app-fix work** from the Track-2 report (parameterize,
allow-list, safe APIs). Real commands only, `<APP_KEY>` already substituted.
8. **Testing & validation** — per-threat test recipes (4d): `securenow event send …` /
`test-span` / dry-run + expected outcome + cleanup (TEST-NET IPs 192.0.2 / 198.51.100 /
203.0.113). Catalog C / L50 = the **signature + instant-block** confirmation, not a custom-rule
test.
9. **Response runbooks** — for each notification type (signature instant-block, fuzzing burst,
blind probing, app-rejected events, second-order/SSTI/deser/XXE): what fired, how to confirm a
true positive, the exact command to respond (copy), the exact command to reverse (copy), **and**
a link to the Track-2 app-fix to schedule so the sink is closed (not just the IP contained).
10. **Known gaps & SecureNow feature requests** — every 🔴 threat: why it's not coverable today,
the interim fix (link to the Track-2 code finding), and the "contact the SecureNow team" line.
11. **Appendix** — resolved SDK/CLI version (Phase 0.5), app key, environment, firewall state,
signature-rule IDs + instant-block state, custom rule IDs created, date, link to the Track-2
code-findings report.
### 5b. Track 2 — Code Findings & Recommendations report (`injection-code-findings.md` + `.html`)
The **code audit**: issues in the codebase + recommendations. State at the top:
*"Findings only — no application code was modified."* Sections, in order:
1. **Executive summary** — findings by severity (critical / high / medium / low), top 3 code
risks for this stack, a one-paragraph posture verdict, and a one-line **A03:2021 + CWE**
coverage note.
2. **Injection surface & sink inventory** — the Phase 1 sink table
(`Sink | File:line | Interpreter | Input source | Parameterized? | 2nd-order? | Risk`), the
input-source → sink flow map, the normalization/filter posture, redaction status, and the
attack-surface paragraph.
3. **Threat catalog** — the exhaustive Phase 2 catalog (A1–L54 grouped, each tagged
**A03:2021 + specific CWE**, modeled or explicit N/A), plus the deferred items 55–58 referenced
to the sibling models (not re-derived).
4. **Code-level findings (audit — not applied)** — the Phase 3 findings: a table
`# | Location (file:line) | Threat | CWE | Sev | Sink | Recommended app fix`, each with the
quoted 1–8 line snippet, a sample payload, and the described fix (never applied). Each finding
**links to the Track-1 detection row** it backs.
5. **Strengths** — sinks already safe (parameterized, `execFile` with an arg array, allow-listed
paths, XML external entities disabled, CRLF-stripped headers/logs) — the posture must be honest.
6. **App / config fixes (primary remediation)** — the config/code changes that remove the root
cause (described, not applied): parameterized queries, allow-list dynamic identifiers,
`execFile`/arg-array (no shell), remove `eval`/dynamic-require, safe deserialization, never
compile input as a template, `realpath`+base-dir assertion for paths, disable XML external
entities, strip CRLF from headers/logs, replace ORM raw fragments with bound params — each
**linked to the Track-1 detection/mitigation row** it backs.
7. **Instrumentation recommendations** — the `injection.*` `track('…')` calls to add and the exact
`file:line` to add them (at the rejection / safe-gate point), so the Track-1 detection rules
light up.
8. **Appendix** — files reviewed, resolved SDK version (Phase 0.5), date, link to the Track-1
detection-mitigation report.
### 5c. HTML reports — two self-contained skeletons (offline; inline CSS + copy JS; no network)
Both HTML files share the `<head>` below (brand tokens + copy-button styles) and the copy
`<script>` at the end of `<body>`. **Change only the `<title>`, the sidebar subtitle, and the
section content** — one body per track (Track 1 = the 5a sections, Track 2 = the 5b sections).
**Wrap EVERY command/SQL block as a `.cmd`** so it gets a Copy button (the Code-Findings HTML may
omit copy buttons on prose, but still wraps any example/fix command in `.cmd`).
**Shared `<head>` + copy CSS + copy `<script>` (identical in both files):**
```html
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<title><!-- "Detection & Mitigation — Injection — SecureNow" OR "Code Findings — Injection — 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 · Injection" OR "Code Findings · Injection" --></div>
<!-- one <a href="#…"> per section of THIS track (5a or 5b) -->
</nav>
<main>
<header class="top"><h1><!-- report title: "Injection — Detection & Mitigation" OR "Injection — Code Findings" --></h1>
<p><code><!-- app name / domain --></code> · <span class="pill">securenow <!-- installed version --></span></p></header>
<div class="stats"><!-- 5 .stat cards; numbers MUST equal the table/finding counts of THIS track --></div>
<!-- <section id="…"> blocks mirroring the Markdown sections of THIS track (5a or 5b) -->
<footer>Generated by the SecureNow injection 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>
```
**Skeleton 1 — Track 1 (`injection-detection-mitigation.html`).** `<title>` = "Detection &
Mitigation — Injection — SecureNow"; sidebar subtitle = "Detection & Mitigation · Injection";
`<h1>` = "Injection — Detection & Mitigation". The five stat cards = threats modeled · covered ·
partial · gaps · rules to create. Body `<section>` blocks mirror the **5a** sections (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 "..." --sql @rules/<name>.sql --apps <APP_KEY> --severity high \
--schedule "*/5 * * * *" --nlp "..."</pre></div>
```
**Skeleton 2 — Track 2 (`injection-code-findings.html`).** Same shared `<head>` + copy
`<script>`; `<title>` = "Code Findings — Injection — SecureNow"; sidebar subtitle = "Code Findings
· Injection"; `<h1>` = "Injection — Code Findings". The five stat cards = critical · high · medium
· low · total findings. Body `<section>` blocks mirror the **5b** sections (1–8). Prose may skip
copy buttons, but every example/fix command is still wrapped in `.cmd` with a Copy button.
Badge usage (both files): severity `<span class="b crit|high|med|low">`; coverage
`<span class="c cov|part|gap">COVERED|PARTIAL|GAP</span>`; OWASP `<span class="owasp">A03</span>`;
CWE `<span class="cwe">CWE-89</span>`; mitigation
`<span class="m firewall|signature|rate|challenge|block|notify|appfix">`; rule IDs
`<span class="rid">`. Stats numbers must equal the matrix/findings row counts of that track.
---
## Quality bar (the report is rejected if any of these fail)
- **Phase 0.5 ran:** the resolved installed `securenow` version appears in **both** reports'
appendix, and no command / flag / `track('…')` event / SQL column is emitted that the installed
SDK/CLI does not expose (else it is annotated `# requires securenow >= <version>`).
- Every *new* detection rule is a **complete copyable unit** (SQL → `rules/<name>.sql` → full
`securenow alerts rules create …` → `… rules test … --mode dry_run --wait`); flags match
`securenow alerts rules --help`. Injection payload classes (catalog C / L50) reference the system
**signature rules + `instant.block`** instead of duplicating their pattern SQL.
- **Four** files are written to `threat/06-injection/` (`injection-detection-mitigation.md` +
`.html`, `injection-code-findings.md` + `.html`); the two tracks **cross-link**; both HTML files
are **self-contained** (inline CSS/JS, no network) and **every command block in the detection
HTML has a working Copy button** (`.cmd` wrapper).
- 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.
- Every catalog item A1–L54 is either a matrix row or an explicit N/A line; each modeled row
carries its **A03:2021 + specific CWE** tag.
- The deferred items 55–58 are **referenced, not re-derived** (rows present, linked to
[../04-xss-csrf-cors/](../04-xss-csrf-cors/), [../15-graphql-security/](../15-graphql-security/),
[../25-ai-llm-features/](../25-ai-llm-features/), [../14-api-security/](../14-api-security/)) —
this model does not duplicate their catalogs.
- **Injection coverage references the system SQLi/XSS/RCE signature rules + `instant.block`** —
Phase 0 records whether they're enabled/armed, and the report tells the user to **confirm and
enable** them rather than authoring duplicate pattern SQL.
- Every matrix row has a concrete signal (threshold + window or "signature match on ingest"),
severity, and mitigation — **no "monitor for suspicious activity" filler.**
- Every injection row **pairs the edge/signature control with the app fix** — the signature
blocks the matching payload; only parameterization / allow-listing / safe APIs close the sink.
- Every code finding has a `file:line`, the quoted snippet, a sample payload, and a described app
fix — and **no application code was modified** (this is an audit).
- Every custom detection SQL keeps `__USER_APP_KEYS__` scoping (correct table column:
`resources_string['service.name']` for logs vs `` `resource_string_service$$name` `` for traces),
selects an `ip` column with `HAVING ip != ''`, and traffic queries keep the `ts_bucket_start`
+ `kind = 2` guards and the `client_ip` coalesce.
- Only commands, flags, events, and SQL columns from this prompt's building blocks appear
(including `securenow challenge …`, `firewall`, the `injection.*` events, and `--mode dry_run`).
- 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, **starting with confirming/
enabling the signature instant-block.**
- Both HTML files are self-contained (no CDN, no fonts, no external scripts) and titled per track
("Detection & Mitigation — Injection" / "Code Findings — Injection"); each file's stats cards
match its own table/finding counts.
- A one-line summary is printed back to the user: the **per-track file paths**, threat counts,
rules-to-create count, code findings by severity, gaps, A03/CWE coverage, signature-rule state,
and the **resolved installed SDK version** (Phase 0.5).
- The Detection report's mitigation section presents the **full toolbox** (§4c: firewall ·
instant-block · block [global / route / method / temporary] · rate-limit [IP / route / IP+route] ·
challenge · auto-block · revoke · trusted · allowlist · fp · app-fix), and **each modeled threat's
matrix row selects specific, scoped mitigation(s) from it** — never a generic "block the IP."
- **Every false-positive-prone rule is tagged `test-first`** and carries the `--mode test` → observe
(3–7 days) → `--mode prod` promotion workflow; only high-precision rules (exploit-signature
SQLi/XSS/RCE 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 ════════════════ -->