github MakazhanAlpamys/Soup v0.75.1
v0.75.1 — hardening, and cloud runs that keep what they produced

9 days ago

v0.75.1 is a backport, not a cut of main. Its tree is tag v0.75.0 plus 25 cherry-picks, one
test adaptation and the release commits. main was 186 commits past v0.75.0 when this release was
cut, so releasing from main would have shipped an entire minor's worth of unreleased work under a
patch number. The diff of v0.75.0...v0.75.1 is exactly what these notes describe.

What's New

  • A layer-streamed LoRA adapter saved as ZERO tensors under peft 0.21.0 (#1005, fixed in #1010).
    peft 0.21 selects an adapter's tensors by the prefixes it reads off model.named_modules(), where
    0.20 filtered by the lora_ substring. The streaming wrapper's module and parameter names carried
    .inner. while its state_dict() keys were canonical, so trainer.save_model(), every
    save_steps checkpoint and get_peft_model_state_dict() returned nothing — and nothing raised.
    Any adapter saved with stream_layers: true on peft>=0.21 before this fix holds zero tensors (a
    40-byte adapter_model.safetensors) and cannot be recovered: re-train it.
    Naming only; nothing
    on the streamed forward path changes.

  • soup runs clean --keep-weights did the opposite of what it said on Click 8.1 (#1057). The
    option was a bare flag with a True default, which Click 8.1 treats as a toggle, so typing
    --keep-weights parsed as False and deleted whole non-best checkpoints. It is now the paired
    --keep-weights/--no-keep-weights: the default and --keep-weights keep weights on every Click
    version, and --no-keep-weights is the one way to delete them. A repo-wide ratchet now fails any
    True-default flag that has no negative form.

  • Cloud runs keep their outputs and their cleanup (#1058, with the follow-up #1073 in #1078).
    soup train --cloud modal ran training in an ephemeral container while the local entrypoint
    printed "download checkpoints to <dir>" with nothing to download; outputs now live on the named
    soup-outputs volume, are committed in a finally, and are downloaded with destination
    containment. On Lambda, Ctrl+C during --cloud-submit could kill the lifecycle controller while it
    was inside the finally that terminates the paid instance; the parent now keeps waiting across the
    interrupt, and the follow-up closes the two remaining windows (a signal between Popen returning
    and the guarded region being entered, and one landing on the notice write).

  • Hardening across eight areas — see Security below.

Install / Upgrade

pip install --upgrade soup-cli
# or with the training stack
pip install --upgrade "soup-cli[train]"

Security

This release is the fix for security advisory
GHSA-63h4-gvp4-r26g,
which has the full detail, the affected version range of each issue, and upgrade guidance. Summary:

  1. Credentials sent to the wrong service. soup push --hub modelscope|modelers resolved the
    Hugging Face token for every hub and presented it to that third party (0.53.10-0.75.0). A judge
    URL of the form https://<host>/<model> was classified as the OpenAI provider for every host,
    so OPENAI_API_KEY was sent as a Bearer token to it (0.33.0-0.75.0). Each hub now authenticates
    only with its own credential, and only api.openai.com receives OPENAI_API_KEY.
  2. soup adapters verify --public-key accepted unsigned adapters (0.71.2-0.75.0): the trusted
    key was consulted only inside the ed25519 branch, so a record with any other backend verified
    with exit code 0 even under --strict.
  3. The Web UI rendered dataset and run content as HTML (0.7.0-0.75.0) and sent no
    Content-Security-Policy. All rendering now goes through one escaping template, inline handlers
    are gone, and the UI sends a CSP without unsafe-inline for scripts plus X-Content-Type-Options,
    Referrer-Policy and X-Frame-Options; Chart.js is pinned with Subresource Integrity.
  4. soup serve tool and adapter routes checked neither Host nor Origin (tool routes
    0.53.7-0.75.0, adapter routes 0.30.0-0.75.0), so a web page the operator visited could reach
    them. They now answer 421 for a foreign Host and 403 for a foreign Origin, and the tool token is
    compared in constant time. Inference routes are deliberately unchanged so a reverse proxy can
    still front them.
    Also (added 2026-09-25): with --tool-auth-token set, GET /v1/adapters,
    POST /v1/adapters/activate/{name} and POST /v1/adapters/deactivate ignored the token in
    0.71.27-0.75.0, so on a non-loopback bind anyone who could reach the port could list adapters and
    switch the one being served. They now require it, so a client that listed adapters without the
    token gets 401.
  5. soup mcp serve --allow-execute did not pin every approved input (0.73.3-0.75.0): several
    config paths could change between plan approval and execution, the one-active-execution check read
    only the 50 newest runs, and a bookkeeping failure after spawn left a child running unsupervised.
  6. Config regexes could hang loading or training (lr_groups 0.41.0-0.75.0,
    unfrozen_parameters 0.71.23-0.75.0). The old guard was itself the problem — it ran the untrusted
    pattern against a 128-character probe. It is replaced by a structural check that parses the
    pattern and never matches with it.
  7. Credential option values were written to the audit log (0.71.3-0.75.0) whenever they did not
    start with hf_, sk- or Bearer. Masking is now by option name.
  8. Reading an untrusted .can archive could exhaust memory through YAML alias expansion or
    unbounded member reads, and manifest fields could emit terminal control sequences
    (0.26.0-0.75.0).

Rotate these credentials

  • Ran soup push --hub modelscope or --hub modelers on 0.53.10-0.75.0 while logged in to Hugging
    Face or with HF_TOKEN set? Rotate your Hugging Face token at
    https://huggingface.co/settings/tokens.
  • Had a judge URL on 0.33.0-0.75.0 pointing anywhere other than api.openai.com while
    OPENAI_API_KEY was set? Rotate that key.
  • Passed --tool-auth-token, --auth-token or --api-key on 0.71.3-0.75.0 and collect your audit
    log anywhere? Rotate those tokens and purge the log.

Found by a full-repository review with OpenCodeReview, verified by the maintainer.

Known Limitations

  1. 26 tests fail on the maintainer's dev box and none of them is a regression. All are the
    NF4/bitsandbytes/streaming files, and they fail with identical IDs on a clean v0.75.0
    git-archive copy — the control that makes "not a regression" checkable. The underlying divergence
    is #776 (two bitsandbytes 4-bit compute paths disagreeing on sm_120); CI cannot see it because
    those modules are skipif(not torch.cuda.is_available()) and GitHub runners have no GPU.
  2. The POSIX SIGINT test for the Lambda controller runs on CI only. It needs a real process group
    and a real signal, so it is skipped on Windows. The platform-independent half (a stderr whose
    first write raises KeyboardInterrupt) runs everywhere.
  3. The Web UI token still lives in sessionStorage. The escaping work removes the sinks that
    could read it from the page and the CSP forbids inline script and eval, but the storage choice
    itself is unchanged.
  4. Three regex sinks still have no complexity check: eval/custom.py, utils/diagnose/format.py
    and utils/recipe_run.py. The check shipped where config-supplied patterns reach peft and the
    trainers; these three take patterns from other inputs and were deliberately left out of a
    security release rather than swept in.
  5. soup can run's extracted config still goes through the generic loader, bounded now by the
    pre-run read_config check, but the loader itself is unchanged.

Release record

  • Gate record: no gate. Nothing in this release was decided by a new measurement; every item is a
    correctness or hardening fix with its own test, so no file was added to benchmarks/. Stated here
    rather than skipped silently.
  • Preprint: unchanged. No measured number the preprint states moves, and its scope does not
    change — nothing here touches layer streaming, its mechanism or its architecture list.
  • Suite: the authoritative signal for the tagged tree is the CI matrix on d1b28dd2, 14/14 green.
    Locally the backport ran 21518 passed / 26 failed on Windows + Python 3.12; the same 26 fail with
    identical IDs on a clean v0.75.0 copy, which is the control that makes "zero regressions"
    checkable. 500 test files, 21734 collected (5 deselected), up from 475 / 21264 at v0.75.0.

Contributors

Every change in this patch release is the maintainer's own work, so there is no contributor list this
time. v0.75.0 — the release this one patches — carried 60 pull requests from 22 people outside the
maintainer, and they are credited in its notes.

Don't miss a new Soup release

NewReleases is sending notifications on new releases.