penthera 1.1.1 → 1.4.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 -3
- package/README.md +489 -286
- package/SECURITY.md +28 -6
- package/docs/METHODOLOGY.md +321 -0
- package/docs/compliance-nis2.md +166 -0
- package/docs/evidence.md +158 -0
- package/docs/examples/github-actions-deps.yml +105 -0
- package/docs/examples/github-actions-scan.yml +55 -0
- 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 +15 -3
- package/docs/local-loop.md +276 -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/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 +416 -0
- package/lib/deps/lockfile.js +106 -0
- package/lib/deps/online.js +132 -0
- package/lib/deps/popular-packages.json +1 -0
- package/lib/deps/report.js +173 -0
- package/lib/deps/source.js +171 -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/injections.js +19 -15
- package/lib/install/index.js +271 -0
- package/lib/install/policy.js +117 -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/frameworks.js +1 -1
- package/lib/whitebox/secrets.js +49 -19
- package/package.json +19 -9
- package/skills/penthera/SKILL.md +104 -23
- package/skills/penthera/references/output-and-ci.md +12 -50
- package/skills/penthera/references/troubleshooting.md +1 -1
- package/skills/penthera/scripts/preflight.sh +4 -4
- 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 +84 -0
- package/src/cli/evidence.js +67 -0
- package/src/cli/fix.js +191 -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 +152 -6
- 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
- package/scripts/package-skill.sh +0 -16
package/SECURITY.md
CHANGED
|
@@ -2,21 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
## Supported versions
|
|
4
4
|
|
|
5
|
+
Security fixes land on the latest minor release only. Upgrade to get them.
|
|
6
|
+
|
|
5
7
|
| Version | Supported |
|
|
6
8
|
| ------- | --------- |
|
|
7
|
-
| 1.
|
|
8
|
-
| < 1.
|
|
9
|
+
| 1.2.x | Yes |
|
|
10
|
+
| < 1.2 | No |
|
|
9
11
|
|
|
10
12
|
## Reporting a vulnerability
|
|
11
13
|
|
|
12
14
|
If you find a security issue in **Penthera itself** (not a finding produced by
|
|
13
15
|
scanning a target), please report it privately:
|
|
14
16
|
|
|
15
|
-
1. Open a [GitHub Security Advisory](https://github.com/danoszz/penthera/security/advisories/new)
|
|
16
|
-
|
|
17
|
+
1. Open a [GitHub Security Advisory](https://github.com/danoszz/penthera/security/advisories/new)
|
|
18
|
+
(preferred: it keeps the report private and lets us credit you), or
|
|
19
|
+
2. Email the maintainer via the address on the [GitHub profile](https://github.com/danoszz)
|
|
20
|
+
with a clear description and reproduction steps.
|
|
21
|
+
|
|
22
|
+
Please do **not** open public issues for undisclosed vulnerabilities.
|
|
23
|
+
|
|
24
|
+
**What to expect.** Penthera is maintained part-time, but security reports come
|
|
25
|
+
first:
|
|
26
|
+
|
|
27
|
+
- Acknowledgement within 3 working days.
|
|
28
|
+
- An initial assessment (accepted, needs more info, or out of scope) within 10
|
|
29
|
+
working days.
|
|
30
|
+
- A fix or mitigation for accepted issues as fast as severity warrants, followed
|
|
31
|
+
by a release, a GitHub advisory, and credit to you unless you prefer otherwise.
|
|
32
|
+
|
|
33
|
+
**In scope:** the CLI, the scanner engine (`lib/`, `src/`), the Agent Skill
|
|
34
|
+
(`skills/penthera/`), the installer (`install.sh`), and the published npm package
|
|
35
|
+
and its build pipeline. Examples: code execution from a malicious scan target or
|
|
36
|
+
repo, a skill instruction that bypasses the authorization gate, a supply-chain
|
|
37
|
+
weakness in our release workflow.
|
|
17
38
|
|
|
18
|
-
|
|
19
|
-
|
|
39
|
+
**Safe harbor.** Good-faith research on Penthera's own code, done on your own
|
|
40
|
+
machine, is welcome and we will not pursue action against it. This does not
|
|
41
|
+
cover scanning third-party systems: the authorization rules below still apply.
|
|
20
42
|
|
|
21
43
|
## Responsible use
|
|
22
44
|
|
|
@@ -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,166 @@
|
|
|
1
|
+
# Compliance: NIS2 and the cyberwetgeving
|
|
2
|
+
|
|
3
|
+
Back to the [README](../README.md).
|
|
4
|
+
|
|
5
|
+
The EU NIS2 directive and its national transpositions (the Cyberbeveiligingswet
|
|
6
|
+
in the Netherlands, the NIS2-wet in Belgium) put a duty of care, the *zorgplicht*,
|
|
7
|
+
on in-scope organisations. That duty is the set of risk-management measures in
|
|
8
|
+
Article 21(2) of the directive. Most of them are organisational. Some are
|
|
9
|
+
technical and testable, and that is the part Penthera helps with.
|
|
10
|
+
|
|
11
|
+
Penthera does not make you compliant and does not certify anything. What it gives
|
|
12
|
+
you is automated, repeatable technical evidence for the measures a scanner can
|
|
13
|
+
honestly verify: the kind of artefact you can attach to a risk file, show an
|
|
14
|
+
auditor, or run in CI so a regression shows up before it ships.
|
|
15
|
+
|
|
16
|
+
| Art. 21(2) measure | What Penthera does | Coverage |
|
|
17
|
+
|--------------------|--------------------|----------|
|
|
18
|
+
| **(a)** Risk analysis & information-security policy | Findings, severity, and baseline history feed your risk analysis | Evidence |
|
|
19
|
+
| **(b)** Incident handling | `--incident` drafts the 24h / 72h / 1-month reports against your authority's required content | Partial |
|
|
20
|
+
| **(c)** Business continuity, backup, crisis management | Organisational; outside a scanner's reach | Out of scope |
|
|
21
|
+
| **(d)** Supply-chain security | Known-CVE checks on JS dependencies, leaked credentials, a CycloneDX SBOM (`--sbom`), and passive supplier rating (`--suppliers`) | Partial |
|
|
22
|
+
| **(e)** Security in acquisition, development & maintenance; **vulnerability handling** | The core: black- + white-box scanning mapped to OWASP WSTG, SARIF → GitHub, baseline regression, scan-and-fix loop | **Strong** |
|
|
23
|
+
| **(f)** Assessing the effectiveness of measures | Baseline diff, re-scan-to-verify, and `--history` tracking of time-to-fix, ageing, and drift | Partial |
|
|
24
|
+
| **(g)** Cyber hygiene & training | Remediation playbook + secure-defaults guidance (guidance, not a training programme) | Partial |
|
|
25
|
+
| **(h)** Cryptography & encryption | TLS protocol/cipher/certificate audit, HSTS, cookie `Secure` (transport layer only) | Partial |
|
|
26
|
+
| **(i)** HR security, **access control**, asset management | Access control tested: JWT, IDOR/BOLA, OAuth redirect, client-side-auth, trust-boundary mapping | Partial |
|
|
27
|
+
| **(j)** Multi-factor authentication & secure communications | MFA probe (on the roadmap) | Planned |
|
|
28
|
+
|
|
29
|
+
**Coverage key:** *Strong* = automated technical testing · *Partial* = part of the
|
|
30
|
+
measure · *Evidence* = output feeds the measure, no direct test · *Planned* = on the
|
|
31
|
+
roadmap · *Out of scope* = organisational or physical.
|
|
32
|
+
|
|
33
|
+
In practice, Penthera today gives you solid automated evidence for the
|
|
34
|
+
secure-development and vulnerability-handling duty (e), which is the technical
|
|
35
|
+
heart of the zorgplicht, plus meaningful coverage of supply chain (d),
|
|
36
|
+
cryptography in transit (h), access control (i), and effectiveness testing (f).
|
|
37
|
+
That is not the whole duty of care, but it is a real, defensible start you can run
|
|
38
|
+
today and re-run on every deploy.
|
|
39
|
+
|
|
40
|
+
> **Not legal advice, not a certification, not a guarantee of compliance.**
|
|
41
|
+
> Penthera automates the technical-testing part of the duty of care. The
|
|
42
|
+
> directive's organisational duties (governance, risk-management policy,
|
|
43
|
+
> incident-notification processes, supply-chain contracts, staff training) need
|
|
44
|
+
> human process and, where it matters, review by a qualified advisor. For an
|
|
45
|
+
> authoritative reading, consult your national authority (the NCSC in the
|
|
46
|
+
> Netherlands, the CCB and its CyFun framework in Belgium) or a specialised jurist.
|
|
47
|
+
|
|
48
|
+
Generate this mapping for your own scan with `--framework nis2`. It adds a
|
|
49
|
+
per-run coverage table (which measures *this* scan actually exercised, driven by
|
|
50
|
+
the probes that ran) to the terminal, Markdown, and JSON reports:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
penthera https://myapp.com --framework nis2 -o reports/scan.json
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Readiness report (clone → report in two commands)
|
|
57
|
+
|
|
58
|
+
Go further than the mapping: combine the scan with a short offline
|
|
59
|
+
self-assessment (the organisational measures a scanner can't test, like policies,
|
|
60
|
+
backups, training, and MFA) into a full NIS2 readiness report. Every Article 21
|
|
61
|
+
measure is marked `met / partial / gap / n-a`, with proportionality notes, your
|
|
62
|
+
incident-reporting deadlines, and provenance on every piece of evidence.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# 1. Create your self-assessment (offline, one-time). Fill in yes | partial | no | na.
|
|
66
|
+
penthera --assessment-init assessment.json
|
|
67
|
+
|
|
68
|
+
# 2. Scan + self-assessment → readiness report (writes reports/scan-readiness.md)
|
|
69
|
+
penthera https://myapp.com --repo . --readiness --assessment assessment.json -o reports/scan.json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The readiness report is **evidence and self-assessment tooling, not a
|
|
73
|
+
certification**. It is provenance-linked (`source, method, collected_at,
|
|
74
|
+
confidence`) and reproducible, and it lists exactly what only you can attest to.
|
|
75
|
+
Jurisdiction specifics live in [`lib/jurisdictions/nl.json`](../lib/jurisdictions/nl.json) (NL today; BE and
|
|
76
|
+
others are just added config).
|
|
77
|
+
|
|
78
|
+
### Answer a customer's security questionnaire
|
|
79
|
+
|
|
80
|
+
The questionnaire blocking your deal, drafted from evidence you already have:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# one question per line, pasted from the customer's form
|
|
84
|
+
penthera https://myapp.com --readiness --assessment assessment.json --questionnaire questions.txt
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Each question is mapped to the Article 21 measure it belongs to and answered
|
|
88
|
+
from the scan and your self-assessment, with the backing evidence cited.
|
|
89
|
+
Anything Penthera can't map is marked `NEEDS HUMAN INPUT` instead of guessed.
|
|
90
|
+
Review every answer before you send it.
|
|
91
|
+
|
|
92
|
+
### Policy pack
|
|
93
|
+
|
|
94
|
+
Generate the proportionate written policies NIS2 expects, pre-filled from your
|
|
95
|
+
readiness gaps and the NL incident deadlines:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
penthera --policy-pack ./policies --org "Your Company" --readiness --assessment assessment.json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Writes editable Markdown starting points: information security, access control,
|
|
102
|
+
incident response, backup/continuity, acceptable use, supplier security, and
|
|
103
|
+
vulnerability management. **Templates, not legal advice.** Keep them short and
|
|
104
|
+
used. A two-page policy that genuinely exists is what "proportionate" means.
|
|
105
|
+
|
|
106
|
+
### Incident-report helper
|
|
107
|
+
|
|
108
|
+
NIS2 has hard reporting deadlines (24h early warning, 72h notification, 1-month
|
|
109
|
+
final report). If something happens, get the timeline and draft reports:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
penthera --incident-init incident.json # fill in what happened (set aware_at)
|
|
113
|
+
penthera --incident incident.json # writes incident-reports.md
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
It computes your three deadlines from when you became aware, flags anything
|
|
117
|
+
overdue, and drafts the early-warning, notification, and final report against
|
|
118
|
+
the required content, with `NEEDS INPUT` where you still have to fill in facts.
|
|
119
|
+
**These are drafts to help you meet the deadlines, not a substitute for your
|
|
120
|
+
authority's official form.**
|
|
121
|
+
|
|
122
|
+
### Software bill of materials (SBOM)
|
|
123
|
+
|
|
124
|
+
Emit a standard CycloneDX SBOM of your dependencies. It's what enterprises
|
|
125
|
+
increasingly require of suppliers, and direct evidence for supply-chain security
|
|
126
|
+
(measure d):
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
penthera --repo . --sbom sbom.cdx.json
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Parses `package-lock.json` / `package.json` / `requirements.txt` offline (no
|
|
133
|
+
network), with resolved versions and package URLs (purls).
|
|
134
|
+
|
|
135
|
+
### Remediation tracking (the audit loop)
|
|
136
|
+
|
|
137
|
+
Closing findings matters more than finding them. Track them over time:
|
|
138
|
+
time-to-fix, ageing of open items, and what changed since the last scan (evidence
|
|
139
|
+
for measure f, effectiveness):
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
penthera https://myapp.com --history reports/history.json -o reports/scan.json
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Each run folds the current findings into a small history keyed by a **stable
|
|
146
|
+
fingerprint** (so a re-worded or re-counted finding isn't mistaken for a new
|
|
147
|
+
one), then reports open vs resolved, median time-to-fix, and ageing buckets.
|
|
148
|
+
Re-run on a cadence to watch the trend.
|
|
149
|
+
|
|
150
|
+
### Supplier rating (passive)
|
|
151
|
+
|
|
152
|
+
Assess your suppliers' public security posture for supply-chain risk (measure d):
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
printf 'supplier1.com\nsupplier2.com\n' > suppliers.txt
|
|
156
|
+
penthera --suppliers suppliers.txt
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Strictly passive and public-data-only.** One homepage GET (security headers),
|
|
160
|
+
a TLS handshake (certificate), and public DNS lookups (SPF/DMARC/MX) per domain.
|
|
161
|
+
No endpoint discovery, no auth probes, no intrusive testing. You get a
|
|
162
|
+
red/amber/green rating per supplier. It's a screening aid for your own vendor
|
|
163
|
+
risk assessment, **not** an authorised penetration test of the supplier.
|
|
164
|
+
|
|
165
|
+
Because the mapping targets the NIS2 directive itself, it applies across the
|
|
166
|
+
national transpositions; jurisdiction-specific profiles (NL, BE) are on the roadmap.
|