penthera 1.1.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +269 -3
  2. package/README.md +489 -286
  3. package/SECURITY.md +28 -6
  4. package/docs/METHODOLOGY.md +321 -0
  5. package/docs/compliance-nis2.md +166 -0
  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 +55 -0
  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 +15 -3
  15. package/docs/local-loop.md +276 -0
  16. package/docs/references.bib +539 -0
  17. package/docs/references.md +77 -0
  18. package/docs/release-plan.md +228 -0
  19. package/lib/adaptive-probes.js +8 -5
  20. package/lib/agents/discover.js +384 -0
  21. package/lib/agents/hidden-content.js +387 -0
  22. package/lib/agents/index.js +286 -0
  23. package/lib/agents/lock.js +115 -0
  24. package/lib/agents/owasp.js +83 -0
  25. package/lib/agents/rules.js +376 -0
  26. package/lib/crawler.js +5 -15
  27. package/lib/data/retire-jsrepository.json.gz +0 -0
  28. package/lib/data/retire-jsrepository.meta.json +7 -0
  29. package/lib/deps/capabilities.js +523 -0
  30. package/lib/deps/diff.js +84 -0
  31. package/lib/deps/index.js +416 -0
  32. package/lib/deps/lockfile.js +106 -0
  33. package/lib/deps/online.js +132 -0
  34. package/lib/deps/popular-packages.json +1 -0
  35. package/lib/deps/report.js +173 -0
  36. package/lib/deps/source.js +171 -0
  37. package/lib/deps/typosquat.js +113 -0
  38. package/lib/fix/apply.js +95 -0
  39. package/lib/fix/diff.js +199 -0
  40. package/lib/fix/index.js +129 -0
  41. package/lib/fix/model.js +108 -0
  42. package/lib/fix/recipes/cookies.js +130 -0
  43. package/lib/fix/recipes/cors.js +116 -0
  44. package/lib/fix/recipes/headers.js +250 -0
  45. package/lib/fix/recipes/rate-limit.js +211 -0
  46. package/lib/fix/recipes/secrets.js +110 -0
  47. package/lib/fix/util.js +231 -0
  48. package/lib/fuzzer.js +14 -8
  49. package/lib/injections.js +19 -15
  50. package/lib/install/index.js +271 -0
  51. package/lib/install/policy.js +117 -0
  52. package/lib/memory/history.js +52 -0
  53. package/lib/memory/index.js +111 -0
  54. package/lib/memory/repo-suppress.js +64 -0
  55. package/lib/memory/store.js +325 -0
  56. package/lib/memory/views.js +198 -0
  57. package/lib/params.js +4 -12
  58. package/lib/provenance.js +2 -0
  59. package/lib/recon.js +12 -20
  60. package/lib/references.js +31 -0
  61. package/lib/references.json +374 -0
  62. package/lib/remediation/index.js +28 -0
  63. package/lib/retirejs.js +50 -22
  64. package/lib/templates.js +7 -10
  65. package/lib/whitebox/frameworks.js +1 -1
  66. package/lib/whitebox/secrets.js +49 -19
  67. package/package.json +19 -9
  68. package/skills/penthera/SKILL.md +104 -23
  69. package/skills/penthera/references/output-and-ci.md +12 -50
  70. package/skills/penthera/references/troubleshooting.md +1 -1
  71. package/skills/penthera/scripts/preflight.sh +4 -4
  72. package/src/agent/git.js +87 -0
  73. package/src/agent/loop.js +519 -0
  74. package/src/agent/tests.js +48 -0
  75. package/src/cli/agent.js +183 -0
  76. package/src/cli/deps.js +84 -0
  77. package/src/cli/evidence.js +67 -0
  78. package/src/cli/fix.js +191 -0
  79. package/src/cli/install.js +225 -0
  80. package/src/cli/memory.js +146 -0
  81. package/src/cli/merge-results.js +2 -0
  82. package/src/cli/report-outputs.js +72 -0
  83. package/src/cli/run-scan.js +2 -0
  84. package/src/cli.js +152 -6
  85. package/src/report/audience.js +270 -0
  86. package/src/report/evidence.js +253 -0
  87. package/src/report/markdown.js +65 -2
  88. package/src/report/model.js +212 -0
  89. package/src/report/template.js +175 -0
  90. package/src/report/timeline.js +145 -0
  91. package/src/reporter.js +109 -5
  92. package/src/scan-url.js +31 -7
  93. package/src/utils/http.js +252 -11
  94. package/src/utils/url.js +23 -0
  95. package/scripts/package-skill.sh +0 -16
