Supply Chain Security (v0.60.0)
"Tensors you can ship through procurement." v0.60 layers 6 supply-chain controls on top of LoRA adapters so a security team can clear shipped weights for production.
soup adapters scan — spectral backdoor detector
Pure-numpy spectral analysis of LoRA weight matrices. Flags four patterns associated with weight-space trojans:
| Pattern | Warn | Fail |
|---|---|---|
| Rank-1 dominance | 50× | 200× |
| Top singular value / energy | > 75% | > 95% |
| Frobenius outlier (σ) | > 4σ | > 8σ |
| NaN / Inf | — | any |
soup adapters scan ./my_adapter --format json | jq '.overall'
# "OK" | "WARN" | "FAIL"Exit codes: 0 = OK, 1 = WARN (advisory), 3 = FAIL (CI gate).
soup adapters sign / verify — Merkle-root tamper detection
soup adapters sign ./my_adapter --backend unsigned
# → ./my_adapter/.soup-signature.json
soup adapters verify ./my_adapter # exit 1 on mismatch (lenient)
soup adapters verify ./my_adapter --strict # exit 3 on mismatch (CI-strict)The unsigned backend (default) computes a Merkle root over all adapter files for offline SHA-256 verification. ed25519 is live as of v0.71.2 — soup adapters sign --backend ed25519 --key <priv.pem> (or --generate-key, or SOUP_SIGNING_KEY) writes a real detached signature; soup adapters verify --public-key <trusted.pem> does a cryptographic verify that fails closed on any tamper or wrong key. Sigstore keyless (OIDC-via-GitHub) stays infra-blocked: soup adapters sign --backend sigstore refuses with exit 2, and soup attest emit --sign sigstore does not refuse but falls back to an unsigned statement (Governance has the detail).
From v0.71.2 to v0.75.0 the
--public-keycheck could be walked straight past, and this page said otherwise. The trusted key was consulted only inside the ed25519 branch, so with--public-key trusted.pema record carrying any other backend was never compared against it and verified with exit 0, even under--strict. An adapter re-signed with the defaultunsignedbackend passed a check that was supposed to authenticate the signer, which is the failure the command exists to prevent. It is fixed in v0.75.1: with--public-key, a record whose backend is not ed25519 fails verification with a stated reason. Without--public-keynothing changed, and anunsignedrecord still verifies by Merkle root alone, as described above. If you gated a pipeline on--public-key, the gate was open; re-verify anything you accepted on an affected version.
soup adapters check-safetensors — refuse pickle at the boundary
soup adapters check-safetensors ./my_adapter --strict && echo "SHIP IT"Rejects any .bin, .pt, or pickle-format weights. The single biggest LoRA attack vector is pickle.load on hostile weights; --strict makes the CI gate hard.
License-conflict gate in soup adapters merge
soup adapters merge a/ b/ c/ --output merged/ \
--license mit --license apache-2.0 --license mit \
--license-override "Approved by legal team 2026-05-20"33-entry SPDX-license compatibility matrix. Conflicting licenses require --license-override "<reason, ≥ 8 chars>" for audit.
Namespace-pin TOFU (anti-AI-Jacking)
Soup keeps a local trust-on-first-use pin of a Hugging Face repo's author, and only three paths consult it: soup spectrum scan <model-id>, soup probe sae-diff --auto-download, and the base model of a stream_layers: true training run. All three download through Soup's own hub wrapper, which records (repo_id, author, created_at) in a SQLite store (~/.soup/namespace_pin.db; SOUP_NAMESPACE_PIN_DB moves it) the first time it sees a repo. A later download through that wrapper is refused if the author changed or if created_at changed in either direction (a pinned repo's creation time never legitimately changes, so any drift looks like a deleted-and-recreated repo).
This does not protect an ordinary training run. The base model that soup train (without stream_layers), chat, serve, merge, export and infer load is fetched by transformers directly and is never checked, so a hijacked organization could still serve you altered weights through any of them. The check also fails open: if the Hub metadata cannot be fetched (offline, rate-limited) or the SQLite store cannot be read, the download goes ahead. And there is no CLI flag to approve an author change; the override exists only as the allow_namespace_shift keyword argument of the Python wrapper. If a hijacked base model is in your threat model, download the base once, check it, and point base at that local directory.
soup airgap-bundle — physical-media transfer
soup airgap-bundle \
--output secure-bundle.tar \
--model ./llama3-8b-merged \
--dataset ./prod-logs \
--wheel ./wheelhouse \
--bundle-size-cap 50Packs model + datasets + wheels + kernels + manifest into a single tarball suitable for transfer through a data diode (physical-media / sneaker-net). Embeds a SHA-256 manifest per file. Refuses to build if total size > --bundle-size-cap (default 100 GiB).
Three details are easy to get wrong. --output is written as a plain, uncompressed tar whatever its extension, so name it .tar (a .tar.gz name produces a file that tar -xzf rejects as "not in gzip format"). --model, --dataset, --wheel and --kernel each take a directory (the last three are repeatable), and every path must sit under the working directory. And a single file passed to --dataset is accepted without an error and packs nothing: the summary panel still counts it under Datasets and the command exits 0, while the tarball lacks it. Compare the Files count in the panel, or run tar -tf secure-bundle.tar, against what you expect before you carry it across. --repro-receipt <json> embeds a receipt from soup train --repro-receipt, and is auto-detected from <model>/repro-receipt.json.
Reporting a vulnerability
This restates upstream's SECURITY.md at v0.75.2; that file is the authority if the two ever differ.
Supported versions. Security fixes are shipped for the latest published release. The policy marks 0.75.x as supported and everything below 0.75 as not, so a v0.74 install has no patched release to wait for: pip install -U soup-cli.
How to report. Report privately, and do not open a public GitHub issue for anything security-sensitive. The preferred route is a private report through GitHub Security Advisories. If you cannot use GitHub, email team@trysoup.dev (the project address), or makazanalpamys@gmail.com (the maintainer's personal address) if that bounces. Do not report in Discord: it is a public channel. Include the affected version or versions and your platform, a minimal reproduction or proof of concept, and the impact you observed.
What to expect. Upstream aims to acknowledge a report within 5 business days and to ship a fix or mitigation for a confirmed, in-scope issue "as promptly as is practical". That is a target, not a service-level agreement, and no other deadline is promised. Disclosure is coordinated: once a fix is released the reporter is credited by name in the release notes, unless they ask for anonymity. There is no bug bounty and no monetary reward; the credit is the whole of what is offered.
Scope. The threat model assumes the operator runs Soup on their own machine with their own data. In scope, as representative examples:
- path traversal or arbitrary file read or write from a user-supplied config, dataset or artifact path;
- SSRF in the synthetic-data providers, the inference server, or the hub and endpoint validators;
- command, Modelfile, Jinja chat-template, or systemd or launchd unit injection;
- secret leakage in logs, crash bundles or generated artifacts;
- sandbox escape in the RLVR code-execution reward path.
Out of scope: vulnerabilities in third-party model weights or datasets you choose to load; issues that require an already-compromised host; and the DNS and email configuration of trysoup.dev (a missing or permissive DMARC, SPF or DKIM record, and anything else a public DNS query establishes), which the project fixes but does not treat as a finding in Soup.
Advisories published so far, each with the page that says what changed and what to re-check:
- GHSA-63h4-gvp4-r26g, fixed in v0.75.1: an adapter with nothing in it, and eight hardening fixes.
- GHSA-9f49-hqpf-c849, fixed in v0.75.2: endpoint URLs can no longer name a private address.
Numbers
+152 new tests in v0.60.0. Security-fix coverage: 12 HIGH, 11 MEDIUM, 6 LOW.
See also
- Compliance pack — v0.71.35 puts signing, scanning and attestation on a HIPAA / SOC 2 / EU AI Act / SR 11-7 path.
- Governance — v0.59 BOM + SLSA-3 + audit log.
- Adapters — v0.57 diff / merge / blame / branch (the rest of the LoRA toolkit).
- Adapter lifecycle (v0.67) —
soup lockSHA256 closure makes signed + scanned adapter artifacts reproducible across the team;soup adapters prputs review + signing on every adapter change.
Soup is free and Apache-2.0. If it saved you a training run, starring the repo costs nothing and helps most. You can also fund the GPU time behind the work a 4 GB laptop cannot reach.