Skip to content

fix: cap how much an SLA archive may unpack - #59

Merged
sarkafa merged 2 commits into
masterfrom
fix/sla-unpack-budget
Sep 22, 2026
Merged

sarkafa merged 2 commits into
masterfrom
fix/sla-unpack-budget

Conversation

@sarkafa

@sarkafa sarkafa commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

SLMetaData read config.json and every thumbnail/ member with no size limit, so a crafted .sl1/.slx deflating a few hundred kB into gigabytes OOM-killed whatever parsed it. A consumer that parses on a queue then crash-loops: the SIGKILL leaves the message unacked and it is redelivered.

extract_metadata and extract_thumbnails now go through read_capped, which reads a member with an explicit byte limit. The declared size in the central directory only serves as an early reject; it bounds nothing by itself, because zipfile truncates to it after the decompressor has already produced the data, and the CRC it would be checked against is written by the archive too. Members are read by ZipInfo rather than by name, since a name resolves to the last duplicate, which lets one entry be checked and another read.

config.json is capped at 256 kB. Thumbnails share a 4 MB budget across the whole archive, as a per-member cap multiplies by a member count the archive chooses. Real files spend ~3 kB and under 150 kB respectively. A refusal is a ValueError; directory entries under thumbnail/ are skipped instead of yielding an empty thumbnail.

The extractors are classmethods so a subclass can override the caps. Calling them on the class or on an instance works as before.

sarkafa and others added 2 commits September 21, 2026 10:43
SLMetaData read config.json and every thumbnail/ member with no size
limit, so a crafted .sl1/.slx deflating a few hundred kB into gigabytes
OOM-killed whatever parsed it. A consumer that parses on a queue then
crash-loops: the SIGKILL leaves the message unacked and it is redelivered.

extract_metadata and extract_thumbnails now go through read_capped,
which reads a member with an explicit byte limit. The declared size in
the central directory only serves as an early reject; it bounds nothing
by itself, because zipfile truncates to it after the decompressor has
already produced the data, and the CRC it would be checked against is
written by the archive too. Members are read by ZipInfo rather than by
name, since a name resolves to the last duplicate, which lets one entry
be checked and another read.

config.json is capped at 256 kB. Thumbnails share a 4 MB budget across
the whole archive, as a per-member cap multiplies by a member count the
archive chooses. Real files spend ~3 kB and under 150 kB respectively.
A refusal is a ValueError; directory entries under thumbnail/ are skipped
instead of yielding an empty thumbnail.

The extractors are classmethods so a subclass can override the caps.
Calling them on the class or on an instance works as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two failures that arrived without a commit, because every tool is
installed with -U and so drifts with time.

Ruff's default rule set grew in 0.16.0 to cover UP, SIM, I, RUF, FA and
more, and the repo carries no ruff configuration, so it lints against
whatever that default happens to be. master passes on 0.15 and reports
41 findings on 0.16 with nothing changed between them. Ruff is pinned to
0.15.*; adopting the wider set is worth doing, but as a reviewed change
rather than one that arrives on its own.

pytest-pylint declares the pytest_collect_file(path, parent) hook, which
pytest 8 removed, so it fails plugin validation before collection starts
and both pytest steps exit 3. Its last release is from 2023 and none
supports the current hookspec, so the plugin goes and pylint runs as its
own step, pinned for the same reason as ruff. 3.3 rather than 4.0
because 4.0 does not install on Python 3.9, which the matrix still
covers.

The "Lint with pylit" step gains its missing n and splits: pylint now
lints both packages, and the pytest run it used to hide inside keeps the
type check and doctests over gcode_metadata.

flake8 and mypy are installed the same way and carry the same risk; left
alone here to keep this to what is currently failing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sarkafa
sarkafa force-pushed the fix/sla-unpack-budget branch from 8c81802 to 99de2c9 Compare September 21, 2026 09:03
@sarkafa
sarkafa merged commit b89f818 into master Sep 22, 2026
5 checks passed
@sarkafa
sarkafa deleted the fix/sla-unpack-budget branch September 22, 2026 08:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants