Offline evidence checker for CVE-2026-104849 — Tinypool worker-options prototype-pollution gadget (alias GHSA-85c8-ppgw-ccpr).
It compares operator-reported tinypool versions with the reviewed advisory rules and writes CSV, JSON or HTML reports. It is version-evidence triage only: it never contacts websites, imports application code, runs a package manager, or executes exploit payloads, and exploitability is always reported as not_tested.
- Scope
- Requirements
- Quick start
- Synthetic report preview
- Reviewed rules
- Interpreting results
- Preconditions
- Providing evidence
- Options
- Reports and output
- Exit status and interruption
- Limitations
- Sources and provenance
- Project documents
- Contributing and security
- License and brand assets
This repository covers only CVE-2026-104849. The checker does not scan deployed systems, fetch package inventories, analyze source code, or test exploitability. It reads versions and optional observations that you supply and reports what those claims imply under the reviewed rules.
A public URL cannot provide installed package inventory. Obtain each deployed package instance's exact version and provenance; declarations and local workstation versions are not deployment evidence. Use it only with inventories you are authorized to assess.
- Python 3.10 or newer, standard library only. No runtime dependencies and no network access.
tinypool_options_rce_check.pycan be copied and run on its own; no companion repository is needed at runtime.- The CI workflow (
.github/workflows/tests.yml) runs the local-only tests and--helpon Python 3.10 and 3.14 on Ubuntu and Windows.
# Assess one reported version; the default format is csv
python tinypool_options_rce_check.py --version 2.1.2 --format json
# Assess a JSON inventory and save an HTML report to a new file
python tinypool_options_rce_check.py --inventory examples/inventory.example.json --format html --output report.html
# Assess a CSV list and exit 1 if any entry is potentially affected
python tinypool_options_rce_check.py --csv-list inventory.csv --fail-on-finding
# Show all options, then run the local tests
python tinypool_options_rce_check.py --help
python -m unittest -vexamples/inventory.example.json is a synthetic entry (2.1.2, label example-package-instance, all observations null). Without --output, the report goes to stdout.
Open the synthetic example report. Example data only; this is not a live assessment.
The primary advisory and reviewed database were retrieved on 2026-10-24 (rules_as_of in every report).
| Example reported version | Result |
|---|---|
| 2.0.9 | potentially_affected |
| 2.1.0 | potentially_affected |
| 2.1.1 | unknown |
| 2.1.2 | fixed_version_reported |
| 2.1.3 | fixed_version_reported |
| 2.2.0 | unknown |
| 3.0.0 | unknown |
- Affected interval:
0.0.0through2.1.0inclusive. - Fixed line:
2.1.x, fixed in2.1.2. Future lines remain unknown rather than extrapolating a patch claim. - Disputed boundary:
2.1.1. The maintainer range (<=2.1.0) and the GitHub Reviewed range (<2.1.2) disagree there, so it is reported asunknownand never as clean. Obtain maintainer evidence. - Not the trigger:
pool.run(task)alone is not the documented trigger. - Severity: the maintainer rates it High; the database rates it Critical. Retain provenance when reporting.
Only plain stable MAJOR.MINOR.PATCH versions are compared. A single leading v and surrounding ASCII whitespace are accepted.
| Status | Meaning |
|---|---|
potentially_affected |
The reported version is in the documented affected interval. This is not confirmed exploitability: preconditions and exploitability were not established. |
fixed_version_reported |
The reported version is at or above the fix within the reviewed 2.1.x line. This describes a version claim only; it never attests an installed patch or safety. |
unknown |
Evidence is insufficient to classify. This is neither a clean result nor a finding. |
Every result retains exploitability: not_tested. evidence_type is operator_reported_version; evidence_confidence is insufficient for unknown and low otherwise.
Each result also carries a more specific version_status:
version_status |
Status | Cause |
|---|---|---|
in_documented_affected_range |
potentially_affected |
Version is within 0.0.0–2.1.0. |
at_or_above_fixed_version |
fixed_version_reported |
Version is 2.1.2 or later within 2.1.x. |
source_discrepancy |
unknown |
Version is 2.1.1, where the sources disagree. |
outside_reviewed_range |
unknown |
Version is outside the affected interval and the reviewed fixed line; no safety or other-vulnerability conclusion is made. |
vendor_build_unverified |
unknown |
build_source is a custom or vendor build, which needs independent patch evidence. |
qualified_version_unverified |
unknown |
Prerelease, nightly/rc or suffixed versions need vendor verification. |
unrecognized_or_partial_version |
unknown |
Version text is malformed or partial. |
version_not_disclosed |
unknown |
No version was reported. Missing metadata does not establish that the runtime is unaffected. |
Operational failures (bad arguments, unreadable or invalid input, output errors) are not assessment results. They exit with status 2 and are never reported as unknown or clean.
The advisory describes conditions that must hold for the gadget to matter. You can record your own observations of them:
| Optional operator observation | Meaning |
|---|---|
caller_supplies_pool_run_options_object |
caller supplies pool.run options object |
options_lacks_an_own_filename_property |
options lacks an own filename property |
separate_prototype_pollution_primitive |
separate prototype-pollution primitive |
attacker_controlled_worker_module_available |
attacker-controlled worker module available |
Each observation is true, false or unset (null or blank). Reports show them as reported_true, reported_false or not_reported. They are unverified, and never change the package-version result. No source search, taint analysis, reachability test, or runtime evaluation is performed. Absent observations do not establish mitigation.
Provide at least one of --version, --inventory or --csv-list; they can be combined and repeated in one run.
Command line. --version (alias --package-version) is repeatable. Each value is a separate instance, not evidence reconciliation for a single instance. --build-source and the observation flags (the key with underscores turned into hyphens, such as --separate-prototype-pollution-primitive true) apply to every --version input.
JSON inventory. One object or an array of objects:
| Field | Required | Notes |
|---|---|---|
schema_version |
yes | Integer 1. |
package_version |
yes | String. |
package |
no | Must be "tinypool" if present. |
label |
no | Display name; defaults to <file stem>:<position>. |
build_source |
no | Provenance of the build. |
| observation keys | no | true, false or null. |
CSV list. A header row is required and must include package_version. Optional columns are label, build_source and the observation keys (true, false or blank). schema_version and package are not CSV columns.
Build provenance. build_source values unknown (the default), blank, upstream, official and official_upstream are compared as upstream builds. Any other value, such as a vendor name, yields vendor_build_unverified and unknown, because vendor and custom builds may backport fixes or differ from upstream.
Rejected input. Unknown or duplicate keys, wrong types, non-finite numbers and malformed JSON are rejected. CSV with duplicate or undocumented columns, rows with a different number of fields than the header, quotes inside unquoted cells, text after a closing quote, unterminated quotes, or NUL characters is rejected as well.
Bounds. Only regular UTF-8 files are accepted. Limits: 4 MiB per file, 100 files per run, 10,000 entries per run (including --version values) and 512 characters per text field. Work from stable copies; these checks are not a filesystem sandbox.
| Option | Description |
|---|---|
--version, --package-version VERSION |
Assess an explicitly supplied tinypool version offline; repeatable. |
--build-source SOURCE |
Provenance of the --version builds (default unknown, compared as upstream). |
--<observation-key> true|false |
Observation applied to --version inputs; one flag per precondition key. |
--inventory FILE |
JSON object or array of entries; repeatable. |
--csv-list FILE |
CSV list with a package_version column; repeatable. |
--format csv|json|html |
Output format (default csv). |
--output PATH |
Save the report to a new file instead of stdout. |
--force |
Replace an existing output file explicitly. |
--brand NAME |
Organization name shown in the HTML report (default CompleteTech LLC, at most 512 characters). It has no other effect. |
--fail-on-finding |
Exit 1 when any result is potentially_affected. |
--tool-version |
Print the checker name and version, then exit. |
The detailed contract is in docs/REFERENCE.md; --help shows the current options.
JSON, formula-neutralized CSV and escaped, script-free HTML share the same assessments.
- JSON: an object with
tool,tool_version,generated_at,report_schema_version("1.0"),cve,rules_as_of,sources,completeand aresultsarray. - CSV: one
record_type=resultrow per asset observation, with the result fields above, aprecondition_<key>column for each observation,report_schema_version,report_completeand the run identity. Cells that could start a spreadsheet formula are prefixed with', and all cells are quoted. NUL characters are rejected before a report is written. A zero-result report has onerecord_type=summaryrow carrying completeness, schema, rule date, CVE andnot_tested; it is not an asset finding. - HTML: a self-contained page with a restrictive content security policy, sorted with potentially affected results first. It shows the same generation time and checker identity, the fixed release, precondition observations, and notes. An interrupted run is flagged with a notice.
All formats preserve the same run identity: tool, tool_version, and ISO 8601 UTC generated_at. CSV repeats these fields on every result and empty summary. The CLI captures generation time once before assessment and retains it when interrupted. rules_as_of remains the advisory verification date; generation time is not evidence acquisition time or deployed-patch attestation.
Safe publication. The report is rendered fully in memory before anything is written. With --output, the directory must already exist and the file is staged then published atomically. An existing file is not overwritten unless you pass --force, and the output must differ from every input file even with --force. Without --force, publication uses a hard link; on a filesystem without hard links the checker reports an error and asks you to choose another filesystem or use --force.
Privacy. Reports contain operator data. Keep real targets, inventories and reports out of commits.
| Exit | Meaning |
|---|---|
0 |
Run completed. |
1 |
At least one result is potentially_affected and --fail-on-finding was given. |
2 |
Usage, input, output or cleanup failure (an operational error, not an assessment result). This does not establish whether report bytes were already written; inspect the output before retrying. |
130 |
Interrupted. Completed observations are preserved when possible. |
unknown results do not trigger --fail-on-finding and are not a clean result.
On interruption (130):
- Before the output path is validated, nothing is written.
- If nothing has been published or started yet, completed results are written with
complete: false(JSON),report_complete=false(CSV) or an interrupted-run notice (HTML). An empty interrupted CSV retains a summary row. - If the complete report was already published, it is preserved, and the checker does not retry.
- If publication is left indeterminate, the checker does not retry. The diagnostic does not establish whether publication succeeded, so inspect the output before retrying.
- If interrupted while writing to stdout, the document can be truncated and no second report is appended.
- Results rest on operator-reported versions, not on observed deployments.
fixed_version_reportedis a reported fixed version, not a verified deployed patch. - Backports, vendor builds, prereleases and custom builds cannot be resolved offline and stay
unknown. - Precondition observations are unverified and never change the version result.
- Versions outside the reviewed interval and fixed line are
unknown; no conclusion about other flaws is made. - The tool does not establish exploitability, reachability or production exposure.
- https://github.com/tinylibs/tinypool/security/advisories/GHSA-85c8-ppgw-ccpr
- https://github.com/advisories/GHSA-85c8-ppgw-ccpr
Bounded input and reporting helpers are bundled from the reviewed Node.js checker at 74aa57ba5df05c34d17ae67912cd26ec2335201b, with package-specific range comparison and the mixed-whitespace CSV fix.
- Evidence and CLI reference
- Implementation status
- Synthetic example report
- Repository instructions
advisory.json: offline rule metadataSHA256SUMS: checksum for the checker script
Read AGENTS.md and CONTRIBUTING.md first. Before proposing a change, run python -m unittest -v, python tinypool_options_rce_check.py --help and git diff --check. Use synthetic evidence only, and never run application code or send exploit payloads. Report flaws in this project privately as described in SECURITY.md.
Available under the GNU AGPL-3.0 (LICENSE) or a separate commercial license from CompleteTech LLC; see LICENSING.md. Contributions are licensed as described in CONTRIBUTING.md.
CompleteTech LLC names, logos and the artwork in docs/images/ are brand assets and are not licensed under the AGPL-3.0 or a commercial code license; see BRAND_ASSETS.md.
