penthera 1.2.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +269 -0
- package/README.md +493 -45
- package/docs/METHODOLOGY.md +321 -0
- package/docs/compliance-cra.md +141 -0
- package/docs/compliance-nis2.md +3 -1
- package/docs/evidence.md +158 -0
- package/docs/examples/github-actions-deps.yml +105 -0
- package/docs/examples/github-actions-scan.yml +1 -1
- package/docs/examples/pr-dependency-review.yml +56 -0
- package/docs/funding/due-diligence-checklist.md +79 -0
- package/docs/funding/nlnet-ngi-zero-draft.md +273 -0
- package/docs/funding/partner-one-pager.md +84 -0
- package/docs/funding/sponsor-tiers.md +81 -0
- package/docs/index.md +12 -1
- package/docs/local-loop.md +276 -0
- package/docs/plan.md +100 -0
- package/docs/references.bib +539 -0
- package/docs/references.md +77 -0
- package/docs/release-plan.md +228 -0
- package/lib/adaptive-probes.js +8 -5
- package/lib/agents/discover.js +384 -0
- package/lib/agents/hidden-content.js +387 -0
- package/lib/agents/index.js +286 -0
- package/lib/agents/lock.js +115 -0
- package/lib/agents/owasp.js +83 -0
- package/lib/agents/rules.js +376 -0
- package/lib/compliance/cra.js +525 -0
- package/lib/crawler.js +5 -15
- package/lib/data/retire-jsrepository.json.gz +0 -0
- package/lib/data/retire-jsrepository.meta.json +7 -0
- package/lib/deps/capabilities.js +523 -0
- package/lib/deps/diff.js +84 -0
- package/lib/deps/index.js +440 -0
- package/lib/deps/license.js +329 -0
- package/lib/deps/lockfile.js +109 -0
- package/lib/deps/online.js +132 -0
- package/lib/deps/popular-packages.json +1 -0
- package/lib/deps/report.js +190 -0
- package/lib/deps/source.js +171 -0
- package/lib/deps/spdx.json +1 -0
- package/lib/deps/typosquat.js +113 -0
- package/lib/fix/apply.js +95 -0
- package/lib/fix/diff.js +199 -0
- package/lib/fix/index.js +129 -0
- package/lib/fix/model.js +108 -0
- package/lib/fix/recipes/cookies.js +130 -0
- package/lib/fix/recipes/cors.js +116 -0
- package/lib/fix/recipes/headers.js +250 -0
- package/lib/fix/recipes/rate-limit.js +211 -0
- package/lib/fix/recipes/secrets.js +110 -0
- package/lib/fix/util.js +231 -0
- package/lib/fuzzer.js +14 -8
- package/lib/hook/index.js +312 -0
- package/lib/hook/policy.js +62 -0
- package/lib/injections.js +19 -15
- package/lib/install/index.js +271 -0
- package/lib/install/policy.js +125 -0
- package/lib/jurisdictions/nl.json +9 -0
- package/lib/memory/history.js +52 -0
- package/lib/memory/index.js +111 -0
- package/lib/memory/repo-suppress.js +64 -0
- package/lib/memory/store.js +325 -0
- package/lib/memory/views.js +198 -0
- package/lib/params.js +4 -12
- package/lib/provenance.js +2 -0
- package/lib/recon.js +12 -20
- package/lib/references.js +31 -0
- package/lib/references.json +374 -0
- package/lib/remediation/index.js +28 -0
- package/lib/retirejs.js +50 -22
- package/lib/templates.js +7 -10
- package/lib/whitebox/aibom.js +384 -0
- package/lib/whitebox/frameworks.js +1 -1
- package/lib/whitebox/sbom.js +45 -10
- package/lib/whitebox/secrets.js +11 -1
- package/package.json +17 -4
- package/skills/penthera/SKILL.md +132 -22
- package/src/agent/git.js +87 -0
- package/src/agent/loop.js +519 -0
- package/src/agent/tests.js +48 -0
- package/src/cli/agent.js +183 -0
- package/src/cli/deps.js +104 -0
- package/src/cli/evidence.js +67 -0
- package/src/cli/fix.js +191 -0
- package/src/cli/hook.js +131 -0
- package/src/cli/install.js +225 -0
- package/src/cli/memory.js +146 -0
- package/src/cli/merge-results.js +2 -0
- package/src/cli/report-outputs.js +72 -0
- package/src/cli/run-scan.js +2 -0
- package/src/cli.js +212 -8
- package/src/report/audience.js +270 -0
- package/src/report/evidence.js +253 -0
- package/src/report/markdown.js +65 -2
- package/src/report/model.js +212 -0
- package/src/report/template.js +175 -0
- package/src/report/timeline.js +145 -0
- package/src/reporter.js +109 -5
- package/src/scan-url.js +31 -7
- package/src/utils/http.js +252 -11
- package/src/utils/url.js +23 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# Methodology
|
|
2
|
+
|
|
3
|
+
This document describes what Penthera tests, how it decides that something is a
|
|
4
|
+
finding, how sure it is, and where its limits are. Each design choice points to
|
|
5
|
+
the research or standard it rests on. Citation keys such as `[bau2010state]`
|
|
6
|
+
refer to [references.md](references.md) and [references.bib](references.bib).
|
|
7
|
+
|
|
8
|
+
Measured accuracy on the bundled benchmark is in
|
|
9
|
+
[bench/RESULTS.md](../bench/RESULTS.md); the method behind those numbers is in
|
|
10
|
+
[bench/README.md](../bench/README.md).
|
|
11
|
+
|
|
12
|
+
## Contents
|
|
13
|
+
|
|
14
|
+
- [Threat model](#threat-model)
|
|
15
|
+
- [Black-box and white-box: what each can find](#black-box-and-white-box-what-each-can-find)
|
|
16
|
+
- [Confidence model](#confidence-model)
|
|
17
|
+
- [False-positive controls](#false-positive-controls)
|
|
18
|
+
- [Non-destructive by default](#non-destructive-by-default)
|
|
19
|
+
- [Offline and no telemetry](#offline-and-no-telemetry)
|
|
20
|
+
- [How new attack techniques are adopted](#how-new-attack-techniques-are-adopted)
|
|
21
|
+
- [Citations in reports](#citations-in-reports)
|
|
22
|
+
- [Known limitations](#known-limitations)
|
|
23
|
+
|
|
24
|
+
## Threat model
|
|
25
|
+
|
|
26
|
+
Penthera is built for small teams shipping web apps and APIs, often with AI
|
|
27
|
+
coding tools, who need a first line of evidence before a human review or a
|
|
28
|
+
paid penetration test. The threat model follows the structure in Shostack
|
|
29
|
+
[shostack2014threat]: what are we building, what can go wrong, what do we do
|
|
30
|
+
about it, and did we do a good job.
|
|
31
|
+
|
|
32
|
+
**Assets.** User data behind the API, credentials and API keys (including AI
|
|
33
|
+
provider keys), the session and identity layer, and the integrity of the code
|
|
34
|
+
and dependencies that get deployed.
|
|
35
|
+
|
|
36
|
+
**Adversaries in scope.**
|
|
37
|
+
|
|
38
|
+
1. A remote, unauthenticated attacker on the internet who can send arbitrary
|
|
39
|
+
HTTP requests to the deployed app. This is what the black-box scan models.
|
|
40
|
+
2. An authenticated user trying to reach another user's data (broken object
|
|
41
|
+
level authorization, the top item in the OWASP API Security Top 10
|
|
42
|
+
[owasp2023api]). Penthera tests this when given credentials.
|
|
43
|
+
3. Anyone who obtains a copy of the source: a leaked repository, a public fork,
|
|
44
|
+
a compromised CI log. Meli et al. found secrets leaked in over 100,000
|
|
45
|
+
public GitHub repositories, with thousands of new, unique secrets leaked
|
|
46
|
+
every day [meli2019bad]. This is what the
|
|
47
|
+
white-box secret scan models.
|
|
48
|
+
4. A supply-chain attacker who publishes or compromises a dependency
|
|
49
|
+
[ladisa2023sok, ohm2020backstabber, zimmermann2019small], including
|
|
50
|
+
packages that code-generating models invent and attackers then register
|
|
51
|
+
[spracklen2025package].
|
|
52
|
+
|
|
53
|
+
**Out of scope.** Physical access, insider threats with production access,
|
|
54
|
+
denial of service, social engineering, and anything that needs exploitation
|
|
55
|
+
beyond a single benign probe. Penthera does not try to replace a human tester;
|
|
56
|
+
it tries to find the issues a scanner can find reliably and say clearly which
|
|
57
|
+
ones it cannot judge.
|
|
58
|
+
|
|
59
|
+
**Penthera itself is in the threat model.** A security tool runs with the
|
|
60
|
+
user's credentials and network position, so a compromised scanner is a
|
|
61
|
+
compromised machine. Thompson's point that you cannot trust code you did not
|
|
62
|
+
build yourself [thompson1984reflections] applies to scanners and their rule
|
|
63
|
+
feeds. This is why Penthera fetches no rules at run time
|
|
64
|
+
([below](#how-new-attack-techniques-are-adopted)).
|
|
65
|
+
|
|
66
|
+
STRIDE, the threat categories that Shostack's book builds on, originates at
|
|
67
|
+
Microsoft [kohnfelder1999threats]. Penthera's probes map onto it roughly as:
|
|
68
|
+
spoofing (auth, JWT, OAuth, email SPF/DMARC), tampering (injection, CORS),
|
|
69
|
+
repudiation (not tested), information disclosure (exposed files, secrets,
|
|
70
|
+
verbose errors), denial of service (not tested, only rate limiting is
|
|
71
|
+
checked), and elevation of privilege (IDOR/BOLA, client-side-only auth).
|
|
72
|
+
|
|
73
|
+
## Black-box and white-box: what each can find
|
|
74
|
+
|
|
75
|
+
Penthera runs two kinds of analysis and reports them together.
|
|
76
|
+
|
|
77
|
+
### Black-box (URL scan)
|
|
78
|
+
|
|
79
|
+
The scanner sees only HTTP responses. Its tests are those of the OWASP Web
|
|
80
|
+
Security Testing Guide [owaspwstg42]; each probe's WSTG IDs are listed in
|
|
81
|
+
[owasp-wstg-coverage.md](owasp-wstg-coverage.md).
|
|
82
|
+
|
|
83
|
+
What black-box testing finds well: properties that are visible in every
|
|
84
|
+
response (missing security headers, TLS configuration, cookie flags, CORS
|
|
85
|
+
policy), files and interfaces that should not be public, and injection flaws
|
|
86
|
+
whose effect shows up in the response or its timing. Protocol checks follow
|
|
87
|
+
the relevant specifications: HSTS [hodges2012rfc6797], JWT
|
|
88
|
+
[jones2015rfc7519, sheffer2020rfc8725], and OAuth redirect and PKCE handling
|
|
89
|
+
[lodderstedt2025rfc9700, sakimura2015rfc7636, fett2016comprehensive].
|
|
90
|
+
|
|
91
|
+
What it misses, and why. Two classic evaluations of black-box scanners point
|
|
92
|
+
the same way. Bau et al. tested eight commercial scanners against a custom
|
|
93
|
+
vulnerable application and older versions of real ones. As a group the tools
|
|
94
|
+
did reasonably well, but second-order flaws, stored XSS and stored SQL
|
|
95
|
+
injection, were missed by many of them [bau2010state]. Doupé et al. tested
|
|
96
|
+
eleven commercial and open-source scanners against WackoPicko, a realistic
|
|
97
|
+
purpose-built app with sixteen planted flaws. Eight of the sixteen were missed
|
|
98
|
+
by every scanner, including stored SQL injection, multi-step stored XSS,
|
|
99
|
+
forceful browsing and a business logic flaw. They concluded that crawling
|
|
100
|
+
matters as much as detection: a scanner cannot test a page it never reaches,
|
|
101
|
+
and losing track of state (logins, multi-step forms) is a main reason it
|
|
102
|
+
doesn't [doupe2010johnny].
|
|
103
|
+
|
|
104
|
+
Penthera's design responds to each point:
|
|
105
|
+
|
|
106
|
+
- **Crawl coverage.** Endpoint discovery uses a wordlist plus the app's own
|
|
107
|
+
OpenAPI document when one is exposed, and records exactly which probes ran.
|
|
108
|
+
The WSTG coverage table in every report lists tests as run or not run, so a
|
|
109
|
+
missing check is never presented as a pass.
|
|
110
|
+
- **State and logic.** Penthera does not claim to find business logic flaws,
|
|
111
|
+
which no scanner in the Doupé et al. study found either.
|
|
112
|
+
IDOR/BOLA results are labelled `needs-human-review` because a scanner cannot
|
|
113
|
+
know whether user A is allowed to read object B.
|
|
114
|
+
- **Stored and second-order flaws.** Not tested. They need a write followed by
|
|
115
|
+
a read elsewhere, which conflicts with the non-destructive default.
|
|
116
|
+
|
|
117
|
+
### White-box (repository scan)
|
|
118
|
+
|
|
119
|
+
The scanner reads source files. It finds hardcoded secrets, route handlers and
|
|
120
|
+
their authentication boundaries, and risky code patterns.
|
|
121
|
+
|
|
122
|
+
Secret detection uses provider-specific prefixes (for example `AKIA` for AWS,
|
|
123
|
+
`sk-ant-` for Anthropic) plus a small number of assignment patterns such as
|
|
124
|
+
`api_key = "..."` with a placeholder filter. Meli et al. took the same
|
|
125
|
+
conservative route, restricting detection to private keys and API keys with
|
|
126
|
+
distinctive formats [meli2019bad]. The cost is recall: a secret with no
|
|
127
|
+
recognisable format is invisible to this approach. SecretBench, a labelled
|
|
128
|
+
dataset of real secrets across many formats, is the natural yardstick for that
|
|
129
|
+
gap [basak2023secretbench]. The benchmark here includes an unprefixed signing
|
|
130
|
+
key that Penthera misses for this reason.
|
|
131
|
+
|
|
132
|
+
What white-box scanning cannot see: runtime configuration (headers added by a
|
|
133
|
+
CDN, a WAF in front of the app), secrets injected at deploy time, and anything
|
|
134
|
+
in dependencies that is only known from a vulnerability database.
|
|
135
|
+
|
|
136
|
+
### Combining them
|
|
137
|
+
|
|
138
|
+
Running both against the same app catches cases neither sees alone: a route
|
|
139
|
+
the repository marks as public that the black-box scan confirms is reachable,
|
|
140
|
+
or a header the code sets that the edge strips. The report keeps the source of
|
|
141
|
+
each finding (`source`, `method`) so a reader can tell which side produced it.
|
|
142
|
+
|
|
143
|
+
## Confidence model
|
|
144
|
+
|
|
145
|
+
Severity says how bad a finding would be if real. Confidence says how likely it
|
|
146
|
+
is to be real. Penthera reports both, and never lets a heuristic look like a
|
|
147
|
+
fact. The labels are defined in
|
|
148
|
+
[lib/provenance.js](../lib/provenance.js):
|
|
149
|
+
|
|
150
|
+
| Label | Meaning | Typical sources |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `confirmed` | Deterministic evidence. The header is absent, the certificate expired, the key matches a provider format. | Header audit, TLS check, cookie audit, secret scan, OpenAPI exposure |
|
|
153
|
+
| `likely` | A strong signal with a small false-positive chance. | Injection probes with a verified canary, auth and JWT probes, framework checks |
|
|
154
|
+
| `potential` | A heuristic or one-shot signal that needs corroboration. | Fuzzer, adaptive probes |
|
|
155
|
+
| `needs-human-review` | Exploitability cannot be judged from outside. | IDOR/BOLA, client-side-only auth |
|
|
156
|
+
|
|
157
|
+
Every finding also carries `source`, `method` and `collected_at`, and these
|
|
158
|
+
travel into SARIF so the label survives into GitHub code scanning.
|
|
159
|
+
|
|
160
|
+
The benchmark reports precision per confidence label, which is the check that
|
|
161
|
+
the labels mean what they say. On the current run all `likely` and
|
|
162
|
+
`needs-human-review` findings were true positives, the one `potential` finding
|
|
163
|
+
was a false positive, and the two `confirmed` false positives are cases where
|
|
164
|
+
the evidence is real but the judgement is wrong (a health endpoint that is
|
|
165
|
+
public by design, AWS's published example key). See
|
|
166
|
+
[bench/RESULTS.md](../bench/RESULTS.md).
|
|
167
|
+
|
|
168
|
+
## False-positive controls
|
|
169
|
+
|
|
170
|
+
Black-box scanners lose trust through false positives faster than through
|
|
171
|
+
misses. Specific controls in the probes:
|
|
172
|
+
|
|
173
|
+
- **Randomized canaries.** The SSTI probe multiplies two random four-digit
|
|
174
|
+
numbers and looks for the seven- or eight-digit product, and only when the
|
|
175
|
+
raw payload and the literal expression are absent from the response
|
|
176
|
+
([lib/injections.js](../lib/injections.js), `buildSstiProbes`). The common
|
|
177
|
+
fixed `7*7 = 49` canary fires on any page that happens to contain "49". XSS
|
|
178
|
+
probes use a per-probe random reflection marker (`makeCanary`).
|
|
179
|
+
- **Context before payload.** The XSS probe first checks where its marker is
|
|
180
|
+
reflected (HTML body, attribute, script) and only sends payloads that would
|
|
181
|
+
execute in that context, then checks they came back unencoded. This is the
|
|
182
|
+
approach of context-aware tools such as Dalfox.
|
|
183
|
+
- **Baseline comparison.** Error-based SQL injection is only reported when the
|
|
184
|
+
database error appears in the injected response and not in the baseline.
|
|
185
|
+
- **Timing re-sampling.** A single slow response proves nothing: garbage
|
|
186
|
+
collection, cold starts and network jitter all look like an injected sleep.
|
|
187
|
+
Time-based SQL and command injection candidates are re-sent and only
|
|
188
|
+
reported when the median delay holds (`confirmTimingDelay`). Crosby et al.
|
|
189
|
+
showed that network jitter dominates single remote timing measurements and
|
|
190
|
+
that filtering over many samples is what makes them usable
|
|
191
|
+
[crosby2009opportunities], building on Kocher's original timing attacks
|
|
192
|
+
[kocher1996timing]. Penthera's delays are seconds, not microseconds, so a
|
|
193
|
+
median of three samples is enough.
|
|
194
|
+
- **Host-checked redirects.** An open redirect is reported only if the
|
|
195
|
+
`Location` header actually resolves to the attacker host, not when the
|
|
196
|
+
payload merely appears in a same-site redirect's query string.
|
|
197
|
+
- **Context-aware suppression.** On localhost and private hosts, findings that
|
|
198
|
+
do not apply (no HTTPS, missing `Secure` on cookies) are suppressed.
|
|
199
|
+
|
|
200
|
+
The benchmark's decoy target exercises each of these with an endpoint built to
|
|
201
|
+
fool a naive scanner. Writing it found one real problem in the decoy itself: a
|
|
202
|
+
"safe" redirect that accepted `/\evil.com`, which browsers treat as
|
|
203
|
+
`//evil.com`. Penthera reported it correctly.
|
|
204
|
+
|
|
205
|
+
## Non-destructive by default
|
|
206
|
+
|
|
207
|
+
The `quick` and `standard` profiles send only requests a normal browser or API
|
|
208
|
+
client would send: GETs, `OPTIONS`, and invalid login attempts. They do not
|
|
209
|
+
send attack payloads, create or modify data, or exploit anything they find.
|
|
210
|
+
|
|
211
|
+
Payload-based testing (`--deep`, `--fuzz`, `--adaptive`, or `--profile deep`)
|
|
212
|
+
is opt-in and gated behind a confirmation prompt. Even then each probe sends a
|
|
213
|
+
benign marker (a random string, arithmetic, a short sleep, `id`) rather than a
|
|
214
|
+
working exploit, and stops at the first confirmation per endpoint. The
|
|
215
|
+
adaptive engine may send unexpected HTTP methods, including `DELETE`, to
|
|
216
|
+
routes with placeholder IDs; it is therefore never part of a default run.
|
|
217
|
+
|
|
218
|
+
This follows Saltzer and Schroeder's fail-safe defaults and least privilege
|
|
219
|
+
[saltzer1975protection]: the default does the least that is still useful, and
|
|
220
|
+
anything riskier needs an explicit decision. NIST SP 800-115 makes the same
|
|
221
|
+
distinction between non-intrusive assessment and penetration testing, and
|
|
222
|
+
recommends written rules of engagement before the latter
|
|
223
|
+
[scarfone2008sp800115].
|
|
224
|
+
|
|
225
|
+
## Offline and no telemetry
|
|
226
|
+
|
|
227
|
+
Penthera does not phone home. It sends no usage data, analytics or crash
|
|
228
|
+
reports, and it has no accounts. Reports are written to local files.
|
|
229
|
+
|
|
230
|
+
Network traffic goes to the target you name, with two documented exceptions:
|
|
231
|
+
|
|
232
|
+
1. `--recon` (and the `deep` profile) queries public OSINT sources (crt.sh,
|
|
233
|
+
the Wayback Machine, AlienVault OTX) with the target domain. It is skipped
|
|
234
|
+
for localhost.
|
|
235
|
+
2. The Retire.js check downloads the public Retire.js vulnerability database
|
|
236
|
+
from GitHub to compare JavaScript library versions locally. This is data,
|
|
237
|
+
not code: it is parsed as JSON and never executed. It is the one place a
|
|
238
|
+
detection input arrives at run time, and the roadmap's `--offline` egress
|
|
239
|
+
guard replaces it with a vendored copy shipped in releases.
|
|
240
|
+
|
|
241
|
+
The benchmark enforces offline operation mechanically: it wraps `fetch` so any
|
|
242
|
+
request to a non-loopback host is refused and counted, and fails if the count
|
|
243
|
+
is not zero.
|
|
244
|
+
|
|
245
|
+
These choices follow economy of mechanism and complete mediation
|
|
246
|
+
[saltzer1975protection]: fewer network paths means fewer things to audit, and
|
|
247
|
+
a tool that talks only to the target is easy to reason about in a regulated
|
|
248
|
+
environment.
|
|
249
|
+
|
|
250
|
+
## How new attack techniques are adopted
|
|
251
|
+
|
|
252
|
+
Attack techniques change quickly. In the last few years: indirect prompt
|
|
253
|
+
injection against LLM-integrated apps [greshake2023signed, perez2022ignore,
|
|
254
|
+
liu2023prompt], package hallucination and slopsquatting
|
|
255
|
+
[spracklen2025package], and attacks on agent tooling and MCP servers
|
|
256
|
+
catalogued by OWASP [owasp2024llmtop10, owasp2025agentic, owasp2025mcptop10]
|
|
257
|
+
and MITRE ATLAS [mitreatlas]. A scanner that does not keep up becomes a
|
|
258
|
+
false sense of security.
|
|
259
|
+
|
|
260
|
+
Penthera keeps up through reviewed releases, not through live updates:
|
|
261
|
+
|
|
262
|
+
- **Rules ship as code in a release.** New probes, patterns and templates are
|
|
263
|
+
added by pull request, reviewed, tested against the benchmark, and published
|
|
264
|
+
in a versioned release with a CHANGELOG entry. There is no rule server, no
|
|
265
|
+
background update, and no command that fetches rules from the internet.
|
|
266
|
+
- **Every new rule carries a source and a date.** A rule added after v1.2.0
|
|
267
|
+
records where the technique comes from (a paper, an advisory, an OWASP or
|
|
268
|
+
ATLAS entry, an incident write-up) and the date it was added. Categories are
|
|
269
|
+
already linked to their sources in [lib/references.json](../lib/references.json);
|
|
270
|
+
the built-in rules that predate this policy are being backfilled.
|
|
271
|
+
- **Every new rule comes with a benchmark case.** A detection rule is added
|
|
272
|
+
together with a labelled true positive and, where a false positive is
|
|
273
|
+
plausible, a decoy in [bench/](../bench/). The CI floor in the benchmark
|
|
274
|
+
stops a rule that adds false positives.
|
|
275
|
+
- **Your own templates stay local.** `--nuclei <path>` and `--templates <dirs>`
|
|
276
|
+
load YAML templates from directories you choose. Penthera does not download
|
|
277
|
+
them.
|
|
278
|
+
|
|
279
|
+
Why not auto-update? A rule feed is a code path from someone else's server
|
|
280
|
+
into a tool that runs with your network access and often your credentials.
|
|
281
|
+
Supply-chain research shows that update channels and package registries are
|
|
282
|
+
exactly where attackers insert themselves [ladisa2023sok, ohm2020backstabber,
|
|
283
|
+
zimmermann2019small], and that securing an update channel needs signed
|
|
284
|
+
metadata, key rotation and explicit trust delegation [samuel2010survivable,
|
|
285
|
+
torresarias2019intoto]. Shipping rules inside the signed, versioned package
|
|
286
|
+
reuses the trust you already place in the release, and gives a clear audit
|
|
287
|
+
trail: the rules you ran are the rules in the version you installed.
|
|
288
|
+
|
|
289
|
+
## Citations in reports
|
|
290
|
+
|
|
291
|
+
Each finding category maps to one to three references in
|
|
292
|
+
[lib/references.json](../lib/references.json). They appear:
|
|
293
|
+
|
|
294
|
+
- in the Markdown report, as a "Why this matters / Sources" line under each
|
|
295
|
+
finding and a "Sources" line under each action-plan item;
|
|
296
|
+
- in SARIF [oasis2023sarif], as the rule's `helpUri` (first source) and
|
|
297
|
+
`help` text (all sources), and as `properties.references` (the BibTeX keys).
|
|
298
|
+
|
|
299
|
+
[references.md](references.md) and the `sources` block of `references.json`
|
|
300
|
+
are generated from [references.bib](references.bib) by
|
|
301
|
+
`node scripts/build-references.mjs`, and a test fails if they drift.
|
|
302
|
+
|
|
303
|
+
## Known limitations
|
|
304
|
+
|
|
305
|
+
- No authenticated crawling beyond the bearer token or cookie you pass, so
|
|
306
|
+
pages behind multi-step flows are not reached [doupe2010johnny].
|
|
307
|
+
- No stored or second-order injection testing [bau2010state].
|
|
308
|
+
- No DOM-based XSS analysis; Penthera does not run JavaScript
|
|
309
|
+
[lekies2013flows, stock2014precise].
|
|
310
|
+
- No business logic testing. IDOR/BOLA findings need a human to confirm.
|
|
311
|
+
- Secret detection is pattern-based and skips Markdown and other
|
|
312
|
+
documentation files, so unprefixed secrets and secrets in notes are missed.
|
|
313
|
+
- The bundled benchmark was written by the same people as the scanner. Its
|
|
314
|
+
numbers measure regressions and the false-positive controls, not accuracy on
|
|
315
|
+
arbitrary applications. Independent benchmarks such as the OWASP Benchmark
|
|
316
|
+
[owaspbenchmark] and the approach of Antunes and Vieira
|
|
317
|
+
[antunes2010benchmarking, antunes2015assessing] are the model for a fairer
|
|
318
|
+
comparison, and scoring against labelled third-party targets (Juice Shop,
|
|
319
|
+
crAPI) is the next step.
|
|
320
|
+
- Compliance output (NIS2 [eu2022nis2]) is evidence toward a duty of care, not
|
|
321
|
+
a certification.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Compliance: the Cyber Resilience Act (CRA)
|
|
2
|
+
|
|
3
|
+
Back to the [README](../README.md). NIS2 lives in
|
|
4
|
+
[compliance-nis2.md](compliance-nis2.md).
|
|
5
|
+
|
|
6
|
+
The Cyber Resilience Act, [Regulation (EU) 2024/2847](https://eur-lex.europa.eu/eli/reg/2024/2847/oj/eng),
|
|
7
|
+
puts security duties on manufacturers of products with digital elements, which
|
|
8
|
+
includes software you sell or make available in the EU. Most of it applies from
|
|
9
|
+
11 December 2027. The reporting duty in **Article 14 has applied since
|
|
10
|
+
11 September 2026** (Art. 71(2)), and it also covers products placed on the
|
|
11
|
+
market before December 2027 (Art. 69(3)).
|
|
12
|
+
|
|
13
|
+
Penthera helps with two pieces of CRA evidence, offline:
|
|
14
|
+
|
|
15
|
+
| Command | What you get |
|
|
16
|
+
|---------|--------------|
|
|
17
|
+
| `--cra-init cra.json` then `--cra cra.json` | Article 14 timeline with real due dates, overdue flags, and drafts of every report |
|
|
18
|
+
| `--repo . --aibom aibom.cdx.json` | CycloneDX 1.6 AI-BOM: AI SDKs, models referenced in code, MCP servers, skills, prompt files |
|
|
19
|
+
|
|
20
|
+
Penthera does not make you CRA compliant and does not certify anything. The
|
|
21
|
+
drafts are not the ENISA platform's official form, and none of this is legal
|
|
22
|
+
advice.
|
|
23
|
+
|
|
24
|
+
## Article 14 reporting helper
|
|
25
|
+
|
|
26
|
+
### What the regulation asks
|
|
27
|
+
|
|
28
|
+
Checked on 2026-10-19 against the Official Journal text and the
|
|
29
|
+
[ENISA Single Reporting Platform (SRP) FAQ](https://www.enisa.europa.eu/topics/product-security/vulnerability-services/eu-incident-response-and-cyber-crisis-management/single-reporting-platform-srp/frequently-asked-questions).
|
|
30
|
+
|
|
31
|
+
| | Actively exploited vulnerability (Art. 14(1)-(2)) | Severe incident (Art. 14(3)-(5)) |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| Early warning | within 24 hours of becoming aware, with the Member States where the product is available | within 24 hours, including whether it is suspected to be caused by unlawful or malicious acts, plus Member States |
|
|
34
|
+
| Notification | within 72 hours: the product, the general nature of the exploit and the vulnerability, corrective or mitigating measures taken and ones users can take, how sensitive the information is | within 72 hours: nature of the incident, an initial assessment, measures taken and ones users can take, sensitivity |
|
|
35
|
+
| Final report | no later than **14 days after a corrective or mitigating measure is available**: description with severity and impact, any malicious actor, details of the security update | within **one month after the incident notification**: detailed description with severity and impact, type of threat or root cause, applied and ongoing mitigations |
|
|
36
|
+
|
|
37
|
+
- **Where to send it.** Through the [ENISA Single Reporting Platform](https://portal.cra-srp.enisa.europa.eu/),
|
|
38
|
+
using the end-point of the CSIRT designated as coordinator in the Member State
|
|
39
|
+
of your main establishment. ENISA receives it at the same time (Art. 14(1),
|
|
40
|
+
14(3), 14(7)). For the Netherlands that CSIRT is **NCSC-NL**, per ENISA's
|
|
41
|
+
[list of CSIRTs designated as coordinators](https://www.enisa.europa.eu/topics/product-security/vulnerability-services/eu-incident-response-and-cyber-crisis-management/single-reporting-platform-srp/list-of-csirts-designated-as-coordinators).
|
|
42
|
+
- **Inform your users.** After becoming aware, inform impacted users (and where
|
|
43
|
+
appropriate all users), with mitigations they can deploy, where appropriate
|
|
44
|
+
in a machine-readable format (Art. 14(8)).
|
|
45
|
+
- **One notification** per vulnerability or incident, even across subsidiaries
|
|
46
|
+
(ENISA SRP FAQ). The CSIRT may ask for an intermediate report (Art. 14(6)).
|
|
47
|
+
- An *actively exploited vulnerability* is one with reliable evidence that a
|
|
48
|
+
malicious actor exploited it without the system owner's permission (Art. 3(42)).
|
|
49
|
+
An incident is *severe* if it can harm the availability, authenticity,
|
|
50
|
+
integrity or confidentiality of sensitive or important data or functions, or
|
|
51
|
+
can lead to malicious code in the product or a user's systems (Art. 14(5)).
|
|
52
|
+
|
|
53
|
+
### Use it
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
penthera --cra-init cra.json # set type ("vulnerability" or "incident") and aware_at
|
|
57
|
+
penthera --cra cra.json # writes cra-reports.md and prints the deadlines
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`cra-reports.md` holds the timeline (UTC and your local time), a status per
|
|
61
|
+
report (`due in 21h 30m`, `submitted`, `submitted late`, `OVERDUE`,
|
|
62
|
+
`waiting for a fix`), a draft of each report with `NEEDS INPUT` for every fact
|
|
63
|
+
still missing, and a draft user advisory for Art. 14(8). Record what you've sent
|
|
64
|
+
in `submitted.early_warning` / `submitted.notification` / `submitted.final_report`
|
|
65
|
+
and re-run; for a severe incident the final report then counts from the real
|
|
66
|
+
submission time instead of the 72-hour deadline.
|
|
67
|
+
|
|
68
|
+
**Prefill from your scan and SBOM.** Name the affected dependency in
|
|
69
|
+
`component.name` and point `prefill` at Penthera output (paths relative to
|
|
70
|
+
`cra.json`):
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"type": "vulnerability",
|
|
75
|
+
"aware_at": "2026-10-24T09:00:00+02:00",
|
|
76
|
+
"component": { "name": "jquery" },
|
|
77
|
+
"prefill": { "scan": "scan.json", "sbom": "sbom.cdx.json" }
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
penthera https://staging.example.com --repo . --sbom sbom.cdx.json -o scan.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
From the SBOM it fills the product name and version and the component's version
|
|
86
|
+
and purl; from the scan, matching CVE ids, the scanner severity, and the finding
|
|
87
|
+
titles. Prefilled values are marked *verify* in the draft, and anything you wrote
|
|
88
|
+
yourself is never overwritten. A full example lives in `tests/fixtures/cra/`.
|
|
89
|
+
|
|
90
|
+
### How the deadlines are counted
|
|
91
|
+
|
|
92
|
+
- **Hours are elapsed hours** from `aware_at`. If the clocks change in between,
|
|
93
|
+
the local due time shifts (24 hours from 09:00 CEST on 24 October 2026 is
|
|
94
|
+
08:00 CET on 25 October).
|
|
95
|
+
- **Days and months are calendar periods** in your `timezone` (default
|
|
96
|
+
`Europe/Amsterdam`), ending at the same clock time. One month from 31 January
|
|
97
|
+
ends on 28 (or 29) February, following
|
|
98
|
+
[Regulation 1182/71](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:31971R1182) Art. 3(2)(c).
|
|
99
|
+
- A timestamp without an offset is read in `timezone`. Use ISO 8601.
|
|
100
|
+
- This is the **strict reading**. Regulation 1182/71 can give a little more
|
|
101
|
+
time (a period runs to the end of the hour or day, and a days or months period
|
|
102
|
+
that ends on a weekend or public holiday moves to the next working day).
|
|
103
|
+
Don't plan on that; report early.
|
|
104
|
+
|
|
105
|
+
The helper warns if `aware_at` is before 11 September 2026, if
|
|
106
|
+
`main_establishment` is not the Netherlands (so NCSC-NL is not your CSIRT), and
|
|
107
|
+
if the type or time zone is unknown.
|
|
108
|
+
|
|
109
|
+
## AI-BOM (CycloneDX 1.6)
|
|
110
|
+
|
|
111
|
+
Customers and auditors increasingly ask what AI a product contains: which
|
|
112
|
+
providers get your data, which models, which agent tooling. `--aibom`
|
|
113
|
+
inventories it from the repo, offline:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
penthera --repo . --aibom aibom.cdx.json
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
| Found | How | CycloneDX |
|
|
120
|
+
|-------|-----|-----------|
|
|
121
|
+
| AI SDKs: `openai`, `@anthropic-ai/sdk`, `@google/genai`, `ai` / `@ai-sdk/*`, `langchain` / `@langchain/*`, `llamaindex`, `ollama`, `@huggingface/transformers`; on PyPI `anthropic`, `transformers`, `torch`, `langchain-*`, `llama-index-*`, `litellm`, `mcp` and others | lockfile, `package.json`, `requirements.txt`, `pyproject.toml` | `library` with purl |
|
|
122
|
+
| Models: `claude-*`, `gpt-*`, `o3`, `text-embedding-*`, `gemini-*`, `gemma*`, `llama*`, `mistral-*`, `deepseek-*`, `qwen*`, `phi-*`, `grok-*`, Hugging Face ids (`meta-llama/Llama-3.1-8B-Instruct`) | whole string literals in code and `model:` keys in YAML/TOML; HF ids only for known orgs or next to a loader call | `machine-learning-model` with supplier, `modelCard` stub, `evidence.occurrences` (file and line) |
|
|
123
|
+
| MCP servers | `lib/agents/discover.js` (`.mcp.json`, `.cursor/`, `.vscode/mcp.json`, ...) | `services` with transport, package, endpoint and trust boundary; env var *names* only, never values or args |
|
|
124
|
+
| Agent Skills | `SKILL.md` folders | `data` with SHA-256 |
|
|
125
|
+
| Prompt files | `*.prompt`, `*.prompty`, `*.prompt.md`, anything under a `prompts/` folder | `data` with SHA-256 |
|
|
126
|
+
|
|
127
|
+
Every item hangs off the root component in `dependencies`. The BOM is only the
|
|
128
|
+
AI slice; use `--sbom` for all dependencies.
|
|
129
|
+
|
|
130
|
+
Spec references: the [CycloneDX 1.6 JSON reference](https://cyclonedx.org/docs/1.6/json/),
|
|
131
|
+
the [ML-BOM capability page](https://cyclonedx.org/capabilities/mlbom/), and the
|
|
132
|
+
[official schema](https://github.com/CycloneDX/specification/blob/master/schema/bom-1.6.schema.json).
|
|
133
|
+
That schema (with its SPDX and JSF dependencies) is vendored in
|
|
134
|
+
`tests/fixtures/cyclonedx/`, and the tests validate every generated AI-BOM
|
|
135
|
+
against it without network. Penthera emits 1.6 (ML-BOM first appeared in 1.5);
|
|
136
|
+
CycloneDX 1.7 is also published and is a later upgrade.
|
|
137
|
+
|
|
138
|
+
**Limits.** A model string in code means the model is referenced, not that it
|
|
139
|
+
runs in production; the evidence lines let a reviewer check. Model ids built at
|
|
140
|
+
runtime (`` `claude-${tier}` ``) are not detected. The `modelCard` is a stub:
|
|
141
|
+
fill in task, datasets and considerations from the provider's model card.
|
package/docs/compliance-nis2.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Compliance: NIS2 and the cyberwetgeving
|
|
2
2
|
|
|
3
|
-
Back to the [README](../README.md).
|
|
3
|
+
Back to the [README](../README.md). Shipping a product? The Cyber Resilience
|
|
4
|
+
Act's Article 14 reporting helper and the AI-BOM are in
|
|
5
|
+
[compliance-cra.md](compliance-cra.md).
|
|
4
6
|
|
|
5
7
|
The EU NIS2 directive and its national transpositions (the Cyberbeveiligingswet
|
|
6
8
|
in the Netherlands, the NIS2-wet in Belgium) put a duty of care, the *zorgplicht*,
|
package/docs/evidence.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Evidence bundles and audience reports
|
|
2
|
+
|
|
3
|
+
Penthera can write the results of one scan as an **evidence bundle**: every
|
|
4
|
+
report, a manifest with the SHA-256 of each file, and an optional SSH
|
|
5
|
+
signature over the manifest. An auditor checks the bundle with one command.
|
|
6
|
+
No hosted service is involved, and nothing needs to be installed beyond Node
|
|
7
|
+
(or `shasum` and `jq`) and OpenSSH 8.0 or later.
|
|
8
|
+
|
|
9
|
+
## For auditors: verify a bundle
|
|
10
|
+
|
|
11
|
+
You need the bundle directory and an **allowed signers file** that names the
|
|
12
|
+
key you expect the bundle to be signed with. Get that public key from the
|
|
13
|
+
organisation through a channel other than the bundle itself (their security
|
|
14
|
+
page, an email you can authenticate, or in person).
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx penthera evidence verify ./evidence --allowed-signers allowed_signers
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Exit code `0` means every file matches the manifest and the manifest was signed
|
|
21
|
+
by a key in `allowed_signers`. Exit code `1` means something changed, is
|
|
22
|
+
missing, was added, or the signature does not check out; the output says which.
|
|
23
|
+
|
|
24
|
+
An `allowed_signers` file is one line per key:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
security@example.com namespaces="penthera-evidence" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Without Penthera
|
|
31
|
+
|
|
32
|
+
The bundle does not depend on Penthera to be checked. With standard tools:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cd evidence
|
|
36
|
+
# 1. The manifest was signed by the expected key (namespace penthera-evidence)
|
|
37
|
+
ssh-keygen -Y verify -f ../allowed_signers -I security@example.com \
|
|
38
|
+
-n penthera-evidence -s manifest.json.sig < manifest.json
|
|
39
|
+
# 2. Every artifact matches its hash in the manifest
|
|
40
|
+
jq -r '.artifacts[] | "\(.sha256) \(.path)"' manifest.json | shasum -a 256 -c -
|
|
41
|
+
# 3. Nothing else is in the directory
|
|
42
|
+
ls
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`penthera evidence verify` does the same three checks, and also rejects
|
|
46
|
+
manifest paths that would leave the directory (`../`, absolute paths) and
|
|
47
|
+
symlinked files.
|
|
48
|
+
|
|
49
|
+
### What the checks prove, and what they do not
|
|
50
|
+
|
|
51
|
+
- **Hashes** prove that the files have not changed since the manifest was
|
|
52
|
+
written. Without a signature they prove nothing about who wrote it: anyone
|
|
53
|
+
can rewrite a file and its hash together.
|
|
54
|
+
- **The signature** proves that the holder of the signing key produced this
|
|
55
|
+
manifest. It does not prove that the scan was run against the system you
|
|
56
|
+
care about, or with the options you expect. Read the manifest's `command`,
|
|
57
|
+
`target`, `scan` and `git` fields, and the auditor report's scope section.
|
|
58
|
+
- Without `--allowed-signers`, `verify` still checks that the signature is
|
|
59
|
+
valid but cannot tell you whose key made it. It reports
|
|
60
|
+
`valid-unknown-signer` as a warning.
|
|
61
|
+
- A scan is a snapshot. The bundle is evidence of what Penthera saw at
|
|
62
|
+
`generated_at`, not a statement that the system is secure.
|
|
63
|
+
|
|
64
|
+
## For the team producing evidence
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
penthera https://staging.example.com --repo . --framework nis2 \
|
|
68
|
+
--evidence evidence/2026-10-19 --sign-key ~/.ssh/id_ed25519
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- `--evidence <dir>` writes the bundle. The directory must be new or empty, so
|
|
72
|
+
a bundle never mixes two runs.
|
|
73
|
+
- `--sign-key <key>` signs `manifest.json` with
|
|
74
|
+
`ssh-keygen -Y sign -n penthera-evidence`. Use the private key path, or the
|
|
75
|
+
`.pub` path when the key lives in `ssh-agent` or on a hardware token. A
|
|
76
|
+
passphrase prompt appears in the terminal.
|
|
77
|
+
- `--sbom <file>` and `--readiness` add their outputs to the bundle.
|
|
78
|
+
- Give auditors the public key out of band, as an `allowed_signers` line.
|
|
79
|
+
|
|
80
|
+
### What is in a bundle
|
|
81
|
+
|
|
82
|
+
| File | Contents |
|
|
83
|
+
|------|----------|
|
|
84
|
+
| `report.json` | The full scan result |
|
|
85
|
+
| `report.md` | The default Markdown report |
|
|
86
|
+
| `report.sarif` | SARIF 2.1.0 |
|
|
87
|
+
| `report-developer.md` | Findings with file:line, fix recipe ids or diffs, and sources |
|
|
88
|
+
| `report-owner.md` | Plain-language summary, what to do first, owner actions, trend |
|
|
89
|
+
| `report-auditor.md` | Scope and method, confidence and provenance, egress, inconclusive probes, suppressions, fix timeline, NIS2 and OWASP mapping |
|
|
90
|
+
| `report-custom.md` | Your `--report-template` output, when given |
|
|
91
|
+
| `nis2-readiness.md` | With `--readiness` |
|
|
92
|
+
| `sbom.cdx.json` | CycloneDX SBOM, with `--sbom` |
|
|
93
|
+
| `manifest.json` | Tool version, command line (secrets redacted), timestamp, target, git commit, SHA-256 and size of every file above |
|
|
94
|
+
| `manifest.json.sig` | SSH signature over `manifest.json`, with `--sign-key` |
|
|
95
|
+
|
|
96
|
+
The command line in the manifest has the values of `--auth-cookie`,
|
|
97
|
+
`--auth-bearer` and any token, secret or password flag replaced with
|
|
98
|
+
`[REDACTED]`, and credentials in URLs masked.
|
|
99
|
+
|
|
100
|
+
## Audience reports
|
|
101
|
+
|
|
102
|
+
`--audience developer|owner|auditor` picks who the Markdown report is for. It
|
|
103
|
+
applies to `--markdown <file>`; without `--markdown` the report goes to stdout.
|
|
104
|
+
The default report (no `--audience`) is unchanged.
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
penthera https://example.com --repo . --audience owner > for-the-owner.md
|
|
108
|
+
penthera https://example.com --repo . --audience auditor --markdown audit.md
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
When a scan used `--memory`, the owner report shows the trend across recent
|
|
112
|
+
scans. When the result has an agent loop run (`loopRun`) or memory has fix
|
|
113
|
+
events, the developer, owner and auditor reports show a fix timeline:
|
|
114
|
+
found → fixed → verified, with time to fix.
|
|
115
|
+
|
|
116
|
+
## Your own template
|
|
117
|
+
|
|
118
|
+
`--report-template file.md` renders your own Markdown from the same data the
|
|
119
|
+
audience reports use. The language is deliberately tiny:
|
|
120
|
+
|
|
121
|
+
```markdown
|
|
122
|
+
# Security status for {{summary.target}}
|
|
123
|
+
|
|
124
|
+
{{summary.critical}} critical, {{summary.high}} high ({{summary.total}} total).
|
|
125
|
+
|
|
126
|
+
{{#each findings}}
|
|
127
|
+
- [{{severity}}] {{title}}{{#if location}} at {{location}}{{/if}} ({{confidence}})
|
|
128
|
+
{{/each}}
|
|
129
|
+
|
|
130
|
+
{{#if suppressed}}
|
|
131
|
+
Set aside: {{#each suppressed}}{{title}} by {{actor}}: {{reason}}; {{/each}}
|
|
132
|
+
{{else}}
|
|
133
|
+
Nothing suppressed.
|
|
134
|
+
{{/if}}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- `{{path.to.value}}` prints a value. Values are HTML- and Markdown-escaped,
|
|
138
|
+
and newlines become spaces, so a finding title cannot add HTML, a heading, a
|
|
139
|
+
list item or a table cell.
|
|
140
|
+
- `{{#each list}}…{{/each}}` loops; inside, `{{this}}` is the item and
|
|
141
|
+
`{{@index}}` its 0-based index. Names not found on the item are looked up in
|
|
142
|
+
the enclosing scope.
|
|
143
|
+
- `{{#if value}}…{{else}}…{{/if}}`; an empty list counts as false.
|
|
144
|
+
|
|
145
|
+
There are no helpers, no expressions, no raw output, no partials and no file
|
|
146
|
+
includes. Paths containing `__proto__`, `constructor` or `prototype` are
|
|
147
|
+
rejected, and only own properties are read. A template that uses anything else
|
|
148
|
+
fails with a line number before the scan starts.
|
|
149
|
+
|
|
150
|
+
Top-level fields: `tool`, `target`, `timestamp`, `duration`, `modes`,
|
|
151
|
+
`profile`, `offline`, `probes`, `summary` (`critical`, `high`, `medium`,
|
|
152
|
+
`low`, `info`, `serious`, `total`, `suppressed`, `inconclusive`), `findings`
|
|
153
|
+
(`fid`, `title`, `severity`, `confidence`, `source`, `method`, `collectedAt`,
|
|
154
|
+
`category`, `location`, `file`, `line`, `url`, `description`, `recipe`,
|
|
155
|
+
`fixText`, `sources`, `owasp`, `wstg`), `suppressed` (`title`, `severity`,
|
|
156
|
+
`reason`, `actor`, `by`, `expires`), `inconclusive`, `egress`, `actions`,
|
|
157
|
+
`topActions`, `ownerActions`, `nis2`, `owasp`, `trend` and `timeline`. The
|
|
158
|
+
model is built in [`src/report/model.js`](../src/report/model.js).
|