v0.1.24
Security
-
The sandbox firewall step said it was not optional and continued without
it. Both failure branches logged aWARNING:and carried on, five lines
after a realfailon a missing.env— so a host without iptables shipped
a container that runs a tenant's own build commands with a route to the host.
It now tries iptables, then nft, then stops, with
CELMIS_ALLOW_UNFIREWALLED_SANDBOX=1as a typed decision for a host isolated
another way.And it did not survive a reboot. Measured on the production box: the rule was
in place,iptables-persistentwas absent and crontab was empty — so the
gap after a restart was not until the next scheduled deploy, it was until
somebody deployed by hand. A systemd unit orderedBefore=docker.service
reinstates it at boot.
Added
-
Publishing is a workflow now, not a person with a token.
publish-verifier.ymluses PyPI Trusted Publishing, so upload rights belong
to this workflow in this repository for the length of a run. The tag prefix
ispypi-, and the first letter is the reason:release.ymltriggers on the
globv*, whichverifier-0.2.1matches, because "verifier" starts with a
v — that tag would have built three container images. -
Images carry a signed build attestation.
gh attestation verifynow
answers "built from which commit, by which workflow" for somebody holding
only the image; before this the OCIrevisionlabel was the only claim, and
a label is text anybody can write. Buildkit's ownprovenance: falsestays —
different mechanism, and the comment there is right. -
packaging/pypi/DIGESTS.mdrecords the sha256 of every published file in
the git repository, because PyPI is the same channel that serves them. It
says plainly what it is: the 0.1.0 and 0.2.0 digests were read from PyPI and
written down, which is a trust-on-first-use anchor and not independent
attestation. From the first workflow-published release the digests are
printed before upload and the artefacts carry a PyPI attestation.
Fixed
-
The lint gate everybody believed in enforced nothing.
ci.ymlsaid the
rules-of-hooks rule was blocking as a test in the python job; those tests
carryskipif(not _eslint_available())and that job never installs
web/node_modules, so they reported SKIPPED in green — while this step ended
in|| echo. Measured for the first time: 42 findings, 31 errors, 24 of them
in the react-hooks family whose breach took every authenticated page down.
scripts/eslint_gate.pymakes rules-of-hooks fatal at any count and ratchets
the rest from 42. -
The verifier read whatever an archive told it to. A zip declares each
member's size before you read it, and neither copy checked. Measured against
the publishedcelmis 0.2.0: an archive of 200 KB on disk declaring 200 MB
verified asOKwith a 215 MB peak, and the number was the sender's to
choose. The person running that got the file from the party they are
checking, on their own laptop, precisely because they do not trust them.Sizes are checked before anything is read — per member, and in total, which
is what a spread of medium files defeats — and hashing streams a megabyte at
a time, counting what actually arrives so a header that lies in the other
direction is refused too. Limits are three orders of magnitude above a real
pack, and identical in both copies, held so by a test. -
A manifest that was valid JSON and not an object crashed the verifier.
manifest.get(...)on a list raised AttributeError: plain output reported it
as a problem found (exit 1) and--jsonprinted a traceback, also exit 1.
Neither was true — nothing had been checked, which is exit 2. Both modes now
agree, and the refusal says what the manifest actually was.