What's New
Six controls that hosted vendors structurally can't provide — every piece is opt-in for operators who want enforcement, every piece is dual-use security with a clear procurement story.
Adapter trust as procurement-grade controls
soup adapters scan <adapter>— spectral analysis of LoRA weights pre-load. Flags rank-1 dominance (the canonical weight-space trojan pattern), top-1 singular-vector energy concentration, NaN/Inf in weights, and Frobenius-norm outliers via robust median+MAD bucketing. Pure numpy (no torch). Exit codes 0=OK / 1=WARN / 3=FAIL.soup adapters sign / verify [--strict]— deterministic Merkle-root manifest over every file in the adapter dir (including nestedtokenizer//processor/subdirs). Tamper any file, verify fails.unsignedbackend live;sigstore+ed25519deferred to v0.60.1.soup adapters check-safetensors [--strict]— refuses pickle / PyTorch-classic weights at the boundary. Closed 8-entry unsafe-extension allowlist. Exit code 3 under--strictfor CI gating.NamespacePinStoretrust-on-first-use SQLite — anti-AI-Jacking. Records(repo_id, author, created_at)first time Soup sees a HF repo; refuses author change or backwardcreated_atjump unless--allow-namespace-shift <new-author>opt-in matches (case-insensitive). Bool opt-in rejected — prevents a free-for-all bypass.- License-conflict matrix at
soup adapters merge— closed allowlist of 33 SPDX-ish licenses;--license <id> --license-override <reason>gate; refuses non-commercial+permissive, strong-copyleft+permissive, restricted-use+permissive merges unless 8+ char justification supplied. soup airgap-bundle --model <m> --output <out.tar>— signed tarball with deterministic dataset labeling (sorted basename, NOT argv order); 100 GiB cap; TOCTOU-hardened atomic write;tarfile.data_filterset on Python 3.12+ for safe extraction.
Test count
9294 → 9446 (+152 net across 6 new test files). Review-fix coverage across 5 waves (python / security / code / tdd / smoke): 0 CRITICAL + 12 HIGH + 11 MEDIUM + 6 LOW fixed before tag.
Install / Upgrade
pip install --upgrade soup-cli==0.60.0Security
All six controls follow the project's standard hardening:
- Path containment via shared
enforce_under_cwd_and_no_symlinkhelper (TOCTOU-safe). - Atomic writes via
tempfile.mkstemp + os.replacefrom the sibling temp file. - Symlink rejection at the target path BEFORE every write (lstat + S_ISLNK, before realpath).
- Bool-as-int rejection on every numeric input (Python treats
Trueas1). - Null-byte / oversize / non-string rejection at every public string boundary.
- Frozen dataclasses for value types;
MappingProxyTypefor public registries.
New SSRF / supply-chain specifics:
NamespacePinStoreconstructor refuses paths outside$HOME / $CWD / $TMPDIR; pre-placed symlinks at the DB path rejected beforesqlite3.connect; POSIX0o600perms.compute_adapter_manifestis recursive (catches nested tokenizer-dir tamper); hardlinks pointing outside the adapter dir caught via per-file realpath+commonpath check._load_signatureuses directos.lstat-then-open (noisfile-then-openrace); 16 MiB cap on.soup-signature.json.inspect_airgap_bundle64 MiB manifest cap;tarfile.data_filterset for any future caller addingextractall.- License override reason validation: 8-char min (defends against
--license-override y), 4096-char max, null-byte rejected, TypeError on non-string.
Known Limitations
- Sigstore + ed25519 signing deferred to v0.60.1 —
UNSIGNEDbackend ships live (offline Merkle-root tamper detection works today); the live signers raiseNotImplementedErrorwith explicit v0.60.1 marker. Same stub-then-live cadence as v0.27.0 MII / v0.50.0 GRPO Plus / v0.56.0 diagnose / v0.59.0 attest. - Namespace-pin not yet wired into HF download path — helpers ship live but the integration into
utils/hubs.download_repolands in v0.60.1. Operators can callverify_namespacemanually via Python API today. - License gate is opt-in, not enforced —
--license <id>required to invoke the gate; auto-extraction fromadapter_config.json/ HF model-card metadata lands in v0.60.1. - Airgap bundle does NOT yet auto-attach v0.59 repro-receipt — train→bundle handoff wiring is v0.60.1.
- Strict-safetensors is extension-based only — a pickle stream renamed to
.safetensorsbypasses the static scan. Magic-byte check (PK\x03\x04/\x80pickle opcodes) is a v0.60.1 follow-up. - License override is captured in the CLI panel but NOT yet auto-emitted to audit-log — v0.59
audit_log.append_audit_eventintegration lands in v0.60.1. - NamespacePinStore SQLite is single-process — concurrent
soup trainruns racing on the TOFU write may serialise via SQLite locking; WAL + cross-process file-lock tracked for v0.60.1. - Adapter scan false-negative on multi-vector trojans — sophisticated attacks distributing energy across many singular vectors will pass; per-arch threshold profiles tracked for v0.60.x. Conservative-by-design false-positive bias (<1% on healthy fine-tunes) is the priority.
🤖 Generated with Claude Code