This release fixes a security issue reported by @andrinoff. Please upgrade.
Security
Path traversal in Git LFS object IDs (GHSA-4h8q-g858-46w2, high)
Soft Serve builds the on-disk path of a Git LFS object directly from the
object ID sent by the client. That ID is expected to be a SHA-256 digest,
and the transfer library ships a validator that enforces exactly that, but
nothing called it before the value became a path. The storage layer then
joined the result to its root without confining it, and passed absolute
paths through untouched.
An object ID of ../../../../etc/passwd therefore escaped the LFS storage
directory. Over the SSH git-lfs-transfer service a download returns that
file to the client, with no hash verification on the way out, exposing the
SSH host key, the JWT signing key, the database, and every other repository
including private ones. Reading the host key and the signing key is enough
to impersonate the server and mint tokens for any user, so an affected
instance should be treated as fully compromised rather than merely read.
An upload could also create a file in any directory the process can write
to, with contents the caller controls. The name is only partly controlled:
the body is staged under a random suffix and the rename to the final path
is never reached, because the staged write fails a hash check first. This
is file creation, not overwrite of a chosen file.
The SSH transfer service is off by default, and both the arbitrary read and
the arbitrary write require lfs.ssh_enabled = true. Instances left on the
default were exposed only through two HTTP endpoints that called Exists
and Stat on the unvalidated path before their own validation ran,
disclosing whether an arbitrary path exists and, in one case, its size.
HTTP download and upload were never affected, since their routes constrain
the object ID in the URL pattern itself.
Object IDs are now validated on entry to every LFS backend method and in
both HTTP handlers, before anything reaches the filesystem. The storage
layer also rejects absolute paths and confines every resolved path to its
root, so a future caller that forgets to validate still cannot escape.
Authorization was never bypassed. Read access was always required to
download and write access to upload; the flaw let a caller reuse access it
already held on one repository to reach files outside it. Affected versions
are v0.7.1 through v0.12.1.
Anyone who ran an affected version with lfs.ssh_enabled = true should
rotate the SSH host key and the JWT signing key, since both were readable
by any user with access to a single repository.
Fixed
charm.land/ssh is updated to v0.4.3, which fixes a data race between the
handshake deadline being cleared and the connection deadline being read on
every packet. Servers running with an idle timeout, which is the default,
could see it reported under -race.
Changelog
Other stuff
- 33a3661: Merge commit from fork (@andrinoff)
- db5f041: v0.12.2 (@taciturnaxolotl)
Verifying the artifacts
First, download the checksums.txt file and the checksums.txt.sigstore.json file files, for example, with wget:
wget 'https://github.com/charmbracelet/soft-serve/releases/download/v0.12.2/checksums.txt'
wget 'https://github.com/charmbracelet/soft-serve/releases/download/v0.12.2/checksums.txt.sigstore.json'Then, verify it using cosign:
cosign verify-blob \
--certificate-identity 'https://github.com/charmbracelet/meta/.github/workflows/goreleaser.yml@refs/heads/main' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
--bundle 'checksums.txt.sigstore.json' \
./checksums.txtIf the output is Verified OK, you can safely use it to verify the checksums of other artifacts you downloaded from the release using sha256sum:
sha256sum --ignore-missing -c checksums.txtDone! You artifacts are now verified!
Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.