Skip to content

0.48.4 — a security pass

Latest

Choose a tag to compare

@Dim145 Dim145 released this 24 Sep 20:41
· 1 commit to main since this release

A security pass

A patch on top of 0.48.3: every open alert on the repository's Security tab, plus a manual review of the code. Upgrading is recommended, especially if you run a 3D-scan (splat) worker.

Fixed — scan storage trusted paths a worker could rewrite

The splat workers report their results by updating the scans row directly in Postgres, with their own credentials. The server then took two of that row's columns — where the scan's files live, and which file is the trained model — at face value, both when serving a scan and when deleting one.

So anything able to write that table could make the server stream, or delete, any object in the bucket: another user's invoices, their photos, the frames of someone else's scan. That takes database write access, which in practice means a compromised or hostile worker, so this is not reachable by an ordinary user of the app. Workers on hardware you don't fully control are the case to worry about.

A scan's files are now always located from the scan's own id, the same way they were laid out at creation; the columns are still written for the workers, but the server no longer believes them.

Two storage leaks close with it: deleting a scan from its page left a 3D scan's model and capture video in the bucket, and .m4v capture videos were never purged at all.

Fixed — dependencies

  • All eleven Dependabot alerts in the web client: adm-zip, sharp, browserslist, baseline-browser-mapping, fast-uri, vitest. The adm-zip symlink advisory still lists "no fix"; 0.6.1 does contain it, under a different commit than the one the advisory cites.
  • Two Rust vulnerabilities Dependabot never raised, found with cargo audit: rustls (TLS 1.3 handshake messages accepted across encryption levels — it carries every outbound TLS connection the server makes) and crossbeam-epoch. Plus two crates flagged unsound and four yanked ones.

Every Rust bump was surgical, and verified with the locked release build rather than a host check.

Hardening

None of these was exploitable as found; each removes a way a future change could make it so.

  • Pasted shop URLs. An orzgk wishlist URL was fetched exactly as pasted once its host checked out, so the port and scheme stayed the caller's to choose — enough to make the server probe ports on a third party. It's now rebuilt from its path, like product URLs already were.
  • Outbound address filter. User-chosen destinations (webhooks, ntfy, Apprise, push endpoints, MangaCollector servers) now also refuse 0.0.0.0/8, and IPv4 addresses embedded in IPv6 through NAT64, which on a NAT64 network can reach the cloud metadata service.
  • Links from third-party data. A crafted tracking link could reach the page as a javascript: URL. React 19 already neutralises those at render time, so this was never exploitable, but the app now enforces http(s) itself on every scraped or externally-sourced link rather than relying on that.
  • Headers. The Content-Security-Policy gains object-src 'none', and the last proxied route now overwrites a client-supplied X-Forwarded-For like all the others.
  • Supply chain. Every GitHub Action the build runs is pinned to a commit SHA, since a tag like @v4 can be moved by whoever controls the action, and the release workflow publishes these images.

The CodeQL alert (#9) was a false positive; its bound is now explicit in the code where the analyser can see it.


🔐 Security contract
🐳 ghcr.io/dim145/figurecollector-{server,client,docs}:0.48.4
📝 Full changelog: 0.48.3...0.48.4