penthera 1.7.0 → 1.8.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 (163) hide show
  1. package/CHANGELOG.md +315 -0
  2. package/README.md +139 -76
  3. package/SECURITY.md +8 -6
  4. package/docs/examples/pr-dependency-review.yml +1 -1
  5. package/lib/adaptive-probes.js +3 -3
  6. package/lib/advisories/evidence.js +248 -0
  7. package/lib/advisories/index.js +228 -0
  8. package/lib/advisories/products.js +83 -0
  9. package/lib/advisories/versions.js +65 -0
  10. package/lib/agents/hidden-content.js +122 -11
  11. package/lib/agents/index.js +26 -6
  12. package/lib/agents/lock.js +33 -5
  13. package/lib/agents/rules.js +33 -4
  14. package/lib/compliance/index.js +4 -4
  15. package/lib/crawler.js +12 -15
  16. package/lib/deps/capabilities.js +21 -5
  17. package/lib/deps/findings.js +309 -0
  18. package/lib/deps/index.js +13 -272
  19. package/lib/deps/iocs.js +74 -0
  20. package/lib/deps/lockfile.js +3 -8
  21. package/lib/deps/online.js +8 -3
  22. package/lib/deps/popular-packages.json.gz +0 -0
  23. package/lib/deps/popular-pypi.json.gz +0 -0
  24. package/lib/deps/python/capabilities.js +16 -3
  25. package/lib/deps/python/index.js +5 -1
  26. package/lib/deps/python/lockfiles.js +11 -5
  27. package/lib/deps/python/source.js +75 -26
  28. package/lib/deps/python/toml.js +15 -3
  29. package/lib/deps/source.js +28 -9
  30. package/lib/deps/typosquat.js +30 -5
  31. package/lib/fix/apply.js +253 -34
  32. package/lib/fix/index.js +7 -4
  33. package/lib/fix/model.js +25 -5
  34. package/lib/fix/util.js +6 -2
  35. package/lib/fuzzer.js +3 -3
  36. package/lib/git.js +65 -0
  37. package/lib/hook/index.js +157 -73
  38. package/lib/hook/policy.js +10 -4
  39. package/lib/install/index.js +4 -7
  40. package/lib/machine.js +0 -13
  41. package/lib/memory/store.js +11 -4
  42. package/lib/nuclei-loader.js +0 -25
  43. package/lib/owasp-wstg.js +4 -0
  44. package/lib/plugins.js +1 -1
  45. package/lib/provenance.js +1 -0
  46. package/lib/recon.js +12 -31
  47. package/lib/references.json +224 -0
  48. package/lib/retirejs.js +4 -4
  49. package/lib/rules/advisories/advisories.json +186 -0
  50. package/lib/rules/advisories/eol.json +413 -0
  51. package/lib/rules/advisories/pack.json +20 -0
  52. package/lib/rules/advisories/pack.json.sig +6 -0
  53. package/lib/rules/allowed_signers +13 -0
  54. package/lib/rules/core/agent-exec.json +29 -0
  55. package/lib/rules/core/agent-instructions.json +17 -0
  56. package/lib/rules/core/pack.json +30 -0
  57. package/lib/rules/core/pack.json.sig +6 -0
  58. package/lib/rules/core/secrets.json +161 -0
  59. package/lib/rules/core/templates.json +52 -0
  60. package/lib/rules/index.js +107 -0
  61. package/lib/rules/ioc-2025-2026/files.json +43 -0
  62. package/lib/rules/ioc-2025-2026/hosts.json +35 -0
  63. package/lib/rules/ioc-2025-2026/pack.json +25 -0
  64. package/lib/rules/ioc-2025-2026/pack.json.sig +6 -0
  65. package/lib/rules/ioc-2025-2026/packages.json +1048 -0
  66. package/lib/rules/pack.js +452 -0
  67. package/lib/rules/regex-guard.js +81 -9
  68. package/lib/rules/sshsig.js +205 -0
  69. package/lib/templates.js +24 -20
  70. package/lib/tmp.js +21 -0
  71. package/lib/whitebox/frameworks.js +1 -1
  72. package/lib/whitebox/secrets.js +117 -41
  73. package/package.json +12 -4
  74. package/scripts/rules/build-pack.mjs +80 -0
  75. package/scripts/rules/check-bundled.mjs +30 -0
  76. package/scripts/rules/sign-pack.sh +32 -0
  77. package/skills/penthera/SKILL.md +16 -15
  78. package/src/agent/git.js +147 -14
  79. package/src/agent/loop.js +165 -46
  80. package/src/cli/agent.js +11 -5
  81. package/src/cli/deps.js +0 -2
  82. package/src/cli/detect.js +14 -7
  83. package/src/cli/fix.js +26 -8
  84. package/src/cli/hook.js +2 -1
  85. package/src/cli/onboarding.js +14 -6
  86. package/src/cli/report-outputs.js +1 -0
  87. package/src/cli/rules.js +144 -0
  88. package/src/cli/ui.js +43 -10
  89. package/src/cli.js +24 -1
  90. package/src/report/evidence.js +105 -21
  91. package/src/report/footer.js +1 -1
  92. package/src/report/md-escape.js +8 -0
  93. package/src/scan-repo.js +6 -0
  94. package/src/scan-url.js +11 -3
  95. package/src/ui/api/data.js +39 -6
  96. package/src/ui/api/routes.js +44 -11
  97. package/src/ui/api/runs.js +32 -7
  98. package/src/ui/security.js +20 -23
  99. package/src/ui/server.js +52 -27
  100. package/src/ui/static/app.css +583 -213
  101. package/src/ui/static/favicon.svg +1 -1
  102. package/src/ui/static/fonts/DepartureMono-LICENSE.txt +93 -0
  103. package/src/ui/static/fonts/DepartureMono-Regular.woff2 +0 -0
  104. package/src/ui/static/index.html +5 -4
  105. package/src/ui/static/js/api.js +114 -6
  106. package/src/ui/static/js/app.js +81 -42
  107. package/src/ui/static/js/center.js +246 -133
  108. package/src/ui/static/js/charts.js +99 -18
  109. package/src/ui/static/js/copy.js +68 -9
  110. package/src/ui/static/js/dialog.js +152 -0
  111. package/src/ui/static/js/left.js +23 -60
  112. package/src/ui/static/js/menubar.js +170 -0
  113. package/src/ui/static/js/onboarding.js +104 -0
  114. package/src/ui/static/js/panels.js +186 -0
  115. package/src/ui/static/js/right.js +69 -188
  116. package/src/ui/static/js/state.js +36 -5
  117. package/src/ui/static/logo.svg +10 -0
  118. package/src/ui/static/mark.svg +3 -0
  119. package/src/utils/http.js +162 -13
  120. package/docs/METHODOLOGY.md +0 -375
  121. package/docs/commands/README.md +0 -24
  122. package/docs/commands/action.md +0 -21
  123. package/docs/commands/agents.md +0 -38
  124. package/docs/commands/deps.md +0 -115
  125. package/docs/commands/fix.md +0 -53
  126. package/docs/commands/hook.md +0 -69
  127. package/docs/commands/init.md +0 -114
  128. package/docs/commands/install.md +0 -53
  129. package/docs/commands/scan.md +0 -100
  130. package/docs/commands/skill.md +0 -58
  131. package/docs/compare/README.md +0 -25
  132. package/docs/compare/github.md +0 -60
  133. package/docs/compare/semgrep.md +0 -54
  134. package/docs/compare/snyk.md +0 -62
  135. package/docs/compare/socket.md +0 -78
  136. package/docs/compare/zap.md +0 -59
  137. package/docs/compliance-cra.md +0 -141
  138. package/docs/compliance-nis2.md +0 -168
  139. package/docs/dashboard.md +0 -187
  140. package/docs/evidence.md +0 -162
  141. package/docs/funding/due-diligence-checklist.md +0 -79
  142. package/docs/funding/nlnet-ngi-zero-draft.md +0 -273
  143. package/docs/funding/partner-one-pager.md +0 -84
  144. package/docs/funding/sponsor-tiers.md +0 -81
  145. package/docs/index.md +0 -81
  146. package/docs/internal-audit.md +0 -83
  147. package/docs/local-loop.md +0 -276
  148. package/docs/marketing.md +0 -151
  149. package/docs/owasp-wstg-coverage.md +0 -79
  150. package/docs/plan.md +0 -102
  151. package/docs/privacy.md +0 -38
  152. package/docs/references.bib +0 -559
  153. package/docs/references.md +0 -119
  154. package/docs/release-plan.md +0 -228
  155. package/docs/research/socket-positioning-notes.md +0 -183
  156. package/docs/use-cases/README.md +0 -14
  157. package/docs/use-cases/agencies.md +0 -71
  158. package/docs/use-cases/ai-coding-teams.md +0 -81
  159. package/docs/use-cases/eu-sme-compliance.md +0 -68
  160. package/docs/use-cases/open-source-maintainers.md +0 -75
  161. package/docs/use-cases/solo-founder.md +0 -65
  162. package/lib/deps/popular-packages.json +0 -1
  163. package/lib/deps/popular-pypi.json +0 -1
