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
@@ -0,0 +1,158 @@
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
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
+ - Without `--allowed-signers`, `verify` still checks that the signature is
59
+ valid but cannot tell you whose key made it. It reports
60
+ `valid-unknown-signer` as a warning.
61
+ - A scan is a snapshot. The bundle is evidence of what Penthera saw at
62
+ `generated_at`, not a statement that the system is secure.
63
+
64
+ ## For the team producing evidence
65
+
66
+ ```bash
67
+ penthera https://staging.example.com --repo . --framework nis2 \
68
+ --evidence evidence/2026-10-19 --sign-key ~/.ssh/id_ed25519
69
+ ```
70
+
71
+ - `--evidence <dir>` writes the bundle. The directory must be new or empty, so
72
+ a bundle never mixes two runs.
73
+ - `--sign-key <key>` signs `manifest.json` with
74
+ `ssh-keygen -Y sign -n penthera-evidence`. Use the private key path, or the
75
+ `.pub` path when the key lives in `ssh-agent` or on a hardware token. A
76
+ passphrase prompt appears in the terminal.
77
+ - `--sbom <file>` and `--readiness` add their outputs to the bundle.
78
+ - Give auditors the public key out of band, as an `allowed_signers` line.
79
+
80
+ ### What is in a bundle
81
+
82
+ | File | Contents |
83
+ |------|----------|
84
+ | `report.json` | The full scan result |
85
+ | `report.md` | The default Markdown report |
86
+ | `report.sarif` | SARIF 2.1.0 |
87
+ | `report-developer.md` | Findings with file:line, fix recipe ids or diffs, and sources |
88
+ | `report-owner.md` | Plain-language summary, what to do first, owner actions, trend |
89
+ | `report-auditor.md` | Scope and method, confidence and provenance, egress, inconclusive probes, suppressions, fix timeline, NIS2 and OWASP mapping |
90
+ | `report-custom.md` | Your `--report-template` output, when given |
91
+ | `nis2-readiness.md` | With `--readiness` |
92
+ | `sbom.cdx.json` | CycloneDX SBOM, with `--sbom` |
93
+ | `manifest.json` | Tool version, command line (secrets redacted), timestamp, target, git commit, SHA-256 and size of every file above |
94
+ | `manifest.json.sig` | SSH signature over `manifest.json`, with `--sign-key` |
95
+
96
+ The command line in the manifest has the values of `--auth-cookie`,
97
+ `--auth-bearer` and any token, secret or password flag replaced with
98
+ `[REDACTED]`, and credentials in URLs masked.
99
+
100
+ ## Audience reports
101
+
102
+ `--audience developer|owner|auditor` picks who the Markdown report is for. It
103
+ applies to `--markdown <file>`; without `--markdown` the report goes to stdout.
104
+ The default report (no `--audience`) is unchanged.
105
+
106
+ ```bash
107
+ penthera https://example.com --repo . --audience owner > for-the-owner.md
108
+ penthera https://example.com --repo . --audience auditor --markdown audit.md
109
+ ```
110
+
111
+ When a scan used `--memory`, the owner report shows the trend across recent
112
+ scans. When the result has an agent loop run (`loopRun`) or memory has fix
113
+ events, the developer, owner and auditor reports show a fix timeline:
114
+ found → fixed → verified, with time to fix.
115
+
116
+ ## Your own template
117
+
118
+ `--report-template file.md` renders your own Markdown from the same data the
119
+ audience reports use. The language is deliberately tiny:
120
+
121
+ ```markdown
122
+ # Security status for {{summary.target}}
123
+
124
+ {{summary.critical}} critical, {{summary.high}} high ({{summary.total}} total).
125
+
126
+ {{#each findings}}
127
+ - [{{severity}}] {{title}}{{#if location}} at {{location}}{{/if}} ({{confidence}})
128
+ {{/each}}
129
+
130
+ {{#if suppressed}}
131
+ Set aside: {{#each suppressed}}{{title}} by {{actor}}: {{reason}}; {{/each}}
132
+ {{else}}
133
+ Nothing suppressed.
134
+ {{/if}}
135
+ ```
136
+
137
+ - `{{path.to.value}}` prints a value. Values are HTML- and Markdown-escaped,
138
+ and newlines become spaces, so a finding title cannot add HTML, a heading, a
139
+ list item or a table cell.
140
+ - `{{#each list}}…{{/each}}` loops; inside, `{{this}}` is the item and
141
+ `{{@index}}` its 0-based index. Names not found on the item are looked up in
142
+ the enclosing scope.
143
+ - `{{#if value}}…{{else}}…{{/if}}`; an empty list counts as false.
144
+
145
+ There are no helpers, no expressions, no raw output, no partials and no file
146
+ includes. Paths containing `__proto__`, `constructor` or `prototype` are
147
+ rejected, and only own properties are read. A template that uses anything else
148
+ fails with a line number before the scan starts.
149
+
150
+ Top-level fields: `tool`, `target`, `timestamp`, `duration`, `modes`,
151
+ `profile`, `offline`, `probes`, `summary` (`critical`, `high`, `medium`,
152
+ `low`, `info`, `serious`, `total`, `suppressed`, `inconclusive`), `findings`
153
+ (`fid`, `title`, `severity`, `confidence`, `source`, `method`, `collectedAt`,
154
+ `category`, `location`, `file`, `line`, `url`, `description`, `recipe`,
155
+ `fixText`, `sources`, `owasp`, `wstg`), `suppressed` (`title`, `severity`,
156
+ `reason`, `actor`, `by`, `expires`), `inconclusive`, `egress`, `actions`,
157
+ `topActions`, `ownerActions`, `nis2`, `owasp`, `trend` and `timeline`. The
158
+ model is built in [`src/report/model.js`](../src/report/model.js).
@@ -0,0 +1,105 @@
1
+ # Example: comment a dependency capability diff on every PR that changes the lockfile.
2
+ #
3
+ # Copy this file into YOUR app's repo as `.github/workflows/penthera-deps.yml`.
4
+ # It installs the base and PR dependency trees with `--ignore-scripts` (no
5
+ # package code runs), then `penthera deps --diff` reports packages that are new
6
+ # or that gained capabilities (network, shell, install scripts, whole
7
+ # environment, dynamic code, ...) and posts that as one PR comment, updated in
8
+ # place on every push. Penthera itself runs offline; nothing is sent anywhere
9
+ # except the comment to your own PR.
10
+ #
11
+ # Pin PENTHERA_VERSION to a release you have reviewed (and, ideally, the actions
12
+ # below to commit SHAs) so this job never runs code you have not looked at.
13
+ #
14
+ # Fork PRs get a read-only token, so the comment step is skipped for them; the
15
+ # report is still uploaded as an artifact. Do not switch this to
16
+ # `pull_request_target`: that would run with write access next to untrusted code.
17
+
18
+ name: Dependency capability diff
19
+
20
+ on:
21
+ pull_request:
22
+ paths:
23
+ - package-lock.json
24
+ - npm-shrinkwrap.json
25
+
26
+ permissions:
27
+ contents: read
28
+
29
+ jobs:
30
+ deps:
31
+ runs-on: ubuntu-latest
32
+ permissions:
33
+ contents: read
34
+ pull-requests: write
35
+ env:
36
+ PENTHERA_VERSION: latest # pin, e.g. 1.3.0
37
+ steps:
38
+ - uses: actions/checkout@v7
39
+ with:
40
+ path: head
41
+ persist-credentials: false
42
+
43
+ - uses: actions/checkout@v7
44
+ with:
45
+ ref: ${{ github.event.pull_request.base.sha }}
46
+ path: base
47
+ persist-credentials: false
48
+
49
+ - uses: actions/setup-node@v6
50
+ with:
51
+ node-version: 22
52
+
53
+ - name: Install both trees without running install scripts
54
+ run: |
55
+ (cd head && npm ci --ignore-scripts --no-audit --no-fund)
56
+ (cd base && npm ci --ignore-scripts --no-audit --no-fund)
57
+
58
+ - name: Diff dependency capabilities
59
+ id: deps
60
+ # Run from the temp dir so `npx` can't pick up a `penthera` binary that
61
+ # the PR's own node_modules might provide.
62
+ working-directory: ${{ runner.temp }}
63
+ run: |
64
+ set +e
65
+ npx --yes "penthera@${PENTHERA_VERSION}" deps "$GITHUB_WORKSPACE/head" \
66
+ --diff "$GITHUB_WORKSPACE/base" \
67
+ --pr-comment "$RUNNER_TEMP/deps-comment.md" \
68
+ --sarif "$RUNNER_TEMP/deps.sarif" \
69
+ -o "$RUNNER_TEMP/deps.json"
70
+ code=$?
71
+ set -e
72
+ echo "code=$code" >> "$GITHUB_OUTPUT"
73
+ # 1 = critical/high findings (reported in the comment); 2 = scan error.
74
+ if [ "$code" -eq 2 ]; then exit 2; fi
75
+
76
+ - name: Comment on the PR (updated in place)
77
+ if: github.event.pull_request.head.repo.full_name == github.repository
78
+ env:
79
+ GH_TOKEN: ${{ github.token }}
80
+ REPO: ${{ github.repository }}
81
+ PR: ${{ github.event.pull_request.number }}
82
+ run: |
83
+ body="$RUNNER_TEMP/deps-comment.md"
84
+ id=$(gh api "repos/$REPO/issues/$PR/comments" --paginate \
85
+ --jq '.[] | select(.body | startswith("<!-- penthera-deps -->")) | .id' | head -n1)
86
+ if [ -n "$id" ]; then
87
+ gh api -X PATCH "repos/$REPO/issues/comments/$id" -F body=@"$body" > /dev/null
88
+ else
89
+ gh pr comment "$PR" --repo "$REPO" --body-file "$body"
90
+ fi
91
+
92
+ - uses: actions/upload-artifact@v7
93
+ if: always()
94
+ with:
95
+ name: penthera-deps
96
+ path: |
97
+ ${{ runner.temp }}/deps-comment.md
98
+ ${{ runner.temp }}/deps.json
99
+ ${{ runner.temp }}/deps.md
100
+ ${{ runner.temp }}/deps.sarif
101
+
102
+ # Optional: block the merge when critical or high findings appear.
103
+ # - name: Fail on critical/high
104
+ # if: steps.deps.outputs.code == '1'
105
+ # run: exit 1
@@ -0,0 +1,55 @@
1
+ # Example: scan your staging URL with Penthera on a schedule.
2
+ #
3
+ # Copy this file into YOUR app's repo as `.github/workflows/penthera.yml` and add
4
+ # a `PENTEST_STAGING_URL` repository secret. Only point it at a URL you own or are
5
+ # authorized to test. Findings appear under Security > Code scanning.
6
+ #
7
+ # Pin the Penthera version (and, ideally, the actions below to commit SHAs) so a
8
+ # scheduled run never picks up code you have not reviewed.
9
+
10
+ name: Penthera security scan
11
+
12
+ on:
13
+ workflow_dispatch:
14
+ schedule:
15
+ - cron: "0 6 * * 1" # weekly, Monday 06:00 UTC
16
+
17
+ permissions:
18
+ contents: read
19
+
20
+ jobs:
21
+ penthera:
22
+ runs-on: ubuntu-latest
23
+ permissions:
24
+ contents: read
25
+ security-events: write
26
+ steps:
27
+ - uses: actions/setup-node@v6
28
+ with:
29
+ node-version: 22
30
+
31
+ - name: Scan staging URL
32
+ env:
33
+ PENTEST_URL: ${{ secrets.PENTEST_STAGING_URL }}
34
+ run: |
35
+ mkdir -p reports
36
+ set +e
37
+ npx --yes penthera@1.4.0 "$PENTEST_URL" \
38
+ --profile standard \
39
+ -o reports/scan.json \
40
+ --sarif reports/scan.sarif
41
+ code=$?
42
+ set -e
43
+ # Exit 1 = critical or high findings (reported via SARIF); only 2 (scan error) fails the job.
44
+ if [ "$code" -eq 2 ]; then exit 2; fi
45
+
46
+ - uses: github/codeql-action/upload-sarif@v4
47
+ if: always()
48
+ with:
49
+ sarif_file: reports/scan.sarif
50
+
51
+ - uses: actions/upload-artifact@v7
52
+ if: always()
53
+ with:
54
+ name: penthera-reports
55
+ path: reports/
@@ -0,0 +1,56 @@
1
+ # Example: comment a dependency capability diff on every PR that changes the lockfile.
2
+ #
3
+ # Copy this file into YOUR repo as `.github/workflows/pr-dependency-review.yml`.
4
+ # The Penthera action reports packages that are new or that gained capabilities
5
+ # (network, shell, install scripts, whole environment, dynamic code, ...) in one
6
+ # PR comment, updated in place on every push, and uploads SARIF to code scanning.
7
+ # Penthera runs offline from the action's own checkout: no npx, no registry
8
+ # lookups, no telemetry. The only API call is the comment on your own PR.
9
+ #
10
+ # Pin the action to a release commit SHA. Find it with:
11
+ # git ls-remote https://github.com/danoszz/penthera refs/tags/v1.4.0
12
+ #
13
+ # Fork PRs get a read-only token: the comment is skipped and the diff goes to the
14
+ # job summary instead. Do not switch this to `pull_request_target`; that would
15
+ # run with write access next to untrusted code.
16
+
17
+ name: Dependency review
18
+
19
+ on:
20
+ pull_request:
21
+ paths:
22
+ - package-lock.json
23
+ - npm-shrinkwrap.json
24
+
25
+ permissions:
26
+ contents: read
27
+ pull-requests: write
28
+ security-events: write
29
+
30
+ jobs:
31
+ penthera:
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
35
+
36
+ - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
37
+ with:
38
+ node-version: 22
39
+
40
+ # Optional but recommended: Penthera reads package code from node_modules
41
+ # and npm's cache. Installing both trees with --ignore-scripts (no package
42
+ # code runs) lets it inspect every changed version. Without this step the
43
+ # comment lists upgrades it could not compare.
44
+ - name: Install base and PR trees without running scripts
45
+ env:
46
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
47
+ run: |
48
+ git fetch --no-tags --depth=1 origin "$BASE_SHA"
49
+ git worktree add --detach "$RUNNER_TEMP/base" "$BASE_SHA"
50
+ (cd "$RUNNER_TEMP/base" && npm ci --ignore-scripts --no-audit --no-fund)
51
+ npm ci --ignore-scripts --no-audit --no-fund
52
+
53
+ - uses: danoszz/penthera@0000000000000000000000000000000000000000 # v1.4.0 (replace with the release commit SHA)
54
+ with:
55
+ mode: deps
56
+ fail-on: none # or high / critical to block the merge
@@ -0,0 +1,79 @@
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