penthera 1.4.0 → 1.6.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.
- package/CHANGELOG.md +227 -0
- package/README.md +148 -575
- package/bin/scan.js +19 -0
- package/docs/commands/README.md +23 -0
- package/docs/commands/action.md +21 -0
- package/docs/commands/agents.md +38 -0
- package/docs/commands/deps.md +115 -0
- package/docs/commands/fix.md +53 -0
- package/docs/commands/hook.md +69 -0
- package/docs/commands/install.md +53 -0
- package/docs/commands/scan.md +100 -0
- package/docs/commands/skill.md +58 -0
- package/docs/compare/README.md +25 -0
- package/docs/compare/github.md +60 -0
- package/docs/compare/semgrep.md +54 -0
- package/docs/compare/snyk.md +62 -0
- package/docs/compare/socket.md +78 -0
- package/docs/compare/zap.md +59 -0
- package/docs/compliance-cra.md +141 -0
- package/docs/compliance-nis2.md +3 -1
- package/docs/dashboard.md +244 -0
- package/docs/evidence.md +8 -4
- package/docs/examples/github-actions-deps.yml +4 -4
- package/docs/examples/github-actions-scan.yml +4 -4
- package/docs/index.md +11 -2
- package/docs/internal-audit.md +83 -0
- package/docs/marketing.md +151 -0
- package/docs/plan.md +101 -0
- package/docs/privacy.md +38 -0
- package/docs/references.bib +10 -0
- package/docs/references.md +1 -0
- package/docs/research/socket-positioning-notes.md +183 -0
- package/docs/use-cases/README.md +14 -0
- package/docs/use-cases/agencies.md +71 -0
- package/docs/use-cases/ai-coding-teams.md +81 -0
- package/docs/use-cases/eu-sme-compliance.md +68 -0
- package/docs/use-cases/open-source-maintainers.md +75 -0
- package/docs/use-cases/solo-founder.md +65 -0
- package/lib/blackbox/email-dns.js +5 -0
- package/lib/compliance/cra.js +525 -0
- package/lib/compliance/questionnaire.js +4 -2
- package/lib/deps/capabilities.js +49 -12
- package/lib/deps/index.js +314 -30
- package/lib/deps/license.js +329 -0
- package/lib/deps/lockfile.js +29 -3
- package/lib/deps/popular-pypi.json +1 -0
- package/lib/deps/python/capabilities.js +447 -0
- package/lib/deps/python/index.js +234 -0
- package/lib/deps/python/lockfiles.js +359 -0
- package/lib/deps/python/source.js +396 -0
- package/lib/deps/python/toml.js +219 -0
- package/lib/deps/report.js +54 -6
- package/lib/deps/source.js +149 -24
- package/lib/deps/spdx.json +1 -0
- package/lib/deps/typosquat.js +24 -13
- package/lib/fix/apply.js +83 -19
- package/lib/hook/index.js +345 -0
- package/lib/hook/policy.js +62 -0
- package/lib/install/index.js +59 -18
- package/lib/install/policy.js +27 -7
- package/lib/jurisdictions/nl.json +9 -0
- package/lib/memory/repo-suppress.js +11 -3
- package/lib/references.json +77 -0
- package/lib/remediation/index.js +9 -0
- package/lib/retirejs.js +69 -17
- package/lib/rules/regex-guard.js +368 -0
- package/lib/templates.js +13 -6
- package/lib/tls.js +7 -0
- package/lib/whitebox/aibom.js +384 -0
- package/lib/whitebox/frameworks.js +8 -5
- package/lib/whitebox/sbom.js +45 -10
- package/lib/whitebox/secrets.js +31 -8
- package/package.json +8 -3
- package/skills/penthera/SKILL.md +55 -8
- package/src/cli/deps.js +81 -6
- package/src/cli/evidence.js +14 -4
- package/src/cli/hook.js +131 -0
- package/src/cli/install.js +14 -5
- package/src/cli/report-outputs.js +25 -2
- package/src/cli/ui.js +89 -0
- package/src/cli.js +81 -11
- package/src/report/audience.js +10 -10
- package/src/report/evidence.js +33 -14
- package/src/report/footer.js +11 -0
- package/src/report/markdown.js +6 -3
- package/src/report/md-escape.js +16 -0
- package/src/reporter.js +1 -0
- package/src/scan-url.js +5 -1
- package/src/ui/api/data.js +309 -0
- package/src/ui/api/demo.js +464 -0
- package/src/ui/api/routes.js +134 -0
- package/src/ui/api/runs.js +243 -0
- package/src/ui/api/summarize.js +451 -0
- package/src/ui/security.js +153 -0
- package/src/ui/server.js +186 -0
- package/src/ui/static/app.css +723 -0
- package/src/ui/static/favicon.svg +1 -0
- package/src/ui/static/index.html +57 -0
- package/src/ui/static/js/api.js +29 -0
- package/src/ui/static/js/app.js +138 -0
- package/src/ui/static/js/charts.js +207 -0
- package/src/ui/static/js/copy.js +79 -0
- package/src/ui/static/js/dom.js +120 -0
- package/src/ui/static/js/format.js +42 -0
- package/src/ui/static/js/theme.js +10 -0
- package/src/ui/static/js/typer.js +99 -0
- package/src/ui/static/js/views/ai.js +68 -0
- package/src/ui/static/js/views/deps.js +96 -0
- package/src/ui/static/js/views/evidence.js +115 -0
- package/src/ui/static/js/views/findings.js +165 -0
- package/src/ui/static/js/views/history.js +92 -0
- package/src/ui/static/js/views/overview.js +184 -0
- package/src/ui/static/js/views/run.js +349 -0
- package/src/ui/static/js/views/settings.js +67 -0
package/docs/evidence.md
CHANGED
|
@@ -14,7 +14,7 @@ organisation through a channel other than the bundle itself (their security
|
|
|
14
14
|
page, an email you can authenticate, or in person).
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
npx penthera evidence verify ./evidence --allowed-signers allowed_signers
|
|
17
|
+
npx penthera evidence verify ./evidence --allowed-signers allowed_signers --require-signature
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
Exit code `0` means every file matches the manifest and the manifest was signed
|
|
@@ -55,9 +55,13 @@ symlinked files.
|
|
|
55
55
|
manifest. It does not prove that the scan was run against the system you
|
|
56
56
|
care about, or with the options you expect. Read the manifest's `command`,
|
|
57
57
|
`target`, `scan` and `git` fields, and the auditor report's scope section.
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
|
|
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.
|
|
61
65
|
- A scan is a snapshot. The bundle is evidence of what Penthera saw at
|
|
62
66
|
`generated_at`, not a statement that the system is secure.
|
|
63
67
|
|
|
@@ -35,18 +35,18 @@ jobs:
|
|
|
35
35
|
env:
|
|
36
36
|
PENTHERA_VERSION: latest # pin, e.g. 1.3.0
|
|
37
37
|
steps:
|
|
38
|
-
- uses: actions/checkout@v7
|
|
38
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
39
39
|
with:
|
|
40
40
|
path: head
|
|
41
41
|
persist-credentials: false
|
|
42
42
|
|
|
43
|
-
- uses: actions/checkout@v7
|
|
43
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
44
44
|
with:
|
|
45
45
|
ref: ${{ github.event.pull_request.base.sha }}
|
|
46
46
|
path: base
|
|
47
47
|
persist-credentials: false
|
|
48
48
|
|
|
49
|
-
- uses: actions/setup-node@v6
|
|
49
|
+
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
|
50
50
|
with:
|
|
51
51
|
node-version: 22
|
|
52
52
|
|
|
@@ -89,7 +89,7 @@ jobs:
|
|
|
89
89
|
gh pr comment "$PR" --repo "$REPO" --body-file "$body"
|
|
90
90
|
fi
|
|
91
91
|
|
|
92
|
-
- uses: actions/upload-artifact@v7
|
|
92
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
93
93
|
if: always()
|
|
94
94
|
with:
|
|
95
95
|
name: penthera-deps
|
|
@@ -24,7 +24,7 @@ jobs:
|
|
|
24
24
|
contents: read
|
|
25
25
|
security-events: write
|
|
26
26
|
steps:
|
|
27
|
-
- uses: actions/setup-node@v6
|
|
27
|
+
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
|
28
28
|
with:
|
|
29
29
|
node-version: 22
|
|
30
30
|
|
|
@@ -34,7 +34,7 @@ jobs:
|
|
|
34
34
|
run: |
|
|
35
35
|
mkdir -p reports
|
|
36
36
|
set +e
|
|
37
|
-
npx --yes penthera@1.
|
|
37
|
+
npx --yes penthera@1.6.0 "$PENTEST_URL" \
|
|
38
38
|
--profile standard \
|
|
39
39
|
-o reports/scan.json \
|
|
40
40
|
--sarif reports/scan.sarif
|
|
@@ -43,12 +43,12 @@ jobs:
|
|
|
43
43
|
# Exit 1 = critical or high findings (reported via SARIF); only 2 (scan error) fails the job.
|
|
44
44
|
if [ "$code" -eq 2 ]; then exit 2; fi
|
|
45
45
|
|
|
46
|
-
- uses: github/codeql-action/upload-sarif@v4
|
|
46
|
+
- uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
|
47
47
|
if: always()
|
|
48
48
|
with:
|
|
49
49
|
sarif_file: reports/scan.sarif
|
|
50
50
|
|
|
51
|
-
- uses: actions/upload-artifact@v7
|
|
51
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
52
52
|
if: always()
|
|
53
53
|
with:
|
|
54
54
|
name: penthera-reports
|
package/docs/index.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Penthera documentation
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
|
@@ -8,8 +8,11 @@ The routine work of a security team, on your own machine: app scanning, dependen
|
|
|
8
8
|
|-------|-------------|
|
|
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
|
+
| [Use cases](./use-cases/README.md) | By situation: [before launch](./use-cases/solo-founder.md), [AI-coding teams](./use-cases/ai-coding-teams.md), [maintainers](./use-cases/open-source-maintainers.md), [EU SMEs](./use-cases/eu-sme-compliance.md), [agencies](./use-cases/agencies.md) |
|
|
12
|
+
| [Comparisons](./compare/README.md) | Using Penthera with [Socket](./compare/socket.md), [Snyk](./compare/snyk.md), [GitHub](./compare/github.md), [ZAP](./compare/zap.md), [Semgrep](./compare/semgrep.md) |
|
|
13
|
+
| [Commands](./commands/README.md) | One page per command: [scan](./commands/scan.md), [agents](./commands/agents.md), [deps](./commands/deps.md), [install](./commands/install.md), [fix](./commands/fix.md), [hook](./commands/hook.md), [action](./commands/action.md), [skill](./commands/skill.md) |
|
|
11
14
|
| [CHANGELOG](../CHANGELOG.md) | Version history |
|
|
12
|
-
| [Roadmap](../ROADMAP.md) and [release plan](./release-plan.md) | What ships next, in what order |
|
|
15
|
+
| [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 |
|
|
13
16
|
|
|
14
17
|
## Scanning
|
|
15
18
|
|
|
@@ -36,6 +39,10 @@ The routine work of a security team, on your own machine: app scanning, dependen
|
|
|
36
39
|
| OWASP WSTG mapping | [owasp-wstg-coverage.md](./owasp-wstg-coverage.md) |
|
|
37
40
|
| NIS2 compliance guide | [compliance-nis2.md](./compliance-nis2.md) |
|
|
38
41
|
| Local loop and learning store | [local-loop.md](./local-loop.md) |
|
|
42
|
+
| CRA reporting and AI-BOM | [compliance-cra.md](./compliance-cra.md) |
|
|
43
|
+
| Reports and evidence bundles | [evidence.md](./evidence.md) |
|
|
44
|
+
| Privacy, network use, `--offline` | [privacy.md](./privacy.md) |
|
|
45
|
+
| Local dashboard (`penthera ui`) | [dashboard.md](./dashboard.md) |
|
|
39
46
|
| Scan your app in GitHub Actions | [examples/github-actions-scan.yml](./examples/github-actions-scan.yml), set `PENTEST_STAGING_URL` |
|
|
40
47
|
| Penthera's own CI | `.github/workflows/ci.yml` (full tests + mock scan), `codeql.yml`, `scorecard.yml` |
|
|
41
48
|
| Docker | `docker build -t penthera . && docker run penthera https://localhost:3000` |
|
|
@@ -48,6 +55,8 @@ The routine work of a security team, on your own machine: app scanning, dependen
|
|
|
48
55
|
| Cited research and standards | [references.md](./references.md) ([BibTeX](./references.bib)) |
|
|
49
56
|
| Accuracy benchmark (`npm run bench`) | [bench/README.md](../bench/README.md), latest [results](../bench/RESULTS.md) |
|
|
50
57
|
| Citing Penthera | [CITATION.cff](../CITATION.cff) |
|
|
58
|
+
| Refresh the README's numbers | `npm run docs:numbers` (`-- --tests` to count tests) |
|
|
59
|
+
| How Socket positions itself, and what Penthera took from it | [research/socket-positioning-notes.md](./research/socket-positioning-notes.md) |
|
|
51
60
|
|
|
52
61
|
## Agent skill
|
|
53
62
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Internal testing audit
|
|
2
|
+
|
|
3
|
+
Penthera is a security tool, so its own release bar has to be easy to check.
|
|
4
|
+
The internal audit is that bar: one command that runs every check a release
|
|
5
|
+
needs and writes the result to [`audit/REPORT.md`](../audit/REPORT.md).
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run audit:internal
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
It exits 1 if any gate fails. A release is only tagged from a commit whose
|
|
12
|
+
audit passed (see the release checklist in
|
|
13
|
+
[CONTRIBUTING.md](../CONTRIBUTING.md#cutting-a-release)). The same audit runs
|
|
14
|
+
every Monday in CI ([`internal-audit.yml`](../.github/workflows/internal-audit.yml))
|
|
15
|
+
so drift shows up between releases.
|
|
16
|
+
|
|
17
|
+
## The gates
|
|
18
|
+
|
|
19
|
+
| Gate | What it checks | Fails when |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| Test suite | `npm test`, with the live suites pointed at the bundled mock server (`PENTEST_BASE_URL`) | Any test fails |
|
|
22
|
+
| Benchmark | `npm run bench` (offline, seeded) against the **Total** row of the committed [`bench/RESULTS.md`](../bench/RESULTS.md) | Precision or recall drops by more than 0.02, or any request leaves loopback |
|
|
23
|
+
| Self-scan | `penthera --repo .`, `penthera agents .`, `penthera deps .`, and `penthera hook run` on a copy of the tree with every file staged | A high or critical finding is not in [`audit/baseline.json`](../audit/baseline.json), or a baseline entry has no reason |
|
|
24
|
+
| CLI smoke | `--help` for every subcommand, plus a fixture run of each: a mock-server scan in every report format, `agents` on a clean and a malicious fixture, `deps`, `install --dry-run`, `fix --plan`, an evidence bundle and its `verify`, one `agent --loop` iteration (every diff declined), `hook install/uninstall` and `memory show/verify` | Any command exits with an unexpected code or produces the wrong output |
|
|
25
|
+
| Packaging | `npm pack --dry-run` | A file outside the allow-list ships, anything that looks like tests, fixtures, reports or secrets ships, or the package exceeds its size budget |
|
|
26
|
+
| Offline | A full `--all --adaptive --offline` scan of the mock server | The report's `egress` field shows any third-party host |
|
|
27
|
+
| Supply chain | `npm audit --omit=dev`; sha512 integrity for every lockfile entry; install scripts in runtime dependencies (via `penthera deps --prod`); every `uses:` in `.github/workflows`, `action.yml` and `docs/examples` | Any vulnerability, missing integrity, runtime install script, typosquat, action not pinned by commit SHA, or workflow without top-level `permissions:` |
|
|
28
|
+
| Versions and docs | The version in `package.json`, the lockfile, the plugin manifests, `SKILL.md` and `CITATION.cff`; a `CHANGELOG.md` section for it; every relative link in `README.md` and `docs/` | Any mismatch or broken link |
|
|
29
|
+
|
|
30
|
+
Everything runs on this machine against this repo or `127.0.0.1`. The only
|
|
31
|
+
network use is `npm audit` (the npm registry) and, with `--with-docker`,
|
|
32
|
+
pulling the Juice Shop image.
|
|
33
|
+
|
|
34
|
+
## Options
|
|
35
|
+
|
|
36
|
+
| Flag | Effect |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `--only bench,versions-docs` | Run some gates (ids: `tests`, `bench`, `self-scan`, `smoke`, `packaging`, `offline`, `supply-chain`, `versions-docs`, `docker`). Prints results and writes nothing. |
|
|
39
|
+
| `--update-baseline` | Rewrite `audit/baseline.json` from this run's self-scan. New entries get a `TODO` reason, which fails the gate until a person writes one. |
|
|
40
|
+
| `--with-docker` | Add the optional heavier targets below. |
|
|
41
|
+
| `--no-write` | Leave `audit/` untouched. |
|
|
42
|
+
|
|
43
|
+
Command output from each run goes to `audit/logs/` (ignored by git, uploaded
|
|
44
|
+
with the report as the CI artifact).
|
|
45
|
+
|
|
46
|
+
## Optional targets: Juice Shop and crAPI
|
|
47
|
+
|
|
48
|
+
`--with-docker` starts [OWASP Juice Shop](https://github.com/juice-shop/juice-shop)
|
|
49
|
+
on `127.0.0.1:3300`, scans it with the benchmark's `juice-shop` target, and
|
|
50
|
+
removes the container. Pin the image for reproducible numbers with
|
|
51
|
+
`JUICE_SHOP_IMAGE=bkimminich/juice-shop@sha256:...`.
|
|
52
|
+
|
|
53
|
+
[OWASP crAPI](https://github.com/OWASP/crAPI) needs its own Docker Compose
|
|
54
|
+
stack, so the audit scans it only if it is already running at `CRAPI_URL`
|
|
55
|
+
(default `http://127.0.0.1:8888`). Start it with the project's compose file
|
|
56
|
+
first.
|
|
57
|
+
|
|
58
|
+
Both are skipped by default. Neither has labelled ground truth yet, so the
|
|
59
|
+
report lists their finding counts and shows precision and recall as
|
|
60
|
+
"unlabelled" until they are labelled in `bench/ground-truth.json` (see
|
|
61
|
+
[bench/README.md](../bench/README.md#optional-targets)).
|
|
62
|
+
|
|
63
|
+
## When a gate fails
|
|
64
|
+
|
|
65
|
+
- **Tests, smoke, offline, packaging, supply chain, versions:** fix the cause.
|
|
66
|
+
These describe what Penthera promises its users.
|
|
67
|
+
- **Benchmark:** if the change is intended (a new rule that trades a little
|
|
68
|
+
precision for recall, a relabelled target), run `npm run bench`, commit the
|
|
69
|
+
new `bench/RESULTS.md`, and say why in the PR.
|
|
70
|
+
- **Self-scan:** fix the finding. If it is a deliberate fixture, run
|
|
71
|
+
`--update-baseline` and replace the `TODO` with the reason, so the
|
|
72
|
+
acceptance is reviewed like any other change.
|
|
73
|
+
|
|
74
|
+
Thresholds (the 0.02 benchmark drop, the package size budget, the allow-list)
|
|
75
|
+
live at the top of [`scripts/audit/run.mjs`](../scripts/audit/run.mjs).
|
|
76
|
+
Change them in their own PR, never to make a release pass.
|
|
77
|
+
|
|
78
|
+
## The report
|
|
79
|
+
|
|
80
|
+
`audit/REPORT.md` has a pass/fail table with the numbers behind each gate,
|
|
81
|
+
the changes since the previous report (read from a data block at the end of
|
|
82
|
+
the previous file), and per-gate details. Commit it with each release so the
|
|
83
|
+
history of the gate lives in git.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Marketing automation
|
|
2
|
+
|
|
3
|
+
Penthera is maintained by one person with no budget, so the routine parts of
|
|
4
|
+
getting the word out run by themselves. The rule is simple: **automation drafts
|
|
5
|
+
and collects, a person posts.**
|
|
6
|
+
|
|
7
|
+
- No social-media API tokens and no auto-posting. Every post is copied and
|
|
8
|
+
pasted by a person.
|
|
9
|
+
- No analytics, tracking pixels or telemetry. The numbers come from public
|
|
10
|
+
pages and APIs that anyone can read.
|
|
11
|
+
- Every workflow is SHA-pinned and has least-privilege permissions. Workflows
|
|
12
|
+
that write only open issues (`issues: write`). Only the demo-media workflow
|
|
13
|
+
pushes, and only to a `media/*` branch for a PR.
|
|
14
|
+
- Nothing can embarrass the project without a person in the loop: drafts land
|
|
15
|
+
in issues, job summaries, a collapsed release section or a PR.
|
|
16
|
+
|
|
17
|
+
## What runs
|
|
18
|
+
|
|
19
|
+
| Automation | When | Output | Workflow / script |
|
|
20
|
+
|------------|------|--------|-------------------|
|
|
21
|
+
| Release post drafts | Every `v*` tag | LinkedIn (~600 chars) and X/Bluesky (≤280) drafts in the job summary and under "Share this release" in the GitHub Release (the read-only publish job drafts; the release job, which has no checkout, only attaches them) | `publish.yml` → `scripts/marketing/release-post.mjs` |
|
|
22
|
+
| Incident radar | Daily 06:00 UTC | One issue per new malware advisory for a popular npm package (max 5 per run); every other new advisory as a comment on one standing batch issue | `incident-radar.yml` → `scripts/marketing/incident-radar.mjs` |
|
|
23
|
+
| Product-led footer | Every report | One line at the end of Markdown reports, audience reports, the PR comment and the evidence bundle README | `src/report/footer.js` |
|
|
24
|
+
| Demo media | Every published release, on demand | A PR with re-rendered GIFs from `assets/tapes/*.tape` | `demo-media.yml` |
|
|
25
|
+
| Monthly stats | 1st of the month, 07:00 UTC | Issue "Penthera this month: <Month YYYY>" with a table, month-over-month changes and a draft post | `monthly-stats.yml` → `scripts/marketing/monthly-stats.mjs` |
|
|
26
|
+
| Contributor flywheel | When an issue closes | If fewer than 3 `good first issue` issues are open: one reminder issue listing `TODO(good-first-issue)` markers | `contributor-flywheel.yml` → `scripts/marketing/contributor-flywheel.mjs` |
|
|
27
|
+
|
|
28
|
+
### Release post drafts
|
|
29
|
+
|
|
30
|
+
Built from the CHANGELOG section for the tag: the first sentence of the
|
|
31
|
+
summary paragraph, then three bullets (bold-titled entries first, Added before
|
|
32
|
+
Fixed), then the npm and GitHub release links. The X/Bluesky post is counted
|
|
33
|
+
the way Bluesky counts (every character), which also fits X. Run it locally:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
node scripts/marketing/release-post.mjs --tag v1.4.0
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The npm release is staged, not live, until a maintainer approves it. Post after
|
|
40
|
+
approving.
|
|
41
|
+
|
|
42
|
+
### Incident radar
|
|
43
|
+
|
|
44
|
+
Reads `/advisories?type=malware&ecosystem=npm` and `ecosystem=pip` from the
|
|
45
|
+
public GitHub Advisory Database, published in the last 48 hours. About 20
|
|
46
|
+
malware advisories appear every day and most are typosquats nobody installs,
|
|
47
|
+
so by default only advisories for packages in `lib/deps/popular-packages.json`
|
|
48
|
+
get their own issue (with the checklist and a draft "check if you're affected
|
|
49
|
+
with one command" post). The rest go into one standing issue, "Incident radar:
|
|
50
|
+
other new malware advisories", one comment per run. Set `mode: all` on a manual
|
|
51
|
+
run to open one issue per advisory instead (still at most 5).
|
|
52
|
+
|
|
53
|
+
State is the issues themselves: an advisory counts as seen when its GHSA id is
|
|
54
|
+
in an `incident-radar` issue or a radar batch comment from the look-back
|
|
55
|
+
window. There is no state file or branch.
|
|
56
|
+
|
|
57
|
+
Each issue's checklist: add the IOCs to a rule pack in `lib/rules/` (or the
|
|
58
|
+
deps rules until rule packs exist), verify `penthera deps` flags a fixture
|
|
59
|
+
lockfile with the package, then post. Do not post before detection is
|
|
60
|
+
verified.
|
|
61
|
+
|
|
62
|
+
### Product-led footer
|
|
63
|
+
|
|
64
|
+
Human-readable Markdown ends with:
|
|
65
|
+
|
|
66
|
+
> Checked by Penthera, open-source security for the apps and AI systems you ship · https://github.com/danoszz/penthera
|
|
67
|
+
|
|
68
|
+
It is never added to SARIF or JSON. Users turn it off with `--no-footer` (CLI)
|
|
69
|
+
or `footer: "false"` (GitHub Action).
|
|
70
|
+
|
|
71
|
+
### Demo media
|
|
72
|
+
|
|
73
|
+
Tapes live in `assets/tapes/` (see its README). The workflow installs
|
|
74
|
+
[VHS](https://github.com/charmbracelet/vhs) with `charmbracelet/vhs-action`,
|
|
75
|
+
renders every tape, uploads the GIFs as a workflow artifact and, unless it is a
|
|
76
|
+
dry run, opens a PR from a `media/*` branch. It needs **Settings → Actions →
|
|
77
|
+
General → Allow GitHub Actions to create and approve pull requests**. PRs
|
|
78
|
+
opened by `GITHUB_TOKEN` do not trigger CI; close and reopen the PR to run it.
|
|
79
|
+
|
|
80
|
+
### Monthly stats
|
|
81
|
+
|
|
82
|
+
Public sources only:
|
|
83
|
+
|
|
84
|
+
- npm downloads for the calendar month (`api.npmjs.org`);
|
|
85
|
+
- stars and forks at run time and PRs merged that month (GitHub API);
|
|
86
|
+
- installs from the public skills.sh skill page;
|
|
87
|
+
- dependent repositories from GitHub's dependents page.
|
|
88
|
+
|
|
89
|
+
Each page is parsed strictly: if the markup changed, the number shows as
|
|
90
|
+
`n/a`, never a guess. The issue keeps its numbers in a hidden JSON comment,
|
|
91
|
+
and next month's issue reads them back for the change column.
|
|
92
|
+
|
|
93
|
+
### Contributor flywheel
|
|
94
|
+
|
|
95
|
+
Write `TODO(good-first-issue): <what to do>` in a comment next to a small,
|
|
96
|
+
well-scoped change. When the open `good first issue` count drops below 3, the
|
|
97
|
+
reminder issue lists these markers with links. Turn one or two into issues,
|
|
98
|
+
then close the reminder. Closing the reminder itself never triggers a new one.
|
|
99
|
+
|
|
100
|
+
## Dry runs
|
|
101
|
+
|
|
102
|
+
Every scheduled workflow has a `workflow_dispatch` trigger whose `dry-run`
|
|
103
|
+
input defaults to **on**: it prints what it would open to the job summary and
|
|
104
|
+
writes nothing.
|
|
105
|
+
|
|
106
|
+
| Workflow | Dry run |
|
|
107
|
+
|----------|---------|
|
|
108
|
+
| Incident radar | Actions → Incident radar → Run workflow (`dry-run` checked; optional `mode`, `since-hours`) |
|
|
109
|
+
| Monthly stats | Actions → Monthly stats → Run workflow (`dry-run` checked; optional `month`, e.g. `2026-09`) |
|
|
110
|
+
| Contributor flywheel | Actions → Contributor flywheel → Run workflow (`dry-run` checked) |
|
|
111
|
+
| Demo media | Actions → Demo media → Run workflow (`dry-run` checked: GIFs as an artifact, no PR) |
|
|
112
|
+
| Release posts | `node scripts/marketing/release-post.mjs --tag vX.Y.Z` locally |
|
|
113
|
+
|
|
114
|
+
From a terminal with the GitHub CLI:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
gh workflow run incident-radar.yml -f dry-run=true
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The scripts also run locally with `--dry-run` (they use your `gh` login for
|
|
121
|
+
reads only):
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
node scripts/marketing/incident-radar.mjs --repo danoszz/penthera --dry-run
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Turning things off
|
|
128
|
+
|
|
129
|
+
- **One workflow:** Actions → the workflow → "…" → Disable workflow. Or delete
|
|
130
|
+
its file.
|
|
131
|
+
- **Release drafts:** remove the "Draft release posts" and "Keep the drafts" steps (publish job) and the two share steps (release job) from `publish.yml`.
|
|
132
|
+
- **Footer, for one run:** `--no-footer` or `footer: "false"` in the Action.
|
|
133
|
+
To drop it for everyone, make `footerLines()` in `src/report/footer.js`
|
|
134
|
+
return `[]`.
|
|
135
|
+
- **Labels** (`incident-radar`, `monthly-stats`, `contributor-flywheel`) are
|
|
136
|
+
created on the first real run and can be deleted afterwards.
|
|
137
|
+
|
|
138
|
+
## The weekly 30-minute routine
|
|
139
|
+
|
|
140
|
+
1. **Incident radar (10 min).** Open the `incident-radar` issues. For a
|
|
141
|
+
popular package: add the IOCs, check `penthera deps` flags a fixture, post
|
|
142
|
+
the draft (LinkedIn, X, Bluesky), close the issue. Skim the batch issue for
|
|
143
|
+
names you recognise, then close it if it is long.
|
|
144
|
+
2. **A clip (5 min).** If a demo-media PR is open, watch the GIFs, merge, and
|
|
145
|
+
post the best one as a short clip (Instagram, LinkedIn, X).
|
|
146
|
+
3. **Contributors (5 min).** If the flywheel reminder is open, turn one or two
|
|
147
|
+
markers into `good first issue` issues. Reply to new contributors.
|
|
148
|
+
4. **Release (5 min, when there is one).** Approve the staged npm package, then
|
|
149
|
+
copy the "Share this release" drafts from the GitHub Release, edit, post.
|
|
150
|
+
5. **Monthly (5 min, first week).** Fill in "what shipped" in the stats issue,
|
|
151
|
+
post it, close it.
|
package/docs/plan.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
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 | Tagged with the pre-commit hook, license policy, CRA helper and AI-BOM; held back from npm and shipped inside 1.6.0 |
|
|
31
|
+
| v1.6.0 | Hardening and the rest of AI apps | Released with the 360 security review fixes, Python `deps`, the internal audit gate and the dashboard. Signed rule packs follow once the maintainer's key is set up |
|
|
32
|
+
| v1.7.0 | Before you deploy | `penthera ship` and preview-deployment scanning work on a real Vercel and Netlify project |
|
|
33
|
+
| v1.8.0 | After you ship | `penthera watch` runs weekly on three real production apps for a month without a false alarm anyone complained about |
|
|
34
|
+
| v2.0.0 | Stable | Public interfaces frozen (CLI flags, JSON report schema, policy file, rule-pack format), documented, and covered by compatibility tests |
|
|
35
|
+
|
|
36
|
+
## What "perfect" means, concretely
|
|
37
|
+
|
|
38
|
+
Quality is measured, not felt. Each item below has a gate that the internal
|
|
39
|
+
audit (`npm run audit:internal`) or CI checks automatically.
|
|
40
|
+
|
|
41
|
+
### 1. Accuracy
|
|
42
|
+
- Benchmark precision and recall never drop between releases (the audit fails
|
|
43
|
+
on a drop of more than 0.02).
|
|
44
|
+
- Expand the benchmark beyond the mock server: OWASP Juice Shop, crAPI, a
|
|
45
|
+
deliberately vulnerable Next.js app, and fixture sets for `agents` and `deps`.
|
|
46
|
+
- Every false positive reported by a user becomes a regression test before it
|
|
47
|
+
is fixed.
|
|
48
|
+
|
|
49
|
+
### 2. First-run experience
|
|
50
|
+
- From `npx penthera` to a useful result in under two minutes on a fresh Next.js
|
|
51
|
+
or Express app, measured on a clean machine each release.
|
|
52
|
+
- Every error message says what happened and what to do next. No stack traces
|
|
53
|
+
for expected failures.
|
|
54
|
+
- Works on macOS, Linux, and Windows (add a Windows CI job; the symlinked Cursor
|
|
55
|
+
skill and path handling are the known risks).
|
|
56
|
+
|
|
57
|
+
### 3. Documentation
|
|
58
|
+
- The README stays a front page: problem, what it does, install, trust. Feature
|
|
59
|
+
detail moves into a docs site (`docs/`), one page per command, each with a
|
|
60
|
+
copy-paste example and its real output.
|
|
61
|
+
- Every command's `--help` and its docs page are generated from or tested
|
|
62
|
+
against the same source, so they never drift.
|
|
63
|
+
|
|
64
|
+
### 4. Stability of interfaces
|
|
65
|
+
- A versioned JSON schema for reports, the policy file, and rule packs, checked
|
|
66
|
+
in CI.
|
|
67
|
+
- A deprecation policy: one minor release of warnings before anything breaks.
|
|
68
|
+
|
|
69
|
+
### 5. Security of Penthera itself
|
|
70
|
+
- Self-scan clean on every release (`--repo`, `agents`, `deps`, the dashboard).
|
|
71
|
+
- Anything that parses untrusted input (packages, configs, scan responses, rule
|
|
72
|
+
packs) has a fuzz or hostile-input test. The ReDoS fixed in `deps` is the
|
|
73
|
+
reason why.
|
|
74
|
+
- An external review before v2.0: ask an academic security group or a volunteer
|
|
75
|
+
from the OWASP community to look at the fix loop, the install firewall, and
|
|
76
|
+
the dashboard, the three parts that write files or run commands.
|
|
77
|
+
|
|
78
|
+
### 6. Real-world proof
|
|
79
|
+
- Three pilot projects (friendly teams, a student project, one of the
|
|
80
|
+
maintainer's own) run Penthera before each deploy for a month. Their feedback
|
|
81
|
+
sets the priorities for the next release.
|
|
82
|
+
- Publish what Penthera found and fixed, with permission, as short case studies.
|
|
83
|
+
|
|
84
|
+
## Working rhythm
|
|
85
|
+
|
|
86
|
+
- One release every two to three weeks, each with a single theme.
|
|
87
|
+
- The internal audit runs weekly and before every tag; a red audit blocks the
|
|
88
|
+
release.
|
|
89
|
+
- Issues labelled `good first issue` are kept stocked (three to five open at a
|
|
90
|
+
time), so contributors can help without needing the maintainer's time.
|
|
91
|
+
- Anything that needs judgement (a new probe that sends payloads, anything
|
|
92
|
+
touching third parties) gets a short design note in `docs/` before code.
|
|
93
|
+
|
|
94
|
+
## Explicitly not doing
|
|
95
|
+
|
|
96
|
+
- A hosted service, telemetry, or an update channel that fetches rules at run
|
|
97
|
+
time.
|
|
98
|
+
- Offensive tooling for targets the user does not own, and attack-payload
|
|
99
|
+
generation for AI systems.
|
|
100
|
+
- An LLM as the judge of what is malicious. Models may help write fixes,
|
|
101
|
+
locally, and always labelled as such.
|
package/docs/privacy.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Privacy and network use
|
|
2
|
+
|
|
3
|
+
Penthera has no telemetry. It does not collect, store, or transmit any usage data, analytics, or personal information to the author or any third party. It does not phone home.
|
|
4
|
+
|
|
5
|
+
- **Scan traffic goes to the target you specify.** Auth tokens passed via `--auth-bearer`, `--auth-cookie`, or `PENTHERA_*` env vars are sent only to that target.
|
|
6
|
+
- **Every request identifies itself** as `penthera/<version> (+https://github.com/danoszz/penthera)`, so a target's operator can tell where the traffic comes from. Override it with `--user-agent`.
|
|
7
|
+
- **Reports stay local.** Output is written to `reports/` (gitignored) on your machine.
|
|
8
|
+
- **Every scan lists the third-party hosts it contacted** besides the target, in the terminal summary and in the report's "Network egress" section.
|
|
9
|
+
|
|
10
|
+
Without `--offline`, a scan may contact these third parties:
|
|
11
|
+
|
|
12
|
+
| Host | When | What it learns |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| `raw.githubusercontent.com`, `cdn.jsdelivr.net` | Retire.js check (standard and deep profiles) | That someone downloaded the public database. Nothing about your target. |
|
|
15
|
+
| `crt.sh`, `web.archive.org`, `otx.alienvault.com` | `--recon` only | The target domain. |
|
|
16
|
+
| CDN hosts serving your page's `<script src>` | Retire.js check, when a script's version can only be read from its contents | That someone fetched that public script. |
|
|
17
|
+
|
|
18
|
+
### Fully local: `--offline`
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
penthera http://localhost:3000 --offline
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
With `--offline`, the HTTP client refuses every host except the scan target. A redirect may only move to a host on the target's registrable domain (`example.com` to `www.example.com`). In practice:
|
|
25
|
+
|
|
26
|
+
- **Recon is skipped.** No queries go to crt.sh, the Wayback Machine or OTX.
|
|
27
|
+
- **The Retire.js database comes from the snapshot vendored in the package** (`lib/data/retire-jsrepository.json.gz`), refreshed at each release by `scripts/refresh-retire-db.mjs`. The source URL, fetch date and SHA-256 sit next to it in `retire-jsrepository.meta.json`, so you can check it against upstream. Online scans fall back to the snapshot too when the live download fails.
|
|
28
|
+
- **Third-party scripts are not fetched.** A refused request is listed in the report as an inconclusive probe, not passed over silently.
|
|
29
|
+
- **`--suppliers` is refused**, because it rates third-party domains.
|
|
30
|
+
- **DNS:** reaching a remote target needs DNS. For non-local targets, the email-authentication check also looks up the target domain's SPF/DMARC/MX records through your system resolver. A `localhost` or IP target needs no DNS at all.
|
|
31
|
+
|
|
32
|
+
Penthera never fetches rules or code at runtime: templates, wordlists and the vulnerability database ship in the package.
|
|
33
|
+
|
|
34
|
+
### Inconclusive probes
|
|
35
|
+
|
|
36
|
+
When a probe's requests time out, fail, or are refused by `--offline`, the scan records `{ probe, url, message }` in `result.errors`. The terminal summary shows "N probes inconclusive", and the Markdown report lists each one. A probe that could not run is not a clean result.
|
|
37
|
+
|
|
38
|
+
You are responsible for ensuring you are authorized to scan any target. See [Ethical use](../README.md#ethical-use--authorization).
|
package/docs/references.bib
CHANGED
|
@@ -537,3 +537,13 @@
|
|
|
537
537
|
urldate = {2026-10-19},
|
|
538
538
|
note = {standards}
|
|
539
539
|
}
|
|
540
|
+
|
|
541
|
+
@misc{spdxlicenselist,
|
|
542
|
+
shorthand = {SPDX License List},
|
|
543
|
+
author = {{SPDX Workgroup, The Linux Foundation}},
|
|
544
|
+
title = {{SPDX} License List},
|
|
545
|
+
howpublished = {Standardized short identifiers for open source licenses, used by package manifests and SBOMs},
|
|
546
|
+
url = {https://spdx.org/licenses/},
|
|
547
|
+
urldate = {2026-10-19},
|
|
548
|
+
note = {standards}
|
|
549
|
+
}
|
package/docs/references.md
CHANGED
|
@@ -75,3 +75,4 @@ Every entry was checked against the publisher or standards body (Crossref DOI me
|
|
|
75
75
|
- `oasis2023sarif`: Fanning, Michael C.; Golding, Laurence J. (2023). *Static Analysis Results Interchange Format (SARIF) Version 2.1.0 Plus Errata 01*. OASIS Standard incorporating Approved Errata, 28 August 2023. <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html>
|
|
76
76
|
- `eu2022nis2`: European Parliament; Council of the European Union (2022). *Directive (EU) 2022/2555 of the European Parliament and of the Council of 14 December 2022 on measures for a high common level of cybersecurity across the Union, amending Regulation (EU) No 910/2014 and Directive (EU) 2018/1972, and repealing Directive (EU) 2016/1148 (NIS 2 Directive)*. Official Journal of the European Union, L 333, 27.12.2022, p. 80. <https://eur-lex.europa.eu/eli/dir/2022/2555/oj>
|
|
77
77
|
- `mitrecwe`: The MITRE Corporation. *CWE: Common Weakness Enumeration*. Community-developed list of software and hardware weakness types, sponsored by CISA. <https://cwe.mitre.org/>
|
|
78
|
+
- `spdxlicenselist`: SPDX Workgroup, The Linux Foundation. *SPDX License List*. Standardized short identifiers for open source licenses, used by package manifests and SBOMs. <https://spdx.org/licenses/>
|