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
@@ -0,0 +1,81 @@
1
+ # GitHub Sponsors tiers (proposal)
2
+
3
+ Status: proposal. The tiers below still need to be created on the GitHub
4
+ Sponsors dashboard once the profile is approved. Prices are in US dollars
5
+ because GitHub Sponsors tiers are priced in USD.
6
+
7
+ ## Tiers
8
+
9
+ | Tier | Monthly | For | What you get |
10
+ |------|---------|-----|--------------|
11
+ | Individual | $5 | People who use Penthera on their own projects | Thanks, and your name on the sponsors list on GitHub if you want it. |
12
+ | Supporter | $25 | Freelancers and consultants who use Penthera for clients | The above, plus a mention in the release notes of the next minor release. |
13
+ | Startup | $100 | Small teams and startups | The above, plus your name and a link in the README under "Sponsors". |
14
+ | Company | $500 | Companies that rely on Penthera or build on it | The above, plus your logo in the README under "Sponsors", and a 30-minute call per quarter to talk through the roadmap. |
15
+
16
+ One-time sponsorships of any amount are also welcome.
17
+
18
+ ## What sponsorship pays for
19
+
20
+ Maintainer time. Specifically, in rough order:
21
+
22
+ 1. Keeping detection rules current: new AI provider key formats, new
23
+ supply chain attack patterns, new checks for developer tooling.
24
+ 2. Security fixes and releases for Penthera itself.
25
+ 3. The roadmap in [ROADMAP.md](../../ROADMAP.md).
26
+
27
+ ## What sponsorship does not buy
28
+
29
+ Written down so nobody is surprised:
30
+
31
+ - **No influence over findings.** Sponsors' products are scanned and reported
32
+ the same way as everyone else's. A sponsor cannot have a rule removed or
33
+ softened.
34
+ - **No roadmap control.** The quarterly call is a conversation, not a vote.
35
+ Priorities stay set by evidence of what users need.
36
+ - **No support contract or SLA.** Penthera is maintained part-time. Bug reports
37
+ are handled in public issues, in the order the maintainer judges best.
38
+ - **No early or private access to vulnerability information.** Security issues
39
+ follow [SECURITY.md](../../SECURITY.md) for everyone.
40
+ - **No certification or endorsement.** A logo in the README means the company
41
+ supports the project. It does not mean Penthera has assessed or approved that
42
+ company's security, and the README will say so next to the logos.
43
+ - **No change to the principles.** No telemetry, offline first, MIT licence.
44
+ Sponsorship will never add a feature that sends data off the user's machine.
45
+
46
+ ## Logo rules (Company tier)
47
+
48
+ - One logo, at most 160 px wide, in the README "Sponsors" section, linked to the
49
+ company site.
50
+ - Logos are listed in the order sponsorships started.
51
+ - The logo is removed in the first README update after a sponsorship ends.
52
+ - The maintainer may decline or end a sponsorship, for example for a company
53
+ whose product is malware or surveillance tooling. Any payment for a month not
54
+ yet started is then refunded.
55
+
56
+ ## Fees and money, for transparency
57
+
58
+ From the GitHub Sponsors docs: GitHub charges no fees on sponsorships from
59
+ personal accounts. Sponsorships from organisation accounts carry up to 6% in
60
+ fees (3% card processing and 3% GitHub service fee; invoiced billing avoids the
61
+ card fee). Tier prices cannot be edited after publishing; a tier has to be
62
+ retired and replaced. Source:
63
+ https://docs.github.com/en/sponsors/receiving-sponsorships-through-github-sponsors/about-github-sponsors-for-open-source-contributors
64
+
65
+ The maintainer intends to publish a short yearly note on total sponsorship
66
+ income and what it paid for.
67
+
68
+ ## Proposed edits in this PR
69
+
70
+ - `.github/FUNDING.yml`: adds a `custom` link to this page, so the Sponsor
71
+ button explains the tiers before the GitHub Sponsors profile is live.
72
+ - `README.md`, "Support Penthera": lists the tiers in short and links here.
73
+
74
+ ## Open questions for the maintainer
75
+
76
+ - Are $100 and $500 right for startups and companies? They are deliberately
77
+ modest. Raise them if the first sponsors suggest so.
78
+ - Is a quarterly call something you want to commit to? If not, drop it from the
79
+ Company tier before publishing, because published tiers cannot be edited.
80
+ - Check with a tax adviser how sponsorship income should be reported in the
81
+ Netherlands before the first payout.
package/docs/index.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Penthera documentation
2
2
 