package/CHANGELOG.md CHANGED
@@ -4,6 +4,272 @@ All notable changes to Penthera are documented here. Versioning follows [SemVer]
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.4.0] - 2026-10-19
8
+
9
+ The biggest release so far, covering the v1.3.0 "AI toolchain" and v1.4.0
10
+ "Local loop" milestones together. Penthera now does the routine work of a small
11
+ security team on your own machine:
12
+ - tests your app;
13
+ - vets and guards your dependencies;
14
+ - audits your AI tooling;
15
+ - fixes what it finds on a local branch and verifies each fix;
16
+ - writes reports and evidence for auditors.
17
+
18
+ It still runs offline, with no telemetry. The first outside contribution, from
19
+ @Nurana100, is in this release.
20
+
21
+ Requires Node 22+. Upgrading from 1.1.x or 1.2.x needs no config changes.
22
+
23
+ ### Added
24
+
25
+ - **GitHub Action** (`uses: danoszz/penthera@<sha>`, `action.yml`): on PRs that
26
+ change the lockfile it runs `penthera deps --base <PR base>`, upserts one PR
27
+ comment marked `<!-- penthera-deps -->` (edited in place on every push), and
28
+ uploads SARIF. Runs from the action's own checkout with no npx or npm install.
29
+ Fork PRs get the diff in the job summary. Also `mode: agents` and `mode: scan`,
30
+ plus `fail-on`. Example: `docs/examples/pr-dependency-review.yml`
31
+ - **`penthera agents [path] [--home] [--pin]`**: an offline, deterministic audit
32
+ of AI developer tooling. It finds and parses `.mcp.json`, `.cursor/`,
33
+ `.claude/`, `.vscode/mcp.json`, `AGENTS.md`, `CLAUDE.md`, `.cursorrules`,
34
+ `.github/copilot-instructions.md` and skill folders; with `--home` also
35
+ `~/.claude` (settings, hooks, permissions, plugins, skills), `~/.claude.json`,
36
+ `~/.cursor/mcp.json` and the Claude Desktop config. Read-only, no network, no
37
+ LLM judge
38
+ - Hidden-content engine (`lib/agents/hidden-content.js`): Unicode tag
39
+ characters (decoded), zero-width and bidi controls, variation-selector
40
+ smuggling, whitespace inflation, instructions in HTML comments, text aimed at
41
+ a scanner, base64 blobs (decoded and classified), binaries and archives in
42
+ skill folders by magic bytes, symlinks that leave a skill, and `curl | sh` /
43
+ `eval` style remote execution
44
+ - Risky-config rules: unpinned `npx -y` / `uvx` / `bunx` / `pnpm dlx` MCP
45
+ servers, inline secrets in server env/headers/args (via `findSecretsInText()`),
46
+ plain-HTTP remote servers, hooks that auto-approve or pipe to a shell,
47
+ `Bash(*)` and other wildcard permissions, `bypassPermissions`, credential-file
48
+ allows, wildcard `allowed-tools`, and unpinned plugin marketplaces
49
+ - `penthera.lock`: `--pin` records SHA-256 hashes of every skill folder, MCP
50
+ server definition, hook and installed plugin; later runs report changed (rug
51
+ pull), new and removed entries
52
+ - A–F grade for the agent setup in the terminal and Markdown reports. Findings
53
+ use category `agent-config`, map to OWASP MCP Top 10 and Agentic (ASI) ids,
54
+ and get per-rule SARIF ids (`agents/<rule>`)
55
+ - Agent Skill: Workflow 6 ("audit my MCP servers / skills / agent config")
56
+ - `docs/METHODOLOGY.md`: threat model, what black-box and white-box scanning
57
+ can and cannot find, the confidence model, false-positive controls, the
58
+ non-destructive default, offline operation, and how new attack techniques
59
+ are adopted (reviewed rules in releases, never fetched at run time). Design
60
+ choices cite published research and standards
61
+ - `docs/references.bib` and `docs/references.md`: 48 references, each checked
62
+ against the publisher or standards body, grouped by topic
63
+ - Per-finding citations: `lib/references.json` maps each finding category to
64
+ 1-3 references. The Markdown report adds a "Why this matters / Sources" line
65
+ per finding and a "Sources" line per action-plan item; SARIF rules get
66
+ `helpUri`, `help` and `properties.references`
67
+ - Accuracy benchmark: `npm run bench` scans the mock server, two new local lab
68
+ targets (vulnerable and decoy) and a generated repo fixture, scores precision
69
+ and recall against `bench/ground-truth.json`, and writes `bench/RESULTS.md`.
70
+ Offline and seeded; `npm test` fails if it drops below the CI floor.
71
+ Optional Juice Shop and crAPI targets are documented
72
+ - `CITATION.cff`, so GitHub shows "Cite this repository"
73
+ - **`penthera deps [path]`**: offline, deterministic dependency risk scanning
74
+ for npm. Reads `package-lock.json` (v1-v3), statically scans every installed
75
+ package and reports what it can do, with `file:line` evidence: install
76
+ scripts (including implicit `node-gyp`), shell, network, writes outside its
77
+ folder, environment reads and bulk access, `eval` / `new Function` / `vm`,
78
+ computed `require`/`import`, native binaries, and obfuscation signals
79
+ - **Capability diff** (`--diff <lockfile|dir>`, `--base <git-ref>`): packages
80
+ that are new or that gained capabilities in an upgrade. Old versions come
81
+ from the base tree or npm's local content cache, verified against the
82
+ lockfile integrity, so the diff works offline
83
+ - Attack indicators: writes to `~/.claude`, `~/.cursor`, `.mcp.json` or the
84
+ Claude Desktop config, running the `claude` / `gemini` / `q` CLIs (the
85
+ s1ngularity pattern), GitHub token harvesting, and the whole environment
86
+ serialised next to network code
87
+ - Typosquat / slopsquat check against a vendored list of ~7,800 popular npm
88
+ names (`lib/deps/popular-packages.json`, from npm-high-impact), refreshed at
89
+ release time by `npm run refresh:popular`. Nothing is fetched at runtime
90
+ - `--online` (off by default, the only network use): npm registry metadata for
91
+ fresh versions, brand-new packages, publisher changes and deprecations
92
+ - `--pr-comment <file>` and an example workflow
93
+ ([`docs/examples/github-actions-deps.yml`](docs/examples/github-actions-deps.yml))
94
+ that comments the capability diff on PRs that change the lockfile
95
+ - Findings use category `dependency-risk` and source `deps-scan`; SARIF
96
+ results now carry a start line when a finding has one
97
+ - **`penthera install [pkg...]`**: an install firewall on the `deps` engine.
98
+ npm resolves the new tree in a temporary copy with scripts off
99
+ (`--package-lock-only --ignore-scripts`), new tarballs are fetched with
100
+ `npm cache add` and read back only after an integrity check, and the
101
+ capability diff, new install scripts (with their commands) and typosquats
102
+ are shown before anything in the project changes. `penthera.policy.json`
103
+ (`allow` names/globs, `deny` and `requireApproval` capabilities) decides;
104
+ by default new install scripts, AI-config writes, AI CLI invocation, token
105
+ harvesting, obfuscation, typosquats and unreadable packages need approval.
106
+ Interactive by default, `--yes` for CI (installs only when nothing breaches
107
+ the policy), `--dry-run` to report. On approval the reviewed lockfile is
108
+ installed as is, with `--ignore-scripts` unless the scripts were approved
109
+ (on npm 12+, approved scripts are pinned in `package.json` `allowScripts`).
110
+ An abort leaves the project byte for byte unchanged. npm's registry traffic
111
+ is the only egress, and it is printed
112
+ - `scanDeps()` accepts `treeRoot` and a list of cache dirs; the diff output
113
+ carries each changed package's install scripts
114
+ - **Fix recipes** (`lib/fix/`, `penthera fix`): deterministic, local fixes for
115
+ common findings, printed as unified diffs. `penthera fix --plan` plans fixes
116
+ for a fresh repo scan or an earlier `-o` report; `penthera fix --apply <id>`
117
+ applies one after a y/N prompt (or `--yes`), then re-scans to verify. Recipes:
118
+ security headers (Next.js, Express, Fastify, Hono), cookie flags, CORS
119
+ wildcard to an allowlist from `CORS_ALLOWED_ORIGINS`, a rate-limit stub on
120
+ login/auth routes, and a hardcoded secret moved to an env var with
121
+ `.env.example` updated (always with the owner action to rotate it)
122
+ - `applyDiff()` refuses paths outside the repo (symlinks included), `.git`,
123
+ and dirty or non-git trees unless `--allow-dirty`, and writes nothing unless
124
+ every hunk applies. Planning never writes; nothing is committed or pushed
125
+ - Recipes edit only at exact anchors and return no plan when the anchor is
126
+ missing, instead of a risky diff
127
+ - Optional `--fix-model <name>` fallback for findings no recipe matches. It
128
+ talks only to Ollama on `127.0.0.1:11434`, is off by default, and labels its
129
+ output `confidence: "needs-human-review"`, `source: "local-model"`
130
+ - Fixtures for Next.js, Express, Fastify and Hono under `tests/fixtures/fix/`
131
+ - `detectFramework()` is now exported from `lib/whitebox/frameworks.js`
132
+ - **Audience reports** (`--audience developer|owner|auditor`): developer gets
133
+ findings with file:line, fix recipe ids or diffs, and sources; owner gets a
134
+ plain-language summary, what to do first, owner actions and the trend from
135
+ memory; auditor gets scope and method (linking `docs/METHODOLOGY.md`),
136
+ confidence labels with provenance per finding, egress and inconclusive
137
+ probes, suppressions with who and why, and NIS2 and OWASP mapping tables.
138
+ The default report is unchanged
139
+ - **User templates** (`--report-template file.md`): `{{a.b}}`,
140
+ `{{#each}}`, `{{#if}}`/`{{else}}`, nothing else. Values are HTML- and
141
+ Markdown-escaped; no helpers, code, includes or prototype access. Written from
142
+ scratch (`src/report/template.js`) and fuzz-tested against injection
143
+ - **Fix timeline** in the audience reports: found → fixed → verified with time
144
+ to fix, from an agent loop run (`loopRun`) or memory fix events
145
+ (`lib/memory/history.js`, also the owner report's trend)
146
+ - **Evidence bundle** (`--evidence <dir>`, `--sign-key <ssh-key>`): JSON,
147
+ Markdown, SARIF, the three audience reports, a custom template report, the
148
+ readiness report and SBOM when requested, and `manifest.json` (tool version,
149
+ command line with secrets redacted, timestamp, target, git commit, SHA-256 of
150
+ every file). Signing uses `ssh-keygen -Y sign -n penthera-evidence`; no new
151
+ dependency. `penthera evidence verify <dir> [--allowed-signers <file>]`
152
+ recomputes every hash, rejects changed, missing, extra and symlinked files,
153
+ and checks the signature. See `docs/evidence.md`
154
+ - **`penthera agent --loop`** (`src/agent/`): the local scan → triage → plan →
155
+ patch → verify → learn loop. Fixes are applied one at a time on a local
156
+ branch `penthera/fix-<yyyymmdd-hhmm>`, verified by a re-scan (and the
157
+ project's tests with `--run-tests`), committed when verified and reverted
158
+ when not. Every diff needs `y/n/skip/quit` approval; `--auto low` auto-applies
159
+ only recipes memory shows verified 3+ times with no failures on the
160
+ framework. Stops when nothing fixable is left or at `--max-iterations`
161
+ (default 5). `-o` writes a `loopRun` JSON (iterations, fixes, time per step),
162
+ also appended to memory as a new `loop_run` event
163
+ - The loop runs git only through an allow-list of local subcommands with
164
+ `protocol.allow=never`, no lazy fetch and the repo's hooks disabled; it never
165
+ pushes, fetches or opens pull requests. Scans run in one offline egress
166
+ context (the `--url` host only; Ollama on 127.0.0.1 only with
167
+ `--fix-model`). It refuses a dirty tree or detached HEAD unless
168
+ `--allow-dirty`, and Ctrl-C leaves only verified commits on the fix branch
169
+
170
+ ### Docs
171
+
172
+ - Sharper positioning: "the routine work of a security team, on your own
173
+ machine". The README now opens with what Penthera does for you, why you can
174
+ trust it, and a factual table of how it fits next to Socket, Snyk, GitHub,
175
+ OWASP ZAP and Anthropic's OSS Scanner. The package, plugin, skill and
176
+ repository descriptions match.
177
+ - Funding and partnership drafts under `docs/funding/`: an NLnet application
178
+ draft, GitHub Sponsors tiers, a partner brief, and a due diligence checklist.
179
+ - CONTRIBUTING: commits are signed off under the Developer Certificate of Origin.
180
+ - **`--offline`**: the scan contacts only the target. Recon is skipped, the
181
+ Retire.js check uses the vendored database snapshot, and every other host is
182
+ refused by the HTTP client (redirects may stay within the target's
183
+ registrable domain). Groundwork for the local agent loop.
184
+ - **Vendored Retire.js database** (`lib/data/retire-jsrepository.json.gz`,
185
+ 72 KB gzipped) with source URL, date and SHA-256 in a meta file.
186
+ `npm run refresh:retire-db` updates it at release time, and
187
+ `npm run refresh:retire-db -- --check` reports drift from upstream. Online
188
+ scans fall back to it when the live download fails.
189
+ - **Third-party egress report**: every scan lists the hosts it contacted
190
+ besides the target, in the terminal summary and in a "Network egress" section
191
+ of the Markdown report (`result.egress` in JSON).
192
+ - **`--user-agent <ua>`** to override the new default User-Agent.
193
+
194
+ ### Changed
195
+
196
+ - Every request now sends `User-Agent: penthera/<version>
197
+ (+https://github.com/danoszz/penthera)` instead of Node's default (#7;
198
+ first added by @Nurana100 in #20, our first outside contribution).
199
+ - Probe failures (timeouts, connection errors, refused hosts) are recorded as
200
+ `result.errors[]` (`{ probe, url, message, count }`). The terminal summary
201
+ shows "N probes inconclusive", and the Markdown report lists them under
202
+ "Inconclusive probes" (#8).
203
+ - All network calls go through one client in `src/utils/http.js`, which owns
204
+ timeouts, the User-Agent and egress policy. The per-module fetch wrappers in
205
+ the crawler, injection, fuzzer, template, recon, parameter, Retire.js and
206
+ adaptive-probe modules are gone.
207
+ - **Local learning store** (`lib/memory/`), the memory behind the v1.4 local
208
+ loop. `--memory` records each scan under `~/.penthera/memory/<project-id>/`
209
+ (or `PENTHERA_HOME`) as an append-only, hash-chained event log, then uses it
210
+ to suppress findings marked as false positives and to rank findings within a
211
+ severity. Suppressed findings are listed in their own section with who
212
+ suppressed them and why. Memory is never read from the scanned repo and
213
+ nothing in it is ever executed; a broken chain means memory is not applied.
214
+ See [docs/local-loop.md](docs/local-loop.md) for the threat model
215
+ - `penthera memory show | verify | forget [--all] | suppress <id> --reason <text>`
216
+ - Optional repo file `penthera.suppress.json` for shared suppressions. Every
217
+ finding it hides is labelled "suppressed by repo file" with its reason
218
+
219
+ ## [1.2.0] - 2026-10-19
220
+
221
+ The first step toward securing the AI toolchain itself: catch AI provider keys,
222
+ harden Penthera's own supply chain, and make CI test what it says it tests. The
223
+ [roadmap](ROADMAP.md) now sets out the AI supply-chain and local agent-loop
224
+ tracks.
225
+
226
+ ### Added
227
+
228
+ - **AI provider key detection** in repo scans: Anthropic, OpenAI project and
229
+ service-account keys, OpenRouter, Google (Gemini / Firebase), Groq, Hugging
230
+ Face, Replicate, xAI, Perplexity, LangSmith, and Pinecone. Jupyter notebooks
231
+ (`.ipynb`) are now scanned too. A provider key inside an `api_key = "..."`
232
+ assignment is reported once, under its provider, instead of twice
233
+ - `findSecretsInText()` exported from `lib/whitebox/secrets.js`, so the same
234
+ rules can run on any text (groundwork for scanning JS bundles)
235
+ - CodeQL and OpenSSF Scorecard workflows, Dependabot for npm and GitHub Actions
236
+ (with a 7-day cooldown before adopting new releases)
237
+ - CI check that `package.json`, the lockfile, the skill, and both plugin
238
+ manifests carry the same version
239
+ - Roadmap: AI supply-chain audit (`penthera agents`), a fully local
240
+ scan → fix → verify → learn loop, and LLM-endpoint probes. Principles now
241
+ state no telemetry, no auto-push, and no remote learning
242
+
243
+ ### Fixed
244
+
245
+ - **Modern OpenAI keys were missed.** The old pattern stopped at the hyphen in
246
+ `sk-proj-...` and `sk-svcacct-...`, so the key format OpenAI has issued since
247
+ 2024 never matched
248
+ - The example scan workflow lived in `.github/workflows/`, so it ran on its
249
+ weekly cron and failed every Monday. It moved to
250
+ `docs/examples/github-actions-scan.yml` and now uses `npx penthera@<version>`,
251
+ so it works when copied into your own repo
252
+ - CI ran only 4 of 24 test files; it now runs the full `npm test`
253
+ - Removed `tests/pentest-static.test.js`, which tested functions defined inside
254
+ the test file rather than anything Penthera ships
255
+ - `.claude-plugin/plugin.json` still said 1.0.0
256
+ - `SECURITY.md` listed 1.0.x as supported. It now covers 1.2.x and gives response
257
+ times, scope, and safe-harbor terms
258
+
259
+ ### Changed
260
+
261
+ - **Node.js 22 or newer is now required.** Node 18 and 20 are end-of-life, and
262
+ the test toolchain (vitest 5) needs 22. CI runs on Node 22 and 24
263
+ - GitHub Actions are pinned to commit SHAs, and every workflow declares
264
+ least-privilege `permissions`
265
+ - Releases use npm trusted publishing (OIDC, no stored token) and **staged
266
+ publishing**: CI stages each version, and it only goes live after a
267
+ maintainer approves it with 2FA. The workflow also creates the GitHub Release
268
+ - The Docker image runs as the unprivileged `node` user on `node:22-alpine`
269
+ - README reordered so install and quick start come before compliance. The full
270
+ NIS2 guide moved to [`docs/compliance-nis2.md`](docs/compliance-nis2.md)
271
+ - vitest 3 → 5 (clears 8 dev-only advisories), yaml 2.8 → 2.9
272
+
7
273
  ## [1.1.1] - 2026-08-16
8
274
 
9
275
  A correctness release. One of these was a live outage: if you are on 1.1.0,
@@ -176,7 +442,7 @@ and generated documents.
176
442
  live integration tests report as skipped (not passed) without a running target
177
443
  - Corrected the built-in template count (14, not 17) in docs and progress output
178
444
 
179
- ## [1.0.0], 2026-06-30
445
+ ## [1.0.0] - 2026-06-30
180
446
 
181
447
  First production-ready release.
182
448
 
@@ -201,11 +467,11 @@ First production-ready release.
201
467
 
202
468
  - URL trailing-slash normalization (double-slash 404 bug)
203
469
 
204
- ## [0.2.0], 2026-04
470
+ ## [0.2.0] - 2026-04
205
471
 
206
472
  - Markdown reports, scan profiles, auth tooling, expanded API discovery
207
473
 
208
- ## [0.1.x], 2026-04
474
+ ## [0.1.x] - 2026-04
209
475
 
210
476
  - Initial CLI: URL, repo, and machine scan modes
211
477
  - Built-in templates, TLS, CORS, SARIF export