penthera 1.1.1 → 1.2.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 +57 -3
- package/README.md +114 -251
- package/SECURITY.md +28 -6
- package/docs/compliance-nis2.md +166 -0
- package/docs/examples/github-actions-scan.yml +55 -0
- package/docs/index.md +4 -3
- package/lib/whitebox/secrets.js +49 -19
- package/package.json +5 -7
- package/skills/penthera/SKILL.md +2 -2
- 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/scripts/package-skill.sh +0 -16
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,60 @@ All notable changes to Penthera are documented here. Versioning follows [SemVer]
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.2.0] - 2026-10-19
|
|
8
|
+
|
|
9
|
+
The first step toward securing the AI toolchain itself: catch AI provider keys,
|
|
10
|
+
harden Penthera's own supply chain, and make CI test what it says it tests. The
|
|
11
|
+
[roadmap](ROADMAP.md) now sets out the AI supply-chain and local agent-loop
|
|
12
|
+
tracks.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **AI provider key detection** in repo scans: Anthropic, OpenAI project and
|
|
17
|
+
service-account keys, OpenRouter, Google (Gemini / Firebase), Groq, Hugging
|
|
18
|
+
Face, Replicate, xAI, Perplexity, LangSmith, and Pinecone. Jupyter notebooks
|
|
19
|
+
(`.ipynb`) are now scanned too. A provider key inside an `api_key = "..."`
|
|
20
|
+
assignment is reported once, under its provider, instead of twice
|
|
21
|
+
- `findSecretsInText()` exported from `lib/whitebox/secrets.js`, so the same
|
|
22
|
+
rules can run on any text (groundwork for scanning JS bundles)
|
|
23
|
+
- CodeQL and OpenSSF Scorecard workflows, Dependabot for npm and GitHub Actions
|
|
24
|
+
(with a 7-day cooldown before adopting new releases)
|
|
25
|
+
- CI check that `package.json`, the lockfile, the skill, and both plugin
|
|
26
|
+
manifests carry the same version
|
|
27
|
+
- Roadmap: AI supply-chain audit (`penthera agents`), a fully local
|
|
28
|
+
scan → fix → verify → learn loop, and LLM-endpoint probes. Principles now
|
|
29
|
+
state no telemetry, no auto-push, and no remote learning
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **Modern OpenAI keys were missed.** The old pattern stopped at the hyphen in
|
|
34
|
+
`sk-proj-...` and `sk-svcacct-...`, so the key format OpenAI has issued since
|
|
35
|
+
2024 never matched
|
|
36
|
+
- The example scan workflow lived in `.github/workflows/`, so it ran on its
|
|
37
|
+
weekly cron and failed every Monday. It moved to
|
|
38
|
+
`docs/examples/github-actions-scan.yml` and now uses `npx penthera@<version>`,
|
|
39
|
+
so it works when copied into your own repo
|
|
40
|
+
- CI ran only 4 of 24 test files; it now runs the full `npm test`
|
|
41
|
+
- Removed `tests/pentest-static.test.js`, which tested functions defined inside
|
|
42
|
+
the test file rather than anything Penthera ships
|
|
43
|
+
- `.claude-plugin/plugin.json` still said 1.0.0
|
|
44
|
+
- `SECURITY.md` listed 1.0.x as supported. It now covers 1.2.x and gives response
|
|
45
|
+
times, scope, and safe-harbor terms
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- **Node.js 22 or newer is now required.** Node 18 and 20 are end-of-life, and
|
|
50
|
+
the test toolchain (vitest 5) needs 22. CI runs on Node 22 and 24
|
|
51
|
+
- GitHub Actions are pinned to commit SHAs, and every workflow declares
|
|
52
|
+
least-privilege `permissions`
|
|
53
|
+
- Releases use npm trusted publishing (OIDC, no stored token) and **staged
|
|
54
|
+
publishing**: CI stages each version, and it only goes live after a
|
|
55
|
+
maintainer approves it with 2FA. The workflow also creates the GitHub Release
|
|
56
|
+
- The Docker image runs as the unprivileged `node` user on `node:22-alpine`
|
|
57
|
+
- README reordered so install and quick start come before compliance. The full
|
|
58
|
+
NIS2 guide moved to [`docs/compliance-nis2.md`](docs/compliance-nis2.md)
|
|
59
|
+
- vitest 3 → 5 (clears 8 dev-only advisories), yaml 2.8 → 2.9
|
|
60
|
+
|
|
7
61
|
## [1.1.1] - 2026-08-16
|
|
8
62
|
|
|
9
63
|
A correctness release. One of these was a live outage: if you are on 1.1.0,
|
|
@@ -176,7 +230,7 @@ and generated documents.
|
|
|
176
230
|
live integration tests report as skipped (not passed) without a running target
|
|
177
231
|
- Corrected the built-in template count (14, not 17) in docs and progress output
|
|
178
232
|
|
|
179
|
-
## [1.0.0]
|
|
233
|
+
## [1.0.0] - 2026-06-30
|
|
180
234
|
|
|
181
235
|
First production-ready release.
|
|
182
236
|
|
|
@@ -201,11 +255,11 @@ First production-ready release.
|
|
|
201
255
|
|
|
202
256
|
- URL trailing-slash normalization (double-slash 404 bug)
|
|
203
257
|
|
|
204
|
-
## [0.2.0]
|
|
258
|
+
## [0.2.0] - 2026-04
|
|
205
259
|
|
|
206
260
|
- Markdown reports, scan profiles, auth tooling, expanded API discovery
|
|
207
261
|
|
|
208
|
-
## [0.1.x]
|
|
262
|
+
## [0.1.x] - 2026-04
|
|
209
263
|
|
|
210
264
|
- Initial CLI: URL, repo, and machine scan modes
|
|
211
265
|
- Built-in templates, TLS, CORS, SARIF export
|
package/README.md
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
<br/><br/>
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/penthera)
|
|
8
|
-

|
|
9
9
|