3
- Security scanner for developers: scan live URLs, local repos, or macOS machines from your AI agent or the CLI.
3
+ Open-source security for the apps and AI systems you ship: check it before you deploy, vet what you build with, keep watching after you ship. Offline, deterministic, free.
4
4
 
5
5
  ## Getting started
6
6
 
@@ -9,6 +9,7 @@ Security scanner for developers: scan live URLs, local repos, or macOS machines
9
9
  | [README](../README.md) | Install, quick start, ethical use |
10
10
  | [Run it](../README.md#run-it) | Install + first-time wizard (`penthera` with no args) |
11
11
  | [CHANGELOG](../CHANGELOG.md) | Version history |
12
+ | [Roadmap](../ROADMAP.md), [plan to v2.0](./plan.md) and [release plan](./release-plan.md) | What ships next, in what order, and how quality is measured |
12
13
 
13
14
  ## Scanning
14
15
 
@@ -34,10 +35,20 @@ Security scanner for developers: scan live URLs, local repos, or macOS machines
34
35
  |-------|------|
35
36
  | OWASP WSTG mapping | [owasp-wstg-coverage.md](./owasp-wstg-coverage.md) |
36
37
  | NIS2 compliance guide | [compliance-nis2.md](./compliance-nis2.md) |
38
+ | Local loop and learning store | [local-loop.md](./local-loop.md) |
37
39
  | Scan your app in GitHub Actions | [examples/github-actions-scan.yml](./examples/github-actions-scan.yml), set `PENTEST_STAGING_URL` |
38
40
  | Penthera's own CI | `.github/workflows/ci.yml` (full tests + mock scan), `codeql.yml`, `scorecard.yml` |
39
41
  | Docker | `docker build -t penthera . && docker run penthera https://localhost:3000` |
40
42
 
43
+ ## Methodology & research
44
+
45
+ | Topic | Link |
46
+ |-------|------|
47
+ | How detection works, confidence model, limits | [METHODOLOGY.md](./METHODOLOGY.md) |
48
+ | Cited research and standards | [references.md](./references.md) ([BibTeX](./references.bib)) |
49
+ | Accuracy benchmark (`npm run bench`) | [bench/README.md](../bench/README.md), latest [results](../bench/RESULTS.md) |
50
+ | Citing Penthera | [CITATION.cff](../CITATION.cff) |
51
+
41
52
  ## Agent skill
42
53
 
43
54
  | Topic | Location |
@@ -0,0 +1,276 @@
1
+ # The local loop
2
+
3
+ Penthera v1.4 adds a loop that runs entirely on your machine: scan, fix on a
4
+ local branch, verify, learn, repeat. Nothing in it talks to a Penthera server,
5
+ because there isn't one. This page covers the loop itself (`penthera agent
6
+ --loop`) and the part that makes it improve over time: the local learning store.
7
+
8
+ ## The agent loop
9
+
10
+ ```bash
11
+ penthera agent --loop --repo . --url http://localhost:3000 -o reports/loop.json
12
+ penthera agent --loop --repo . --run-tests # also run the project's test script
13
+ penthera agent --loop --repo . --auto low # auto-apply only recipes memory has proven
14
+ ```
15
+
16
+ One iteration:
17
+
18
+ 1. **Scan** the repo (white-box scan plus the fix recipes' static probes) and,
19
+ with `--url`, the running app.
20
+ 2. **Triage**: drop findings suppressed in memory or `penthera.suppress.json`,
21
+ and rank the rest with memory (severity always first).
22
+ 3. **Plan**: a deterministic recipe first. The local model is asked only with
23
+ `--fix-model <name>`, and only for findings no recipe matches.
24
+ 4. **Patch** on a local branch `penthera/fix-<yyyymmdd-hhmm>`, created from the
25
+ current HEAD when the run starts.
26
+ 5. **Verify**: re-scan. A fix passes when the findings it targeted are gone from
27
+ the repo scan, no new finding appeared, and, with `--run-tests`, the
28
+ project's `test` script exits 0 (`node --run test`).
29
+ 6. **Learn**: `scan`, `fix_applied` and `fix_verified` or `fix_failed` go to
30
+ memory.
31
+ 7. **Commit** each verified fix on its own, with a message that lists the
32
+ findings, the recipe, and how it was verified. A fix that fails is reverted
33
+ (`git checkout` of the files it changed; files it created are removed).
34
+
35
+ Plans are applied one at a time and re-planned against the tree after each
36
+ fix, since two recipes can touch the same file. The loop repeats until nothing
37
+ fixable is left, `--max-iterations` (default 5) is reached, or you stop it.
38
+ A fix you decline (`n`) or that fails verification is not proposed again in
39
+ the same run; `s` skips it until the next iteration.
40
+
41
+ A live URL serves the code it was started with, so a fix to response headers
42
+ shows up in the URL scan only after the app restarts. The loop counts those as
43
+ `liveRemaining` in the fix's `verify` block, not as a failure; the repo scan
44
+ decides whether the fix landed.
45
+
46
+ ### What it guarantees
47
+
48
+ Each of these has a test in `tests/agent.test.js`.
49
+
50
+ - **No remote operations.** All git goes through `src/agent/git.js`, an
51
+ allow-list of local subcommands (`rev-parse`, `symbolic-ref`, `status`,
52
+ `diff`, `log`, `show-ref`, `checkout`, `add`, `commit`, `config --get`).
53
+ `push`, `fetch`, `pull`, `remote`, `clone`, `ls-remote`, `submodule` and
54
+ everything else are refused before a process starts, as are URL arguments
55
+ and `-c` overrides. Every call also runs with `protocol.allow=never` and
56
+ `GIT_NO_LAZY_FETCH=1`, so no transport can work. The loop never runs `gh`
57
+ and never opens a pull request.
58
+ - **The repo's hooks never run.** Git runs with `core.hooksPath=/dev/null`,
59
+ `core.fsmonitor=false` and `commit --no-verify`: a scanned repo can ship hooks
60
+ that run anything.
61
+ - **Network.** The whole run is one W1 egress context with `--offline` always
62
+ on: the URL target is the only host. Loopback is added only when
63
+ `--fix-model` is set, and the model client then accepts only
64
+ `http://127.0.0.1:11434`.
65
+ - **Clean start.** A dirty tree (untracked files included), a detached HEAD, a
66
+ repo without commits, or a directory that is not a git repo is refused.
67
+ `--allow-dirty` lets the loop start anyway, but it never patches a file that
68
+ had uncommitted changes.
69
+ - **Approval.** Every diff is shown, secrets masked, with
70
+ `[y]es / [n]o / [s]kip / [q]uit`. When input runs out (piped stdin), the
71
+ answer is `n`. `--auto low` applies a diff without asking only when it comes
72
+ from a recipe (not the model), its confidence is not `needs-human-review`,
73
+ memory's chain verifies, and memory shows that recipe with at least 3
74
+ verified fixes and no failures for this framework.
75
+ - **Interruption.** The first Ctrl-C lets the fix in flight finish: it is
76
+ verified and committed, or reverted. The loop then stops on the fix branch,
77
+ which holds only verified commits. A second Ctrl-C reverts the fix in flight
78
+ at once and exits.
79
+
80
+ `--run-tests` runs the project's own code, so it can do anything that code
81
+ does, including network calls. It is off by default for that reason.
82
+
83
+ ### The loopRun object
84
+
85
+ `-o <file>` writes `{ "loopRun": { ... } }`. W6's report timeline renders it.
86
+ Every step carries `startedAt` and `durationMs`.
87
+
88
+ ```js
89
+ {
90
+ schema: "penthera.loopRun/1",
91
+ id, startedAt, finishedAt, durationMs,
92
+ target: { repo, url },
93
+ branch: { base, baseSha, fix }, // base is null on a detached HEAD
94
+ options: { maxIterations, auto: "off" | "low", runTests, fixModel, offline: true, allowDirty },
95
+ stopReason: "no-fixable-findings" | "declined-or-failed-left" | "max-iterations"
96
+ | "user-quit" | "interrupted" | "error",
97
+ error, // message when stopReason is "error"
98
+ findings: { initial, final }, // non-info, unsuppressed
99
+ totals: { iterations, planned, applied, verified, failed, declined, skipped, commits: [sha] },
100
+ memory: { projectId, chain: "ok" | "broken", eventsAppended },
101
+ iterations: [{
102
+ n, startedAt, durationMs,
103
+ findings: { total, suppressed, fixable },
104
+ steps: [{ step: "scan" | "triage" | "plan", startedAt, durationMs }],
105
+ fixes: [{
106
+ planId, recipe, title, framework, source: "recipe" | "local-model", confidence,
107
+ files, findings: [{ fid, title, severity, category }],
108
+ memory: { verified, failed } | null, // recipe history when the plan was ranked
109
+ decision: "approved" | "auto" | "declined" | "skipped" | "quit" | "obsolete",
110
+ status: "verified" | "failed" | "not-applied",
111
+ reason, commit,
112
+ verify: { targetedRemaining, newFindings, liveRemaining, tests: { ok, exitCode, durationMs } | null } | null,
113
+ testOutput, // tail of the test output, only when tests failed
114
+ steps: [{ step: "approve" | "patch" | "learn" | "verify" | "commit" | "revert", startedAt, durationMs }],
115
+ }],
116
+ }],
117
+ }
118
+ ```
119
+
120
+ `obsolete` means an earlier fix in the same iteration already covered the plan.
121
+ The same object, without `testOutput` and with finding ids in place of finding
122
+ objects, is appended to memory as a `loop_run` event.
123
+
124
+ Exit codes: `0` nothing fixable left, `1` fixable findings remain (declined,
125
+ failed, or out of iterations), `2` the loop could not start (dirty tree,
126
+ detached HEAD, bad options), `130` interrupted.
127
+
128
+ ## The learning store
129
+
130
+ Scans run with `--memory` record what happened, and the next scan uses that
131
+ record to rank and suppress findings.
132
+
133
+ ```bash
134
+ penthera --repo . --memory # scan, apply memory, record this scan
135
+ penthera memory show # what memory knows about this project
136
+ penthera memory verify # check the hash chain
137
+ penthera memory suppress <id> --reason "set by the CDN" [--expires 2027-01-01]
138
+ penthera memory forget # delete this project's memory
139
+ penthera memory forget --all # delete all memory
140
+ ```
141
+
142
+ `<id>` is the 16-character `fid` shown next to each finding in a `--memory`
143
+ report.
144
+
145
+ ### Where it lives
146
+
147
+ ```
148
+ ~/.penthera/memory/<project-id>/events.jsonl the event log
149
+ ~/.penthera/memory/<project-id>/head.json count and hash of the last event
150
+ ~/.penthera/memory/<project-id>/meta.json the project's path or origin, for `memory show`
151
+ ```
152
+
153
+ `<project-id>` is a SHA-256 of the repo's absolute path plus its git remote URL,
154
+ or of the target origin for URL-only scans. Set `PENTHERA_HOME` to move the
155
+ root (the tests do). Penthera reads the remote from `.git/config` itself rather
156
+ than running `git`, because running git inside an untrusted repo can execute
157
+ that repo's hooks or `core.fsmonitor` command.
158
+
159
+ ### What it records
160
+
161
+ One JSON event per line, append-only:
162
+
163
+ ```json
164
+ { "v": 1, "ts": "...", "type": "scan", "data": { ... }, "prev": "<hash of previous line>", "hash": "..." }
165
+ ```
166
+
167
+ `hash = sha256(prev + canonical JSON of { v, ts, type, data })`, with object
168
+ keys sorted. The first line's `prev` is derived from the project id.
169
+
170
+ | Type | Data |
171
+ | --- | --- |
172
+ | `scan` | stack, modes, counts by severity, ids of the actionable findings |
173
+ | `probe_yield` | probe id, stack, findings count |
174
+ | `fix_applied` | finding id, recipe id, framework, category, diff SHA-256 |
175
+ | `fix_verified` / `fix_failed` | finding id (recipe and framework are joined from `fix_applied`) |
176
+ | `fp_suppressed` | finding id, reason, actor, optional expiry |
177
+ | `loop_run` | one `penthera agent --loop` run: the [loopRun](#the-looprun-object) without test output, finding ids only |
178
+
179
+ From these, `penthera memory show` derives four views: recipe success rate per
180
+ framework, active suppressions, probe ranking by yield per stack, and median
181
+ time to fix (first seen in a scan to `fix_verified`).
182
+
183
+ ### How a scan uses it
184
+
185
+ Memory can do two things to a report, and nothing else:
186
+
187
+ 1. **Suppress** a finding the user (or the loop, on the user's behalf) marked as
188
+ a false positive, with a reason. Suppressed findings move to a separate
189
+ "Suppressed" section in the terminal and Markdown reports, each with who
190
+ suppressed it and why. They do not count toward the exit code.
191
+ 2. **Re-order** findings within a severity: categories with a fix recipe that
192
+ has worked on this framework come first, then findings that have shown up in
193
+ more past scans. Severity is never changed and no finding is dropped.
194
+
195
+ ## Threat model
196
+
197
+ The learning store is an attack surface. An agent loop that learns from its own
198
+ past is open to memory poisoning (OWASP Agentic Security Initiative, ASI06): if
199
+ an attacker can write to what the loop remembers, they can steer what it does
200
+ next. In Penthera's case the obvious targets are hiding a real finding and
201
+ getting the loop to run something. The store is designed so neither works
202
+ quietly.
203
+
204
+ ### Why it never executes
205
+
206
+ Memory is data. No value read from it is ever executed, evaluated, imported,
207
+ used as a file path, or passed to a shell. A recipe id in memory is only a
208
+ string that gets counted; the recipe code lives in Penthera's own source
209
+ (`lib/fix/`) and is chosen by Penthera, not by the log. Every field a view reads
210
+ is type- and length-checked, and an event that fails a check is ignored rather
211
+ than interpreted. Strings shown in the terminal have control characters stripped,
212
+ so a stored reason cannot inject ANSI escape sequences.
213
+
214
+ The worst a fully poisoned memory can do is hide findings, and hidden findings
215
+ are listed, with their reason, in every report.
216
+
217
+ ### Why it is hash-chained
218
+
219
+ The chain makes tampering visible. Editing a line, reordering lines, deleting a
220
+ line from the start or middle, or splicing in another project's log all break
221
+ the chain, and `penthera memory verify` names the first broken line. `head.json`
222
+ records the count and hash of the last event, so cutting lines off the end is
223
+ caught too.
224
+
225
+ When the chain is broken, scans **fail closed**: memory suppressions and
226
+ ranking are not applied, nothing new is appended, and the scan prints a warning
227
+ telling you to run `penthera memory verify`. You can then inspect the log or
228
+ start over with `penthera memory forget`.
229
+
230
+ What the chain does not do: it is tamper-evident, not tamper-proof. Anyone who
231
+ can write to your home directory can rewrite the whole log and recompute every
232
+ hash. Keying the chain would not help, since the key would sit in the same home
233
+ directory. The chain is there to catch a partial edit, an accidental truncation,
234
+ or a tool that writes where it should not; it is not a defence against malware
235
+ already running as you.
236
+
237
+ ### Why it lives in your home directory
238
+
239
+ Memory is never read from the scanned repo. If it were, a repo could ship
240
+ pre-poisoned memory, for example a suppression for the secret it is about to
241
+ leak. Keeping memory under `~/.penthera` means only runs on your machine, as
242
+ you, write to it.
243
+
244
+ ### The one thing a repo can say: `penthera.suppress.json`
245
+
246
+ Teams do need to share decisions like "this key is a revoked test fixture". For
247
+ that a repo may contain `penthera.suppress.json`:
248
+
249
+ ```json
250
+ {
251
+ "version": 1,
252
+ "suppress": [
253
+ { "id": "16e06eb3fd94664b", "reason": "test fixture, revoked", "expires": "2027-01-01" }
254
+ ]
255
+ }
256
+ ```
257
+
258
+ It is held to a stricter standard than memory:
259
+
260
+ - Every entry needs a reason. Entries without one, with a malformed id or date,
261
+ or past their expiry are dropped with a warning.
262
+ - Every finding it hides is listed in the report as **suppressed by repo file**
263
+ with the reason, so a reviewer sees both the file change in the PR and its
264
+ effect in the report.
265
+ - It only applies with `--memory`, and a local suppression for the same finding
266
+ takes precedence.
267
+
268
+ Treat a change to `penthera.suppress.json` in a pull request like a change to
269
+ CI config: it decides what the scanner reports.
270
+
271
+ ### Concurrency
272
+
273
+ Two scans of the same project can run at once. Each append takes an exclusive
274
+ lock file (`events.lock`, created with `O_EXCL`), reads the tip, and writes one
275
+ line with a single `O_APPEND` write. Lines never interleave and the chain never
276
+ forks. A lock older than 30 seconds is treated as stale and removed.
package/docs/plan.md ADDED
@@ -0,0 +1,100 @@
1
+ # Plan to v2.0
2
+
3
+ How Penthera gets from "many features, shipped fast" to a tool a team can rely
4
+ on without thinking about it. Written 2026-10-19. The [roadmap](../ROADMAP.md)
5
+ says *what* ships; this says *how well*, and in what order.
6
+
7
+ Penthera is maintained part-time alongside a PhD, so the plan is built around
8
+ small, finishable pieces and automation that keeps quality from depending on
9
+ anyone's free evenings.
10
+
11
+ ## The problem we solve
12
+
13
+ Teams ship apps and AI agents faster than anyone can secure them. Penthera is
14
+ the open-source security team for teams that don't have one, in three moments:
15
+
16
+ 1. **Before you deploy.** Test the app on localhost or a preview URL, fix what
17
+ it finds on a local branch, and prove the fix.
18
+ 2. **What you build with.** Vet dependencies, installs, and AI tooling (skills,
19
+ MCP servers, hooks) before they get access to your machine and credentials.
20
+ 3. **After you ship.** Keep watching your own production for drift, and produce
21
+ the reports and evidence that customers and regulators ask for.
22
+
23
+ All of it local, deterministic, and free. Anything that doesn't serve one of
24
+ these three moments is out of scope.
25
+
26
+ ## Milestones
27
+
28
+ | Release | Theme | Done when |
29
+ |---------|-------|-----------|
30
+ | v1.5.0 | AI apps and compliance | Pre-commit hook, license policy, CRA helper, AI-BOM, signed rule packs, Python `deps`, dashboard, internal audit all merged and passing the audit |
31
+ | v1.6.0 | Before you deploy | `penthera ship` and preview-deployment scanning work on a real Vercel and Netlify project |
32
+ | v1.7.0 | After you ship | `penthera watch` runs weekly on three real production apps for a month without a false alarm anyone complained about |
33
+ | v2.0.0 | Stable | Public interfaces frozen (CLI flags, JSON report schema, policy file, rule-pack format), documented, and covered by compatibility tests |
34
+
35
+ ## What "perfect" means, concretely
36
+
37
+ Quality is measured, not felt. Each item below has a gate that the internal
38
+ audit (`npm run audit:internal`) or CI checks automatically.
39
+
40
+ ### 1. Accuracy
41
+ - Benchmark precision and recall never drop between releases (the audit fails
42
+ on a drop of more than 0.02).
43
+ - Expand the benchmark beyond the mock server: OWASP Juice Shop, crAPI, a
44
+ deliberately vulnerable Next.js app, and fixture sets for `agents` and `deps`.
45
+ - Every false positive reported by a user becomes a regression test before it
46
+ is fixed.
47
+
48
+ ### 2. First-run experience
49
+ - From `npx penthera` to a useful result in under two minutes on a fresh Next.js
50
+ or Express app, measured on a clean machine each release.
51
+ - Every error message says what happened and what to do next. No stack traces
52
+ for expected failures.
53
+ - Works on macOS, Linux, and Windows (add a Windows CI job; the symlinked Cursor
54
+ skill and path handling are the known risks).
55
+
56
+ ### 3. Documentation
57
+ - The README stays a front page: problem, what it does, install, trust. Feature
58
+ detail moves into a docs site (`docs/`), one page per command, each with a
59
+ copy-paste example and its real output.
60
+ - Every command's `--help` and its docs page are generated from or tested
61
+ against the same source, so they never drift.
62
+
63
+ ### 4. Stability of interfaces
64
+ - A versioned JSON schema for reports, the policy file, and rule packs, checked
65
+ in CI.
66
+ - A deprecation policy: one minor release of warnings before anything breaks.
67
+
68
+ ### 5. Security of Penthera itself
69
+ - Self-scan clean on every release (`--repo`, `agents`, `deps`, the dashboard).
70
+ - Anything that parses untrusted input (packages, configs, scan responses, rule
71
+ packs) has a fuzz or hostile-input test. The ReDoS fixed in `deps` is the
72
+ reason why.
73
+ - An external review before v2.0: ask an academic security group or a volunteer
74
+ from the OWASP community to look at the fix loop, the install firewall, and
75
+ the dashboard, the three parts that write files or run commands.
76
+
77
+ ### 6. Real-world proof
78
+ - Three pilot projects (friendly teams, a student project, one of the
79
+ maintainer's own) run Penthera before each deploy for a month. Their feedback
80
+ sets the priorities for the next release.
81
+ - Publish what Penthera found and fixed, with permission, as short case studies.
82
+
83
+ ## Working rhythm
84
+
85
+ - One release every two to three weeks, each with a single theme.
86
+ - The internal audit runs weekly and before every tag; a red audit blocks the
87
+ release.
88
+ - Issues labelled `good first issue` are kept stocked (three to five open at a
89
+ time), so contributors can help without needing the maintainer's time.
90
+ - Anything that needs judgement (a new probe that sends payloads, anything
91
+ touching third parties) gets a short design note in `docs/` before code.
92
+
93
+ ## Explicitly not doing
94
+
95
+ - A hosted service, telemetry, or an update channel that fetches rules at run
96
+ time.
97
+ - Offensive tooling for targets the user does not own, and attack-payload
98
+ generation for AI systems.
99
+ - An LLM as the judge of what is malicious. Models may help write fixes,
100
+ locally, and always labelled as such.