Soup Cans

A .can is a portable, verifiable recipe bundle — tar.gz of a manifest, the soup.yaml, and a data_ref. v0.26.0 ships soup can pack / inspect / verify / fork for sharing and forking fine-tunes without shipping checkpoints or training data directly.

Pack

bash
soup can pack \
  --entry-id chat-llama:v1 \
  --out chat-llama-v1.can

--entry-id takes a registry entry id, a unique id prefix or name:tag; the tag is separated by a colon, as in the registry, not by @. --author and --description fill those manifest fields (the author defaults to unknown), and --attest <statement.json> embeds an in-toto Statement in the manifest and can be repeated. soup can fork takes --author too.

The pack includes:

  • manifest.json — name, tag, parent, created-at, schema version
  • soup.yaml — exact training config
  • data_ref.json — a hash + URI pointing at the dataset (not the dataset itself)

Inspect

bash
soup can inspect chat-llama-v1.can

Prints manifest + config without extracting the archive.

Verify

bash
soup can verify chat-llama-v1.can

Checks schema version, manifest integrity, and whether the embedded soup.yaml still parses against the current Pydantic schema.

Fork and modify

bash
soup can fork chat-llama-v1.can \
  --out chat-llama-v2.can \
  --modify training.lr=3e-4 \
  --modify training.lora.r=32

Fork re-packs the archive with your overrides and a fresh manifest (new id, parent = original).

Safety

Tar extraction is the classic attack surface. Soup Cans neutralize it:

  • Rejects absolute paths and .. traversal
  • Rejects symlinks that point outside the extraction root
  • 100 MB cap on archive size, and since v0.75.1 the reads inside it are bounded too: manifest and config members are size-capped rather than read whole, extraction is bounded in both member count and total bytes, and an attestation's YAML is refused above an expanded-node limit before it is deserialised. Until then, reading an untrusted can through inspect, verify, fork, publish or run without --yes could exhaust memory, because the alias expansion was counted after the size cap rather than before it (0.26.0 to 0.75.0)
  • Manifest fields are escaped for terminal control sequences, not only for Rich markup. Before v0.75.1 a crafted manifest could rewrite the soup can run confirmation panel, which is the one screen whose whole job is telling you what you are about to allow
  • Format version was locked to 1 when this shipped. It is 3 at v0.74.0, and versions 1, 2 and 3 are all accepted on read; anything else is refused
  • dunder keys (__class__, __import__, etc.) and null bytes are rejected in fork --modify
  • Path containment uses shared os.path.realpath + commonpath so Windows short-name / junction tricks don't escape

See also

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.