penthera 1.7.0 → 1.8.0-rc.1
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 +238 -0
- package/README.md +112 -74
- package/SECURITY.md +8 -6
- package/docs/examples/pr-dependency-review.yml +1 -1
- package/lib/adaptive-probes.js +3 -3
- package/lib/agents/hidden-content.js +122 -11
- package/lib/agents/index.js +26 -6
- package/lib/agents/lock.js +33 -5
- package/lib/agents/rules.js +33 -4
- package/lib/crawler.js +4 -15
- package/lib/deps/capabilities.js +21 -5
- package/lib/deps/index.js +46 -7
- package/lib/deps/iocs.js +74 -0
- package/lib/deps/lockfile.js +3 -8
- package/lib/deps/online.js +8 -3
- package/lib/deps/popular-packages.json.gz +0 -0
- package/lib/deps/popular-pypi.json.gz +0 -0
- package/lib/deps/python/capabilities.js +16 -3
- package/lib/deps/python/index.js +5 -1
- package/lib/deps/python/lockfiles.js +11 -5
- package/lib/deps/python/source.js +75 -26
- package/lib/deps/python/toml.js +15 -3
- package/lib/deps/source.js +28 -9
- package/lib/deps/typosquat.js +30 -5
- package/lib/fix/apply.js +253 -34
- package/lib/fix/index.js +7 -4
- package/lib/fix/model.js +25 -5
- package/lib/fix/util.js +6 -2
- package/lib/fuzzer.js +3 -3
- package/lib/git.js +65 -0
- package/lib/hook/index.js +157 -73
- package/lib/hook/policy.js +10 -4
- package/lib/install/index.js +4 -7
- package/lib/machine.js +0 -13
- package/lib/memory/store.js +11 -4
- package/lib/nuclei-loader.js +0 -25
- package/lib/plugins.js +1 -1
- package/lib/recon.js +12 -31
- package/lib/references.json +44 -0
- package/lib/retirejs.js +4 -4
- package/lib/rules/allowed_signers +13 -0
- package/lib/rules/core/agent-exec.json +29 -0
- package/lib/rules/core/agent-instructions.json +17 -0
- package/lib/rules/core/pack.json +30 -0
- package/lib/rules/core/pack.json.sig +6 -0
- package/lib/rules/core/secrets.json +161 -0
- package/lib/rules/core/templates.json +52 -0
- package/lib/rules/index.js +107 -0
- package/lib/rules/ioc-2025-2026/files.json +43 -0
- package/lib/rules/ioc-2025-2026/hosts.json +35 -0
- package/lib/rules/ioc-2025-2026/pack.json +25 -0
- package/lib/rules/ioc-2025-2026/pack.json.sig +6 -0
- package/lib/rules/ioc-2025-2026/packages.json +1048 -0
- package/lib/rules/pack.js +362 -0
- package/lib/rules/regex-guard.js +81 -9
- package/lib/rules/sshsig.js +205 -0
- package/lib/templates.js +24 -20
- package/lib/tmp.js +21 -0
- package/lib/whitebox/frameworks.js +1 -1
- package/lib/whitebox/secrets.js +117 -41
- package/package.json +11 -4
- package/scripts/rules/build-pack.mjs +80 -0
- package/scripts/rules/check-bundled.mjs +30 -0
- package/scripts/rules/sign-pack.sh +32 -0
- package/skills/penthera/SKILL.md +15 -15
- package/src/agent/git.js +147 -14
- package/src/agent/loop.js +165 -46
- package/src/cli/agent.js +11 -5
- package/src/cli/deps.js +0 -2
- package/src/cli/detect.js +14 -7
- package/src/cli/fix.js +26 -8
- package/src/cli/hook.js +2 -1
- package/src/cli/onboarding.js +14 -6
- package/src/cli/report-outputs.js +1 -0
- package/src/cli/rules.js +144 -0
- package/src/cli/ui.js +43 -10
- package/src/cli.js +23 -0
- package/src/report/evidence.js +105 -21
- package/src/report/md-escape.js +8 -0
- package/src/scan-url.js +2 -2
- package/src/ui/api/data.js +39 -6
- package/src/ui/api/routes.js +44 -11
- package/src/ui/api/runs.js +32 -7
- package/src/ui/security.js +20 -23
- package/src/ui/server.js +52 -27
- package/src/ui/static/app.css +583 -213
- package/src/ui/static/favicon.svg +1 -1
- package/src/ui/static/fonts/DepartureMono-LICENSE.txt +93 -0
- package/src/ui/static/fonts/DepartureMono-Regular.woff2 +0 -0
- package/src/ui/static/index.html +5 -4
- package/src/ui/static/js/api.js +114 -6
- package/src/ui/static/js/app.js +81 -42
- package/src/ui/static/js/center.js +246 -133
- package/src/ui/static/js/charts.js +99 -18
- package/src/ui/static/js/copy.js +67 -8
- package/src/ui/static/js/dialog.js +152 -0
- package/src/ui/static/js/left.js +23 -60
- package/src/ui/static/js/menubar.js +170 -0
- package/src/ui/static/js/onboarding.js +104 -0
- package/src/ui/static/js/panels.js +186 -0
- package/src/ui/static/js/right.js +69 -188
- package/src/ui/static/js/state.js +36 -5
- package/src/ui/static/logo.svg +10 -0
- package/src/ui/static/mark.svg +3 -0
- package/src/utils/http.js +162 -13
- package/docs/METHODOLOGY.md +0 -375
- package/docs/commands/README.md +0 -24
- package/docs/commands/action.md +0 -21
- package/docs/commands/agents.md +0 -38
- package/docs/commands/deps.md +0 -115
- package/docs/commands/fix.md +0 -53
- package/docs/commands/hook.md +0 -69
- package/docs/commands/init.md +0 -114
- package/docs/commands/install.md +0 -53
- package/docs/commands/scan.md +0 -100
- package/docs/commands/skill.md +0 -58
- package/docs/compare/README.md +0 -25
- package/docs/compare/github.md +0 -60
- package/docs/compare/semgrep.md +0 -54
- package/docs/compare/snyk.md +0 -62
- package/docs/compare/socket.md +0 -78
- package/docs/compare/zap.md +0 -59
- package/docs/compliance-cra.md +0 -141
- package/docs/compliance-nis2.md +0 -168
- package/docs/dashboard.md +0 -187
- package/docs/evidence.md +0 -162
- package/docs/funding/due-diligence-checklist.md +0 -79
- package/docs/funding/nlnet-ngi-zero-draft.md +0 -273
- package/docs/funding/partner-one-pager.md +0 -84
- package/docs/funding/sponsor-tiers.md +0 -81
- package/docs/index.md +0 -81
- package/docs/internal-audit.md +0 -83
- package/docs/local-loop.md +0 -276
- package/docs/marketing.md +0 -151
- package/docs/owasp-wstg-coverage.md +0 -79
- package/docs/plan.md +0 -102
- package/docs/privacy.md +0 -38
- package/docs/references.bib +0 -559
- package/docs/references.md +0 -119
- package/docs/release-plan.md +0 -228
- package/docs/research/socket-positioning-notes.md +0 -183
- package/docs/use-cases/README.md +0 -14
- package/docs/use-cases/agencies.md +0 -71
- package/docs/use-cases/ai-coding-teams.md +0 -81
- package/docs/use-cases/eu-sme-compliance.md +0 -68
- package/docs/use-cases/open-source-maintainers.md +0 -75
- package/docs/use-cases/solo-founder.md +0 -65
- package/lib/deps/popular-packages.json +0 -1
- package/lib/deps/popular-pypi.json +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,212 @@ All notable changes to Penthera are documented here. Versioning follows [SemVer]
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.8.0-rc.1] - 2026-10-20
|
|
8
|
+
|
|
9
|
+
Release candidate for 1.8.0, published on npm's `next` dist-tag
|
|
10
|
+
(`npm install penthera@next`). It becomes 1.8.0 on `latest` after it has run
|
|
11
|
+
on real projects without a blocking issue.
|
|
12
|
+
|
|
13
|
+
### Security
|
|
14
|
+
|
|
15
|
+
Fixes from a second independent review. Most also apply to 1.7.0; upgrading
|
|
16
|
+
is recommended.
|
|
17
|
+
|
|
18
|
+
- **Dependency scans fail closed.** An npm or Python package that could not be
|
|
19
|
+
read in full (a code file over 64 MB, more files than the reader takes, an
|
|
20
|
+
archive with inconsistent sizes or other structural anomalies, a matched
|
|
21
|
+
artifact that cannot be opened) is now `unscanned`: a high "could not be
|
|
22
|
+
scanned" finding that `penthera install` always holds for approval. The
|
|
23
|
+
Python scanner gained this state. Wheels are capped on the bytes actually
|
|
24
|
+
inflated. Pax global headers that set a size or path are refused. Deeply
|
|
25
|
+
nested `uv.lock`/`poetry.lock` files are a parse error, not a crash.
|
|
26
|
+
- **The fix loop and `fix --apply` run git hardened.** Commit and tag signing,
|
|
27
|
+
signing programs, attribute files and every configured filter driver are
|
|
28
|
+
overridden on every call (`--attr-source` on git 2.40+). Pathspecs are
|
|
29
|
+
literal. Writes are refused when the repository's own config sets
|
|
30
|
+
`filter.*`, `gpg.*` or `include.*`, or on git older than 2.31; the loop then
|
|
31
|
+
does not start, and its one message names the key and how to go on.
|
|
32
|
+
- **The fix loop keeps your uncommitted work.** Dirty files are read with
|
|
33
|
+
`status -z` and re-checked right before every write and commit. Reverts
|
|
34
|
+
restore the exact bytes from before the fix, and only while the file still
|
|
35
|
+
holds what penthera wrote (otherwise the fix is a `conflict` and the file is
|
|
36
|
+
left alone). Commits use `--only`. Plans that touch untracked or ignored
|
|
37
|
+
files are skipped, and every revert step runs even when one fails.
|
|
38
|
+
- **`applyDiff` is all-or-nothing and writes only regular files in the repo.**
|
|
39
|
+
Duplicate and nested paths are refused, a failed apply puts back what it
|
|
40
|
+
wrote, files are replaced through a temp file (never written through a hard
|
|
41
|
+
link), NTFS stream names and 8.3 aliases are refused, and added lines may not
|
|
42
|
+
contain control characters. Approval prompts show control characters as
|
|
43
|
+
escapes.
|
|
44
|
+
- **The pre-commit hook checks everything it should.** Staged content is read
|
|
45
|
+
by object id from NUL-separated output, type changes are included, files
|
|
46
|
+
with NUL bytes are scanned, and a staged file over 16 MB blocks the commit
|
|
47
|
+
(or warns, with `"largeFiles": "warn"`). `hook install` no longer writes
|
|
48
|
+
through a symlinked `pre-commit`.
|
|
49
|
+
- **Secret scanning checks every match.** A placeholder no longer hides a real
|
|
50
|
+
secret of the same rule later in the file. Only regular files are read, with
|
|
51
|
+
a per-file size cap.
|
|
52
|
+
- **Dashboard sessions are bound to the page.** The one-time token travels in
|
|
53
|
+
the URL fragment and is exchanged for a per-tab session sent as an
|
|
54
|
+
`Authorization` header; no cookie is set or accepted. Approvals carry the
|
|
55
|
+
sha256 of the diff shown, and a cut diff says so and needs an explicit
|
|
56
|
+
acknowledgement. A symlinked default `reports/` is refused and every write
|
|
57
|
+
re-checks the folder. Event ids keep counting past the buffer; requests,
|
|
58
|
+
bodies (413), finished runs and open streams are bounded. `--open` no
|
|
59
|
+
longer puts the token on a command line.
|
|
60
|
+
- **Evidence bundles** are built in a private folder and renamed into place,
|
|
61
|
+
and verification never follows a symlinked folder and caps what it reads.
|
|
62
|
+
- **HTTP responses are bounded.** Every body is read with a size cap (counted
|
|
63
|
+
after decompression) and a deadline that covers the body, not just the
|
|
64
|
+
headers. `--offline` compares host and port, and cross-origin redirects drop
|
|
65
|
+
credentials and do not replay request bodies.
|
|
66
|
+
- **Linear-time matching on untrusted text.** Agent-config exec patterns and
|
|
67
|
+
other agent and hook checks no longer backtrack on long input. The rule-pack
|
|
68
|
+
regex guard refuses ambiguous alternation and optional items inside repeated
|
|
69
|
+
groups and end-anchored rescans; community templates use the same guard.
|
|
70
|
+
- **Hostile repos no longer hang or crash scans**: a FIFO is never read, and
|
|
71
|
+
deeply nested JSON is handled; one unreadable agent config becomes a finding
|
|
72
|
+
instead of aborting the run.
|
|
73
|
+
- `--fix-model` never sends a file from outside the repo as context.
|
|
74
|
+
- Installing the hook through pre-commit.com no longer runs the test suite:
|
|
75
|
+
the `prepack` script is gone (publishing still runs `npm test`).
|
|
76
|
+
- Workflows that push or only read no longer keep credentials in the checkout.
|
|
77
|
+
- **The fix loop cannot game its own checker.** A fix diff, from a local
|
|
78
|
+
model or a recipe, may not edit tests, test runner config or the test
|
|
79
|
+
script, Penthera's policy, suppressions, baseline, lock or memory, CI
|
|
80
|
+
workflows, git hooks or lockfiles; `applyDiff` refuses it with `PROTECTED`
|
|
81
|
+
and says to make that change by hand. The loop verifies only against the
|
|
82
|
+
checker the run began with. "Could not fix" is now a recorded outcome
|
|
83
|
+
(`gave_up` in memory, `loopRun.gaveUp`, a summary line): the finding is not
|
|
84
|
+
retried in that run, and a give-up never counts against a recipe. Older
|
|
85
|
+
Penthera versions report a memory chain with `gave_up` events as malformed.
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
|
|
89
|
+
- Release checks in CI (`.github/workflows/install-matrix.yml`): the packed
|
|
90
|
+
tarball is installed into an empty project and run with `npx penthera` on
|
|
91
|
+
Linux, macOS and Windows with Node 22 and 24 (setup wizard dry run, deps,
|
|
92
|
+
agents, repo scan, the pre-commit hook on a real commit, the dashboard's
|
|
93
|
+
token check, bundled rule pack signatures). An upgrade job installs 1.7.0
|
|
94
|
+
from npm, writes memory and dashboard state, upgrades to the tarball and
|
|
95
|
+
checks the old memory chain still verifies and grows. All of it runs under a
|
|
96
|
+
network guard that fails on anything past loopback.
|
|
97
|
+
- Docs as tests: `scripts/docs-commands.mjs` checks every command in the
|
|
98
|
+
README, `docs/` and the skill against `--help` and runs the offline ones on a
|
|
99
|
+
fixture, in `npm test` and against the installed package.
|
|
100
|
+
- Provenance check in CI (`scripts/ci/provenance.mjs`): every commit in a pull
|
|
101
|
+
request that names an AI co-author must carry an `AI-Assisted` line, and the
|
|
102
|
+
other way round.
|
|
103
|
+
|
|
104
|
+
### Fixed
|
|
105
|
+
|
|
106
|
+
- Windows: `npx penthera` / `penthera init` in a git repository failed with
|
|
107
|
+
"Refusing to write outside the project" for its own `reports/` folder, because
|
|
108
|
+
git prints the repo root with forward slashes. Git roots are now native paths.
|
|
109
|
+
Found by the new Windows install checks.
|
|
110
|
+
- `penthera --help` lists `--rules-signers` and `--allow-unsigned-rules`. Both
|
|
111
|
+
worked and were documented in `docs/rule-packs.md`, but the help left them
|
|
112
|
+
out.
|
|
113
|
+
- `penthera ui`, Advanced mode: a run's transcript no longer shows a stray
|
|
114
|
+
"null" under its title.
|
|
115
|
+
|
|
116
|
+
### Changed
|
|
117
|
+
|
|
118
|
+
- **`penthera ui` redesigned for people who are not programmers.** A first
|
|
119
|
+
visit runs a three-screen setup (what Penthera does, Simple or Advanced, the
|
|
120
|
+
project and a first check). Simple mode, the default, leads every project
|
|
121
|
+
with a one-sentence verdict ("Not ready to ship: 1 critical problem to fix
|
|
122
|
+
first."), lists problems in plain words, explains each one as what is wrong,
|
|
123
|
+
how sure and what to do, and tells a run as one sentence. Advanced keeps
|
|
124
|
+
everything, now as project tabs (Findings, Dependencies, AI tooling, History,
|
|
125
|
+
Proof) with a severity by confidence triage matrix. A menu bar replaces the
|
|
126
|
+
prompt bar and carries every action with its shortcut and the run status;
|
|
127
|
+
runs start from a New scan window; every change waits in its own window
|
|
128
|
+
(`y`, `n`, `q`). New look: a 1-bit "System" register of tiled windows, dither
|
|
129
|
+
patterns instead of greys, `#0000ed` as the one accent, Departure Mono (OFL,
|
|
130
|
+
self-hosted) for chrome, the serif Penthera wordmark, and a halftone field
|
|
131
|
+
whose density is the open risk. Both themes pass WCAG AA in every state.
|
|
132
|
+
`scripts/dashboard-screenshots.mjs` regenerates the docs screenshots.
|
|
133
|
+
- Smaller npm package: 797 kB to 657 kB packed, 2.3 MB to 1.7 MB unpacked. The
|
|
134
|
+
package ships only `docs/examples/` (the documentation lives on GitHub, and
|
|
135
|
+
README links resolve there), and the popular-package lists for the typosquat
|
|
136
|
+
check are stored gzipped. No behaviour change.
|
|
137
|
+
- Code cleanup, no behaviour change: one git runner (`lib/git.js`), one
|
|
138
|
+
temp-folder helper, one Markdown table-cell escape and one JSON fetch
|
|
139
|
+
helper replace their copies; unused exports and helpers are gone. CI now
|
|
140
|
+
runs `npm run lint` (ESLint, dev-only, minimal rules). Still one runtime
|
|
141
|
+
dependency.
|
|
142
|
+
|
|
143
|
+
### Docs
|
|
144
|
+
|
|
145
|
+
- **Penthera is described as an open R&D project with a usable tool:** "an
|
|
146
|
+
open-source cyber security agent you can use today, and verify yourself."
|
|
147
|
+
The README, `docs/index.md`, `package.json`, the plugin and marketplace
|
|
148
|
+
manifests, the Agent Skill (trigger phrases kept), the GitHub Action and the
|
|
149
|
+
repository description carry the approved wording. The README has a "How it
|
|
150
|
+
is measured" section (method, evidence, tool) with Wilson intervals next to
|
|
151
|
+
the benchmark numbers, and stays under 14 KB.
|
|
152
|
+
- **One roadmap, one research plan.** `ROADMAP.md` now says what ships next,
|
|
153
|
+
starting with the 1.8.0 feature freeze. `docs/vision-2.0.md` is the research
|
|
154
|
+
plan and holds the quality gates from `docs/plan.md`. `docs/plan.md`,
|
|
155
|
+
`docs/release-plan.md` (v1.2 to v1.4) and the Socket positioning notes moved
|
|
156
|
+
to `docs/archive/`.
|
|
157
|
+
- **Every user-facing page names the question it answers**, for a starter ("is
|
|
158
|
+
my app safe to ship?") or a pro ("how do I put it in CI?"), and
|
|
159
|
+
`docs/index.md` has a Start here path from starter to pro.
|
|
160
|
+
- **Voice guide** (`docs/voice.md`): plain words, short sentences, numbers with
|
|
161
|
+
intervals over adjectives, limits next to claims, no hype. Every user-facing
|
|
162
|
+
page was edited against it.
|
|
163
|
+
- **Release cadence** in CONTRIBUTING: at most one minor release every 2 to 4
|
|
164
|
+
weeks, always through an `-rc.N` on npm's `next` tag, patches for fixes only,
|
|
165
|
+
and a feature freeze before each minor. Current freeze: 1.8.0 = main + #63.
|
|
166
|
+
- Fixed stale facts: SECURITY.md's supported versions (1.2.x to 1.7.x), README
|
|
167
|
+
anchors that no longer existed, Agent Skill links into `docs/` that broke in
|
|
168
|
+
the npm package since the docs stopped shipping there, and the partner brief
|
|
169
|
+
and due diligence notes that still called shipped features "in review".
|
|
170
|
+
- Limitations stated in full: the methodology's "Known limitations" now covers
|
|
171
|
+
every module (deps, AI tooling, fix loop), the evidence (small sample, wide
|
|
172
|
+
intervals, same-team benchmark, single labeller, uncalibrated confidence
|
|
173
|
+
labels), reproducibility, AI-assisted development, and compliance output. The
|
|
174
|
+
2.0 research program adds "Threats to validity".
|
|
175
|
+
- GenAI disclosure: `docs/genai.md` states how Penthera is built with
|
|
176
|
+
generative AI (models, scale, review gates, licensing, logs), the README has a
|
|
177
|
+
short "How Penthera is built" section (107 of 117 non-merge commits up to
|
|
178
|
+
v1.7.0 written with Claude), and CONTRIBUTING requires an
|
|
179
|
+
`AI-Assisted` trailer on AI-assisted commits. Follows NLnet's generative AI
|
|
180
|
+
policy.
|
|
181
|
+
- `docs/MANIFESTO.md`: what Penthera is today (and is not), seven design
|
|
182
|
+
decisions with their costs and what would change them, and an FAQ. Written to
|
|
183
|
+
be challenged in Discussions.
|
|
184
|
+
- **After the 2026 agent incidents:** `docs/research/agent-incidents-2026.md`
|
|
185
|
+
says what the OpenAI–Hugging Face and Anthropic evaluation incidents mean
|
|
186
|
+
for Penthera, with sources and caveats. `docs/agents-safely.md` is a
|
|
187
|
+
one-page checklist for running AI agents. `docs/research/spec-harness-rules.md`
|
|
188
|
+
specifies planned `harness-*`, `sandbox-*` and `scorer-*` rules (not built
|
|
189
|
+
yet), and the research plan adds E7 and a draft pre-registration for E8
|
|
190
|
+
(do harness guardrails reduce judge tampering?).
|
|
191
|
+
- README: a "Building in public" section linking the CHANGELOG, the freeze, the
|
|
192
|
+
research in progress and the roadmap. Counts refreshed (57 references);
|
|
193
|
+
`npm run docs:numbers` now also counts the secret patterns in the core rule
|
|
194
|
+
pack (20, as the README says, not 8).
|
|
195
|
+
- `docs/genai.md` covers commits since v1.7.0 and how the agents that build
|
|
196
|
+
Penthera are contained; the manifesto says the fix loop cannot edit what
|
|
197
|
+
judges it.
|
|
198
|
+
- `docs/vision-2.0.md`: the plan for 2.0, covering the biggest gaps, an AI roadmap
|
|
199
|
+
(models propose, deterministic checks decide; local models first) and a
|
|
200
|
+
pre-registered research program with sample sizes and safety rules.
|
|
201
|
+
- **An 80-second walkthrough video** (`assets/walkthrough.mp4`, with a poster
|
|
202
|
+
and `walkthrough.vtt` captions) shows `npx penthera`, an offline scan of the
|
|
203
|
+
mock server, `penthera ui --demo` in Simple and Advanced mode, one fix
|
|
204
|
+
approved and the owner report. It is linked from the README and
|
|
205
|
+
`docs/index.md`. `scripts/media/walkthrough.mjs` renders it from demo data
|
|
206
|
+
only, and the Demo media workflow re-renders it on every release.
|
|
207
|
+
- **The terminal GIFs are re-rendered** in the dashboard's black and white
|
|
208
|
+
look. `scan.tape` now writes the README hero (`assets/demo.gif`) directly
|
|
209
|
+
and scans with `--offline`. The `agents` and `deps --diff` tapes rendered a
|
|
210
|
+
"Cannot find module" error, because they ran `bin/penthera.js` by a
|
|
211
|
+
relative path after changing folder; they use an absolute path now.
|
|
212
|
+
|
|
7
213
|
## [1.7.0] - 2026-10-19
|
|
8
214
|
|
|
9
215
|
One view and one command. `npx penthera` now sets Penthera up in a project:
|
|
@@ -140,6 +346,38 @@ reproduced; each fix has a regression test that fails without it.
|
|
|
140
346
|
|
|
141
347
|
### Added
|
|
142
348
|
|
|
349
|
+
- **Signed rule packs** (`lib/rules/`, [docs/rule-packs.md](docs/rule-packs.md)):
|
|
350
|
+
detection data (secret formats, hidden-instruction and exec patterns, IOC
|
|
351
|
+
lists, typosquat targets, Nuclei-compatible templates) ships as versioned
|
|
352
|
+
directories. Each pack has a `pack.json` index of SHA-256 hashes, signed
|
|
353
|
+
with `ssh-keygen -Y sign -n penthera-rules`. Penthera verifies the signature
|
|
354
|
+
(in Node; ssh-keygen is not needed at run time), then every hash, the schema
|
|
355
|
+
and every regex, before it loads anything. Packs arrive bundled in a release,
|
|
356
|
+
or by path with `--rules <dir>` (plus `--rules-signers <file>` for your own
|
|
357
|
+
keys). Unsigned packs are refused unless `--allow-unsigned-rules`, which
|
|
358
|
+
warns loudly. Nothing is ever fetched
|
|
359
|
+
- `penthera rules list` and `penthera rules verify <dir>`; tooling in
|
|
360
|
+
`scripts/rules/` (`build-pack.mjs`, maintainer-only `sign-pack.sh`,
|
|
361
|
+
`check-bundled.mjs`). The publish workflow now refuses to stage a release
|
|
362
|
+
whose bundled packs are not signed by a key in `lib/rules/allowed_signers`
|
|
363
|
+
- ReDoS guard for data-supplied regexes (`lib/rules/regex-guard.js`): length
|
|
364
|
+
cap, no backreferences or lookaround, no nested quantifiers, no alternation
|
|
365
|
+
under unbounded repeats, no unbounded quantifier that every start position
|
|
366
|
+
can rescan, and a backtracking budget for overlapping quantifier chains.
|
|
367
|
+
Fuzz-tested: everything it accepts runs in milliseconds on adversarial
|
|
368
|
+
input. The v1.1.1 template guard moved here too, rewritten from a regex
|
|
369
|
+
(itself polynomial on hostile pattern strings, flagged by CodeQL) into a
|
|
370
|
+
linear scan that also catches deeper nesting
|
|
371
|
+
- Bundled `core` pack: 12 AI-provider key formats, the credential-path
|
|
372
|
+
instruction pattern, two exec patterns and the `.env` template moved out of
|
|
373
|
+
code. Behaviour is identical; a test compares against a snapshot of the old
|
|
374
|
+
built-ins
|
|
375
|
+
- Bundled `ioc-2025-2026` pack (**unsigned in this PR; the maintainer signs it
|
|
376
|
+
before release**): s1ngularity/nx versions (GHSA-cxm3-wv7p-598c), Shai-Hulud
|
|
377
|
+
(StepSecurity's list, exfil URL, `bundle.js` by hash), Shai-Hulud 2.0 (Wiz
|
|
378
|
+
Research's CSV, loader file names) and postmark-mcp 1.0.16 to 1.0.18 with its
|
|
379
|
+
exfil domain. `penthera deps` reports hits as critical `deps-ioc` findings
|
|
380
|
+
that cite the source, wherever the package sits in the tree, installed or not
|
|
143
381
|
- **Internal testing audit** (`npm run audit:internal`), the release gate:
|
|
144
382
|
tests, benchmark drift, a self-scan against `audit/baseline.json`, a CLI
|
|
145
383
|
smoke matrix, package contents and size, zero third-party egress offline,
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<br/><br/>
|
|
6
6
|
|
|
7
|
-
**Penthera:
|
|
7
|
+
**Penthera: an open-source cyber security agent you can use today, and verify yourself.**
|
|
8
8
|
|
|
9
9
|
<br/>
|
|
10
10
|
|
|
@@ -20,23 +20,33 @@
|
|
|
20
20
|
|
|
21
21
|
<br/><br/>
|
|
22
22
|
|
|
23
|
-
| `penthera ui`, dark | `penthera ui`, light |
|
|
23
|
+
| `penthera ui`, Simple, dark | `penthera ui`, Advanced, light |
|
|
24
24
|
|---|---|
|
|
25
|
-
|  |  |
|
|
26
|
+
|
|
27
|
+
<a href="assets/walkthrough.mp4"><img src="assets/walkthrough-poster.jpg" alt="Walkthrough video: setup, a scan, the dashboard in Simple and Advanced mode, one fix approved, the report" width="760"></a>
|
|
28
|
+
|
|
29
|
+
[Watch the 80-second walkthrough](assets/walkthrough.mp4): setup, a scan, the dashboard, a fix approved, the report. Demo data, no sound, captions in the video.
|
|
26
30
|
|
|
27
31
|
</div>
|
|
28
32
|
|
|
29
33
|
---
|
|
30
34
|
|
|
31
|
-
##
|
|
35
|
+
## What Penthera is
|
|
36
|
+
|
|
37
|
+
Teams ship apps and AI agents faster than anyone can check them, and most
|
|
38
|
+
security scanners offer no evidence of how well they actually work. Penthera is
|
|
39
|
+
an open-source R&D project that tackles both.
|
|
40
|
+
|
|
41
|
+
**Use it today.** A cyber security agent for your coding tools that runs
|
|
42
|
+
offline on your own machine: it checks your app, its dependencies and your AI
|
|
43
|
+
tooling, and fixes what it can with your approval, through a dashboard that
|
|
44
|
+
works for first-timers and pros alike.
|
|
32
45
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
servers, poisoned agent skills, and leaked AI provider keys. Penthera does the
|
|
38
|
-
repeatable security work on your own machine, with rules you can read, so the
|
|
39
|
-
people you have can spend their time on judgement.
|
|
46
|
+
**Trust it because it's measured.** Every check is tested in public,
|
|
47
|
+
pre-registered experiments, and the results are published, good or bad. What
|
|
48
|
+
holds up ships in the next release, so Penthera keeps pace with new attacks and
|
|
49
|
+
with EU rules such as NIS2 and the Cyber Resilience Act.
|
|
40
50
|
|
|
41
51
|
## Get started
|
|
42
52
|
|
|
@@ -101,7 +111,7 @@ Penthera needs Node.js 22 or later. To run it in a container:
|
|
|
101
111
|
| Job | What Penthera does | Command |
|
|
102
112
|
|---|---|---|
|
|
103
113
|
| Report and prove | Writes reports for developers, owners, and auditors; a hashed, signable evidence bundle; NIS2 and CRA readiness reports and drafts | [`penthera <url> --audience auditor --evidence out/`](docs/evidence.md) |
|
|
104
|
-
| See it all | A local dashboard on 127.0.0.1
|
|
114
|
+
| See it all | A local dashboard on 127.0.0.1 with a Simple mode for non-programmers: a one-sentence verdict, problems in plain words, a guided setup, and the fix loop with Apply, Skip, and Stop per change. Advanced adds dependencies, AI tooling, history and evidence | [`penthera ui`](docs/dashboard.md) |
|
|
105
115
|
| Keep watching | Scheduled checks of your production for drift: certificates, headers, new endpoints, keys in bundles, new CVEs in your SBOM | on the [roadmap](ROADMAP.md) |
|
|
106
116
|
|
|
107
117
|
Every finding carries a severity, a confidence label (`confirmed`, `likely`,
|
|
@@ -110,60 +120,69 @@ Reports come out as Markdown, JSON, and SARIF for GitHub code scanning. Exit
|
|
|
110
120
|
code `1` means critical or high findings. Every command and flag is in
|
|
111
121
|
[docs/commands/](docs/commands/README.md).
|
|
112
122
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
123
|
+
## How it is measured
|
|
124
|
+
|
|
125
|
+
Each feature goes through the same path: **method → evidence → tool.**
|
|
126
|
+
|
|
127
|
+
- **Method.** The [methodology](docs/METHODOLOGY.md) states the threat model,
|
|
128
|
+
what black-box (HTTP) and white-box (source) analysis can and cannot see, the
|
|
129
|
+
confidence model, and the false-positive controls on each probe. Each design
|
|
130
|
+
choice cites the research or standard it rests on: 57 verified references, in
|
|
131
|
+
APA style, in [docs/references.md](docs/references.md).
|
|
132
|
+
- **Evidence.** A benchmark runs on every release, and the release gate fails
|
|
133
|
+
if precision or recall drops by more than 0.02. Experiments that decide what
|
|
134
|
+
ships next are pre-registered in [docs/research/prereg/](docs/research/prereg/)
|
|
135
|
+
before any data is collected, and the results are published either way.
|
|
136
|
+
- **Tool.** What holds up ships in a reviewed, signed release. What does not
|
|
137
|
+
hold up is dropped, and the rules stay.
|
|
138
|
+
|
|
139
|
+
| Measure | Result today |
|
|
140
|
+
|---|---|
|
|
141
|
+
| Precision on the bundled benchmark | 0.86, 95% CI 0.67 to 0.95 (19 true positives, 3 false positives) |
|
|
142
|
+
| Recall on the bundled benchmark | 0.90, 95% CI 0.71 to 0.97 (2 misses, [each one explained](bench/RESULTS.md)) |
|
|
143
|
+
| Requests to non-loopback hosts during the benchmark | 0 |
|
|
144
|
+
| Detection rules, all readable | 14 URL scan templates, 20 secret and AI-key patterns, 15 dependency capabilities, 39 AI-tooling rules, 5 fix recipes |
|
|
145
|
+
|
|
146
|
+
These numbers come from 22 findings in small labs that we wrote ourselves, so
|
|
147
|
+
they catch regressions; they do not measure real-world accuracy. Measuring that
|
|
148
|
+
on real projects is experiment E1, the first step of the
|
|
149
|
+
[research plan](docs/vision-2.0.md). Intervals are Wilson. What Penthera cannot
|
|
150
|
+
see is listed under [Known limitations](docs/METHODOLOGY.md#known-limitations).
|
|
151
|
+
Counts as of 2026-10-20; refresh them with `npm run docs:numbers` and
|
|
152
|
+
`npm run bench`.
|
|
153
|
+
|
|
154
|
+
**Cite this repository** with the button on GitHub or
|
|
155
|
+
[CITATION.cff](CITATION.cff). APA and BibTeX forms are in the
|
|
156
|
+
[methodology](docs/METHODOLOGY.md#how-to-cite).
|
|
116
157
|
|
|
117
158
|
## Why you can trust it
|
|
118
159
|
|
|
119
160
|
- **Local and offline.** No account, no telemetry, no cloud. `--offline` limits
|
|
120
161
|
network traffic to the target you named, and every report lists the hosts it
|
|
121
|
-
contacted.
|
|
122
|
-
- **Deterministic.** Rules you can read, with `file:line` evidence.
|
|
123
|
-
|
|
162
|
+
contacted ([network use](docs/privacy.md)).
|
|
163
|
+
- **Deterministic.** Rules you can read, with `file:line` evidence. A local
|
|
164
|
+
model may propose a fix; a deterministic re-scan and your tests decide.
|
|
124
165
|
- **Non-destructive by default.** The default scan sends only requests a
|
|
125
166
|
browser would send. Payload testing (`--deep`, `--fuzz`, `--all`) is opt-in
|
|
126
167
|
and asks for confirmation.
|
|
127
|
-
- **
|
|
128
|
-
before every change by default, and never pushes or opens pull requests.
|
|
168
|
+
- **Asks before it changes anything.** The fix loop works on a local branch,
|
|
169
|
+
asks before every change by default, and never pushes or opens pull requests.
|
|
129
170
|
- **No update channel to hijack.** Detection rules ship inside reviewed
|
|
130
|
-
releases
|
|
171
|
+
releases as signed, versioned [rule packs](docs/rule-packs.md). You can add
|
|
172
|
+
your own by path (`--rules <dir>`). Nothing is downloaded and run at run time.
|
|
131
173
|
- **Releases you can verify.** npm provenance through trusted publishing, a
|
|
132
174
|
maintainer approval with 2FA for every version, SHA-pinned CI, CodeQL, and
|
|
133
175
|
OpenSSF Scorecard. Every release passes an
|
|
134
176
|
[internal audit](docs/internal-audit.md) of eight gates
|
|
135
177
|
([latest report](audit/REPORT.md)).
|
|
136
178
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
[methodology](docs/METHODOLOGY.md) states the threat model, separates what
|
|
141
|
-
black-box (HTTP) and white-box (source) analysis can and cannot observe, and
|
|
142
|
-
defines a confidence model that keeps heuristics from posing as facts. Each
|
|
143
|
-
probe has explicit false-positive controls, such as randomized canaries and
|
|
144
|
-
repeated timing measurement, and each design choice cites the research or
|
|
145
|
-
standard it rests on: 49 verified references, listed in APA style in
|
|
146
|
-
[docs/references.md](docs/references.md).
|
|
147
|
-
|
|
148
|
-
| Measure | Result |
|
|
149
|
-
|---|---|
|
|
150
|
-
| Precision on the bundled benchmark | 0.86 (19 true positives, 3 false positives) |
|
|
151
|
-
| Recall on the bundled benchmark | 0.90 (2 misses, [each one explained](bench/RESULTS.md)) |
|
|
152
|
-
| Requests to non-loopback hosts during the benchmark | 0 |
|
|
153
|
-
| Detection rules, all readable | 14 URL scan templates, 20 secret and AI-key patterns, 15 dependency capabilities, 39 AI-tooling rules, 5 fix recipes |
|
|
154
|
-
|
|
155
|
-
The benchmark targets are small and were written by the same people as the
|
|
156
|
-
scanner, so the numbers catch regressions rather than measure real-world
|
|
157
|
-
accuracy ([limits](bench/RESULTS.md#limits-of-these-numbers)). Counts as of
|
|
158
|
-
2026-10-19; refresh them with `npm run docs:numbers` and `npm run bench`.
|
|
159
|
-
|
|
160
|
-
**Cite this repository.** Use the button on GitHub, or the metadata in
|
|
161
|
-
[CITATION.cff](CITATION.cff). The APA and BibTeX forms are in the
|
|
162
|
-
[methodology](docs/METHODOLOGY.md#how-to-cite).
|
|
179
|
+
Penthera finds known classes of problems, not novel ones, and it does not
|
|
180
|
+
replace a human pentester or incident response. The decisions behind it, its
|
|
181
|
+
limits, and how to challenge them are in the [manifesto and FAQ](docs/MANIFESTO.md).
|
|
163
182
|
|
|
164
183
|
## How it fits with other tools
|
|
165
184
|
|
|
166
|
-
Penthera works next to the tools you already use
|
|
185
|
+
Penthera works next to the tools you already use; its SARIF output goes
|
|
167
186
|
straight into GitHub code scanning, for example. Each comparison page says where
|
|
168
187
|
the other tool goes further and how to run the two together.
|
|
169
188
|
|
|
@@ -176,29 +195,19 @@ the other tool goes further and how to run the two together.
|
|
|
176
195
|
| [OWASP ZAP](docs/compare/zap.md) | Dynamic testing of web apps | Your machine or CI | Active and passive scan rules |
|
|
177
196
|
| [Semgrep](docs/compare/semgrep.md) | Static analysis of your own code | Your machine, CI, cloud platform | Pattern rules, plus a paid cross-file engine |
|
|
178
197
|
|
|
179
|
-
|
|
180
|
-
welcome by pull request.
|
|
181
|
-
|
|
182
|
-
## Use cases
|
|
198
|
+
## Start here
|
|
183
199
|
|
|
184
|
-
| You
|
|
200
|
+
| You want to know | Read |
|
|
185
201
|
|---|---|
|
|
186
|
-
|
|
|
187
|
-
|
|
|
188
|
-
|
|
|
189
|
-
|
|
|
190
|
-
|
|
|
191
|
-
|
|
192
|
-
Compliance output is readiness tooling. It is not a certification and not
|
|
193
|
-
legal advice.
|
|
194
|
-
|
|
195
|
-
## Privacy
|
|
202
|
+
| Is my app safe to ship? | [Before launch](docs/use-cases/solo-founder.md), then [scan](docs/commands/scan.md) |
|
|
203
|
+
| Is this package or MCP server safe? | [deps](docs/commands/deps.md), [install](docs/commands/install.md), [agents](docs/commands/agents.md) |
|
|
204
|
+
| How do I put it in CI? | [The GitHub Action](docs/commands/action.md), [scan in CI](docs/commands/scan.md#ci-integration) |
|
|
205
|
+
| What do I give my auditor? | [Evidence bundles](docs/evidence.md), [NIS2](docs/compliance-nis2.md), [CRA](docs/compliance-cra.md) |
|
|
206
|
+
| How do I run the fix loop? | [Fix and verify](docs/commands/fix.md), [the local loop](docs/local-loop.md) |
|
|
196
207
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
host except the target. Which hosts a normal scan may contact, and why, is in
|
|
201
|
-
[docs/privacy.md](docs/privacy.md).
|
|
208
|
+
The full map, by situation and from first scan to pro use, is in
|
|
209
|
+
[docs/index.md](docs/index.md). Compliance output is readiness tooling. It is
|
|
210
|
+
not a certification and not legal advice.
|
|
202
211
|
|
|
203
212
|
## Ethics and authorization
|
|
204
213
|
|
|
@@ -210,17 +219,46 @@ or harm anyone. Unauthorized testing may break computer-misuse laws such as the
|
|
|
210
219
|
US CFAA, the UK Computer Misuse Act, and their EU equivalents. When in doubt, do
|
|
211
220
|
not scan.
|
|
212
221
|
|
|
213
|
-
The software is provided "as is", without warranty
|
|
214
|
-
|
|
215
|
-
|
|
222
|
+
The software is provided "as is", without warranty. The author accepts no
|
|
223
|
+
liability for misuse, and you are responsible for making sure every scan you
|
|
224
|
+
run is lawful.
|
|
225
|
+
|
|
226
|
+
## How Penthera is built
|
|
227
|
+
|
|
228
|
+
Penthera is developed with substantial help from generative AI: 91% of the
|
|
229
|
+
commits up to v1.7.0 were written with Claude (Anthropic) through Claude Code,
|
|
230
|
+
directed and reviewed by the maintainer, and marked with a co-author trailer.
|
|
231
|
+
Generated code passes the same gates as any change: tests, CodeQL, an
|
|
232
|
+
independent review, the internal audit, and a 2FA-approved release. Inside the
|
|
233
|
+
product, models only ever propose; deterministic checks decide. The full
|
|
234
|
+
disclosure, and the rules for AI-assisted contributions, are in
|
|
235
|
+
[docs/genai.md](docs/genai.md).
|
|
236
|
+
|
|
237
|
+
## Building in public
|
|
238
|
+
|
|
239
|
+
What changed, what we are measuring, and what comes next, in one place.
|
|
240
|
+
|
|
241
|
+
| | Where it stands | Read more |
|
|
242
|
+
|---|---|---|
|
|
243
|
+
| **Latest release** | 1.7.0 (2026-10-19): one-command setup (`npx penthera`), the dashboard, signed rule packs | [CHANGELOG](CHANGELOG.md) |
|
|
244
|
+
| **On main, unreleased** | Fixes from a second independent security review, a fix loop that cannot edit the tests that judge it, release checks on Linux, macOS and Windows, docs tested as code | [Unreleased](CHANGELOG.md#unreleased) |
|
|
245
|
+
| **Now** | 1.8.0 feature freeze: fixes only, a release candidate on npm's `next` tag first | [Roadmap: now](ROADMAP.md#now-180-feature-freeze) |
|
|
246
|
+
| **Research** | E1, accuracy on real code, is pre-registered; the benchmark tooling is in review. After the 2026 agent incidents: a checklist for running agents safely and a draft experiment (E8) | [E1 pre-registration](docs/research/prereg/E1-real-world-accuracy.md), [agents safely](docs/agents-safely.md), [what the incidents mean](docs/research/agent-incidents-2026.md) |
|
|
247
|
+
| **Next** | Real-world precision and recall with intervals, published whatever the result | [Roadmap: next](ROADMAP.md#next), [research plan](docs/vision-2.0.md) |
|
|
248
|
+
|
|
249
|
+
Releases are deliberately slow: a feature freeze, an independent review, the
|
|
250
|
+
release checks, and a release candidate before any new minor version. To
|
|
251
|
+
follow along, watch the repository for releases or join the
|
|
252
|
+
[Discussions](https://github.com/danoszz/penthera/discussions).
|
|
216
253
|
|
|
217
254
|
## Contributing and support
|
|
218
255
|
|
|
219
256
|
Clone the repository and run `npm install`, then `npm test` for the suite and
|
|
220
257
|
`npm run bench` for precision and recall.
|
|
221
|
-
[CONTRIBUTING.md](CONTRIBUTING.md) covers the project layout
|
|
222
|
-
|
|
223
|
-
|
|
258
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) covers the project layout, the release
|
|
259
|
+
cadence and the release checklist. The [roadmap](ROADMAP.md) shows what ships
|
|
260
|
+
next, and the [research plan](docs/vision-2.0.md) what we measure. Issues and
|
|
261
|
+
pull requests are welcome.
|
|
224
262
|
|
|
225
263
|
Penthera is free. If it caught something before you shipped it, you can
|
|
226
264
|
[sponsor its maintenance](https://github.com/sponsors/danoszz). Sponsorship pays
|
package/SECURITY.md
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# Security Policy
|
|
2
2
|
|
|
3
|
+
> **Starter and pro.** For "I found a vulnerability in Penthera itself: how do I
|
|
4
|
+
> report it?"
|
|
5
|
+
|
|
3
6
|
## Supported versions
|
|
4
7
|
|
|
5
8
|
Security fixes land on the latest minor release only. Upgrade to get them.
|
|
6
9
|
|
|
7
10
|
| Version | Supported |
|
|
8
11
|
| ------- | --------- |
|
|
9
|
-
| 1.
|
|
10
|
-
| < 1.
|
|
12
|
+
| 1.7.x | Yes |
|
|
13
|
+
| < 1.7 | No |
|
|
11
14
|
|
|
12
15
|
## Reporting a vulnerability
|
|
13
16
|
|
|
@@ -21,7 +24,7 @@ scanning a target), please report it privately:
|
|
|
21
24
|
|
|
22
25
|
Please do **not** open public issues for undisclosed vulnerabilities.
|
|
23
26
|
|
|
24
|
-
**What to expect.** Penthera is maintained part-time,
|
|
27
|
+
**What to expect.** Penthera is maintained part-time, and security reports come
|
|
25
28
|
first:
|
|
26
29
|
|
|
27
30
|
- Acknowledgement within 3 working days.
|
|
@@ -48,6 +51,5 @@ XSS, command injection, API fuzzing).
|
|
|
48
51
|
|
|
49
52
|
**Only scan systems you own or have explicit written permission to test.**
|
|
50
53
|
Unauthorized scanning may be illegal in your jurisdiction. Penthera is built for
|
|
51
|
-
defensive research and hardening your own applications
|
|
52
|
-
[
|
|
53
|
-
[disclaimer](README.md#disclaimer) in the README.
|
|
54
|
+
defensive research and hardening your own applications. See
|
|
55
|
+
[Ethics and authorization](README.md#ethics-and-authorization) in the README.
|
|
@@ -50,7 +50,7 @@ jobs:
|
|
|
50
50
|
(cd "$RUNNER_TEMP/base" && npm ci --ignore-scripts --no-audit --no-fund)
|
|
51
51
|
npm ci --ignore-scripts --no-audit --no-fund
|
|
52
52
|
|
|
53
|
-
- uses: danoszz/penthera@
|
|
53
|
+
- uses: danoszz/penthera@525895575f4a9bebe911707a43804a934eac0c4a # v1.6.1 (or a later release commit SHA)
|
|
54
54
|
with:
|
|
55
55
|
mode: deps
|
|
56
56
|
fail-on: none # or high / critical to block the merge
|
package/lib/adaptive-probes.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Results feed into the SecurityKnowledgeGraph for chain discovery.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { request } from "../src/utils/http.js";
|
|
11
|
+
import { request, readBody } from "../src/utils/http.js";
|
|
12
12
|
|
|
13
13
|
const DEFAULT_TIMEOUT = 10_000;
|
|
14
14
|
|
|
@@ -26,9 +26,9 @@ async function probe(base, path, opts = {}) {
|
|
|
26
26
|
let body = null;
|
|
27
27
|
const ct = res.headers.get("content-type") || "";
|
|
28
28
|
if (ct.includes("json")) {
|
|
29
|
-
try { body = await res
|
|
29
|
+
try { body = JSON.parse(await readBody(res)); } catch { body = null; }
|
|
30
30
|
} else {
|
|
31
|
-
try { body = await res
|
|
31
|
+
try { body = await readBody(res); } catch { body = null; }
|
|
32
32
|
}
|
|
33
33
|
return {
|
|
34
34
|
url: path, status: res.status,
|