Fixes #164. Objects fetched from the store are untrusted; several decode and print paths let one expand or corrupt the terminal without limit.
Decompression bounds. New blobgen.LimitReader yields at most N bytes then fails with ErrOutputTooLarge (unlike io.LimitReader's silent EOF, which a decoder can't distinguish from a truncated value). DecodeManifest reads through caps on both the compressed input and the decompressed output — far above any manifest the writer emits, but enough to stop a small, highly compressible manifest from expanding in json.Decode, which buffers the whole value. FetchAndDecryptBlob bounds a blob's decompression to the uncompressed_size recorded in the snapshot database (deliberately not the restoring host's blob_size_limit, which can differ from the backup host's).
Streaming.downloadSnapshotDB now decrypts straight from the storage stream and io.Copys the plaintext to its temp file, replacing two ReadAll calls that held the database in memory twice.
Dead round-trip.FetchBlob drops the per-blob Stat, its expectedSize parameter and returned size, which only fed a debug log; both callers discarded the size.
Terminal escaping.TTYHandler and ui.Writer escape control characters in messages, attribute keys/values, and rendered identifiers/paths before colour codes are applied (escaping the finished line would corrupt its own colour codes), so a crafted path, symlink target or error text cannot drive the terminal.
Tests: a highly-compressible object fails each reader within its bound; an ESC-and-newline value logged through both writers shows neither byte raw. make check green. Existing assertions unchanged; test call sites updated only for the two changed signatures.
Model: opus-4-8
Fixes https://git.eeqj.de/sneak/vaultik/issues/164. Objects fetched from the store are untrusted; several decode and print paths let one expand or corrupt the terminal without limit.
**Decompression bounds.** New `blobgen.LimitReader` yields at most N bytes then fails with `ErrOutputTooLarge` (unlike `io.LimitReader`'s silent EOF, which a decoder can't distinguish from a truncated value). `DecodeManifest` reads through caps on both the compressed input and the decompressed output — far above any manifest the writer emits, but enough to stop a small, highly compressible manifest from expanding in `json.Decode`, which buffers the whole value. `FetchAndDecryptBlob` bounds a blob's decompression to the `uncompressed_size` recorded in the snapshot database (deliberately not the restoring host's `blob_size_limit`, which can differ from the backup host's).
**Streaming.** `downloadSnapshotDB` now decrypts straight from the storage stream and `io.Copy`s the plaintext to its temp file, replacing two `ReadAll` calls that held the database in memory twice.
**Dead round-trip.** `FetchBlob` drops the per-blob `Stat`, its `expectedSize` parameter and returned size, which only fed a debug log; both callers discarded the size.
**Terminal escaping.** `TTYHandler` and `ui.Writer` escape control characters in messages, attribute keys/values, and rendered identifiers/paths *before* colour codes are applied (escaping the finished line would corrupt its own colour codes), so a crafted path, symlink target or error text cannot drive the terminal.
Tests: a highly-compressible object fails each reader within its bound; an ESC-and-newline value logged through both writers shows neither byte raw. `make check` green. Existing assertions unchanged; test call sites updated only for the two changed signatures.
Model: opus-4-8
Objects fetched from the store are untrusted; several decode paths let one
expand or print without limit.
- blobgen.LimitReader errors past a byte cap (not io.LimitReader's silent
EOF). DecodeManifest reads through caps on both compressed input and
decompressed output, far above any real manifest, so json.Decode cannot
buffer a compressible bomb. FetchAndDecryptBlob bounds decompression to
the blob's recorded uncompressed_size (not the restoring host's
blob_size_limit).
- downloadSnapshotDB streams straight from storage to its temp file with
io.Copy, replacing two ReadAll calls that held the whole database twice.
- FetchBlob drops the per-blob Stat round-trip, its expectedSize parameter
and returned size, all of which only fed a debug log.
- TTYHandler and ui.Writer escape control characters in messages,
attribute keys/values, and rendered identifiers/paths before colour
codes are applied, so a crafted value cannot drive the terminal.
Model: opus-4-8
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes #164. Objects fetched from the store are untrusted; several decode and print paths let one expand or corrupt the terminal without limit.
Decompression bounds. New
blobgen.LimitReaderyields at most N bytes then fails withErrOutputTooLarge(unlikeio.LimitReader's silent EOF, which a decoder can't distinguish from a truncated value).DecodeManifestreads through caps on both the compressed input and the decompressed output — far above any manifest the writer emits, but enough to stop a small, highly compressible manifest from expanding injson.Decode, which buffers the whole value.FetchAndDecryptBlobbounds a blob's decompression to theuncompressed_sizerecorded in the snapshot database (deliberately not the restoring host'sblob_size_limit, which can differ from the backup host's).Streaming.
downloadSnapshotDBnow decrypts straight from the storage stream andio.Copys the plaintext to its temp file, replacing twoReadAllcalls that held the database in memory twice.Dead round-trip.
FetchBlobdrops the per-blobStat, itsexpectedSizeparameter and returned size, which only fed a debug log; both callers discarded the size.Terminal escaping.
TTYHandlerandui.Writerescape control characters in messages, attribute keys/values, and rendered identifiers/paths before colour codes are applied (escaping the finished line would corrupt its own colour codes), so a crafted path, symlink target or error text cannot drive the terminal.Tests: a highly-compressible object fails each reader within its bound; an ESC-and-newline value logged through both writers shows neither byte raw.
make checkgreen. Existing assertions unchanged; test call sites updated only for the two changed signatures.Model: opus-4-8
PASS
Model: opus-4-8