Skip to content

Commit eb4e2b0

Browse files
Merge pull request #29 from CompleTetech-Security/docs/readme-cleanup-20261026
Polish README structure, wording and navigation
2 parents cf42023 + 8810784 commit eb4e2b0

1 file changed

Lines changed: 129 additions & 51 deletions

File tree

‎README.md‎

Lines changed: 129 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,109 @@
99
<a href="LICENSING.md"><img src="https://img.shields.io/badge/license-AGPL--3.0%20%7C%20commercial-64748B" alt="AGPL-3.0 or commercial license"></a>
1010
</p>
1111

12+
<h1 align="center">node2shell-check</h1>
13+
1214
<p align="center">
13-
<b>Find Node.js runtimes and websites reporting a version affected by Node2Shell</b><br>
14-
CVE-2026-111497 · offline runtime assessment · passive, single-file, dependency-free
15+
<b>Version-based triage for Node2Shell (CVE-2026-111497) in Node.js runtimes and websites</b><br>
16+
Offline runtime assessment · passive website metadata · single-file, dependency-free Python
1517
</p>
1618

1719
---
1820

19-
`node2shell-check` compares the Node.js version a runtime or website reports with the official upstream fix for each supported branch. Assess a version you already know, collect metadata inside a deployed service, or inspect a website's ordinary public response. It sends **no exploit payloads**, never decodes Brotli, and reports uncertainty honestly: a hidden, vendor-built, or conflicting version is `unknown`, never "safe".
21+
`node2shell-check` compares the Node.js version a runtime or website reports with the official upstream fix for each supported branch. You can assess a version you already know, collect metadata inside a deployed service, or inspect a website's ordinary public response.
22+
23+
It sends **no exploit payloads**, never decodes Brotli, and reports uncertainty honestly: a hidden, vendor-built, or conflicting version is `unknown`, never "safe".
24+
25+
> [!IMPORTANT]
26+
> **Potential exposure is not confirmed exploitability.** Every result keeps exploitability `not_tested`. The vulnerability involves decoding attacker-controlled Brotli data, and a version alone cannot show whether an application reaches that code path. `fixed_version_reported` is not verification of a deployed patch. Treat every result as a prompt for an administrator to check.
27+
28+
Check only targets you are authorized to assess, and keep real target lists, inventories, and reports out of version control.
29+
30+
## Contents
31+
32+
- [Requirements](#requirements)
33+
- [Quick start](#quick-start)
34+
- [Input modes](#input-modes)
35+
- [Understanding results](#understanding-results)
36+
- [Reports and output](#reports-and-output)
37+
- [Exit codes](#exit-codes)
38+
- [Scope and limitations](#scope-and-limitations)
39+
- [Optional tooling](#optional-tooling)
40+
- [Documentation](#documentation)
41+
- [Development and contributing](#development-and-contributing)
42+
- [License](#license)
43+
44+
## Requirements
45+
46+
- Python 3.10 or later on Windows, macOS, or Linux. There is nothing to install; use `python3` where `python` is not Python 3.
47+
- Node.js only if you run the optional collector, `collect-runtime.cjs`.
2048

2149
## Quick start
2250

2351
```sh
2452
git clone https://github.com/completetech-security/node2shell-check.git
2553
cd node2shell-check
2654

27-
python node2shell_check.py --node-version 24.22.0 # offline, no network calls
28-
node collect-runtime.cjs > runtime.json # run inside the deployed service
55+
# Compare a known version. Offline, no network calls.
56+
python node2shell_check.py --node-version 24.22.0
57+
58+
# Collect metadata where the service runs (same Node.js binary), then assess it offline.
59+
node collect-runtime.cjs > runtime.json
2960
python node2shell_check.py --inventory runtime.json --format html --output report.html
61+
62+
# Check authorized websites listed one per line (start from websites.example.txt).
63+
cp websites.example.txt websites.txt # then replace the entries with your own authorized targets
3064
python node2shell_check.py --input websites.txt --format html --output websites-report.html
3165
```
3266

33-
Python 3.10+ on Windows, macOS, or Linux. Nothing to install; use `python3` where `python` is not Python 3. Node.js is needed only to run the optional collector.
67+
With no `--format`, results are written as CSV to stdout. Run `python node2shell_check.py --help` for the complete option list, or see the [full reference](docs/REFERENCE.md).
68+
69+
## Input modes
3470

35-
## The report
71+
All three modes can be combined in one run. Each input becomes its own observation with its own evidence; a supplied runtime version is never assumed to belong to a supplied URL.
3672

37-
`--format html` produces a single offline, script-free file with a logo, summary tiles, every result with its evidence and next step, and the upstream fix table. It follows the reader's light or dark theme and prints cleanly.
73+
| Mode | Option | Network | What it does |
74+
| --- | --- | --- | --- |
75+
| Known version | `--node-version VERSION` (repeatable) | None | Compares a version you obtained elsewhere. |
76+
| Runtime inventory | `--inventory FILE` (repeatable) | None | Reads collector-compatible JSON (schema `1`) produced by `collect-runtime.cjs`. |
77+
| Website metadata | URLs or domains as arguments, or `--input FILE` | Yes | Reads public response metadata from each supplied target. |
78+
79+
**Runtime inventory.** `collect-runtime.cjs` records version and build metadata from the Node.js binary that runs it. It never loads application code, reads environment variables, decompresses data, or opens a connection. It cannot attest vendor patch provenance, and its `build_source` defaults to `unknown`. Run it in the actual container or runtime you intend to assess; a host installation does not describe every container, function, or worker.
80+
81+
**Website metadata.** For each target the checker reads `robots.txt` and, if policy allows, makes an ordinary `GET` request to the supplied page. It uses verified TLS, an honest User-Agent, bounded responses, and request pacing. Redirects stay on the same host or its `www` counterpart, and HTTPS downgrades stop. It fetches nothing else and sends no compressed bodies, credentials, or crafted requests. Express or Next.js hints never become Node.js version evidence, and Brotli response compression does not prove that an application decodes request bodies. See [Requests made in website mode](docs/REFERENCE.md#requests-made-in-website-mode) for the full rules.
82+
83+
## Understanding results
84+
85+
Each observation receives one of four assessment statuses.
86+
87+
| Status | Meaning |
88+
| --- | --- |
89+
| `potentially_affected` | A plain Node.js version below its branch's fix is reported. Verify the deployed build and prioritize remediation. |
90+
| `unsupported_runtime` | The version is on an older, unsupported upstream line. Verify vendor support and move to a supported, patched runtime. |
91+
| `fixed_version_reported` | The reported version meets the fix. This is not independent verification of installed patches. |
92+
| `unknown` | The version is missing, malformed, conflicting, vendor-qualified, custom-built, pre-release, possibly backported, or outside this rule snapshot. |
93+
94+
Upstream fixes in the embedded rule snapshot:
95+
96+
| Node.js branch | 22.x | 24.x | 26.x |
97+
| --- | --- | --- | --- |
98+
| **Fixed in** | 22.23.4 | 24.22.1 | 26.12.1 |
99+
100+
Thresholds come from the [Node.js October 2026 security releases](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases) and the release notes for [22.23.4](https://nodejs.org/en/blog/release/v22.23.4), [24.22.1](https://nodejs.org/en/blog/release/v24.22.1), and [26.12.1](https://nodejs.org/en/blog/release/v26.12.1), released 2026-10-21. The embedded rule snapshot is dated **2026-10-23**. The checker does not download or refresh rules, so rerunning an old copy does not update them.
101+
102+
Keep these distinctions in mind when reading a report:
103+
104+
- **Unknown is not an error.** A successful request may legitimately reveal no runtime version. Conversely, a `fixed_version_reported` observation elsewhere in a report does not erase an unsuccessful website check. Inspect each result's fetch outcome and the run's completeness as well as its assessment.
105+
- **Reported is not verified.** A version copied from a Dockerfile, deployment label, or public header may differ from the process serving traffic. Headers can be stale, spoofed, or proxy-generated.
106+
- **Vendor builds stay uncertain.** Vendor-qualified, custom-built, and pre-release versions remain `unknown`. Brotli metadata from the collector is context, not a patch verdict.
107+
108+
The [assessment methodology](docs/NODE2SHELL.md) documents each classification rule and the checks an administrator should perform. Result and evidence fields are listed in the [reference](docs/REFERENCE.md#result-fields).
109+
110+
## Reports and output
111+
112+
### HTML report
113+
114+
`--format html` produces a single offline, script-free file with a logo, summary tiles, every result with its evidence and next step, the upstream fix table, and run-completeness information. It follows the reader's light or dark theme and prints cleanly.
38115

39116
<p align="center">
40117
<picture>
@@ -45,49 +122,69 @@ Python 3.10+ on Windows, macOS, or Linux. Nothing to install; use `python3` wher
45122

46123
<p align="center"><sub>Synthetic example built from reserved <code>example.*</code> domains and invented runtimes. <a href="docs/example-report.html">View the HTML</a>.</sub></p>
47124

48-
Use `--logo path/to/logo.png` and `--brand "Your Company"` to brand it for your own organization. CSV and JSON remain available for spreadsheets and pipelines. `--fail-on-finding` exits 1 when any result is `potentially_affected` or `unsupported_runtime`; operational errors exit 2.
125+
Use `--logo path/to/logo.png` and `--brand "Your Company"` to brand the report for your own organization.
49126

50-
## What the results mean
127+
### CSV and JSON
51128

52-
| Status | Meaning |
53-
| --- | --- |
54-
| 🔴 `potentially_affected` | A plain Node.js version below its branch's fix is reported. Verify the deployed build and prioritize remediation. |
55-
| 🟠 `unsupported_runtime` | The version is on an older, unsupported upstream line. Verify vendor support and move to a supported, patched runtime. |
56-
| 🟢 `fixed_version_reported` | The reported version meets the fix. Not independent verification of installed patches. |
57-
| ⚪ `unknown` | Version missing, malformed, conflicting, vendor-qualified, custom-built, pre-release, possibly backported, or outside this rule snapshot. |
129+
CSV (the default) and JSON suit spreadsheets and pipelines. Progress and diagnostics go to stderr, leaving stdout available for data.
58130

59-
| Node.js branch | 22.x | 24.x | 26.x |
60-
| --- | --- | --- | --- |
61-
| **Fixed in** | 22.23.4 | 24.22.1 | 26.12.1 |
131+
Report schema `1.1` records the checker's executable SHA-256 (`tool_sha256`, not a publisher signature) and explicit run completeness in JSON, CSV, and HTML. Existing observation columns and inventory schema `1` remain compatible. An empty CSV report contains one summary row rather than a fabricated target. The JSON report is an object with top-level `tool`, `tool_version`, `tool_sha256`, `report_schema_version`, `rules_as_of`, `generated_at`, `complete`, and `results` fields. `complete: true` means the input loop finished, not that every request succeeded or every version was identified. See [CSV](docs/REFERENCE.md#csv), [JSON](docs/REFERENCE.md#json), and [result fields](docs/REFERENCE.md#result-fields) for details.
62132

63-
Thresholds come from the [Node.js October 2026 security releases](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases) and the release notes for [22.23.4](https://nodejs.org/en/blog/release/v22.23.4), [24.22.1](https://nodejs.org/en/blog/release/v24.22.1), and [26.12.1](https://nodejs.org/en/blog/release/v26.12.1), released 2026-10-21. The embedded rule snapshot is dated **2026-10-23**.
133+
### Writing reports safely
64134

65-
> [!IMPORTANT]
66-
> **Potential exposure is not confirmed exploitability.** Every result keeps exploitability `not_tested`. The vulnerability involves decoding attacker-controlled Brotli data, and a version alone cannot show whether an application reaches that code path. Treat every result as a prompt for an administrator to check.
135+
- Existing output files are refused unless you pass `--force`. Input and logo files can never be the output destination, even with `--force`.
136+
- The output directory must already exist. Files are prepared in a temporary file and published atomically; the default mode needs a filesystem that supports hard links and fails rather than weakening overwrite protection.
137+
- The report is written after the input loop finishes or a keyboard interruption is handled. An interruption keeps completed observations and marks the report incomplete (`complete: false`). An interruption after a complete report has been published preserves that report. Abrupt termination, a crash, or power loss before the write can prevent report creation; there are no incremental checkpoints or automatic resume.
138+
- CSV neutralizes spreadsheet formulas with a leading apostrophe and rejects NUL characters; a rejected write is an output error (exit 2).
139+
- Reports can contain target URLs, response metadata, and operator-supplied inventory. Handle them as assessment records. There is no telemetry or automatic upload.
140+
141+
## Exit codes
142+
143+
| Code | Meaning |
144+
| --- | --- |
145+
| `0` | The run completed without an operational error. Findings do not change this unless `--fail-on-finding` is set. |
146+
| `1` | `--fail-on-finding` was set and at least one result is `potentially_affected` or `unsupported_runtime`. |
147+
| `2` | An argument, input, or output error, or an operational failure, prevented one or more requested checks. This takes precedence over `1`. |
148+
| `130` | The run was interrupted. Completed observations are retained where output can be written. |
149+
150+
Operational outcomes (for example timeouts or blocked requests) are separate from assessment results. Review both.
151+
152+
## Scope and limitations
67153

68-
## How it works
154+
- **Version triage only.** The checker never triggers the Brotli decoder, sends compressed request bodies, tests a crash, or attempts code execution. It does not review whether an application decompresses untrusted Brotli input through `node:zlib`.
155+
- **Node.js only.** Thresholds are not applied to Bun, browser runtimes, standalone Brotli libraries, or other packages with similar version numbers. It is not a general vulnerability scanner.
156+
- **Conservative by design.** Missing, malformed, conflicting, vendor-qualified, custom-build, pre-release, and uncovered-future versions stay `unknown`. `unsupported_runtime` is an upgrade-and-review result, not a claim that the vulnerability was reproduced on that release.
157+
- **Fixed version reported is not a patched deployment.** Backports and vendor patches are not detected, and a missing header or successful scan does not show that a service is patched.
158+
- **Local CLI, not a service.** Private and loopback addresses are supported for authorized internal assessment. The program is not designed to accept untrusted URLs from the internet.
159+
- **Website checks stay narrow.** There is no crawling or endpoint enumeration, no automatic retries or access-control bypasses, no HTTP fallback after a TLS error, and no option to disable TLS verification or skip robots policy. A permissive `robots.txt` does not by itself grant permission to assess a website.
69160

70-
- **Known version:** `--node-version` (repeatable) compares a version you obtained elsewhere. No network calls.
71-
- **Runtime inventory:** `collect-runtime.cjs` records version and build metadata from the Node.js binary that runs the service. It never loads application code, reads environment variables, decompresses data, or opens a connection, and it cannot attest vendor patch provenance.
72-
- **Website metadata:** reads `robots.txt`, then makes an ordinary `GET` request to the page if allowed, with verified TLS, an honest User-Agent, bounded responses, and pacing. Redirects stay on the same host or its `www` counterpart, and HTTPS downgrades stop. It fetches nothing else and sends no compressed bodies, credentials, or crafted requests.
161+
## Optional tooling
73162

74-
Input modes can be combined in one report; each observation keeps its own evidence. Express or Next.js hints never become Node.js version evidence, and Brotli response compression does not prove request decoding.
163+
None of these changes the standalone checker, its embedded offline rule snapshot, or the Node2Shell inventory schema.
75164

76-
Check only targets you are authorized to assess, and keep real target lists, inventories, and results out of version control.
165+
- **Signed-rule verification:** [offline TUF bundle verification](tools/signed_rules/README.md) uses separate optional dependencies and an explicitly provisioned public trust root. It never applies a rule update automatically.
166+
- **Authenticated rule publication:** the [keyless publication channel](docs/KEYLESS_RULES.md) uses pinned GitHub/Sigstore publisher identity, expiring artifacts, and durable rollback checks. Updates produce a separate review artifact.
167+
- **Family metadata tools:** [Node capability and build-context collection](docs/NODE_METADATA.md) produces separate formats that do not infer application exposure from available APIs, lockfile declarations, or image tags.
77168

78169
## Documentation
79170

80171
- [Full reference](docs/REFERENCE.md): every CLI option, inventory field, output field, and request rule
81172
- [Assessment methodology](docs/NODE2SHELL.md): how each version is classified, with sources
173+
- [Node advisory matrix](docs/NODE_ADVISORY_MATRIX.md): named Node.js advisories and their checker coverage
82174
- [Exploit-specific checker repositories](docs/CHECKER_FAMILY.md): roadmap for one independent repository per exploit
83-
- [Checker delivery status](docs/CHECKER_STATUS.md): dated merge/CI receipts and remaining roadmap scope
84-
- [Contributing](CONTRIBUTING.md) · [Security policy](SECURITY.md)
175+
- [Checker delivery status](docs/CHECKER_STATUS.md): dated merge and CI receipts and remaining roadmap scope
176+
- [Changelog](CHANGELOG.md)
177+
178+
## Development and contributing
179+
180+
Issues and pull requests are welcome; start with [CONTRIBUTING.md](CONTRIBUTING.md). Report a security flaw in this checker as described in [SECURITY.md](SECURITY.md).
85181

86182
```sh
87-
python -m unittest -v # local-only tests; no external websites are contacted
183+
python -m unittest -v # local-only tests; no external websites are contacted
184+
python node2shell_check.py --help
88185
```
89186

90-
For agents contributing to this repository, [AGENTS.md](AGENTS.md) contains the shared development instructions; [CLAUDE.md](CLAUDE.md) points Claude to them.
187+
Hosted CI runs the suite on Linux, Windows, and macOS with Python 3.10 and 3.14. For agents contributing to this repository, [AGENTS.md](AGENTS.md) contains the shared development instructions; [CLAUDE.md](CLAUDE.md) points Claude to them.
91188

92189
## License
93190

@@ -97,22 +194,3 @@ Dual licensed: [AGPL-3.0](LICENSE), including commercial use that meets its term
97194
<a href="https://complete.tech"><img src="assets/logo.png" alt="CompleteTech LLC" width="160"></a><br>
98195
<sub>Built by <a href="https://complete.tech">CompleteTech LLC</a> · Innovation at Every Integration</sub>
99196
</p>
100-
101-
An interruption after a complete report is published preserves that report and exits 130.
102-
CSV rejects NUL characters before writing a report.
103-
Report schema `1.1` records the executable SHA-256 and explicit
104-
run completeness in JSON, CSV and HTML. Empty CSV reports contain a summary row;
105-
existing observation columns and inventory schema `1` remain compatible.
106-
107-
Optional release tooling: [offline TUF bundle verification](tools/signed_rules/README.md)
108-
uses separate optional dependencies and an explicitly provisioned public trust root.
109-
It never changes the standalone checker or automatically applies a rule update.
110-
111-
The optional [authenticated rule-publication channel](docs/KEYLESS_RULES.md)
112-
uses pinned GitHub/Sigstore publisher identity, expiring artifacts and durable
113-
rollback checks. Updates produce a separate review artifact; the checker keeps
114-
its embedded offline snapshot.
115-
116-
Optional family metadata tools: [Node capability and build-context collection](docs/NODE_METADATA.md).
117-
These separate formats do not change the Node2Shell inventory schema or infer
118-
application exposure from available APIs, lockfile declarations or image tags.

0 commit comments

Comments
 (0)