#18Serverless Top 10 · API4
Serverless & Edge
FaaS & edge functions, event-source injection, and denial-of-wallet.
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/18-serverless-and-edge/— openserverless-and-edge-code-findings.html(the audit) andserverless-and-edge-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
# Serverless & Edge 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 ships
**serverless functions** (AWS Lambda, Google Cloud Functions, Azure Functions) or **edge
runtimes** (Cloudflare Workers / Durable Objects / KV, Vercel & Netlify Edge, Lambda@Edge) and
has the `securenow` CLI installed and logged in. The agent will first **ground every rule and
command in the SecureNow SDK actually installed in the repo** (never guessing flags, subcommands,
event names, or SQL columns), inventory the function & trigger surface, build an exhaustive
**serverless & edge** threat model mapped to the **OWASP Serverless Top 10** (plus **API4:2023
denial-of-wallet**, **A05:2021 Security Misconfiguration**, **A08:2021 Software & Data Integrity
Failures**), audit the code for serverless-layer flaws, and emit a **two-track** SecureNow-branded
deliverable set in **Markdown + self-contained HTML** — a Detection & Mitigation runbook (the
detection rules to create as **ready-to-copy** command units, the mitigation commands to run, how
to test each one) and a Code Findings & Recommendations report (the code-level findings, audited,
**not** fixed) — including which threats still need the SecureNow team.
This model owns what is unique to a **serverless / event-driven / edge** deployment: there is no
long-lived server, **triggers arrive from many event sources** (not just HTTP), each function
runs with its **own execution role**, cost scales with **invocation × duration**, and warm
containers **persist state between requests**. It owns event-source trust, function permission
scope, denial-of-wallet, function-to-function trust, deployment config, inventory drift,
warm-container/global-scope bleed, and edge-runtime isolation. It **defers** the HTTP API layer
in front of functions to [api-security](../14-api-security/api-security-threat-model-prompt.md),
function **execution-role identity / IMDS / secret material** to
[secrets-and-cloud-iam](../20-secrets-and-cloud-iam/), and the **network / compute / container**
posture to [cloud-infrastructure](../19-cloud-infrastructure/). Run those three too; together
they cover the full cloud attack surface around your functions.
> SecureNow is fundamentally an **API / traffic** security layer (firewall, rate-limit,
> challenge, exploit-signature instant-block, ASN enrichment, forensics). For serverless, native
> coverage is **MEDIUM**: **HTTP-triggered functions get traffic spans automatically** — so
> invocation-rate, denial-of-wallet, distinct-endpoint and 5xx detection works with **no
> instrumentation**. **Non-HTTP triggers** (S3/SQS/SNS/DynamoDB-streams/EventBridge/Kinesis/IoT)
> are invisible to traffic capture and need `track()` events. **Role-scoping, concurrency caps,
> and event-source validation are cloud/app config** — SecureNow contains the abuser at the edge
> but the missing control is the primary fix. Pair them on every config row.
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 Serverless & Edge Threat Model Report (SecureNow)
You are a senior application-security engineer specializing in serverless and edge security.
Produce an **exhaustive serverless & edge threat model for THIS codebase**, organized along the
**OWASP Serverless Top 10** and mapped to **API4:2023 (denial-of-wallet)**, **A05:2021** and
**A08:2021**, mapped to **SecureNow** detections and mitigations, with a ready-to-run action plan
**and** a code-level audit of every serverless-layer flaw you find. Ground every rule, flag,
event name, and SQL column in the **installed** `securenow` SDK (Phase 0.5) — never guess. Every
detection is emitted as a **ready-to-copy** command unit (SQL → `rules/<name>.sql` → full
`securenow alerts rules create …` → dry-run test). You write **four** deliverables (two tracks)
into `threat/18-serverless-and-edge/` (create the folder if needed):
1. `serverless-and-edge-detection-mitigation.md` — the **operational runbook**: what to run in
SecureNow (rules to create, mitigations, tests), all as ready-to-copy command units.
2. `serverless-and-edge-detection-mitigation.html` — the same runbook as a **self-contained**
HTML page (inline CSS + copy-button JS, no network requests), using the SecureNow branding
template at the end; every command block has a working Copy button.
3. `serverless-and-edge-code-findings.md` — the **code audit**: serverless-layer issues in the
codebase + recommendations (described, never applied).
4. `serverless-and-edge-code-findings.html` — the same code audit as a self-contained HTML page.
The two tracks **cross-link** each other: detection-report gap/instrumentation rows link to the
relevant code finding, and code-findings app/config fixes link back to the detection row they
back. `<slug>` here is `serverless-and-edge` (the folder name without the `18-` prefix).
Work in the seven phases below, in order. **Never invent facts**: if something is not in the
codebase or not returned by a CLI command, say "not found" — do not guess. **Do not modify
application code, function handlers, or infrastructure-as-code.** You are auditing: every
code/config-level fix is *described in the report*, never applied to the repo.
**Scope discipline.** This model owns the **serverless & edge runtime**: event-source injection
& trust, over-privileged execution roles (observed *behaviorally*, not the IAM policy itself),
denial-of-wallet, downstream-sink injection from event data, function-to-function trust,
deployment config, exception/poison-message handling, serverless inventory drift,
cold-start/warm-container/global-scope state bleed, recursion/fan-out loops, per-function
rate/concurrency limits, and edge-specific isolation (KV / Durable Objects, geo/header trust,
edge cache poisoning). For threats that belong to a sibling model, do **not** re-derive them —
list them in a "Deferred to sibling models" subsection, link the report, and only model their
**serverless-observable** symptoms where SecureNow adds value:
- **The HTTP API layer in front of functions** (BOLA/BFLA, payload injection over HTTP, CORS,
WAF evasion, generic rate-limits) → [../14-api-security/](../14-api-security/api-security-threat-model-prompt.md).
- **Function execution-role IAM identity, IMDS/metadata credential theft, secret material in
Secrets Manager/SSM/KMS** → [../20-secrets-and-cloud-iam/](../20-secrets-and-cloud-iam/).
- **VPC/network reachability, container/runtime base-image CVEs, compute hardening** →
[../19-cloud-infrastructure/](../19-cloud-infrastructure/).
---
## Phase 0 — Verify SecureNow tooling
Run and record (use `--json` where supported):
```bash
securenow doctor # connectivity must be healthy
securenow whoami # admin auth + runtime app
securenow status --json # app key(s), environment, firewall state
securenow alerts rules --json # detection rules that already exist (incl. system signature rules)
securenow automation --json # blocklist automations that already exist
securenow challenge list --json # CAPTCHA / proof-of-work challenge rules
securenow env --json # resolved SDK config (service name, endpoints)
```
If the CLI is missing or not logged in, **stop** and tell the user to run
`npm i -g securenow && securenow login`, then re-run this prompt. Capture the **app key**
(UUID) — every rule and command in the report must use it. If multiple apps exist (e.g. a
function fleet split across services), ask the user which app this codebase maps to before
continuing. Note the **firewall state** and any **system signature rules** (SQLi/XSS/RCE)
already present — those back the injection coverage for HTTP-triggered functions and must not be
duplicated.
---
## 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 serverless & edge surface (codebase analysis)
Serverless security starts with an **inventory of functions and their triggers** (the serverless
analogue of OWASP API9). Document what is **actually deployed and invocable**, not what is
intended. Read the IaC / deployment manifests as the source of truth: `serverless.yml`,
`template.yaml` / SAM, AWS CDK / Terraform / Pulumi / CloudFormation, `wrangler.toml`
(Cloudflare), `vercel.json` / `netlify.toml`, `functions.json` / `host.json` (Azure),
`*.tf`, and any `*.functions.*` configs. Cover at minimum:
- **Platform & runtime** — Lambda / Cloud Functions / Azure Functions / Cloudflare Workers /
Vercel Edge / Netlify Edge / Lambda@Edge; language & runtime version per function (note EOL /
deprecated runtimes — `nodejs14.x`, `python3.7`, etc.). Edge vs region-bound.
- **Function catalog** — enumerate **every** function: name, handler `file:export`, runtime,
memory, timeout, reserved/provisioned concurrency, the IaC stanza that defines it. This catalog
is a report deliverable. Flag which are public, which are internal/service-to-service, which
are admin/ops.
- **Trigger / event-source map** — for each function, the **full set of triggers**:
- **HTTP** — API Gateway / ALB / Function URL / Cloudflare route / Vercel route (these get
SecureNow traffic spans automatically).
- **Non-HTTP** — S3 object events, SQS/SNS, DynamoDB / Kinesis streams, EventBridge /
scheduled (cron) rules, IoT, Cognito, SES, Step Functions, queue/pubsub, KV/D1 bindings.
- For each: is the event **payload treated as trusted**? Is the source **authenticated /
validated** (event-source ARN check, message-attribute signature, SNS subscription
confirmation)? (OWASP Serverless: event-data injection & broken authentication.)
- **Function URLs & public invocation** — any Lambda **Function URL** with `AuthType: NONE`,
any unauthenticated edge route, any function invocable without going through the API/WAF in
front. (Broken function-to-function trust / public function URL.)
- **Execution-role behavior (observed, not the policy)** — for each function, which resources its
handler code **actually touches** (S3 buckets, tables, queues, other functions via
`lambda.invoke`, secrets, external APIs). Flag a function whose code touches resources
**unrelated to its job** (over-privilege smell). The deep IAM-policy audit is **deferred to
../20-secrets-and-cloud-iam/** — here, record the *behavioral* footprint and any
`iam:PassRole`, `sts:AssumeRole`, wildcard `Action`/`Resource` you can read from IaC.
- **Cost & concurrency controls (denial-of-wallet surface)** — per function: is there
**reserved concurrency** / a **max-instances** cap? a **timeout** ceiling? a **budget alarm**?
Is invocation gated by auth or open? Note every function that can be driven to unbounded
invocation or long duration with **no concurrency cap** (API4 denial-of-wallet). List paid
downstreams each invocation triggers (SMS/email/LLM/maps/another paid function).
- **Downstream sinks from event data** — every place the function passes **event payload** into
a sink: SQL/NoSQL query, `exec`/shell, `eval`, template render, file path, **another function
invoke**, an outbound HTTP call (SSRF), or a queue/topic publish. (Event-data injection into
downstream sinks.)
- **Function-to-function trust** — internal `lambda.invoke` / service-bus / queue calls between
functions: is the caller authenticated? Can an external actor inject into the same queue/topic
to invoke the "internal" function directly? (Broken function-to-function trust.)
- **Deployment config & secrets posture** — secrets in **environment variables** or **Lambda
layers** instead of Secrets Manager/SSM/KMS; debug flags (`DEBUG`, `NODE_ENV !== production`,
verbose log levels) on in prod; `.env` files bundled into the artifact; source maps shipped to
edge; public Function URLs; overly broad CORS on Function URLs. (Insecure deployment config /
A05.) *Secret material handling is deferred to ../20-secrets-and-cloud-iam/* — here, record the
*config* exposure.
- **Exception handling & poison messages** — handlers that leak stack traces / framework
versions in the response or logs; SQS/Kinesis consumers with **no dead-letter queue (DLQ)** or
`maxReceiveCount`, so a failing message is **redelivered forever** (poison-message loop &
cost); partial-batch-failure not reported (whole batch reprocessed); `try/catch` that swallows
and re-invokes. (Improper exception handling.)
- **Inventory drift** — functions present in IaC/console that are **no longer referenced** by any
route or caller (orphaned but still invocable); old aliases/versions still live; functions on
**EOL runtimes**; functions with a live Function URL but no owner. (Serverless inventory drift
/ A05.)
- **Warm-container & global-scope state** — module-level (global) variables that hold
**per-request data** (user id, tenant, auth context) and persist across warm invocations;
caches/connection pools keyed wrong; secrets cached in globals; use of **`/tmp`** to write
per-invocation files without unique paths (data bleed across warm invocations); cold-start
initialization races. (Warm-container data bleed / cold-start race.)
- **Recursion / fan-out / loops** — a function that writes to the **same trigger it consumes**
(S3 function writing to its own bucket; SNS→Lambda→SNS), self-invocation, or a fan-out
(one event → N invokes) with no depth/breadth cap. (Recursion / fan-out cost loop.)
- **Edge-specific posture** (model only if an edge runtime is present, else N/A):
- **KV / Durable Object / D1 isolation** — keys namespaced per tenant? a Durable Object id
derived from untrusted input letting one tenant address another's object?
- **Geo / header trust** — code trusting `cf-connecting-ip`, `cf-ipcountry`,
`x-vercel-ip-country`, `x-real-ip`, `true-client-ip` from the client without verifying it
came from the trusted edge.
- **Edge cache poisoning** — `caches.default` / CDN cache keyed on unkeyed/spoofable inputs
(Host, query, headers), authenticated responses cached at the edge.
- **SecureNow instrumentation already present** — `securenow/register` / `securenow run` /
`securenow init` (HTTP-triggered functions get traffic spans automatically), any
`securenow/events` `track()` calls (especially `serverless.*` / `api.sensitive.flow` /
`api.upstream.error` on non-HTTP triggers), and whether the firewall is engaged in front of
the HTTP triggers. This determines what works *today* vs *after instrumentation*.
- **Telemetry privacy & redaction** — confirm whether the SDK/log pipeline redacts secrets,
tokens, raw event payloads (S3 keys, queue bodies, PII) and IMDS responses before ingestion.
If event payloads are emitted into `track()` attributes without redaction, create a
high-severity finding (a new leak path).
Output of this phase = the report's **Serverless surface & inventory** section: the function
catalog (name/handler/runtime/memory/timeout/concurrency/visibility), the **trigger/event-source
map** (function × trigger × trusted?), the **cost & concurrency control table** (reserved
concurrency / timeout / budget alarm / auth-gated — per function), the **downstream-sink list**,
the **function-to-function trust map**, the **deployment-config posture**, the **exception/DLQ
posture**, the **inventory-drift list** (orphaned/EOL/zombie functions), the **warm-container &
global-scope findings**, the **recursion/fan-out map**, the **edge posture** (KV/DO isolation,
geo/header trust, edge cache), the **telemetry redaction status**, and a short paragraph naming
the real serverless attack surface for this stack.
---
## Phase 2 — Enumerate threats (exhaustive catalog)
Evaluate **every** threat below against the discovered surface. Each item is either **modeled**
(a row in the threat matrix) or **explicitly N/A** (one line in an "Out of scope" subsection
with the reason — e.g. "Edge items: N/A, Lambda-only, no edge runtime"). Never silently drop an
item. Add stack-specific threats you discover that are not listed — this catalog is the floor,
not the ceiling. Tag each modeled row with its framework code: **OWASP Serverless Top 10** (SVL),
**API4:2023** (denial-of-wallet), **A05:2021**, or **A08:2021**.
**A. Event-source injection & trust (OWASP Serverless — untrusted event data treated as trusted)**
1. S3 object-event injection — object key/metadata/content from an upload treated as trusted into a sink
2. SQS/SNS message injection — message body/attributes parsed and trusted (cross-account SNS publish, unconfirmed subscription)
3. DynamoDB / Kinesis stream-record injection — stream image fields fed into a query/exec without validation
4. EventBridge / scheduled-event spoofing — custom-bus event from an untrusted publisher, or cron payload treated as authenticated
5. IoT / Cognito / SES / custom-trigger payload injection — non-HTTP event treated as a trusted caller
6. Event-source not validated — function does not verify the source ARN / account / signature, so any source that can write to the trigger invokes it
7. Cross-format event confusion — same handler invoked by multiple sources, parser differential between HTTP body vs S3 record vs SQS body
**B. Function-event-data injection into downstream sinks (OWASP Serverless A1 — injection)**
8. SQL / NoSQL injection from event payload into a DB query
9. OS / command injection (RCE) from event field into `exec`/shell
10. Code injection — event data into `eval` / dynamic `require` / template (SSTI) / deserialization
11. NoSQL operator injection (`$gt`/`$ne`/`$where`) from a JSON event attribute
12. Path traversal / arbitrary file write from an event-supplied key into `/tmp` or storage
13. Downstream **function invoke** injection — event data controls the target function name / ARN / payload of a `lambda.invoke`
14. Log / queue / topic injection — event data forwarded unescaped to logs or a downstream queue (poisoning the next consumer)
**C. Broken authentication & function-to-function trust (OWASP Serverless A2)**
15. Public Function URL with `AuthType: NONE` invocable by anyone
16. Unauthenticated internal invocation — an "internal" function reachable directly (via its URL, its queue, or a shared topic)
17. Broken function-to-function trust — callee assumes the caller is trusted because it's in the same account/VPC; no caller authentication
18. Replayable invocation — no idempotency/nonce on an invoke, so a captured event can be re-fired (double effect / cost)
19. Missing event-source authentication — SNS/SQS/EventBridge accept events from any principal that can publish
**D. Denial-of-wallet & resource abuse (API4:2023 + OWASP Serverless A4/A10)**
20. Invocation flood — attacker drives invocation count to inflate cost, no per-function rate/concurrency cap
21. Duration inflation — crafted input makes the function run to its (long) timeout every time (CPU/regex/IO)
22. Reserved-concurrency starvation as DoS — flooding one function exhausts the account concurrency pool, starving others
23. Recursion / self-trigger loop — function writes to the trigger it consumes → unbounded invoke billing
24. Fan-out amplification — one event spawns N invokes (per-record, per-message) with no breadth cap
25. Paid-downstream amplification — each invoke triggers a paid call (SMS/email/LLM/maps/Textract) with no quota
26. Memory/`/tmp` exhaustion forcing higher billed memory tier or failures
27. Poison-message redelivery loop — failing SQS/Kinesis message with no DLQ / `maxReceiveCount` redelivered forever (cost + log flood)
28. Step-Functions / orchestration state explosion driving execution cost
**E. Insecure deployment configuration (A05:2021 + OWASP Serverless A5)**
29. Secrets in environment variables / Lambda layers instead of a secret store
30. Debug / verbose-error flags enabled in production (stack traces in responses/logs)
31. `.env` / config files bundled into the deployment artifact (readable from layer/source)
32. Source maps / unminified code shipped to edge runtimes (logic & secret disclosure)
33. Over-broad CORS on a Function URL / edge route
34. Public Function URL or open edge route unintentionally internet-reachable
35. Excessive permissions in the deployment principal (CI deploy role can touch unrelated resources) — *config view; identity deferred to ../20-secrets-and-cloud-iam/*
36. Unsigned / unverified deployment artifact or third-party layer (A08 — integrity of what's deployed)
**F. Improper exception handling & poison messages (OWASP Serverless A6)**
37. Verbose error / stack trace / runtime banner leaked to the caller
38. Partial execution — function fails mid-side-effect leaving inconsistent state (charged but not shipped)
39. Swallowed exception that silently re-invokes or re-queues (loop)
40. Missing DLQ / `maxReceiveCount` so failures cycle (also denial-of-wallet, row 27)
41. Sensitive data (event payload, secret, IMDS token) written to logs on error
**G. Serverless inventory drift (A05:2021 + OWASP Serverless inventory)**
42. Orphaned / obsolete function still deployed and invocable (no owner, no route, live URL)
43. Old function version / alias still live and reachable
44. Function on an EOL / deprecated runtime still serving traffic
45. Zombie Function URL — URL live for a function that's no longer used
46. Sensitive event flow still routed through a deprecated function
**H. Warm-container, global-scope & cold-start state (OWASP Serverless A8 — insecure shared resources)**
47. Global-scope variable holding per-request data (user/tenant/auth) bleeds into the next warm invocation
48. Secret / token cached in module scope and reused across tenants beyond its intended lifetime
49. `/tmp` reuse across warm invocations — file written by request A read by request B
50. Connection pool / client keyed wrong (tenant/region) reused across invocations
51. Cold-start initialization race — concurrent cold starts double-run one-time setup (duplicate side effect)
52. Mutable shared cache (in-memory) poisoned for subsequent invocations
**I. Edge-runtime specific** (model only if an edge runtime is present, else N/A)
53. KV namespace / D1 not partitioned per tenant — one tenant reads/writes another's keys
54. Durable Object id derived from untrusted input — addressing another tenant's object / actor
55. Geo / client-IP header trust — `cf-connecting-ip`/`cf-ipcountry`/`x-vercel-ip-*`/`true-client-ip` trusted from the client (geo bypass, IP spoof)
56. Edge cache poisoning — `caches.default`/CDN keyed on spoofable Host/query/header; authenticated response cached at edge
57. Edge isolate global-scope bleed — Worker module globals shared across requests in the same isolate (analogue of H47)
58. Subrequest / fan-out abuse at the edge (Worker making many outbound subrequests per request)
**J. Over-privileged execution role (behavioral — OWASP Serverless A3; identity deferred)**
59. One function's role touches resources unrelated to its job (lateral-movement surface)
60. Wildcard `Action`/`Resource` or `iam:PassRole`/`sts:AssumeRole` readable in IaC
61. Function can invoke / read / write across tenant or environment boundaries
> Rows 59–61 are modeled here only as a **behavioral/observed** footprint (what the code
> touches + what IaC shows). The authoritative **IAM identity & least-privilege audit is
> deferred** to ../20-secrets-and-cloud-iam/ — add the row, note the smell, link the model.
**K. Negative-space & evasion**
62. Direct Function URL access bypassing the API Gateway / WAF / SecureNow firewall in front
63. Non-HTTP trigger path bypassing all HTTP-layer controls (write to the bucket/queue directly)
64. Client-IP / geo header spoofing to evade per-IP limits at the edge (overlaps row 55)
65. Invoking a function below the rate threshold per source but high in aggregate (distributed across IPs/ASN)
66. Async/event-driven invocation that produces no synchronous response to observe (blind cost)
**L. Deferred — modeled in sibling models (reference, do not re-derive)**
67. HTTP API layer in front of functions — BOLA/BFLA, payload injection over HTTP, CORS, WAF evasion, generic rate-limits → [../14-api-security/](../14-api-security/api-security-threat-model-prompt.md)
68. Function execution-role IAM identity, IMDS/metadata credential theft, secret material in Secrets Manager/SSM/KMS → [../20-secrets-and-cloud-iam/](../20-secrets-and-cloud-iam/)
69. VPC/network reachability, container/runtime base-image CVEs, compute hardening → [../19-cloud-infrastructure/](../19-cloud-infrastructure/)
> For 67–69, add **one** matrix row each marked *"deferred — see linked model"*, and only note
> the serverless-observable symptom (e.g. invocation spike, SSRF-to-IMDS attempt surfacing as an
> `api.upstream.error`/`ssrf.blocked` event). The full detection/mitigation lives in those
> reports.
**M. Observable abuse (what telemetry actually catches — the workhorse rules)**
70. Invocation-rate spike on an HTTP-triggered function from one IP (denial-of-wallet onset)
71. Distributed invocation flood (many IPs within one ASN / botnet) on a function endpoint
72. 5xx amplification from one source (expensive-input duration abuse, crashing the function)
73. One IP touching an anomalous count of **distinct** function routes (function-fleet mapping)
74. Exploit-signature match in an HTTP-triggered request (SQLi/XSS/RCE) → instant block
75. Sudden traffic spike / new high-volume source onto a function URL (volumetric onset)
76. Hits to a deprecated / orphaned / non-prod function path surfacing in live traffic (inventory drift)
77. Repeated app-emitted `serverless.*` rejections (event-source validation, denial-of-wallet guard, poison message) from one source
> Items in groups A–M are mostly **OWASP Serverless Top 10** with denial-of-wallet rows tagged
> **API4**, deployment/inventory rows tagged **A05**, and integrity/artifact rows tagged
> **A08**. Each must be either a matrix row or an explicit N/A line with a reason — do not
> silently drop any item.
---
## Phase 3 — Audit the code (findings only — do not fix)
For **each** modeled threat that maps to real code or IaC, locate the responsible code and record
a **finding** for the report's "Code-level findings" section. A finding is:
- **Location** — `file:line` (clickable), the handler/function name and the trigger it serves,
or the IaC stanza (`serverless.yml` / `template.yaml` / `*.tf` / `wrangler.toml`).
- **Pattern** — quote the 1–8 relevant lines. State the missing control precisely (e.g. "handler
reads `event.Records[0].s3.object.key` and passes it into a shell `exec` — event-data injection";
"`functionUrlConfig: { authType: NONE }` — public unauthenticated invocation"; "no
`reservedConcurrency` on a paid-downstream function — denial-of-wallet"; "SQS event source with
no `onFailure`/DLQ and no `maxReceiveCount` — poison-message loop"; "`let currentUser` at module
scope reused across warm invocations — global-scope bleed"; "secrets in `environment:` block").
- **Why exploitable** — the concrete event an attacker sends (or the object/message they drop
into the trigger) and what they achieve (cost, RCE, cross-tenant read, partial state).
- **Severity** — critical / high / medium / low (impact × reachability).
- **Recommended fix (described, not applied)** — the specific change: e.g. "validate the event
source ARN/account before processing"; "treat all event payload as untrusted and
parameterize/escape into every sink"; "set `AuthType: AWS_IAM` or front the function with the
API/WAF"; "set `reservedConcurrency` and a budget alarm"; "add a DLQ + `maxReceiveCount`";
"move per-request state into the handler scope, never module scope"; "write `/tmp` files with a
unique per-invocation path and clean up"; "namespace KV/DO keys per tenant"; "move secrets to
Secrets Manager/SSM and reference at runtime"; "derive client IP/geo only from the trusted edge
header, not a client-spoofable one". Reference the secure pattern, not a code diff. **You must
not edit the codebase or IaC.**
If a control exists and is correct (event-source ARN validated, reserved concurrency set, DLQ
present, per-tenant KV namespacing, secrets in a vault, handler-scoped state), note it as a
**strength** — the posture must be honest. Absence of a control where the surface exists is
itself a finding ("no reserved concurrency on any function").
Look specifically for:
**Event-source trust flaws** — handlers that index into `event.Records`, `event.detail`,
`Sns.Message`, `body`, `dynamodb.NewImage` and feed fields into a sink without validating the
**source** (ARN/account/subscription) or the **payload** (schema, type). *Recommended fixes
must mention* event-source ARN/account allowlists, SNS subscription confirmation, message-
attribute/signature verification, strict schema validation of the event, and treating all event
data as untrusted.
**Downstream-sink injection flaws** — event fields flowing into SQL/NoSQL, `exec`/`spawn`,
`eval`, template engines, file paths, `lambda.invoke` target/payload, or outbound URLs.
*Recommended fixes must mention* parameterized queries, no shell, allowlisted invoke targets,
escaped log/queue writes, and SSRF allowlists for outbound calls from the function.
**Denial-of-wallet / concurrency flaws** — functions with no `reservedConcurrency` /
`maxInstances`, long timeouts on user-driven input, recursion into their own trigger, fan-out
with no cap, paid-downstream calls with no quota, missing budget alarms. *Recommended fixes must
mention* reserved/provisioned-concurrency caps, short timeouts, budget alarms, idempotency on
paid downstreams, recursion/self-trigger guards, and DLQ + `maxReceiveCount`.
**Deployment-config / integrity flaws** — secrets in `environment:`/layers, debug flags in prod,
`.env` bundled, source maps to edge, public Function URLs, broad CORS, unsigned/unverified
layers or artifacts. *Recommended fixes must mention* secret stores, prod debug-off, artifact
exclusion of `.env`, no source maps at edge, `AuthType`/WAF in front, tight CORS, and signed/
verified deployment artifacts (A08).
**Exception / poison-message flaws** — stack traces returned to callers, side effects without
transactional/idempotent guarding, swallowed re-invoke/re-queue, missing DLQ, secrets/event
payload logged on error. *Recommended fixes must mention* generic error responses, idempotent +
checkpointed side effects, DLQ + retry caps, partial-batch-failure reporting, and redacted error
logging.
**Warm-container / global-scope flaws** — module-scope variables holding per-request/tenant data,
secrets cached indefinitely in globals, `/tmp` reuse without unique paths, connection pools keyed
wrong, cold-start one-time setup not guarded against concurrent double-run. *Recommended fixes
must mention* handler-scoped state, no per-request data in module scope, unique `/tmp` paths with
cleanup, correctly-keyed pools, and idempotent initialization.
**Edge-isolation flaws** — KV/DO/D1 keys not namespaced per tenant, Durable Object ids from
untrusted input, client-spoofable geo/IP headers trusted, edge cache keyed on spoofable inputs,
authenticated responses cached at the edge, Worker globals holding per-request data. *Recommended
fixes must mention* per-tenant key namespacing, DO ids derived from trusted server-side identity,
trusting only the runtime-provided geo/IP (`request.cf` / verified edge header), cache-key
hardening + `private/no-store` on authenticated responses, and request-scoped state in isolates.
**Execution-role over-privilege (behavioral)** — code that touches resources unrelated to the
function's purpose; IaC with wildcard `Action`/`Resource`, `iam:PassRole`, `sts:AssumeRole`, or
cross-tenant/env resource ARNs. *Recommended fix*: least-privilege per-function roles —
**and reference the full identity audit in ../20-secrets-and-cloud-iam/** (do not re-derive the
IAM model here).
---
## Phase 4 — Map every modeled threat to SecureNow detection + mitigation
Classify each threat with exactly one coverage badge:
- 🟢 **COVERED** — detectable + mitigable with SecureNow today on telemetry that is already
flowing. For serverless this is the **HTTP-triggered** functions: invocation/denial-of-wallet
rate, distinct-route mapping, 5xx amplification, deprecated-path hits, and injection (system
signature rules + `instant.block`). These need **no instrumentation**.
- 🟡 **PARTIAL** — works after the customer adds instrumentation (`track('serverless.*')` /
`api.sensitive.flow` / `api.upstream.error` for **non-HTTP triggers**), or the detection is
inherently pattern-based / FP-prone (notify-only), or SecureNow can only *contain the abuser at
the edge* of the HTTP trigger while the real fix is **cloud/app config** (reserved concurrency,
event-source validation, role scoping, DLQ).
- 🔴 **GAP** — SecureNow cannot detect or mitigate this today (e.g. a purely internal
cross-account SNS injection that emits no traffic and no event, KV/DO isolation, global-scope
bleed). **Still include it**: give the cloud/app-level fix, then add the line *"Requires
SecureNow team — contact your SecureNow account contact (or in-dashboard support) to request
support for this threat."* Collect all gaps in the report's "Known gaps & SecureNow feature
requests" section.
> **Be honest about what is edge-detectable vs a cloud/app fix.** SecureNow sees **traffic** to
> HTTP-triggered functions and **events** the app emits; it contains actors via firewall /
> rate-limit / challenge / block / signature instant-block **on the HTTP path**. It is **blind**
> to a non-HTTP trigger (S3/SQS/SNS/streams/EventBridge) unless the function `track()`s a signal,
> and it **cannot** see IAM policy, reserved-concurrency settings, a missing DLQ, KV isolation, or
> global-scope bleed — those are **cloud/app config fixes**. SecureNow detects the *abuse* (the
> invocation flood, the cost spike, the event-validation rejection the app reports) and contains
> the source; the missing control is the primary fix. **Pair the edge-containment WITH the
> cloud/app fix on every such row.** A flaw that emits no traffic and no event is a 🔴 until the
> function emits the event in Phase 3's recommended fix.
Use **only** the SecureNow building blocks below. Never invent CLI flags, event names, or SQL
columns.
### 4a. Instrumentation (what serverless detections feed on)
The key fact: once an **HTTP-triggered** function runs under `securenow run` /
`securenow/register` / `securenow init`, **its HTTP traffic is captured automatically** — status
codes (incl. **429** and **5xx**), methods, paths, client IPs, response sizes — so invocation
rate, denial-of-wallet onset, distinct-route mapping, 5xx amplification, and deprecated-path hits
need **no events**. **Non-HTTP triggers produce no spans** — the function must `track()` the
signal.
Reuse the existing API events where they fit, and emit `serverless.*` only for non-HTTP-trigger
signals traffic can't see (all `securenow/events` `track()`, **never throws**):
```js
const { track } = require('securenow/events');
// Reuse: a sensitive business flow ran inside a function (feeds abuse-rate detection):
track('api.sensitive.flow', { userId, ip, attributes: { flow: 'checkout', function: 'process-order' } });
// Reuse: a consumed upstream/third-party (or paid downstream) call from the function failed (API10):
track('api.upstream.error', { ip, attributes: { upstream: 'sms', status: '502', function: 'send-otp' } });
// --- serverless.* : NON-HTTP-trigger signals (no traffic span exists for these) ---
// A non-HTTP function was invoked (feeds invocation-rate / denial-of-wallet for triggers traffic can't see):
track('serverless.invocation', { ip, attributes: { function: 'image-thumbnail', trigger: 's3|sqs|sns|dynamodb|kinesis|eventbridge|iot|schedule', duration_ms: '4200' } });
// The function REJECTED an event because its source/payload failed validation (high-signal, event-source injection):
track('serverless.event.rejected', { ip, attributes: { function: 'ingest', trigger: 'sns', reason: 'untrusted_source_arn|unconfirmed_subscription|bad_signature|schema_invalid|wrong_account', source: '<hash_or_arn_tail>' } });
// A denial-of-wallet guard tripped (concurrency/budget/duration ceiling hit inside the app):
track('serverless.cost.guard', { ip, attributes: { function: 'render', reason: 'concurrency_cap|duration_cap|fanout_cap|paid_downstream_quota|recursion_blocked' } });
// A poison message hit the retry/DLQ limit:
track('serverless.poison_message', { ip, attributes: { function: 'consumer', trigger: 'sqs|kinesis', queue: '<hash_or_name>', receive_count: '5' } });
// An unauthenticated / untrusted internal invocation was blocked (function-to-function trust):
track('serverless.invoke.unauthorized', { ip, attributes: { function: 'internal-billing', caller: '<hash_or_arn_tail>', reason: 'no_caller_auth|untrusted_caller|public_url' } });
// An outbound fetch from the function was blocked by an SSRF guard (reuse the API event, e.g. IMDS attempt):
track('ssrf.blocked', { ip, attributes: { function: 'fetch-preview', target_host: '169.254.169.254', reason: 'imds|link_local|private' } });
```
> Hash or omit any PII / secret before it becomes an attribute value (raw S3 keys, queue bodies,
> ARNs, tokens, IMDS responses) — see the Phase 1 **telemetry privacy & redaction** check.
> Attributes feed detection; they must not become a new leak path.
Recommended serverless event taxonomy — rules match these **exact strings**:
| Event | Emit when |
|---|---|
| `api.sensitive.flow` | a sensitive business flow runs inside a function (checkout/payout/export) |
| `api.upstream.error` | a consumed upstream / paid downstream call from a function fails or times out |
| `serverless.invocation` | a **non-HTTP** function is invoked (feeds invocation-rate / denial-of-wallet) |
| `serverless.event.rejected` | a function rejects an event for source/payload validation failure |
| `serverless.cost.guard` | a concurrency / duration / fan-out / paid-downstream / recursion ceiling trips |
| `serverless.poison_message` | a message reaches the retry/DLQ limit (poison-message loop) |
| `serverless.invoke.unauthorized` | an unauthenticated / untrusted internal invocation is blocked |
| `ssrf.blocked` | an outbound fetch from a function is denied by an SSRF/IMDS guard |
Custom `attributes` become queryable as `attributes_string['<key>']` (e.g.
`attributes_string['function']`, `attributes_string['trigger']`, `attributes_string['reason']`).
Ingest enriches every IP with **ASN/org** (`client.asn`, `client.as_org`) — enabling
botnet/datacenter-origin detection on the HTTP path with no extra code.
### 4b. Detection rules — SQL conventions
Two query shapes. Both **must** keep the tenant scope and **must** select an `ip` column
(per-IP aggregation is what remediation/auto-block keys on). **The tenant-scope column differs
by table** — using the wrong one fails with `UNKNOWN_IDENTIFIER`:
- **logs/events** (`signoz_logs.distributed_logs_v2`) → `resources_string['service.name'] IN (__USER_APP_KEYS__)`
- **traces/HTTP** (`signoz_traces.distributed_signoz_index_v3`) → `` `resource_string_service$$name` IN (__USER_APP_KEYS__) ``
When grouping by `ip`, add `HAVING ip != '' AND …` so rows with no client IP don't aggregate
into an empty-key bucket. Traffic columns proven available: `response_status_code`, `kind`
(server span = 2), `ts_bucket_start`, `attributes_string['http.target']`, the `client_ip`
coalesce below. Confirm any other column with a `--mode dry_run` before relying on it; OTEL SDK
versions vary (`http.method` vs `http.request.method`).
**Traffic-based — denial-of-wallet onset on an HTTP-triggered function (single IP, no events):**
```sql
WITH coalesce(nullIf(attributes_string['http.client_ip'], ''), nullIf(attributes_string['net.peer.ip'], ''), nullIf(attributes_string['network.peer.address'], '')) AS client_ip
SELECT client_ip AS ip,
count() AS invocations,
uniqExact(attributes_string['http.target']) AS distinct_functions
FROM signoz_traces.distributed_signoz_index_v3
WHERE `resource_string_service$$name` IN (__USER_APP_KEYS__)
AND timestamp >= now64(9) - INTERVAL 5 MINUTE
AND ts_bucket_start >= toUInt64(toUnixTimestamp(now() - INTERVAL 5 MINUTE)) - 1800
AND kind = 2
GROUP BY ip
HAVING ip != '' AND invocations >= 600
```
**Traffic-based — function-fleet mapping (many distinct function routes + client errors):**
```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_functions,
countIf(response_status_code IN ('400','401','403','404','405','422')) AS client_errors
FROM signoz_traces.distributed_signoz_index_v3
WHERE `resource_string_service$$name` IN (__USER_APP_KEYS__)
AND timestamp >= now64(9) - INTERVAL 15 MINUTE
AND ts_bucket_start >= toUInt64(toUnixTimestamp(now() - INTERVAL 15 MINUTE)) - 1800
AND kind = 2
GROUP BY ip
HAVING ip != '' AND distinct_functions >= 40 AND client_errors >= 30
```
**Traffic-based — 5xx amplification (duration-abuse / crashing input on a function):**
```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,
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 >= 50
```
**Traffic-based — deprecated / orphaned function path access (inventory drift, A05):**
```sql
WITH coalesce(nullIf(attributes_string['http.client_ip'], ''), nullIf(attributes_string['net.peer.ip'], ''), nullIf(attributes_string['network.peer.address'], '')) AS client_ip
SELECT client_ip AS ip,
count() AS hits,
uniqExact(attributes_string['http.target']) AS paths
FROM signoz_traces.distributed_signoz_index_v3
WHERE `resource_string_service$$name` IN (__USER_APP_KEYS__)
AND timestamp >= now64(9) - INTERVAL 30 MINUTE
AND ts_bucket_start >= toUInt64(toUnixTimestamp(now() - INTERVAL 30 MINUTE)) - 1800
AND kind = 2
AND (attributes_string['http.target'] LIKE '/v1/%' OR attributes_string['http.target'] LIKE '%-old%' OR attributes_string['http.target'] LIKE '%/internal%')
GROUP BY ip
HAVING ip != '' AND hits >= 1
```
**Events-based — non-HTTP invocation flood / denial-of-wallet (query the logs table):**
```sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['function'] AS function,
attributes_string['trigger'] AS trigger,
count() AS invocations
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] = 'serverless.invocation'
AND timestamp >= now() - INTERVAL 5 MINUTE
GROUP BY ip, function, trigger
HAVING ip != '' AND invocations >= 500
```
**Events-based — event-source validation rejected (event-source injection) — high-signal:**
```sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['function'] AS function,
attributes_string['reason'] AS reason,
count() AS rejections
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] = 'serverless.event.rejected'
AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, function, reason
HAVING ip != '' AND rejections >= 1
```
**Events-based — cost guard / poison message / unauthorized invoke (same logs-table shape):**
```sql
SELECT
attributes_string['http.client_ip'] AS ip,
attributes_string['event.type'] AS signal,
attributes_string['function'] AS function,
count() AS attempts
FROM signoz_logs.distributed_logs_v2
WHERE resources_string['service.name'] IN (__USER_APP_KEYS__)
AND attributes_string['event.type'] IN ('serverless.cost.guard','serverless.poison_message','serverless.invoke.unauthorized')
AND timestamp >= now() - INTERVAL 15 MINUTE
GROUP BY ip, signal, function
HAVING ip != '' AND attempts >= 1
```
The other serverless events follow the **same shape** — swap the `event.type` filter and the
threshold: `ssrf.blocked` (≥1 → IMDS/internal fetch attempt from a function),
`api.upstream.error` (≥20/10m → paid-downstream amplification or broken integration → notify),
`api.sensitive.flow` (rate-based, per `userId`/`ip` → business-flow abuse inside a function).
`serverless.cost.guard` / `serverless.poison_message` ≥1 are **notify-first** (route to the
runbook before auto-block — they may be a runaway integration, not an attacker).
**Injection on HTTP-triggered functions (catalog B over HTTP) — use the SecureNow system
signature rules, don't write SQL.** 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` and enable
`instant.block` rather than authoring duplicate pattern SQL. (Note: signatures only fire on the
**HTTP** path — event-source injection over S3/SQS/SNS is **not** signature-covered; that's an
app-side `serverless.event.rejected` event + validation fix.)
Useful attributes/columns: `event.type`, `http.client_ip`, `http.target`,
`response_status_code`, `kind`, `client.asn`, `client.as_org`, and your serverless attributes
(`function`, `trigger`, `reason`, `source`, `queue`, `caller`, `duration_ms`).
**Every detection is a complete, ready-to-copy command unit — never a fragment.** For each rule
emit, in order: (1) the SQL, (2) a line saving it to `rules/<name>.sql`, (3) the full
`securenow alerts rules create …` command, (4) the dry-run test. In the Markdown each is its own
fenced block so it copies cleanly. The exact flags **must** match `securenow alerts rules --help`
from Phase 0.5 (`--name` / `--sql` / `--apps` / `--severity` / `--schedule` / `--nlp`). Save each
rule's SQL to `rules/<name>.sql` so `--sql @rules/<name>.sql` resolves. **Note pre-existing /
system rules instead of duplicating them** — injection-class detections reference the system
signature rules + `instant.block`, never a hand-written copy.
```sql
-- rules/serverless-dow.sql
WITH coalesce(nullIf(attributes_string['http.client_ip'], ''), nullIf(attributes_string['net.peer.ip'], ''), nullIf(attributes_string['network.peer.address'], '')) AS client_ip
SELECT client_ip AS ip,
count() AS invocations,
uniqExact(attributes_string['http.target']) AS distinct_functions
FROM signoz_traces.distributed_signoz_index_v3
WHERE `resource_string_service$$name` IN (__USER_APP_KEYS__)
AND timestamp >= now64(9) - INTERVAL 5 MINUTE
AND ts_bucket_start >= toUInt64(toUnixTimestamp(now() - INTERVAL 5 MINUTE)) - 1800
AND kind = 2
GROUP BY ip
HAVING ip != '' AND invocations >= 600
```
```bash
securenow alerts rules create \
--name "Serverless: invocation flood / denial-of-wallet (single IP)" \
--sql @rules/serverless-dow.sql \
--apps <APP_KEY> \
--severity high \
--schedule "*/5 * * * *" \
--nlp "single IP invoking a function 600+ times in 5 minutes"
securenow alerts rules test <RULE_ID> --mode dry_run --wait # validate before it runs live
```
Reuse this unit shape for every rule above: distinct columns/thresholds per threat, but always
**SQL block → save-to-file → create → dry-run test**, with the flags verified in Phase 0.5.
#### 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 (denial-of-wallet
invocation / fleet-mapping / 5xx / deprecated-path counts), broad patterns, anomaly / volume rules,
anything tuned to YOUR function 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 (internal schedulers, load tests, partner batch),
then `--mode prod` to arm mitigation. Only **high-precision** rules (exploit-signature SQLi/XSS/RCE
matches on the HTTP path, exact-match IoCs, `serverless.event.rejected` / `serverless.invoke.unauthorized`
/ `ssrf.blocked` ≥1 high-signal app-emitted events, known-bad ASN hits) may go straight to `prod`.
In the report, **tag each rule `test-first` or `prod-ready`** and say why. (`securenow alerts rules
test <id> --mode dry_run --wait` is the separate one-off *query* validation — run it before either.)
### 4c. Mitigation commands (the only allowed remediation surface)
For serverless abuse, SecureNow **contains the actor at the edge of the HTTP trigger**; the
**cloud/app config fix** removes the underlying weakness (and is the *only* fix for non-HTTP
triggers, role scope, concurrency, and isolation). **Always pair them.** Once a threat is
confirmed, **choose the narrowest effective mitigation(s) from ALL of these** and combine them
(e.g. rate-limit a function URL + block the worst IPs + challenge a NAT egress). HTTP-fronted
functions can be **rate-limited / blocked / challenged / signature-instant-blocked at the edge**;
**role-scoping, reserved concurrency, event-source validation, DLQs, and KV/DO isolation are
cloud/app config fixes** SecureNow cannot apply. Re-check every command/flag against the installed
SDK in Phase 0.5 (`securenow <cmd> --help`); annotate `# requires securenow >= <ver>` if absent.
Scope by **app / env / route / method / IP / duration** to avoid hitting real users.
| # | Mitigation | Command (ready-to-copy) | Use / scope |
|---|---|---|---|
| 1 | **Free firewall (network)** | `securenow firewall enable --app <APP_KEY> --env production` · `securenow run --firewall-only` · test `securenow firewall test-ip <ip> --path /x --method GET` | 500k+ known-bad IPs, hourly refresh; drop scanners before they reach an HTTP-triggered function. 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 on **HTTP-triggered** functions (catalog B over HTTP). Don't duplicate pattern SQL. (Signatures fire on the HTTP path only; event-source injection over S3/SQS/SNS is app-side validation, not signatures.) |
| 3 | **IP block — global** | `securenow blocklist add <ip> --app <APP_KEY> --env production --reason "..."` | confirmed-malicious source, all routes. |
| 4 | **IP block — scoped to route (+ method)** | `securenow blocklist add <ip> --route /fn* --mode prefix --method ALL --app <APP_KEY> --env production --reason "..."` (`--mode exact\|prefix\|regex`, `--method GET\|POST\|…\|ALL`) | block an IP only on a function path; least collateral. |
| 5 | **IP block — temporary / time-boxed** | `securenow blocklist add <ip> --duration 24h --reason "..."` (`30m`,`24h`,`7d`) · reverse `securenow blocklist unblock <id> --reason "..."` | auto-expiring containment of a function-fleet scanner; audit-preserving unblock. |
| 6 | **Rate limit — per IP** | `securenow ratelimit add <ip> --limit 100 --window 1m --duration 24h --reason "..."` | throttle one abusive client across the app (denial-of-wallet onset). |
| 7 | **Rate limit — per route (all clients, per-IP budget)** | `securenow ratelimit add --route /api/fn --mode prefix --method GET --limit 60 --window 1m --key-by ip` | cap an expensive/abusable function URL for everyone, budgeted per IP. |
| 8 | **Rate limit — per route + IP** | `securenow ratelimit add <ip> --route /api/fn --mode exact --method POST --limit 5 --window 1m --duration 24h` · NL `securenow ratelimit from-text "rate limit /api/fn to 5/min for 24h" --yes` · test `securenow ratelimit test <ip> --path /api/fn --method POST` | precise throttle of one client on one function route. |
| 9 | **CAPTCHA / proof-of-work challenge** | `securenow challenge add --route /api/fn --difficulty 16 --clearance 30m` (route-wide) **or** `securenow challenge add <ip> --route /api/fn --difficulty 18 --clearance 30m` · test `securenow challenge test <ip> --path /api/fn --method GET` | bot-driven invocation cost abuse from **shared / NAT / CGNAT** egress — a human passes once, a script can't keep billing you. 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; actions include block / rate_limit / requireCaptcha. |
| 11 | **Session revocation** | `securenow revoke …` (SDK `securenow/sessions` `guard()` / `isRevoked()`) | session theft / account takeover surfacing through a function — kill the stolen session, not the IP. |
| 12 | **Trusted IP (suppress)** | `securenow trusted add <ip> --label "Internal batch / partner / scheduler"` | stop false positives from known-good infra (internal schedulers, partner batch jobs) — suppresses detection **and** mitigation. NOT deny-by-default. |
| 13 | **Allowlist (deny-by-default)** | `securenow allowlist add <ip> --label "..." --reason "..."` ⚠️ once any entry exists, ONLY listed IPs reach the app | lockdown of an internal/admin-only function surface. Never for a public function URL. |
| 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 denial-of-wallet/5xx rule quiet (load tests, legit batch) without weakening it. |
| 15 | **Cloud / app / config / code fix (PRIMARY for non-HTTP triggers, role scope, concurrency, isolation)** | *code or IaC change described in the Code-Findings report, never auto-applied* | the actual fix: validate event source/payload, set reserved concurrency + budget alarm, add DLQ + `maxReceiveCount`, scope the execution role least-privilege, namespace KV/DO per tenant, move per-request state out of module scope, set `AuthType: AWS_IAM` / front with WAF, move secrets to a vault, kill orphaned functions. SecureNow contains on the HTTP path; the fix removes. |
**Choosing per threat** — by **confidence**: exploit-signature/exact IoC on the HTTP path →
instant-block or block; probable bot invoking a function URL from shared egress → **challenge**;
noisy/legit-mixed traffic (denial-of-wallet thresholds, 5xx, fleet-mapping counts) → **rate-limit
(test-mode first)**; session compromise → **revoke**; known-good schedulers/batch → **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. **Edge
containment never replaces** setting reserved concurrency, validating the event source, scoping
the role, or fixing KV isolation — those are cloud/app config fixes (the Code-Findings report) and
SecureNow only buys you time on the HTTP path, and is the *only* fix for non-HTTP triggers. Always
pair an edge mitigation with the **cloud/app config fix**. Recommend **notify-only** (no auto
action) for FP-prone signals — 5xx spikes from a broken integration, legitimate bulk batch jobs,
internal load tests, and **all `serverless.cost.guard` / `serverless.poison_message`** signals (a
runaway integration looks identical to an attacker at first) — with the runbook command the human
runs after confirming.
### 4d. Testing every detection and mitigation
Only test against apps/environments the user owns; prefer `--env local`/staging. For synthetic
source IPs use TEST-NET ranges (`192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24`).
```bash
# Synthetic non-HTTP invocation flood — exercise the events-based denial-of-wallet rule:
for i in $(seq 1 600); do
securenow event send serverless.invocation --ip 203.0.113.50 \
--attrs function=image-thumbnail,trigger=s3,duration_ms=4200,test=true
done
# Synthetic event-source rejection (event-source injection):
securenow event send serverless.event.rejected --ip 203.0.113.50 \
--attrs function=ingest,trigger=sns,reason=untrusted_source_arn,test=true
# Synthetic cost guard / poison message / unauthorized invoke:
securenow event send serverless.cost.guard --ip 203.0.113.51 \
--attrs function=render,reason=concurrency_cap,test=true
securenow event send serverless.poison_message --ip 203.0.113.51 \
--attrs function=consumer,trigger=sqs,receive_count=5,test=true
securenow event send serverless.invoke.unauthorized --ip 203.0.113.51 \
--attrs function=internal-billing,reason=public_url,test=true
# Synthetic SSRF-to-IMDS attempt from a function:
securenow event send ssrf.blocked --ip 203.0.113.52 \
--attrs function=fetch-preview,target_host=169.254.169.254,reason=imds,test=true
# Validate a rule query without waiting for the schedule:
securenow alerts rules test <RULE_ID> --mode dry_run --wait
# Traffic-based rules (denial-of-wallet / fleet-mapping / deprecated paths) — generate spans, then check pipeline:
securenow test-span "threat-model.serverless.smoke"
securenow forensics "function invocations and 4xx/5xx by IP in the last hour" --env production
# Injection signatures on HTTP-triggered functions — confirm a payload triggers the system rule + instant block (staging):
# send a request carrying a benign-but-matching marker (e.g. ?q=' OR '1'='1) to a staging function URL,
# then verify the block fired:
securenow firewall test-ip 203.0.113.50 --app <APP_KEY> --env production
# Mitigation verification:
securenow ratelimit test 203.0.113.50 --path /api/fn --method GET
securenow challenge test 203.0.113.50 --path /api/fn --method GET
# Confirm + clean up:
securenow notifications list --limit 10
securenow blocklist list # then: securenow blocklist unblock <id> --reason "threat-model test"
securenow challenge list # then: securenow challenge remove <id>
```
Every 🟢/🟡 threat row in the report must have a concrete test recipe (commands + expected
outcome: which rule fires, which notification appears, what the mitigation does).
---
## Phase 5 — Write the FOUR deliverables (two tracks)
Write **four** files into `threat/18-serverless-and-edge/`: the **Detection & Mitigation** track
(`serverless-and-edge-detection-mitigation.md` + `.html`) and the **Code Findings &
Recommendations** track (`serverless-and-edge-code-findings.md` + `.html`). The tracks
**cross-link**: gap/instrumentation rows in the detection report link to the relevant code
finding; app/config fixes in the code report link back to the detection row they back. Both HTML
files are self-contained (inline CSS + copy-button JS, no network) and every command block in the
detection HTML carries a working Copy button.
### 5a. Detection & Mitigation report — sections (both `.md` and `.html`), in order
This is the **operational runbook**: what to run in SecureNow. No application code lives here —
code/config changes belong in the Code Findings report and are referenced by link.
1. **Executive summary** — stats line (threats modeled · covered · partial · gaps · rules to
create · mitigations), top 3 detectable serverless risks for this specific stack, installed
`securenow` version (from Phase 0.5) + app key + firewall state, and a one-line framework
coverage note (which OWASP Serverless Top 10 items + API4 / A05 / A08 are owned here vs
deferred to siblings).
2. **SDK & environment** — installed SDK version (from `node_modules/securenow`), app key(s),
environment, firewall state, existing rules / automations / challenge rules (from Phase 0),
and the **system signature rules** present (SQLi/XSS/RCE) that back HTTP-path injection
coverage.
3. **Threat → Detection → Mitigation matrix** — one row per modeled threat:
`# | Threat | Framework (SVL/API4/A05/A08) | Coverage 🟢/🟡/🔴 | Detection rule | Mode (test-first/prod-ready) | Signal (threshold+window) | Schedule | Sev | Mitigation`.
Severity ∈ {critical, high, medium, low}. Each row's **Mitigation** cell must pick **specific,
scoped** mitigation(s) from the §4c toolbox (e.g. "rate-limit `/api/fn` per-IP 60/1m **+** reserved
concurrency config fix" or "challenge function URL @diff18 **+** event-source validation fix") —
never a generic "block the IP." Each row's **Mode** cell tags the rule `test-first` (FP-prone:
denial-of-wallet / fleet-mapping / 5xx / deprecated-path heuristics — ship `--mode test`) or
`prod-ready` (high-precision: signature instant-block, exact IoC, high-signal `serverless.*`
rejections). Then the **"Out of scope"** N/A list and the **deferred-to-sibling** rows (HTTP API /
IAM / network) pointing to the other models. Injection rows reference the system signature rules +
`instant.block`, not duplicate SQL.
4. **Detection rules to create** — each as the **ready-to-copy command unit** from Phase 4 (SQL →
save to `rules/<name>.sql` → full `securenow alerts rules create …` → dry-run test). **Mark each
rule `test-first` or `prod-ready`**; for every `test-first` rule include the `--mode test` →
observe (3–7 days) → tune + `securenow fp` → `--mode prod` promotion step (from Phase 4b).
Note rules that already exist (from Phase 0, incl. system signature rules) instead of
duplicating them.
5. **Instrumentation the detections need** — only the `track('…')` events the rules above consume
(`serverless.*` / `api.sensitive.flow` / `api.upstream.error` / `ssrf.blocked` for **non-HTTP
triggers**), each as a copyable snippet; point to the **code-findings report** for *where*
(file:line) to add them.
6. **Mitigation mechanisms** — render the **full 15-row §4c mitigation toolbox table** (firewall ·
exploit-signature `instant.block` · block [global / route+method / temporary] · rate-limit
[IP / route / IP+route] · challenge · auto-block · session revocation · trusted · allowlist ·
fp · **cloud/app config fix**) + the "Choosing per threat" guidance + per-threat ready-to-copy
mitigation command + reversibility. Make explicit that the cloud/app config fix is **primary**
for non-HTTP triggers, role scope, concurrency caps, event-source validation, and edge
isolation, and the SecureNow control is **HTTP-path containment only**.
7. **Action plan (copy-paste, ordered)** — ① engage the firewall + enable signature instant-block
on the HTTP-triggered functions, ② add the `serverless.*` event instrumentation on non-HTTP
triggers where traffic is blind, ③ create rules — **create every FP-prone (`test-first`) rule in
`--mode test`** (detect-only) and add an explicit **"promote after N days" step** (`--mode prod`
once tuned, typically 3–7 days, after adding `securenow fp` exclusions for legitimate hits);
`prod-ready` rules go straight to `--mode prod`, ④ enable automations / challenge rules, ⑤ test,
⑥ verify in dashboard, ⑦ schedule the cloud/app-config-fix work (reserved concurrency,
event-source validation, DLQs, role scoping, KV isolation, secret-store migration, orphaned-
function cleanup) from the **code-findings report**. Real commands only, `<APP_KEY>` already
substituted.
8. **Testing & validation** — per-rule recipe from Phase 4d: `securenow event send …` /
`test-span` / dry-run + expected outcome + cleanup (TEST-NET IPs
`192.0.2`/`198.51.100`/`203.0.113`).
9. **Response runbooks** — for each notification type: what fired, how to confirm a true positive
(incl. distinguishing a runaway integration from an attacker for denial-of-wallet /
poison-message), the exact command to respond (rate-limit / challenge / block / signature /
cloud-config), the exact command to reverse.
10. **Known gaps & SecureNow feature requests** — every 🔴 threat: why it's not coverable today
(no traffic, no event, cloud-internal), interim cloud/app-config fix (link to the code
report), and the "contact the SecureNow team" line.
11. **Appendix** — resolved SDK/CLI version (from Phase 0.5), app key, environment, firewall
state, rule IDs created, date, and a link to the **code-findings** report.
### 5b. Code Findings & Recommendations report — sections (both `.md` and `.html`), in order
State at the top: *"Findings only — no application code or IaC was modified."*
1. **Executive summary** — findings by severity (critical / high / medium / low), top 3 code
risks for this stack, one-paragraph posture verdict.
2. **Serverless surface & inventory** — the Phase 1 inventory: function catalog
(name/handler/runtime/memory/timeout/concurrency/visibility) + trigger/event-source map +
cost & concurrency control table + downstream-sink list + function-to-function trust map +
deployment-config posture + exception/DLQ posture + inventory-drift list + warm-container /
global-scope findings + recursion/fan-out map + edge posture + telemetry redaction status.
3. **Threat catalog** — the exhaustive Phase 2 catalog (groups A–M), each tagged
OWASP Serverless / API4 / A05 / A08, modeled or explicit N/A. Include the deferred sibling rows.
4. **Code-level findings (audit — not applied)** — table
`# | Location (file:line) | Threat | Framework | Sev | Issue | Recommended fix`, each with the
quoted 1–8 line snippet and the described fix (never applied). Cross-link each finding to the
detection-report row it backs.
5. **Strengths** — controls already present and correct (event-source ARN validated, reserved
concurrency set, DLQ present, per-tenant KV namespacing, secrets in a vault, handler-scoped
state) — the posture must be honest.
6. **App / config fixes (primary remediation)** — the cloud/IaC/code changes that remove the root
cause (described, not applied): validate event source/payload, set reserved concurrency +
budget alarm, add DLQ + `maxReceiveCount`, scope the execution role least-privilege, namespace
KV/DO per tenant, move per-request state out of module scope, set `AuthType: AWS_IAM` / front
with WAF, move secrets to a vault, kill orphaned functions. Each linked to the
detection-report row it backs.
7. **Instrumentation recommendations** — the `track('…')` calls to add (`serverless.*` / reused
`api.*` / `ssrf.blocked`) and the **exact file:line** to add them so the detection rules light
up; this is the *where* for the detection report's section 5.
8. **Appendix** — files reviewed, resolved SDK version (from Phase 0.5), date, and a link to the
**detection & mitigation** report.
### HTML reports — SecureNow branding template (TWO skeletons, one per track)
Both HTML files are self-contained single files: inline CSS + the copy `<script>`, no external
fonts/scripts/network. They **share** the `<head>` (brand tokens + copy-button styles) and the
copy `<script>` at the end of `<body>`; change only the `<title>`, the sidebar subtitle, the
header title, and the section content (the body sections mirror the Markdown of **that track** —
5a or 5b). Wrap **every** command/SQL/code block as a `.cmd` (so it gets a Copy button) in the
detection HTML; the code-findings HTML may omit copy buttons on prose but still wraps any
example/fix command in `.cmd`. Extend content, do not restyle.
**Shared `<head>` + copy CSS + copy `<script>` (identical in both files — change only the title /
subtitle / header / sections):**
```html
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<title><!-- "Detection & Mitigation — Serverless & Edge — SecureNow" OR "Code Findings — Serverless & Edge — 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 · Serverless & Edge" OR "Code Findings · Serverless & Edge" --></div>
<!-- one <a href="#…"> per section of THIS track -->
</nav>
<main>
<header class="top"><h1><!-- report title for THIS track --></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 serverless & edge 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 — `serverless-and-edge-detection-mitigation.html`.** Set the title to
`Detection & Mitigation — Serverless & Edge — SecureNow`, the sidebar subtitle to
`Detection & Mitigation · Serverless & Edge`, the header `<h1>` to
`Serverless & Edge — Detection & Mitigation`, the five stat cards to **threats modeled · covered ·
partial · gaps — SecureNow team · rules to create**, and the `<section>` blocks to the **5a**
sections (1–11). Wrap **every** SQL/command block in the copyable wrapper below.
**Skeleton 2 — `serverless-and-edge-code-findings.html`.** Same shared head/CSS/script; set the
title to `Code Findings — Serverless & Edge — SecureNow`, the sidebar subtitle to
`Code Findings · Serverless & Edge`, the header `<h1>` to `Serverless & Edge — Code Findings`, the
five stat cards to **critical · high · medium · low · total findings**, and the `<section>` blocks
to the **5b** sections (1–8). Open with *"Findings only — no application code or IaC was
modified."* Wrap any example/fix command in `.cmd`; prose may omit copy buttons.
**Every SQL/command block uses the copyable wrapper** (so it gets a Copy button):
```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>
```
Badge usage: severity → `<span class="b crit|high|med|low">`, coverage →
`<span class="c cov|part|gap">COVERED|PARTIAL|GAP</span>`, framework tag →
`<span class="owasp">SVL-A4</span>` / `<span class="owasp">API4</span>` /
`<span class="owasp">A05</span>` (and `<span class="cwe">CWE-…</span>` where you cite a CWE),
mitigation type → `<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. Both files are self-contained (inline CSS/JS, no network) and the copy `<script>` is
present in both.
---
## 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 catalog item A1–M77 is either a matrix row or an explicit N/A line; each modeled row
carries its framework tag (OWASP Serverless Top 10 / API4 / A05 / A08, or "—").
- The serverless-specific threats are each modeled or explicit N/A: **event-source injection**
(S3/SQS/SNS/DynamoDB-streams/EventBridge/Kinesis/IoT), **over-privileged execution role**
(behavioral), **denial-of-wallet** (no concurrency cap), **downstream-sink injection** from
event data, **broken function-to-function trust / unauthenticated internal invocation**,
**insecure deployment config** (secrets in env/layers, debug flags, public Function URLs),
**improper exception handling** (verbose errors / partial execution / poison-message loops),
**orphaned/obsolete functions** (inventory drift), **cold-start race / shared `/tmp` /
global-scope bleed**, **recursion / fan-out loops**, **missing per-function rate/concurrency
limits + reserved-concurrency starvation**, and **edge-specific** (KV/DO isolation, geo/header
trust, edge cache poisoning) — each with the matching `serverless.*` / reused `api.*` /
`ssrf.blocked` event where detection needs instrumentation.
- The HTTP API layer in front of functions, function execution-role IAM identity / IMDS / secret
material, and network/compute/container posture are **deferred** to the numbered sibling models
(rows present, linked by path `../14-api-security/`, `../20-secrets-and-cloud-iam/`,
`../19-cloud-infrastructure/`, not re-derived) — this model does not duplicate them.
- Every matrix row has a concrete signal (threshold + window), severity, and mitigation — no
"monitor for suspicious activity" filler.
- Every detection rule is a **complete copyable unit** (SQL → `rules/<name>.sql` → full
`securenow alerts rules create …` → dry-run test); flags match `alerts rules --help` from
Phase 0.5. Pre-existing / system rules are noted, not duplicated.
- Every code finding in the code-findings report has a `file:line`, the quoted snippet, and a
described fix — and **no application code or IaC was modified** (this is an audit).
- Every detection SQL keeps `__USER_APP_KEYS__` scoping (correct table column —
`` `resource_string_service$$name` `` for traces vs `resources_string['service.name']` for
logs), the `client_ip` coalesce, and selects an `ip` column with `HAVING ip != ''`; traffic
queries keep the `ts_bucket_start` + `kind = 2` guards and use `--mode dry_run` before relying
on any unconfirmed column.
- Injection coverage on the HTTP path references the **system signature rules + `instant.block`**,
not duplicate pattern SQL; event-source injection over non-HTTP triggers is handled by the
app-side `serverless.event.rejected` event + validation fix (not signatures).
- Coverage badges are **honest** about MED native coverage: HTTP-triggered functions get traffic
spans automatically (🟢 for invocation/cost/5xx/distinct-route); non-HTTP triggers are 🟡 and
need `track()`; role-scoping, concurrency caps, event-source validation, and KV/DO isolation
are cloud/app config (🟡 edge-containment + config fix, or 🔴 if no traffic and no event).
- Detection vs. fix is honest and **paired**: where SecureNow can only contain the actor on the
HTTP path, the row pairs the control with the **cloud/app config fix**, which is the primary
fix for non-HTTP triggers, concurrency, role scope, and isolation.
- 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 · cloud/app-fix), and **each modeled
threat's matrix row selects specific, scoped mitigation(s) from it** — never a generic "block the IP."
- **Every false-positive-prone rule is tagged `test-first`** and carries the `--mode test` → observe
(3–7 days) → `--mode prod` promotion workflow; only high-precision rules are `prod-ready`. The action
plan creates the test-first rules in `--mode test` and has an explicit "promote after N days" step.
- Every 🔴 gap appears in the gaps section with an interim cloud/app-config fix **and** the
"contact the SecureNow team" line.
- Only commands, flags, events, and SQL columns from this prompt's building blocks appear
(including `securenow challenge …`, `firewall`, and the `serverless.*` / `api.sensitive.flow` /
`api.upstream.error` / `ssrf.blocked` events).
- The action plan runs top-to-bottom with `<APP_KEY>` substituted in.
- **Four** files are written to `threat/18-serverless-and-edge/`
(`serverless-and-edge-detection-mitigation.md` + `.html`,
`serverless-and-edge-code-findings.md` + `.html`); the two tracks **cross-link** (detection
gap/instrumentation rows → code findings; code app/config fixes → detection rows). The split is
honest: SecureNow-runnable detections/mitigations live in the Detection report, code/config
changes in the Code-Findings report, and nothing security-relevant is dropped.
- Both HTML files are **self-contained** (inline CSS + copy `<script>`, no CDN / fonts / network),
the stats cards match the table/findings counts of their track, and **every command block in
the detection HTML has a working Copy button** (wrapped in `.cmd`).
- A one-line summary is printed back to the user: per-track file paths, threat counts,
rules-to-create count, code findings by severity, gaps, and the resolved SDK version.
<!-- ════════════════ END OF PROMPT ════════════════ -->