|
|
10
10
|
[](https://github.com/danoszz/penthera/actions/workflows/ci.yml)
|
|
11
|
+
[](https://scorecard.dev/viewer/?uri=github.com/danoszz/penthera)
|
|
11
12
|
|
|
12
13
|
<br/>
|
|
13
14
|
|
|
@@ -33,7 +34,8 @@ issues." The agent picks the scan, runs it, and gives you the fixes.
|
|
|
33
34
|
You shipped something fast: vibecoded, AI-assisted, already live. The same models
|
|
34
35
|
that helped you build it also make it easy for someone to probe it. Exposed API
|
|
35
36
|
routes, missing security headers, weak auth, a hardcoded key in the repo. These
|
|
36
|
-
are the common mistakes that get apps broken into.
|
|
37
|
+
are the common mistakes that get apps broken into. An AI provider key in the
|
|
38
|
+
frontend bundle is the newest and most expensive of them.
|
|
37
39
|
|
|
38
40
|
Penthera is the check you run before you launch. Ask your agent to "scan my app
|
|
39
41
|
for security issues" and it runs an authorized, non-destructive scan, then lists
|
|
@@ -114,7 +116,7 @@ Prefer the terminal? Run `penthera` with no arguments for a guided wizard.
|
|
|
114
116
|
| Mode | Command | Coverage |
|
|
115
117
|
|------|---------|----------|
|
|
116
118
|
| **URL scan** | `penthera https://example.com` | TLS, fingerprinting, endpoint discovery, templates, CORS, cookies, JS CVEs, param discovery, OpenAPI/FastAPI probes, security headers, auth hardening |
|
|
117
|
-
| **Repo scan** | `penthera --repo ./my-app` | API route discovery (Next.js, Express, Hono, Fastify), trust-boundary mapping, hardcoded-secret scanning |
|
|
119
|
+
| **Repo scan** | `penthera --repo ./my-app` | API route discovery (Next.js, Express, Hono, Fastify), trust-boundary mapping, hardcoded-secret scanning (including Anthropic, OpenAI, Gemini, Groq, Hugging Face and other AI provider keys) |
|
|
118
120
|
| **Combined** | `penthera https://staging.app --repo .` | Black-box and white-box in one run |
|
|
119
121
|
| **Interactive** | `penthera` *(no args)* | Guided wizard (URL plus optional repo scan, Markdown report) |
|
|
120
122
|
| **Machine audit** | `penthera --machine` | macOS checks (keyloggers, persistence, rootkits) |
|
|
@@ -157,245 +159,6 @@ Findings map to the [OWASP Web Security Testing Guide](docs/owasp-wstg-coverage.
|
|
|
157
159
|
|
|
158
160
|
---
|
|
159
161
|
|
|
160
|
-
## Compliance: NIS2 and the cyberwetgeving
|
|
161
|
-
|
|
162
|
-
The EU NIS2 directive and its national transpositions (the Cyberbeveiligingswet
|
|
163
|
-
in the Netherlands, the NIS2-wet in Belgium) put a duty of care, the *zorgplicht*,
|
|
164
|
-
on in-scope organisations. That duty is the set of risk-management measures in
|
|
165
|
-
Article 21(2) of the directive. Most of them are organisational. Some are
|
|
166
|
-
technical and testable, and that is the part Penthera helps with.
|
|
167
|
-
|
|
168
|
-
Penthera does not make you compliant and does not certify anything. What it gives
|
|
169
|
-
you is automated, repeatable technical evidence for the measures a scanner can
|
|
170
|
-
honestly verify: the kind of artefact you can attach to a risk file, show an
|
|
171
|
-
auditor, or run in CI so a regression shows up before it ships.
|
|
172
|
-
|
|
173
|
-
| Art. 21(2) measure | What Penthera does | Coverage |
|
|
174
|
-
|--------------------|--------------------|----------|
|
|
175
|
-
| **(a)** Risk analysis & information-security policy | Findings, severity, and baseline history feed your risk analysis | Evidence |
|
|
176
|
-
| **(b)** Incident handling | `--incident` drafts the 24h / 72h / 1-month reports against your authority's required content | Partial |
|
|
177
|
-
| **(c)** Business continuity, backup, crisis management | Organisational; outside a scanner's reach | Out of scope |
|
|
178
|
-
| **(d)** Supply-chain security | Known-CVE checks on JS dependencies, leaked credentials, a CycloneDX SBOM (`--sbom`), and passive supplier rating (`--suppliers`) | Partial |
|
|
179
|
-
| **(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** |
|
|
180
|
-
| **(f)** Assessing the effectiveness of measures | Baseline diff, re-scan-to-verify, and `--history` tracking of time-to-fix, ageing, and drift | Partial |
|
|
181
|
-
| **(g)** Cyber hygiene & training | Remediation playbook + secure-defaults guidance (guidance, not a training programme) | Partial |
|
|
182
|
-
| **(h)** Cryptography & encryption | TLS protocol/cipher/certificate audit, HSTS, cookie `Secure` (transport layer only) | Partial |
|
|
183
|
-
| **(i)** HR security, **access control**, asset management | Access control tested: JWT, IDOR/BOLA, OAuth redirect, client-side-auth, trust-boundary mapping | Partial |
|
|
184
|
-
| **(j)** Multi-factor authentication & secure communications | MFA probe (on the roadmap) | Planned |
|
|
185
|
-
|
|
186
|
-
**Coverage key:** *Strong* = automated technical testing · *Partial* = part of the
|
|
187
|
-
measure · *Evidence* = output feeds the measure, no direct test · *Planned* = on the
|
|
188
|
-
roadmap · *Out of scope* = organisational or physical.
|
|
189
|
-
|
|
190
|
-
In practice, Penthera today gives you solid automated evidence for the
|
|
191
|
-
secure-development and vulnerability-handling duty (e), which is the technical
|
|
192
|
-
heart of the zorgplicht, plus meaningful coverage of supply chain (d),
|
|
193
|
-
cryptography in transit (h), access control (i), and effectiveness testing (f).
|
|
194
|
-
That is not the whole duty of care, but it is a real, defensible start you can run
|
|
195
|
-
today and re-run on every deploy.
|
|
196
|
-
|
|
197
|
-
> **Not legal advice, not a certification, not a guarantee of compliance.**
|
|
198
|
-
> Penthera automates the technical-testing part of the duty of care. The
|
|
199
|
-
> directive's organisational duties (governance, risk-management policy,
|
|
200
|
-
> incident-notification processes, supply-chain contracts, staff training) need
|
|
201
|
-
> human process and, where it matters, review by a qualified advisor. For an
|
|
202
|
-
> authoritative reading, consult your national authority (the NCSC in the
|
|
203
|
-
> Netherlands, the CCB and its CyFun framework in Belgium) or a specialised jurist.
|
|
204
|
-
|
|
205
|
-
Generate this mapping for your own scan with `--framework nis2`. It adds a
|
|
206
|
-
per-run coverage table (which measures *this* scan actually exercised, driven by
|
|
207
|
-
the probes that ran) to the terminal, Markdown, and JSON reports:
|
|
208
|
-
|
|
209
|
-
```bash
|
|
210
|
-
penthera https://myapp.com --framework nis2 -o reports/scan.json
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### Readiness report (clone → report in two commands)
|
|
214
|
-
|
|
215
|
-
Go further than the mapping: combine the scan with a short offline
|
|
216
|
-
self-assessment (the organisational measures a scanner can't test, like policies,
|
|
217
|
-
backups, training, and MFA) into a full NIS2 readiness report. Every Article 21
|
|
218
|
-
measure is marked `met / partial / gap / n-a`, with proportionality notes, your
|
|
219
|
-
incident-reporting deadlines, and provenance on every piece of evidence.
|
|
220
|
-
|
|
221
|
-
```bash
|
|
222
|
-
# 1. Create your self-assessment (offline, one-time). Fill in yes | partial | no | na.
|
|
223
|
-
penthera --assessment-init assessment.json
|
|
224
|
-
|
|
225
|
-
# 2. Scan + self-assessment → readiness report (writes reports/scan-readiness.md)
|
|
226
|
-
penthera https://myapp.com --repo . --readiness --assessment assessment.json -o reports/scan.json
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
The readiness report is **evidence and self-assessment tooling, not a
|
|
230
|
-
certification**. It is provenance-linked (`source, method, collected_at,
|
|
231
|
-
confidence`) and reproducible, and it lists exactly what only you can attest to.
|
|
232
|
-
Jurisdiction specifics live in `lib/jurisdictions/nl.json` (NL today; BE and
|
|
233
|
-
others are just added config).
|
|
234
|
-
|
|
235
|
-
### Answer a customer's security questionnaire
|
|
236
|
-
|
|
237
|
-
The questionnaire blocking your deal, drafted from evidence you already have:
|
|
238
|
-
|
|
239
|
-
```bash
|
|
240
|
-
# one question per line, pasted from the customer's form
|
|
241
|
-
penthera https://myapp.com --readiness --assessment assessment.json --questionnaire questions.txt
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
Each question is mapped to the Article 21 measure it belongs to and answered
|
|
245
|
-
from the scan and your self-assessment, with the backing evidence cited.
|
|
246
|
-
Anything Penthera can't map is marked `NEEDS HUMAN INPUT` instead of guessed.
|
|
247
|
-
Review every answer before you send it.
|
|
248
|
-
|
|
249
|
-
### Policy pack
|
|
250
|
-
|
|
251
|
-
Generate the proportionate written policies NIS2 expects, pre-filled from your
|
|
252
|
-
readiness gaps and the NL incident deadlines:
|
|
253
|
-
|
|
254
|
-
```bash
|
|
255
|
-
penthera --policy-pack ./policies --org "Your Company" --readiness --assessment assessment.json
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
Writes editable Markdown starting points: information security, access control,
|
|
259
|
-
incident response, backup/continuity, acceptable use, supplier security, and
|
|
260
|
-
vulnerability management. **Templates, not legal advice.** Keep them short and
|
|
261
|
-
used. A two-page policy that genuinely exists is what "proportionate" means.
|
|
262
|
-
|
|
263
|
-
### Incident-report helper
|
|
264
|
-
|
|
265
|
-
NIS2 has hard reporting deadlines (24h early warning, 72h notification, 1-month
|
|
266
|
-
final report). If something happens, get the timeline and draft reports:
|
|
267
|
-
|
|
268
|
-
```bash
|
|
269
|
-
penthera --incident-init incident.json # fill in what happened (set aware_at)
|
|
270
|
-
penthera --incident incident.json # writes incident-reports.md
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
It computes your three deadlines from when you became aware, flags anything
|
|
274
|
-
overdue, and drafts the early-warning, notification, and final report against
|
|
275
|
-
the required content, with `NEEDS INPUT` where you still have to fill in facts.
|
|
276
|
-
**These are drafts to help you meet the deadlines, not a substitute for your
|
|
277
|
-
authority's official form.**
|
|
278
|
-
|
|
279
|
-
### Software bill of materials (SBOM)
|
|
280
|
-
|
|
281
|
-
Emit a standard CycloneDX SBOM of your dependencies. It's what enterprises
|
|
282
|
-
increasingly require of suppliers, and direct evidence for supply-chain security
|
|
283
|
-
(measure d):
|
|
284
|
-
|
|
285
|
-
```bash
|
|
286
|
-
penthera --repo . --sbom sbom.cdx.json
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
Parses `package-lock.json` / `package.json` / `requirements.txt` offline (no
|
|
290
|
-
network), with resolved versions and package URLs (purls).
|
|
291
|
-
|
|
292
|
-
### Remediation tracking (the audit loop)
|
|
293
|
-
|
|
294
|
-
Closing findings matters more than finding them. Track them over time:
|
|
295
|
-
time-to-fix, ageing of open items, and what changed since the last scan (evidence
|
|
296
|
-
for measure f, effectiveness):
|
|
297
|
-
|
|
298
|
-
```bash
|
|
299
|
-
penthera https://myapp.com --history reports/history.json -o reports/scan.json
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
Each run folds the current findings into a small history keyed by a **stable
|
|
303
|
-
fingerprint** (so a re-worded or re-counted finding isn't mistaken for a new
|
|
304
|
-
one), then reports open vs resolved, median time-to-fix, and ageing buckets.
|
|
305
|
-
Re-run on a cadence to watch the trend.
|
|
306
|
-
|
|
307
|
-
### Supplier rating (passive)
|
|
308
|
-
|
|
309
|
-
Assess your suppliers' public security posture for supply-chain risk (measure d):
|
|
310
|
-
|
|
311
|
-
```bash
|
|
312
|
-
printf 'supplier1.com\nsupplier2.com\n' > suppliers.txt
|
|
313
|
-
penthera --suppliers suppliers.txt
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
**Strictly passive and public-data-only.** One homepage GET (security headers),
|
|
317
|
-
a TLS handshake (certificate), and public DNS lookups (SPF/DMARC/MX) per domain.
|
|
318
|
-
No endpoint discovery, no auth probes, no intrusive testing. You get a
|
|
319
|
-
red/amber/green rating per supplier. It's a screening aid for your own vendor
|
|
320
|
-
risk assessment, **not** an authorised penetration test of the supplier.
|
|
321
|
-
|
|
322
|
-
Because the mapping targets the NIS2 directive itself, it applies across the
|
|
323
|
-
national transpositions; jurisdiction-specific profiles (NL, BE) are on the roadmap.
|
|
324
|
-
|
|
325
|
-
---
|
|
326
|
-
|
|
327
|
-
## Ethical use & authorization
|
|
328
|
-
|
|
329
|
-
Penthera sends HTTP requests to the targets you give it. Some modes (`--deep`,
|
|
330
|
-
`--fuzz`, `--all`) send attack payloads designed to find vulnerabilities. Use it
|
|
331
|
-
responsibly.
|
|
332
|
-
|
|
333
|
-
**You may use Penthera when:**
|
|
334
|
-
|
|
335
|
-
- You **own** the target (your app, your server, your project).
|
|
336
|
-
- You have **written authorization** from the system owner (email, ticket, signed scope).
|
|
337
|
-
- You scan **localhost** or private lab environments you control.
|
|
338
|
-
- You use it for **defensive security**: research, coursework, CI hardening, pre-release audits.
|
|
339
|
-
|
|
340
|
-
**You must not use Penthera to:**
|
|
341
|
-
|
|
342
|
-
- Scan systems you do not own or lack explicit permission to test.
|
|
343
|
-
- Probe government, healthcare, financial, or third-party production systems without authorization.
|
|
344
|
-
- Exfiltrate data, disrupt services, or bypass access controls.
|
|
345
|
-
- Use findings to attack, extort, or harm anyone.
|
|
346
|
-
|
|
347
|
-
> Before scanning any URL that is not localhost, confirm you have permission.
|
|
348
|
-
> **When in doubt, do not scan.**
|
|
349
|
-
|
|
350
|
-
Unauthorized security testing may violate computer-misuse laws (for example the US
|
|
351
|
-
CFAA, the UK Computer Misuse Act, and EU national equivalents) and can result in
|
|
352
|
-
criminal prosecution, civil liability, IP blocking, account termination, and
|
|
353
|
-
academic or professional penalties.
|
|
354
|
-
|
|
355
|
-
---
|
|
356
|
-
|
|
357
|
-
## Disclaimer
|
|
358
|
-
|
|
359
|
-
Penthera is provided for legitimate security research and defensive testing of
|
|
360
|
-
systems you own or are authorized to test, and for that purpose only.
|
|
361
|
-
|
|
362
|
-
- It is a research and self-assessment tool. Scan your own apps to find and fix
|
|
363
|
-
weaknesses before attackers do.
|
|
364
|
-
- The same capabilities that harden a system can cause harm if pointed at someone
|
|
365
|
-
else's. Use it carefully and lawfully.
|
|
366
|
-
- The author does not condone unauthorized or malicious use, and accepts no
|
|
367
|
-
liability for any damage, loss, or legal consequence arising from use or misuse
|
|
368
|
-
of this software.
|
|
369
|
-
- The software is provided "as is", without warranty of any kind. See [LICENSE](LICENSE) (MIT).
|
|
370
|
-
- You are solely responsible for ensuring every scan you run is authorized and
|
|
371
|
-
legal in your jurisdiction.
|
|
372
|
-
|
|
373
|
-
By using Penthera you accept these terms.
|
|
374
|
-
|
|
375
|
-
---
|
|
376
|
-
|
|
377
|
-
## Install
|
|
378
|
-
|
|
379
|
-
The [one-line installer](#run-it) is the fastest path. To set it up manually:
|
|
380
|
-
|
|
381
|
-
```bash
|
|
382
|
-
git clone https://github.com/danoszz/penthera.git
|
|
383
|
-
cd penthera
|
|
384
|
-
npm install
|
|
385
|
-
npm link # exposes `penthera` and `penthera-scan` globally
|
|
386
|
-
penthera --version
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
Requirements: Node.js 18+ and npm. Optional: nuclei-templates for `--nuclei`, and
|
|
390
|
-
Docker. To run it in a container:
|
|
391
|
-
|
|
392
|
-
```bash
|
|
393
|
-
docker build -t penthera .
|
|
394
|
-
docker run --rm penthera https://your-app.example
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
---
|
|
398
|
-
|
|
399
162
|
## Quick start
|
|
400
163
|
|
|
401
164
|
```bash
|
|
@@ -510,19 +273,100 @@ Fixes come from a [remediation playbook](skills/penthera/references/remediation.
|
|
|
510
273
|
|
|
511
274
|
---
|
|
512
275
|
|
|
276
|
+
## Compliance: NIS2 and the cyberwetgeving
|
|
277
|
+
|
|
278
|
+
Shipping in the EU? NIS2 (the Cyberbeveiligingswet in the Netherlands, the
|
|
279
|
+
NIS2-wet in Belgium) puts a duty of care on in-scope organisations. Penthera
|
|
280
|
+
turns the technical, testable part of that duty into repeatable evidence, and
|
|
281
|
+
helps with the paperwork around it. Offline, no accounts.
|
|
282
|
+
|
|
283
|
+
| Command | What you get |
|
|
284
|
+
|---------|--------------|
|
|
285
|
+
| `--framework nis2` | Per-run Article 21(2) coverage table in every report |
|
|
286
|
+
| `--readiness --assessment a.json` | Full readiness report: every measure marked met / partial / gap |
|
|
287
|
+
| `--questionnaire questions.txt` | Customer security questionnaire drafted from your evidence |
|
|
288
|
+
| `--policy-pack ./policies` | Proportionate policy templates, prefilled from your gaps |
|
|
289
|
+
| `--incident incident.json` | 24h / 72h / 1-month incident report drafts and deadlines |
|
|
290
|
+
| `--sbom sbom.cdx.json` | CycloneDX software bill of materials |
|
|
291
|
+
| `--history history.json` | Time-to-fix, ageing, and drift across scans |
|
|
292
|
+
| `--suppliers suppliers.txt` | Passive red/amber/green rating of your suppliers |
|
|
293
|
+
|
|
294
|
+
**Full guide, the measure-by-measure coverage table, and the limits:**
|
|
295
|
+
[docs/compliance-nis2.md](docs/compliance-nis2.md).
|
|
296
|
+
|
|
297
|
+
> Readiness tooling, not a certification and not legal advice.
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
513
301
|
## CI integration
|
|
514
302
|
|
|
515
303
|
```yaml
|
|
516
304
|
# Scan a staging URL on a schedule and upload results to GitHub Security.
|
|
517
|
-
|
|
518
|
-
-
|
|
519
|
-
- uses: github/codeql-action/upload-sarif@v3
|
|
305
|
+
- run: npx penthera@1.2.0 "$PENTEST_URL" --profile standard --sarif reports/scan.sarif
|
|
306
|
+
- uses: github/codeql-action/upload-sarif@v4
|
|
520
307
|
with:
|
|
521
308
|
sarif_file: reports/scan.sarif
|
|
522
309
|
```
|
|
523
310
|
|
|
524
|
-
|
|
525
|
-
|
|
311
|
+
Copy the full workflow from
|
|
312
|
+
[`docs/examples/github-actions-scan.yml`](docs/examples/github-actions-scan.yml)
|
|
313
|
+
into your app's repo. Pin the Penthera version so a scheduled run never picks up
|
|
314
|
+
code you have not reviewed.
|
|
315
|
+
|
|
316
|
+
Penthera's own CI runs the full test suite on Node 22 and 24, self-scans a mock
|
|
317
|
+
vulnerable API on every push, and runs CodeQL and OpenSSF Scorecard.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Ethical use & authorization
|
|
322
|
+
|
|
323
|
+
Penthera sends HTTP requests to the targets you give it. Some modes (`--deep`,
|
|
324
|
+
`--fuzz`, `--all`) send attack payloads designed to find vulnerabilities. Use it
|
|
325
|
+
responsibly.
|
|
326
|
+
|
|
327
|
+
**You may use Penthera when:**
|
|
328
|
+
|
|
329
|
+
- You **own** the target (your app, your server, your project).
|
|
330
|
+
- You have **written authorization** from the system owner (email, ticket, signed scope).
|
|
331
|
+
- You scan **localhost** or private lab environments you control.
|
|
332
|
+
- You use it for **defensive security**: research, coursework, CI hardening, pre-release audits.
|
|
333
|
+
|
|
334
|
+
**You must not use Penthera to:**
|
|
335
|
+
|
|
336
|
+
- Scan systems you do not own or lack explicit permission to test.
|
|
337
|
+
- Probe government, healthcare, financial, or third-party production systems without authorization.
|
|
338
|
+
- Exfiltrate data, disrupt services, or bypass access controls.
|
|
339
|
+
- Use findings to attack, extort, or harm anyone.
|
|
340
|
+
|
|
341
|
+
> Before scanning any URL that is not localhost, confirm you have permission.
|
|
342
|
+
> **When in doubt, do not scan.**
|
|
343
|
+
|
|
344
|
+
Unauthorized security testing may violate computer-misuse laws (for example the US
|
|
345
|
+
CFAA, the UK Computer Misuse Act, and EU national equivalents) and can result in
|
|
346
|
+
criminal prosecution, civil liability, IP blocking, account termination, and
|
|
347
|
+
academic or professional penalties.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Install
|
|
352
|
+
|
|
353
|
+
The [one-line installer](#run-it) is the fastest path. To set it up manually:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
git clone https://github.com/danoszz/penthera.git
|
|
357
|
+
cd penthera
|
|
358
|
+
npm install
|
|
359
|
+
npm link # exposes `penthera` and `penthera-scan` globally
|
|
360
|
+
penthera --version
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Requirements: Node.js 22+ and npm. Optional: nuclei-templates for `--nuclei`, and
|
|
364
|
+
Docker. To run it in a container:
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
docker build -t penthera .
|
|
368
|
+
docker run --rm penthera https://your-app.example
|
|
369
|
+
```
|
|
526
370
|
|
|
527
371
|
---
|
|
528
372
|
|
|
@@ -547,9 +391,8 @@ npm run pentest:mock # offline tests against a local mock vuln API
|
|
|
547
391
|
npm run mock-server # start the mock API on port 8765
|
|
548
392
|
```
|
|
549
393
|
|
|
550
|
-
See the [roadmap](ROADMAP.md) for what is next
|
|
551
|
-
|
|
552
|
-
See the [roadmap](ROADMAP.md) for what is next and what is deliberately not
|
|
394
|
+
See the [roadmap](ROADMAP.md) for what is next, including auditing the skills,
|
|
395
|
+
MCP servers, and plugins in your AI toolchain, and what is deliberately not
|
|
553
396
|
planned.
|
|
554
397
|
|
|
555
398
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the project layout, how to add a probe
|
|
@@ -559,7 +402,7 @@ or template, the test matrix, and the release and publishing checklist.
|
|
|
559
402
|
|
|
560
403
|
## Status
|
|
561
404
|
|
|
562
|
-
**v1.
|
|
405
|
+
**v1.2, production-ready.** URL, repo, and machine scanning, NIS2 readiness tooling, scan profiles,
|
|
563
406
|
Markdown, JSON, and SARIF reports, session-aware auth, JWT, IDOR, OAuth, and
|
|
564
407
|
header probes, adaptive knowledge-graph probing, a plugins and templates API,
|
|
565
408
|
Docker, CI, and the Agent Skill are all shipped. See the [CHANGELOG](CHANGELOG.md)
|
|
@@ -594,6 +437,26 @@ You are responsible for ensuring you are authorized to scan any target. See [Eth
|
|
|
594
437
|
|
|
595
438
|
---
|
|
596
439
|
|
|
440
|
+
## Disclaimer
|
|
441
|
+
|
|
442
|
+
Penthera is provided for legitimate security research and defensive testing of
|
|
443
|
+
systems you own or are authorized to test, and for that purpose only.
|
|
444
|
+
|
|
445
|
+
- It is a research and self-assessment tool. Scan your own apps to find and fix
|
|
446
|
+
weaknesses before attackers do.
|
|
447
|
+
- The same capabilities that harden a system can cause harm if pointed at someone
|
|
448
|
+
else's. Use it carefully and lawfully.
|
|
449
|
+
- The author does not condone unauthorized or malicious use, and accepts no
|
|
450
|
+
liability for any damage, loss, or legal consequence arising from use or misuse
|
|
451
|
+
of this software.
|
|
452
|
+
- The software is provided "as is", without warranty of any kind. See [LICENSE](LICENSE) (MIT).
|
|
453
|
+
- You are solely responsible for ensuring every scan you run is authorized and
|
|
454
|
+
legal in your jurisdiction.
|
|
455
|
+
|
|
456
|
+
By using Penthera you accept these terms.
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
597
460
|
## License & disclosure
|
|
598
461
|
|
|
599
462
|
MIT. See [LICENSE](LICENSE). Report vulnerabilities in Penthera itself via
|
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,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.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Example: scan your staging URL with Penthera on a schedule.
|
|
2
|
+
#
|
|
3
|
+
# Copy this file into YOUR app's repo as `.github/workflows/penthera.yml` and add
|
|
4
|
+
# a `PENTEST_STAGING_URL` repository secret. Only point it at a URL you own or are
|
|
5
|
+
# authorized to test. Findings appear under Security > Code scanning.
|
|
6
|
+
#
|
|
7
|
+
# Pin the Penthera version (and, ideally, the actions below to commit SHAs) so a
|
|
8
|
+
# scheduled run never picks up code you have not reviewed.
|
|
9
|
+
|
|
10
|
+
name: Penthera security scan
|
|
11
|
+
|
|
12
|
+
on:
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
schedule:
|
|
15
|
+
- cron: "0 6 * * 1" # weekly, Monday 06:00 UTC
|
|
16
|
+
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
penthera:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
security-events: write
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/setup-node@v6
|
|
28
|
+
with:
|
|
29
|
+
node-version: 22
|
|
30
|
+
|
|
31
|
+
- name: Scan staging URL
|
|
32
|
+
env:
|
|
33
|
+
PENTEST_URL: ${{ secrets.PENTEST_STAGING_URL }}
|
|
34
|
+
run: |
|
|
35
|
+
mkdir -p reports
|
|
36
|
+
set +e
|
|
37
|
+
npx --yes penthera@1.2.0 "$PENTEST_URL" \
|
|
38
|
+
--profile standard \
|
|
39
|
+
-o reports/scan.json \
|
|
40
|
+
--sarif reports/scan.sarif
|
|
41
|
+
code=$?
|
|
42
|
+
set -e
|
|
43
|
+
# Exit 1 = critical or high findings (reported via SARIF); only 2 (scan error) fails the job.
|
|
44
|
+
if [ "$code" -eq 2 ]; then exit 2; fi
|
|
45
|
+
|
|
46
|
+
- uses: github/codeql-action/upload-sarif@v4
|
|
47
|
+
if: always()
|
|
48
|
+
with:
|
|
49
|
+
sarif_file: reports/scan.sarif
|
|
50
|
+
|
|
51
|
+
- uses: actions/upload-artifact@v7
|
|
52
|
+
if: always()
|
|
53
|
+
with:
|
|
54
|
+
name: penthera-reports
|
|
55
|
+
path: reports/
|
package/docs/index.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Penthera documentation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Security scanner for developers: scan live URLs, local repos, or macOS machines from your AI agent or the CLI.
|
|
4
4
|
|
|
5
5
|
## Getting started
|
|
6
6
|
|
|
@@ -33,8 +33,9 @@ Lightweight security scanner for developers, scan live URLs, local repos, or mac
|
|
|
33
33
|
| Topic | Link |
|
|
34
34
|
|-------|------|
|
|
35
35
|
| OWASP WSTG mapping | [owasp-wstg-coverage.md](./owasp-wstg-coverage.md) |
|
|
36
|
-
|
|
|
37
|
-
|
|
|
36
|
+
| NIS2 compliance guide | [compliance-nis2.md](./compliance-nis2.md) |
|
|
37
|
+
| Scan your app in GitHub Actions | [examples/github-actions-scan.yml](./examples/github-actions-scan.yml), set `PENTEST_STAGING_URL` |
|
|
38
|
+
| Penthera's own CI | `.github/workflows/ci.yml` (full tests + mock scan), `codeql.yml`, `scorecard.yml` |
|
|
38
39
|
| Docker | `docker build -t penthera . && docker run penthera https://localhost:3000` |
|
|
39
40
|
|
|
40
41
|
## Agent skill
|
package/lib/whitebox/secrets.js
CHANGED
|
@@ -21,7 +21,23 @@ const TEST_VALUE = /\b(?:placeholder|example|changeme|dummy|sample|redacted|inva
|
|
|
21
21
|
const SECRET_PATTERNS = [
|
|
22
22
|
{ name: "AWS access key", pattern: /AKIA[0-9A-Z]{16}/, severity: "critical" },
|
|
23
23
|
{ name: "GitHub token", pattern: /ghp_[a-zA-Z0-9]{36,}/, severity: "critical" },
|
|
24
|
-
|
|
24
|
+
// AI provider keys. Vibecoded apps leak these more than any other credential,
|
|
25
|
+
// often via a NEXT_PUBLIC_/VITE_ variable or a committed notebook. Specific
|
|
26
|
+
// prefixes come first so a key is reported under its real provider.
|
|
27
|
+
{ name: "Anthropic API key", pattern: /sk-ant-(?:api|admin)\d{2}-[A-Za-z0-9_-]{32,}/, severity: "critical" },
|
|
28
|
+
{ name: "OpenRouter API key", pattern: /sk-or-v1-[a-f0-9]{64}/, severity: "critical" },
|
|
29
|
+
{ name: "OpenAI API key", pattern: /sk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}/, severity: "critical" },
|
|
30
|
+
{ name: "OpenAI API key (legacy format)", pattern: /sk-[a-zA-Z0-9]{20,}/, severity: "critical" },
|
|
31
|
+
// AIza keys are also Firebase web keys, which are public by design, so this
|
|
32
|
+
// is high rather than critical: it matters if the key has Gemini/Vertex access.
|
|
33
|
+
{ name: "Google API key (Gemini / Firebase / Maps)", pattern: /AIza[0-9A-Za-z_-]{35}/, severity: "high" },
|
|
34
|
+
{ name: "Groq API key", pattern: /gsk_[A-Za-z0-9]{48,}/, severity: "critical" },
|
|
35
|
+
{ name: "Hugging Face token", pattern: /hf_[A-Za-z0-9]{34,}/, severity: "critical" },
|
|
36
|
+
{ name: "Replicate API token", pattern: /r8_[A-Za-z0-9]{37,}/, severity: "critical" },
|
|
37
|
+
{ name: "xAI API key", pattern: /xai-[A-Za-z0-9]{70,}/, severity: "critical" },
|
|
38
|
+
{ name: "Perplexity API key", pattern: /pplx-[A-Za-z0-9]{40,}/, severity: "critical" },
|
|
39
|
+
{ name: "LangSmith API key", pattern: /lsv2_(?:pt|sk)_[a-f0-9]{32}_[a-f0-9]{10}/, severity: "high" },
|
|
40
|
+
{ name: "Pinecone API key", pattern: /pcsk_[A-Za-z0-9_]{50,}/, severity: "high" },
|
|
25
41
|
{ name: "Stripe secret key", pattern: /sk_live_[a-zA-Z0-9]{20,}/, severity: "critical" },
|
|
26
42
|
{
|
|
27
43
|
name: "Generic API key assignment",
|
|
@@ -35,8 +51,8 @@ const SECRET_PATTERNS = [
|
|
|
35
51
|
severity: "high",
|
|
36
52
|
skipIf: TEST_VALUE,
|
|
37
53
|
},
|
|
38
|
-
{ name: "Private key block", pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/, severity: "critical" },
|
|
39
54
|
{ name: "GCP/Firebase service account", pattern: /"private_key"\s*:\s*"-----BEGIN/, severity: "critical" },
|
|
55
|
+
{ name: "Private key block", pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/, severity: "critical" },
|
|
40
56
|
{
|
|
41
57
|
name: "JWT secret assignment",
|
|
42
58
|
pattern: /(?:jwt[_-]?secret|session[_-]?secret)\s*[:=]\s*['"][^'"\s]{8,}['"]/i,
|
|
@@ -48,6 +64,7 @@ const SECRET_PATTERNS = [
|
|
|
48
64
|
const SCAN_EXTENSIONS = new Set([
|
|
49
65
|
".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs", ".py", ".rb", ".go", ".java",
|
|
50
66
|
".env", ".yaml", ".yml", ".json", ".toml", ".cfg", ".ini", ".sh",
|
|
67
|
+
".ipynb", // notebooks are a classic place for a pasted provider key
|
|
51
68
|
]);
|
|
52
69
|
|
|
53
70
|
function walkFiles(dir, files = []) {
|
|
@@ -69,9 +86,34 @@ function maskMatch(match) {
|
|
|
69
86
|
return match.length <= 8 ? "***" : match.slice(0, 4) + "…" + match.slice(-4);
|
|
70
87
|
}
|
|
71
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Find secrets in a string of text (a source file, a JS bundle, a config).
|
|
91
|
+
* Returns one hit per rule: `{ name, severity, match }`, with the raw match so
|
|
92
|
+
* callers decide how to mask it. At most one rule fires per provider key: once a
|
|
93
|
+
* specific AI-provider rule matches a span, broader rules skip that span.
|
|
94
|
+
*/
|
|
95
|
+
export function findSecretsInText(content) {
|
|
96
|
+
const hits = [];
|
|
97
|
+
const claimed = [];
|
|
98
|
+
for (const rule of SECRET_PATTERNS) {
|
|
99
|
+
const match = content.match(rule.pattern);
|
|
100
|
+
if (!match) continue;
|
|
101
|
+
// For assignment-style rules, test the placeholder/dummy filter against
|
|
102
|
+
// the assigned value only (not the whole `key = "value"` match), so a
|
|
103
|
+
// variable named e.g. `testToken` can't mask a real secret.
|
|
104
|
+
const value = (match[0].match(/['"]([^'"]+)['"]/) || [, match[0]])[1];
|
|
105
|
+
if (rule.skipIf && rule.skipIf.test(value)) continue;
|
|
106
|
+
const start = match.index;
|
|
107
|
+
const end = start + match[0].length;
|
|
108
|
+
if (claimed.some(([a, b]) => start < b && end > a)) continue;
|
|
109
|
+
claimed.push([start, end]);
|
|
110
|
+
hits.push({ name: rule.name, severity: rule.severity, match: match[0] });
|
|
111
|
+
}
|
|
112
|
+
return hits;
|
|
113
|
+
}
|
|
114
|
+
|
|
72
115
|
export function scanSecrets(repoPath) {
|
|
73
116
|
const findings = [];
|
|
74
|
-
const seen = new Set();
|
|
75
117
|
|
|
76
118
|
for (const filePath of walkFiles(repoPath)) {
|
|
77
119
|
let content;
|
|
@@ -83,23 +125,11 @@ export function scanSecrets(repoPath) {
|
|
|
83
125
|
|
|
84
126
|
const rel = relative(repoPath, filePath);
|
|
85
127
|
|
|
86
|
-
for (const
|
|
87
|
-
const match = content.match(rule.pattern);
|
|
88
|
-
if (!match) continue;
|
|
89
|
-
// For assignment-style rules, test the placeholder/dummy filter against
|
|
90
|
-
// the assigned value only (not the whole `key = "value"` match), so a
|
|
91
|
-
// variable named e.g. `testToken` can't mask a real secret.
|
|
92
|
-
const value = (match[0].match(/['"]([^'"]+)['"]/) || [, match[0]])[1];
|
|
93
|
-
if (rule.skipIf && rule.skipIf.test(value)) continue;
|
|
94
|
-
|
|
95
|
-
const key = `${rel}::${rule.name}`;
|
|
96
|
-
if (seen.has(key)) continue;
|
|
97
|
-
seen.add(key);
|
|
98
|
-
|
|
128
|
+
for (const hit of findSecretsInText(content)) {
|
|
99
129
|
findings.push({
|
|
100
|
-
severity:
|
|
101
|
-
title: `${
|
|
102
|
-
description: `Possible secret in \`${rel}\`: \`${maskMatch(match
|
|
130
|
+
severity: hit.severity,
|
|
131
|
+
title: `${hit.name} in source`,
|
|
132
|
+
description: `Possible secret in \`${rel}\`: \`${maskMatch(hit.match)}\``,
|
|
103
133
|
url: rel,
|
|
104
134
|
category: "secrets",
|
|
105
135
|
source: "secret-scan",
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "penthera",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Security scanner for web apps and AI-built code: scan URLs and repos from Claude Code, Cursor or the CLI. OWASP-mapped, SARIF output, NIS2 evidence. Authorized testing only.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/danoszz/penthera.git"
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"lib",
|
|
22
22
|
"skills",
|
|
23
23
|
"docs",
|
|
24
|
-
"scripts/package-skill.sh",
|
|
25
24
|
"pentest.config.example.js",
|
|
26
25
|
"CHANGELOG.md",
|
|
27
26
|
"LICENSE",
|
|
@@ -34,7 +33,7 @@
|
|
|
34
33
|
"./wstg": "./lib/owasp-wstg.js"
|
|
35
34
|
},
|
|
36
35
|
"engines": {
|
|
37
|
-
"node": ">=
|
|
36
|
+
"node": ">=22"
|
|
38
37
|
},
|
|
39
38
|
"scripts": {
|
|
40
39
|
"test": "vitest run --reporter=verbose",
|
|
@@ -42,7 +41,6 @@
|
|
|
42
41
|
"scan:full": "node bin/scan.js",
|
|
43
42
|
"pentest": "vitest run --reporter=verbose",
|
|
44
43
|
"pentest:watch": "vitest --reporter=verbose",
|
|
45
|
-
"pentest:static": "vitest run tests/pentest-static.test.js --reporter=verbose",
|
|
46
44
|
"pentest:live": "vitest run tests/pentest-live.test.js --reporter=verbose",
|
|
47
45
|
"pentest:adaptive": "vitest run tests/pentest-adaptive.test.js --reporter=verbose",
|
|
48
46
|
"pentest:recon": "vitest run tests/pentest-recon.test.js --reporter=verbose",
|
|
@@ -68,9 +66,9 @@
|
|
|
68
66
|
],
|
|
69
67
|
"license": "MIT",
|
|
70
68
|
"devDependencies": {
|
|
71
|
-
"vitest": "^
|
|
69
|
+
"vitest": "^5.0.5"
|
|
72
70
|
},
|
|
73
71
|
"dependencies": {
|
|
74
|
-
"yaml": "^2.
|
|
72
|
+
"yaml": "^2.9.1"
|
|
75
73
|
}
|
|
76
74
|
}
|
package/skills/penthera/SKILL.md
CHANGED
|
@@ -9,11 +9,11 @@ description: >
|
|
|
9
9
|
against a baseline; or find hardcoded secrets before deploy. Do NOT use for targets
|
|
10
10
|
without explicit authorization.
|
|
11
11
|
license: MIT
|
|
12
|
-
compatibility: Node.js
|
|
12
|
+
compatibility: Node.js 22+, network access to target, Penthera CLI (npm link or node bin/penthera.js from repo). macOS only for --machine.
|
|
13
13
|
allowed-tools: "Bash(node:*) Bash(npm:*) Bash(penthera:*) Read Write Edit"
|
|
14
14
|
metadata:
|
|
15
15
|
author: danoszz
|
|
16
|
-
version: 1.
|
|
16
|
+
version: 1.2.0
|
|
17
17
|
category: security
|
|
18
18
|
---
|
|
19
19
|
|
|
@@ -44,59 +44,21 @@ Output includes: new findings count, resolved count, unchanged count. Use for we
|
|
|
44
44
|
|
|
45
45
|
## GitHub Actions
|
|
46
46
|
|
|
47
|
-
**Penthera CI** (`.github/workflows/ci.yml`) runs on every push:
|
|
47
|
+
**Penthera's own CI** (`.github/workflows/ci.yml`) runs on every push and PR:
|
|
48
48
|
|
|
49
|
-
1.
|
|
50
|
-
2.
|
|
51
|
-
3.
|
|
52
|
-
4. `validate-report.mjs` on the JSON output
|
|
53
|
-
5. SARIF upload to GitHub Security tab
|
|
49
|
+
1. The full test suite (`npm test`) on Node 22 and 24
|
|
50
|
+
2. A scan of the deliberately vulnerable mock API, producing JSON + SARIF
|
|
51
|
+
3. `validate-report.mjs` on the JSON output, plus the skill preflight
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
The mock-server SARIF is kept as a build artifact, not uploaded to code scanning,
|
|
54
|
+
because those findings are about the test fixture, not the repo.
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
on
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
- cron: "0 6 * * 1"
|
|
64
|
-
|
|
65
|
-
jobs:
|
|
66
|
-
penthera:
|
|
67
|
-
runs-on: ubuntu-latest
|
|
68
|
-
steps:
|
|
69
|
-
- uses: actions/checkout@v4
|
|
70
|
-
|
|
71
|
-
- uses: actions/setup-node@v4
|
|
72
|
-
with:
|
|
73
|
-
node-version: 20
|
|
74
|
-
cache: npm
|
|
75
|
-
|
|
76
|
-
- run: npm ci
|
|
77
|
-
|
|
78
|
-
- name: Scan staging URL
|
|
79
|
-
env:
|
|
80
|
-
PENTEST_URL: ${{ secrets.PENTEST_STAGING_URL }}
|
|
81
|
-
run: |
|
|
82
|
-
node bin/penthera.js "$PENTEST_URL" \
|
|
83
|
-
--profile standard \
|
|
84
|
-
-o reports/scan.json \
|
|
85
|
-
--sarif reports/scan.sarif
|
|
86
|
-
|
|
87
|
-
- uses: github/codeql-action/upload-sarif@v3
|
|
88
|
-
if: always()
|
|
89
|
-
with:
|
|
90
|
-
sarif_file: reports/scan.sarif
|
|
91
|
-
|
|
92
|
-
- uses: actions/upload-artifact@v4
|
|
93
|
-
if: always()
|
|
94
|
-
with:
|
|
95
|
-
name: penthera-reports
|
|
96
|
-
path: reports/
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Store `PENTEST_STAGING_URL` as a repository secret.
|
|
56
|
+
**For your own staging URL**, copy
|
|
57
|
+
[`docs/examples/github-actions-scan.yml`](https://github.com/danoszz/penthera/blob/main/docs/examples/github-actions-scan.yml)
|
|
58
|
+
into your app's repo as `.github/workflows/penthera.yml` and add a
|
|
59
|
+
`PENTEST_STAGING_URL` repository secret. It runs `npx penthera@<version>` on a
|
|
60
|
+
weekly schedule and uploads SARIF to your Security tab. Pin the version so a
|
|
61
|
+
scheduled run never picks up code you have not reviewed.
|
|
100
62
|
|
|
101
63
|
## Agent reporting workflow
|
|
102
64
|
|
|
@@ -21,7 +21,7 @@ penthera https://myapp.com --verbose --timeout 30000
|
|
|
21
21
|
|
|
22
22
|
**Symptoms:** `node: command not found` or engine error
|
|
23
23
|
|
|
24
|
-
**Solution:** Install Node.js
|
|
24
|
+
**Solution:** Install Node.js 22+ from [nodejs.org](https://nodejs.org/). Verify:
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
27
|
node -v # should be v18.x or higher
|
|
@@ -20,14 +20,14 @@ ok() {
|
|
|
20
20
|
echo "preflight: OK: $*"
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
# Node.js
|
|
23
|
+
# Node.js 22+
|
|
24
24
|
if ! command -v node >/dev/null 2>&1; then
|
|
25
|
-
fail "Node.js not found. Install Node.js
|
|
25
|
+
fail "Node.js not found. Install Node.js 22+ from https://nodejs.org/"
|
|
26
26
|
fi
|
|
27
27
|
|
|
28
28
|
NODE_MAJOR=$(node -e "process.stdout.write(String(process.version).replace(/^v/, '').split('.')[0])")
|
|
29
|
-
if [ "$NODE_MAJOR" -lt
|
|
30
|
-
fail "Node.js $(node -v) is too old. Requires Node.js
|
|
29
|
+
if [ "$NODE_MAJOR" -lt 22 ]; then
|
|
30
|
+
fail "Node.js $(node -v) is too old. Requires Node.js 22+."
|
|
31
31
|
fi
|
|
32
32
|
ok "Node.js $(node -v)"
|
|
33
33
|
|
package/scripts/package-skill.sh
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Package the Agent Skill for Claude.ai upload or GitHub Releases.
|
|
3
|
-
set -euo pipefail
|
|
4
|
-
|
|
5
|
-
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
6
|
-
OUT="$ROOT/dist"
|
|
7
|
-
ZIP="$OUT/penthera-skill.zip"
|
|
8
|
-
|
|
9
|
-
mkdir -p "$OUT"
|
|
10
|
-
rm -f "$ZIP"
|
|
11
|
-
|
|
12
|
-
cd "$ROOT/skills"
|
|
13
|
-
zip -r "$ZIP" penthera/ -x "*.DS_Store"
|
|
14
|
-
|
|
15
|
-
echo "Created $ZIP"
|
|
16
|
-
ls -lh "$ZIP"
|