Catalog is the trust root for asset checksums; pin against a vendored floor
- Dominant language
- Python
- Stars
- 3
- Forks
- 3
- Avg merge
- 16h 46m
- Merged PRs (30d)
- 35
Description
## Context
Since #20, `polyxios/fetcher.py` resolves every asset through a `models.json` catalog fetched at runtime from `https://raw.githubusercontent.com/fury-gl/polyxios-data/master/models.json`. Asset checksums come from that catalog rather than being pinned in the source tree (they previously were, in `_PACKAGES`).
That trade was deliberate — a live catalog means newly published assets are picked up without cutting a polyxios release — but it moves the trust root out of the reviewed code, and the `sha256` check no longer proves anything about provenance.
Not urgent, and not an open exploit. Filing so it isn't forgotten.
## What the checksum currently proves
That the bytes downloaded match what the catalog said at that moment. Nothing more. The same actor publishes the asset and its checksum, and an automated step rewrites both together, so anyone able to write to `fury-gl/polyxios-data` can swap them in one move and every client accepts the result. The check defends against a corrupted transfer or a stale CDN edge — real, but limited.
Note also that #20's stale-cache fix (a cached file is reused only while its checksum still matches the current catalog) means a rewritten catalog now propagates to machines that already hold the file. Correct behavior for a legitimate re-release; it also widens the swap window. Layer 1 below closes that.
## Severity
These are sample meshes fed to polyxios codecs. The realistic ceiling is parser exploitation, disk exhaustion, or archive path traversal — the last of which is already fixed in #20 (`_safe_extract_zip`), along with https-only enforcement on catalog URLs. Defense in depth, not an incident.
## Proposed work (client side)
**Layer 1 — vendor the catalog as a floor.** No data-repo change, no new dependency. `models.json` is ~78 KB / 277 files; ship it in the wheel. At runtime the remote catalog may **add** entries but may not **redefine** the `sha256` of a filename already present in the vendored floor — mismatch refuses the download with a clear error. Every asset that existed at release time then becomes pinned as strongly as the wheel itself; new assets keep the current weaker model until the next release absorbs them.
**Layer 2 — rollback and freeze protection.** Once fury-gl/polyxios-data emits `version` / `generated` / `expires` (see the companion issue), store the highest catalog version seen under `~/.polyxios/` and refuse anything lower; warn once past `expires`. ~20 lines, and it's the two cheapest guarantees TUF offers.
**Layer 5 (optional, later) — verify a catalog signature.** Only signatures give provenance rather than integrity. Ed25519/minisign needs `cryptography` or `PyNaCl` as an optional extra (verify when installed, warn when not), and the private key must live outside the data repo or a repo compromise takes key and payload together. Sigstore keyless via GitHub OIDC avoids key custody entirely at the cost of a heavier client dependency.
`POLYXIOS_MODELS_URL` already exists as an escape hatch for anyone wanting to pin the catalog to an immutable raw URL today.
## Related
- Publishing-side counterpart: fury-gl/polyxios-data#4
- Introduced in #20
Happy to convert this to a private security advisory instead if you'd rather not track it in the open.
Contributor guide
Research direction
Start in polyxios/fetcher.py and inspect how models.json is fetched, merged, and used for checksum validation; review the prior _PACKAGES approach and how package data is included in the wheel. Done means the vendored catalog ships with the wheel, remote entries can only add assets, and a changed checksum for an existing filename is rejected clearly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- security
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100