~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/knotless

git that lives in your Atmosphere account

knotless.dev
1 collaborator

knotless 0.13.0 Latest 2 packages, 1 image

1
10

Release files are read only by a hash that is a SHA-256

Merged
Merged chadtmiller.com asked to merge into main from chadtmiller.com/knotless:serve/release-hash

What changes

knotless-serve reads a release's file only when the file's sha256 in the release record is 64 lowercase hex digits. Otherwise it answers 502 without touching the disk. knotless::release::valid_sha256 is the check.

Why

A release's files are kept under the cache by their hash, as kept.join(sha256), and the file is fetched into .{sha256}.{n} beside it. The hash comes from a record the repository's owner wrote, and nothing checked it.

  • Read: a record whose file names ../../../../etc/passwd, padded to 64 characters, made try_exists true for that path, so serve streamed the host's file back as the release's.
  • Write: a hash such as ./../x wrote the fetch's temporary file outside the cache.

The review of the pages serving pull found this in its copy of the code, and that copy is fixed there. This is the same fix for releases, which are live now.

The check is at the one place a hash becomes a path. The OCI registry names what it keeps by the hash it measured itself, so it is not affected.

How it was checked

A unit test in releases.rs refuses a traversal hash before any path is built. The existing release tests still read files by their real hashes. CI builds and runs it.

Shortcomings

A release with such a file is still listed. Only reading the file is refused.

Changeset: none

🤖 Generated with Claude Code

Approved

This adds release::valid_sha256 (exactly 64 lowercase hex digits) and makes Records::fetch refuse any other hash before it builds a path. serve answers 502 without touching the disk.

I checked the points that matter:

  • Both holes are closed. The check runs before self.kept.join(hash) and before the .{sha256}.{n} temp name. Both uses read the same validated hash, so the read and the write paths are covered.
  • No other path uses a record's hash. kept_dir is used only by releases.rs and by oci.rs, and the OCI side builds its file entries from a hash it measured itself. The sha256: name.into() entries in the oci.rs tests only go into Staged, never into fetch. The .sha256 text and the etag only print the value and build no path.
  • Real hashes still work. release::sha256 formats with {:x}, which is lowercase 64-digit hex, so the existing release tests are unaffected.
  • The new test is valid. The padded ../../../../etc/passwd value is 64 characters long, so it fails on the hex check and not on length. It shows the new check is what refuses the value.
  • Changeset. crates/knotless/src changes and the body has Changeset: none on its own line. That is reasonable, since the change is server-side.

The test, check and e2e runs were still queued when I looked. The pull needs them to pass, and I'm not withholding approval on them.

Merged as 383d652