package/docs/dashboard.md DELETED
@@ -1,187 +0,0 @@
1
- # The local dashboard: `penthera ui`
2
-
3
- `penthera ui` opens one view in your browser for what Penthera knows about
4
- your projects, laid out like the Claude Code desktop app: projects and runs on
5
- the left, the selected project or run in the middle, and details on the right.
6
- It can also run scans and the fix loop for you, stopping at every diff. It runs
7
- on `127.0.0.1` only, reads only the folders you give it and `~/.penthera`, and
8
- never contacts the network.
9
-
10
- | Dark | Light |
11
- |---|---|
12
- | ![Projects, findings, and a finding's detail in the dark theme](../assets/dashboard/ui-dark.jpg) | ![All projects and the side cards in the light theme](../assets/dashboard/ui-light.jpg) |
13
-
14
- ## Start it
15
-
16
- ```bash
17
- penthera ui --open # current folder, reports in ./reports
18
- penthera ui --project ~/code/shop --project ~/code/api --reports ~/code/shop/reports
19
- penthera ui --demo # fake data, nothing read from disk
20
- ```
21
-
22
- | Option | Default | What it does |
23
- |--------|---------|--------------|
24
- | `--port <n>` | `0` (random free port) | Port on 127.0.0.1 |
25
- | `--open` | off | Open the one-time URL in your browser |
26
- | `--project <dir>` | the current folder | A folder the dashboard may read and run in. Repeat for more |
27
- | `--reports <dir>` | `<first project>/reports` | Where report JSON is read from, and where runs started here write theirs |
28
- | `--demo` | off | Realistic fake data and a scripted fix loop. For a first look, screenshots and docs |
29
-
30
- It prints a URL with a one-time token. Open it once: the token is swapped for
31
- a session cookie and stops working, so a second browser (or a link that leaked
32
- into history) gets a 403. Restart `penthera ui` for a new link. Ctrl-C stops
33
- the server.
34
-
35
- ## The view
36
-
37
- **Left sidebar** (`[` to hide or show). "+ New scan" on top. Then every
38
- project with its A to F grade, open findings and last scan, with "All
39
- projects" first; project folders you passed with `--project` show up even
40
- before their first scan. Below them, recent runs grouped by project, like the
41
- session list in Claude Code: live runs from this session (a green dot while
42
- running) and past fix-loop runs from the reports folder. The gear in the
43
- footer opens settings; the half-moon switches the theme.
44
-
45
- **Centre.** One scrolling stream for what you selected:
46
-
47
- - a project (or all of them): a compact header with the grade, a severity bar
48
- drawn in 1-bit shades, the trend sparkline and open vs fixed; what changed
49
- since the last scan (new and fixed); then the findings, "Fix first"
50
- (critical and high) before the rest. `/` searches, `j` and `k` move through
51
- findings and open each one on the right.
52
- - a run: its output streams in as a transcript, typed out as it arrives. Each
53
- diff of the fix loop appears inline as a small dialog: the recipe, the files,
54
- what memory knows about the recipe, the diff, and Apply (`y`), Skip (`n`) or
55
- Stop (`q`).
56
- - a past fix-loop run: rebuilt from its `loopRun` file, round by round, with
57
- each fix's decision, result and commit.
58
-
59
- At the bottom, a prompt-style bar starts things: **Scan** (with an optional
60
- URL), **Deps**, **AI tooling** or **Fix loop**, always for the selected
61
- project's folder. These are plain controls, not a chat: there is no model
62
- behind them. There is one primary button on screen at a time; while a run is
63
- going, its dialog owns it and the bar waits.
64
-
65
- **Right panel** (`]` to hide or show). Stacked cards, each one collapsible:
66
-
67
- | Card | What it shows |
68
- |---|---|
69
- | Environment | Offline or online for scans started here (switch it here), the project folder, its git branch and HEAD, and the hosts the last scan contacted (and the ones offline mode blocked) |
70
- | Memory | Chain status, events, verified and failed fixes, median time to fix, recipe success rates |
71
- | Dependencies | Package count and ecosystem, the capability diff against your base, typosquats, capabilities in the tree, install firewall decisions |
72
- | AI tooling | The A to F grade and its history, `penthera.lock` drift, inventory, hidden-content hits |
73
- | Evidence | Create a bundle from the latest scan, download the auditor, owner or developer report, verify any bundle in one click |
74
- | Changes | Fixes the loop committed, with their local commits, and the `penthera/fix-*` branches in the folder |
75
-
76
- Picking a finding replaces the cards with its detail: "In plain words" (how
77
- serious, how sure and what that means, what to do, the OWASP mapping), what
78
- was seen, the evidence, sources, and the exact fix-recipe diff with a button
79
- that starts the fix loop on that project. `Esc` goes back to the cards.
80
-
81
- Below 1180px the right panel becomes a drawer; below 760px the sidebar does
82
- too. A tap outside closes them.
83
-
84
- | Narrow, dark | Narrow, light, a finding open |
85
- |---|---|
86
- | <img src="../assets/dashboard/narrow-dark.jpg" alt="The view at 390px, dark" width="280"> | <img src="../assets/dashboard/narrow-light.jpg" alt="A finding in the drawer at 390px, light" width="280"> |
87
-
88
- ## User stories
89
-
90
- | Story | Where it lives |
91
- |---|---|
92
- | *As a solo founder before launch, I want to know what to do first when there is no data yet.* | With no reports, the centre shows the wordmark and three steps (scan, dependencies, AI tooling); each focuses the bar on that action. ![First run](../assets/dashboard/ui-start.jpg) |
93
- | *As a developer who does not live in the terminal, I want to start a scan from the page and watch it work.* | The bar at the bottom; the run streams into the centre and appears in the sidebar. |
94
- | *As a founder with no security engineer, I want fixes applied only when I approve them.* | The fix loop's inline dialog: Apply, Skip or Stop on every diff; verified fixes land as local commits, failed ones are reverted, nothing is pushed. ![A diff waiting for a decision](../assets/dashboard/ui-run.jpg) |
95
- | *As a product owner, I want one finding explained in plain words.* | Pick it: the right panel opens with "In plain words", the evidence, sources and the fix diff. |
96
- | *As a tech lead after a release, I want to see what changed.* | The project header's sparkline, "Since the last scan" (new, fixed), and the runs in the sidebar. |
97
- | *As the person answering the NIS2 questionnaire, I want evidence an auditor can check.* | The Evidence card: create a bundle, verify it, download the auditor report. |
98
- | *As a developer reviewing a lockfile bump, I want to see what packages gained.* | The Dependencies card: the capability diff and install firewall decisions. |
99
- | *As someone who installs skills and MCP servers, I want to know if any hide instructions or drifted.* | The AI tooling card: grade, hidden content, `penthera.lock` drift. |
100
- | *As someone on a bright screen, I want a light theme.* | The half-moon in the sidebar footer, or Settings; the first visit follows your system. |
101
-
102
- ## Running things from the page
103
-
104
- The bar calls the same functions as the CLI (`executeScan`, `scanDeps`,
105
- `scanAgents`, `runLoop`) with the same rules. The fix loop needs a clean git
106
- tree on a branch, works on a local `penthera/fix-<yyyymmdd-hhmm>` branch, runs
107
- only allow-listed local git commands with hooks off, is always offline with
108
- egress limited to the URL's host, and never pushes. On top of that:
109
-
110
- - The project folder must be inside one of the `--project` folders.
111
- - A URL that is not on this machine (anything but `localhost`, `127.x.x.x`,
112
- `[::1]` and `*.localhost`) needs its hostname typed back before the run
113
- starts. Only scan what you own or have written permission to test.
114
- - One run at a time. Closing the tab does not stop a run; pick it in the
115
- sidebar and its transcript replays and continues. Stop finishes the current step, and the fix branch holds only
116
- verified commits.
117
- - Reports land in the reports folder (`loop-<timestamp>.json`,
118
- `deps_<timestamp>.json`, and so on), so every screen picks them up.
119
-
120
- ## Security model
121
-
122
- The dashboard is a web server on your machine, so any page open in the same
123
- browser can send it requests. Every request passes, in this order:
124
-
125
- 1. **Host check.** The `Host` header must be exactly `127.0.0.1:<port>` or
126
- `localhost:<port>`. This blocks DNS rebinding, where `attacker.example`
127
- re-resolves to 127.0.0.1 so the attacker's page can read the response. It
128
- runs before the token check, so a rebinding page cannot even redeem the
129
- link.
130
- 2. **Path check.** No dot segments, encoded dots or slashes, backslashes or NUL
131
- bytes. Static files come from an allowlist built at startup; no part of a
132
- request path is ever joined onto a filesystem path.
133
- 3. **Session.** The one-time URL token is exchanged for an `HttpOnly;
134
- SameSite=Strict` cookie and a redirect to `/`, so the token leaves the
135
- address bar. Every request, static files included, needs the cookie.
136
- Requests without it are rate-limited (429 with `Retry-After`).
137
- 4. **Origin.** Every `POST` needs an `Origin` equal to the server's own (and
138
- `Sec-Fetch-Site: same-origin` when the browser sends it) and a JSON body, so
139
- a cross-site form or `fetch` cannot change anything.
140
-
141
- Every response carries:
142
-
143
- ```
144
- Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self';
145
- font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self';
146
- frame-ancestors 'none'; require-trusted-types-for 'script'
147
- X-Frame-Options: DENY X-Content-Type-Options: nosniff
148
- Referrer-Policy: no-referrer Cache-Control: no-store
149
- Cross-Origin-Opener-Policy / -Resource-Policy / -Embedder-Policy
150
- ```
151
-
152
- There are no CORS headers, and `OPTIONS` gets a 405. The page has no inline
153
- script or style, loads nothing from another host (no CDN, fonts, analytics or
154
- chart library), and never turns data into HTML: finding titles and evidence
155
- come from the sites and repos you scanned, so they are only ever set as text.
156
- Report downloads are served as `text/markdown` attachments from the same
157
- checks; the audience is one of `auditor`, `owner` or `developer`, nothing else.
158
-
159
- Penthera scans its own dashboard clean with `--profile standard`, signed in and
160
- not; `tests/ui.test.js` runs that scan on every test run, alongside tests for
161
- the Host check, the token and cookie, Origin, the headers, the rate limit and
162
- path traversal.
163
-
164
- ## Design
165
-
166
- Calm, terminal-native, and close to the Claude Code desktop app: hairlines
167
- instead of boxes, one stream in the middle, details on the side. The 1-bit
168
- register from earlier versions stays in the details: severity as a word plus a
169
- pattern swatch (solid, hatched, half-tone, dotted, empty), bars drawn with the
170
- shade characters `█ ▓ ▒ ░`, the pinstriped title bar on the approval dialog,
171
- and the CLI's `#0000ed` block-letter wordmark on the empty state.
172
-
173
- - **One accent.** `#0000ed` marks the primary button, the selected finding,
174
- links and chart lines. On black it measures 2.2:1, so there it is only a fill
175
- with white on it (9.5:1), a frame or a glow; text in that hue uses `#7a7aff`
176
- (6.1:1). On white, `#0000ed` is text-safe (9.3:1). Green is kept for the
177
- cursor, the prompt and "OK".
178
- - **Both themes pass WCAG AA** for every text node in every state (all
179
- projects with every card open, a project with a finding open, settings, a
180
- past run), measured in the browser, composited.
181
- - **Motion.** Run output types itself out and the cursor blinks; with
182
- `prefers-reduced-motion` both stop and output appears whole. Nothing else
183
- animates.
184
- - **Keyboard.** `/` search, `j`/`k` findings, `[`/`]` side panels, `y`/`n`/`q`
185
- answer a diff, `Esc` closes the detail, clears the search or closes a
186
- drawer. Every control is a real button or link; the approval dialog takes
187
- focus when a diff arrives; screen readers get each log line whole.
package/docs/evidence.md DELETED
@@ -1,162 +0,0 @@
1
- # Evidence bundles and audience reports
2
-
3
- Penthera can write the results of one scan as an **evidence bundle**: every
4
- report, a manifest with the SHA-256 of each file, and an optional SSH
5
- signature over the manifest. An auditor checks the bundle with one command.
6
- No hosted service is involved, and nothing needs to be installed beyond Node
7
- (or `shasum` and `jq`) and OpenSSH 8.0 or later.
8
-
9
- ## For auditors: verify a bundle
10
-
11
- You need the bundle directory and an **allowed signers file** that names the
12
- key you expect the bundle to be signed with. Get that public key from the
13
- organisation through a channel other than the bundle itself (their security
14
- page, an email you can authenticate, or in person).
15
-
16
- ```bash
17
- npx penthera evidence verify ./evidence --allowed-signers allowed_signers --require-signature
18
- ```
19
-
20
- Exit code `0` means every file matches the manifest and the manifest was signed
21
- by a key in `allowed_signers`. Exit code `1` means something changed, is
22
- missing, was added, or the signature does not check out; the output says which.
23
-
24
- An `allowed_signers` file is one line per key:
25
-
26
- ```text
27
- security@example.com namespaces="penthera-evidence" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
28
- ```
29
-
30
- ### Without Penthera
31
-
32
- The bundle does not depend on Penthera to be checked. With standard tools:
33
-
34
- ```bash
35
- cd evidence
36
- # 1. The manifest was signed by the expected key (namespace penthera-evidence)
37
- ssh-keygen -Y verify -f ../allowed_signers -I security@example.com \
38
- -n penthera-evidence -s manifest.json.sig < manifest.json
39
- # 2. Every artifact matches its hash in the manifest
40
- jq -r '.artifacts[] | "\(.sha256) \(.path)"' manifest.json | shasum -a 256 -c -
41
- # 3. Nothing else is in the directory
42
- ls
43
- ```
44
-
45
- `penthera evidence verify` does the same three checks, and also rejects
46
- manifest paths that would leave the directory (`../`, absolute paths) and
47
- symlinked files.
48
-
49
- ### What the checks prove, and what they do not
50
-
51
- - **Hashes** prove that the files have not changed since the manifest was
52
- written. Without a signature they prove nothing about who wrote it: anyone
53
- can rewrite a file and its hash together.
54
- - **The signature** proves that the holder of the signing key produced this
55
- manifest. It does not prove that the scan was run against the system you
56
- care about, or with the options you expect. Read the manifest's `command`,
57
- `target`, `scan` and `git` fields, and the auditor report's scope section.
58
- - A signed bundle only verifies with `--allowed-signers`. Without it,
59
- `verify` cannot tell whose key made the signature (anyone can re-sign a
60
- rewritten manifest with their own key), so it fails with status
61
- `unverified`. A manifest that declares a signature with no `.sig` file
62
- fails too.
63
- - An unsigned bundle passes with an `UNSIGNED` warning. Pass
64
- `--require-signature` (with `--allowed-signers`) to make it fail.
65
- - A scan is a snapshot. The bundle is evidence of what Penthera saw at
66
- `generated_at`, not a statement that the system is secure.
67
-
68
- ## For the team producing evidence
69
-
70
- ```bash
71
- penthera https://staging.example.com --repo . --framework nis2 \
72
- --evidence evidence/2026-10-19 --sign-key ~/.ssh/id_ed25519
73
- ```
74
-
75
- - `--evidence <dir>` writes the bundle. The directory must be new or empty, so
76
- a bundle never mixes two runs.
77
- - `--sign-key <key>` signs `manifest.json` with
78
- `ssh-keygen -Y sign -n penthera-evidence`. Use the private key path, or the
79
- `.pub` path when the key lives in `ssh-agent` or on a hardware token. A
80
- passphrase prompt appears in the terminal.
81
- - `--sbom <file>` and `--readiness` add their outputs to the bundle.
82
- - Give auditors the public key out of band, as an `allowed_signers` line.
83
-
84
- ### What is in a bundle
85
-
86
- | File | Contents |
87
- |------|----------|
88
- | `report.json` | The full scan result |
89
- | `report.md` | The default Markdown report |
90
- | `report.sarif` | SARIF 2.1.0 |
91
- | `report-developer.md` | Findings with file:line, fix recipe ids or diffs, and sources |
92
- | `report-owner.md` | Plain-language summary, what to do first, owner actions, trend |
93
- | `report-auditor.md` | Scope and method, confidence and provenance, egress, inconclusive probes, suppressions, fix timeline, NIS2 and OWASP mapping |
94
- | `report-custom.md` | Your `--report-template` output, when given |
95
- | `nis2-readiness.md` | With `--readiness` |
96
- | `sbom.cdx.json` | CycloneDX SBOM, with `--sbom` |
97
- | `manifest.json` | Tool version, command line (secrets redacted), timestamp, target, git commit, SHA-256 and size of every file above |
98
- | `manifest.json.sig` | SSH signature over `manifest.json`, with `--sign-key` |
99
-
100
- The command line in the manifest has the values of `--auth-cookie`,
101
- `--auth-bearer` and any token, secret or password flag replaced with
102
- `[REDACTED]`, and credentials in URLs masked.
103
-
104
- ## Audience reports
105
-
106
- `--audience developer|owner|auditor` picks who the Markdown report is for. It
107
- applies to `--markdown <file>`; without `--markdown` the report goes to stdout.
108
- The default report (no `--audience`) is unchanged.
109
-
110
- ```bash
111
- penthera https://example.com --repo . --audience owner > for-the-owner.md
112
- penthera https://example.com --repo . --audience auditor --markdown audit.md
113
- ```
114
-
115
- When a scan used `--memory`, the owner report shows the trend across recent
116
- scans. When the result has an agent loop run (`loopRun`) or memory has fix
117
- events, the developer, owner and auditor reports show a fix timeline:
118
- found → fixed → verified, with time to fix.
119
-
120
- ## Your own template
121
-
122
- `--report-template file.md` renders your own Markdown from the same data the
123
- audience reports use. The language is deliberately tiny:
124
-
125
- ```markdown
126
- # Security status for {{summary.target}}
127
-
128
- {{summary.critical}} critical, {{summary.high}} high ({{summary.total}} total).
129
-
130
- {{#each findings}}
131
- - [{{severity}}] {{title}}{{#if location}} at {{location}}{{/if}} ({{confidence}})
132
- {{/each}}
133
-
134
- {{#if suppressed}}
135
- Set aside: {{#each suppressed}}{{title}} by {{actor}}: {{reason}}; {{/each}}
136
- {{else}}
137
- Nothing suppressed.
138
- {{/if}}
139
- ```
140
-
141
- - `{{path.to.value}}` prints a value. Values are HTML- and Markdown-escaped,
142
- and newlines become spaces, so a finding title cannot add HTML, a heading, a
143
- list item or a table cell.
144
- - `{{#each list}}…{{/each}}` loops; inside, `{{this}}` is the item and
145
- `{{@index}}` its 0-based index. Names not found on the item are looked up in
146
- the enclosing scope.
147
- - `{{#if value}}…{{else}}…{{/if}}`; an empty list counts as false.
148
-
149
- There are no helpers, no expressions, no raw output, no partials and no file
150
- includes. Paths containing `__proto__`, `constructor` or `prototype` are
151
- rejected, and only own properties are read. A template that uses anything else
152
- fails with a line number before the scan starts.
153
-
154
- Top-level fields: `tool`, `target`, `timestamp`, `duration`, `modes`,
155
- `profile`, `offline`, `probes`, `summary` (`critical`, `high`, `medium`,
156
- `low`, `info`, `serious`, `total`, `suppressed`, `inconclusive`), `findings`
157
- (`fid`, `title`, `severity`, `confidence`, `source`, `method`, `collectedAt`,
158
- `category`, `location`, `file`, `line`, `url`, `description`, `recipe`,
159
- `fixText`, `sources`, `owasp`, `wstg`), `suppressed` (`title`, `severity`,
160
- `reason`, `actor`, `by`, `expires`), `inconclusive`, `egress`, `actions`,
161
- `topActions`, `ownerActions`, `nis2`, `owasp`, `trend` and `timeline`. The
162
- model is built in [`src/report/model.js`](../src/report/model.js).
@@ -1,79 +0,0 @@
1
- # Due diligence checklist
2
-
3
- What a funder, sponsor or acquirer is likely to check, what is already in
4
- place, and what is still open. Checked on 2026-10-19 against the `main` branch
5
- at commit 156eda6.
6
-
7
- Legend: **Done**, **Open** (needs action), **Decide** (maintainer's call).
8
-
9
- ## 1. Contributions and sign-off (DCO)
10
-
11
- | Item | Status | Notes |
12
- |------|--------|-------|
13
- | Contributors are all known | Done | `git shortlog -sne --all`: 54 commits as "Daan van der Zwaag <daan@devign.it>", 6 as "Daan <daan@devign.it>" (same person, commits made in the GitHub web UI), 1 from dependabot. No outside human contributors. All 8 pull requests so far were opened by the maintainer or dependabot. |
14
- | DCO sign-off required | Open | The Developer Certificate of Origin 1.1 (https://developercertificate.org/) lets a contributor certify they have the right to submit their code under the project licence. This PR adds a DCO section to CONTRIBUTING.md. Enforcement is not on yet. To enforce, install a DCO check app on the repo, or turn on "Require contributors to sign off on web-based commits" in repository settings. |
15
- | Past commits | Decide | Past commits are not signed off, but all are by the maintainer, so no outside rights are in question. A short statement in the README or this file is enough; rewriting history is not worth it. |
16
- | Author identity | Open | Add a `.mailmap` mapping "Daan" to "Daan van der Zwaag" so tools show one author. |
17
- | AI-assisted commits | Done | 45 commit messages carry `Co-Authored-By: Claude ...` trailers, recording which work was AI-assisted. NLnet's GenAI policy names commit messages as one acceptable way to log this (https://nlnet.nl/foundation/policies/generativeAI/). |
18
-
19
- ## 2. Licence and copyright
20
-
21
- | Item | Status | Notes |
22
- |------|--------|-------|
23
- | Project licence | Done | MIT, in `LICENSE`, `package.json` and `.claude-plugin/plugin.json`. |
24
- | Copyright line | Open | `LICENSE` says "Copyright (c) 2026 daan". Change to the full legal name, or to a company if the maintainer moves the project into one: "Copyright (c) 2026 Daan van der Zwaag". |
25
- | Runtime dependencies | Done | One: `yaml` (ISC). Dev dependency: `vitest` (MIT). Both permissive. |
26
- | Third-party data in the source | Open | `lib/params.js` has a parameter wordlist with the comment "Curated from SecLists, Arjun defaults, and CommonCrawl analysis." SecLists is MIT and Arjun is AGPL-3.0 (GitHub licence metadata for both repositories). A list of common parameter names like `id` and `page` is unlikely to carry copyright on its own, but an acquirer's lawyer will ask. Options: document which entries came from where, or rebuild the list from MIT sources only and change the comment. Add a `THIRD_PARTY_NOTICES` file crediting SecLists. |
27
- | Other inspiration notes | Decide | The same file says "Inspired by: Arjun (6.2k stars), ParamSpider". Inspiration for an algorithm is fine; make sure no code was copied, and keep the note. |
28
- | Copyright of AI-assisted code | Decide | The US Copyright Office's report "Copyright and Artificial Intelligence, Part 2: Copyrightability" (29 January 2025, https://www.copyright.gov/ai/) addresses protection for AI-generated material. It is a US source; EU and Dutch law may differ. This matters little for an MIT project but may come up in an acquisition. Keep the co-author trailers as the record of human direction and review. Not legal advice. |
29
- | Assets | Done | `assets/` holds the project's own logo, social image and demo GIF. Confirm the logo was made by the maintainer or under a licence that allows transfer. |
30
-
31
- ## 3. Name and trademark
32
-
33
- | Item | Status | Notes |
34
- |------|--------|-------|
35
- | Existing "Penthera" company | Decide | Penthera (penthera.com) is a US company, founded around 2004 to 2005, that sells a video download SDK for mobile apps and describes itself as a SaaS company for streaming providers (https://www.penthera.com/about, https://pitchbook.com/profiles/company/61416-37). Different field (video delivery, not security), but the same word. A search found no registered PENTHERA mark; that search was not exhaustive. |
36
- | Similar name in security | Decide | Pentera (pentera.io), formerly Pcysys, sells automated security validation and pentesting software (https://en.wikipedia.org/wiki/Pentera). Pentera Security Ltd. has filed US trademark applications for PENTERA covering security software (https://trademarks.justia.com/owners/pentera-security-ltd-5403910). "Penthera" and "Pentera" differ by one letter in the same product category. This is the bigger risk: an acquirer will notice, and a confusion claim is plausible. |
37
- | Recommended next steps | Open | 1. Search EUIPO eSearch plus, TMview and the BOIP register for PENTHERA and PENTERA in classes 9 and 42. 2. Ask an IP lawyer for a short clearance opinion before spending on a filing or a brand. 3. If cleared, a Benelux filing at BOIP costs EUR 244 for one class (https://boip.int/en/node/2163, verify before filing); an EU trade mark at EUIPO costs more. 4. If not cleared, a rename is cheapest now, while adoption is small. |
38
- | npm and GitHub names | Done | `penthera` on npm and `danoszz/penthera` on GitHub are held by the maintainer. |
39
-
40
- ## 4. Security posture of the project itself
41
-
42
- | Item | Status | Notes |
43
- |------|--------|-------|
44
- | Pinned GitHub Actions | Done | Every `uses:` in `.github/workflows/` is pinned to a full commit SHA with the version in a comment. Dependabot proposes updates (an update to `actions/setup-node` is open as pull request #11). |
45
- | Staged npm publishing with 2FA | Done | `publish.yml` runs `npm stage publish`; nothing goes live until the maintainer approves the staged version with 2FA on npmjs.com. A hijacked workflow can stage, never ship. Merged in pull request #13. |
46
- | Trusted publishing, no stored token | Done | Publishing uses npm trusted publishing (OIDC). No npm token is stored in the repo or in secrets. |
47
- | npm provenance | Done | `--provenance` links each tarball to the workflow run and commit. |
48
- | CodeQL | Done | `.github/workflows/codeql.yml`. |
49
- | OpenSSF Scorecard | Done | Runs weekly and on push. Score 6.3 / 10 on 2026-10-19 (https://scorecard.dev/viewer/?uri=github.com/danoszz/penthera). |
50
- | Scorecard: Token-Permissions 0 | Open | Scorecard reports workflow tokens with excessive permissions. `publish.yml` grants `contents: write` and `id-token: write` at the top level; move them to the job and set `permissions: read-all` or `{}` at the top. Check the other workflows the same way. |
51
- | Scorecard: Code-Review 0 | Decide | 0 of 15 changesets were reviewed by a second person. Expected for a solo project. A second maintainer or an outside reviewer would fix it. |
52
- | Scorecard: Signed-Releases 0 | Open | Scorecard looks for signatures or provenance attached to GitHub Releases. npm provenance exists, but the GitHub Release has no signed artefact. Attaching the packed tarball with a provenance attestation (for example `actions/attest-build-provenance`) would cover it. |
53
- | Scorecard: CII-Best-Practices 0 | Open | Apply for the OpenSSF Best Practices badge (https://www.bestpractices.dev/). Most criteria are already met. |
54
- | Scorecard: Fuzzing 0 | Decide | Penthera has property-based API fuzzing as a feature, but the project's own code is not fuzzed. Low priority. |
55
- | Scorecard: Branch-Protection not scored | Open | Scorecard could not read branch protection (needs an admin token). Check that `main` requires PRs and passing CI. |
56
- | Security policy | Done | `SECURITY.md` with private advisory reporting, response times and safe harbour. |
57
- | Offline tests | Done | Tests do not reach the internet (CONTRIBUTING.md). |
58
- | No telemetry | Done | Stated in README "Privacy". An acquirer may want to verify it; the egress guard on the roadmap would make it testable. |
59
-
60
- ## 5. Business and governance
61
-
62
- | Item | Status | Notes |
63
- |------|--------|-------|
64
- | Legal entity | Decide | The project is held by an individual. Funders such as NLnet accept individuals. An acquisition is simpler if the code, names and accounts sit in one entity. |
65
- | Accounts that would transfer | Open | List them: GitHub repo, npm package and its trusted publisher config, skills.sh listing, plugin marketplace entries, domain (if any), GitHub Sponsors profile. Make sure each has 2FA and a recovery path. |
66
- | Bus factor | Open | One maintainer. A documented release runbook exists (CONTRIBUTING.md, "Cutting a release"). Consider a second npm owner with 2FA for continuity. |
67
- | Funding history | Done | None so far. Keep a simple record of any grant or sponsorship income from the start. |
68
- | Claims in public material | Done | README and docs say "readiness" and "evidence", never "certified" or "compliant". Keep it that way in pitches. |
69
-
70
- ## Sources
71
-
72
- - DCO 1.1: https://developercertificate.org/
73
- - NLnet GenAI policy: https://nlnet.nl/foundation/policies/generativeAI/
74
- - US Copyright Office AI reports: https://www.copyright.gov/ai/
75
- - Penthera (video SDK company): https://www.penthera.com/about , https://pitchbook.com/profiles/company/61416-37
76
- - Pentera: https://en.wikipedia.org/wiki/Pentera , https://trademarks.justia.com/owners/pentera-security-ltd-5403910
77
- - BOIP fees: https://boip.int/en/node/2163
78
- - OpenSSF Scorecard result: https://api.scorecard.dev/projects/github.com/danoszz/penthera
79
- - Arjun licence: https://github.com/s0md3v/Arjun ; SecLists licence: https://github.com/danielmiessler/SecLists