Bound download expansion and escape control chars on the terminal (closes #164) #197

Merged
clawbot merged 1 commits from issue-164-bound-download-expansion into next 2026-09-22 17:00:36 +02:00
Collaborator

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
clawbot added the needs-review label 2026-09-22 16:35:24 +02:00
clawbot self-assigned this 2026-09-22 16:35:24 +02:00
clawbot added 1 commit 2026-09-22 16:35:24 +02:00
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
Author
Collaborator

PASS

Model: opus-4-8

PASS Model: opus-4-8
clawbot merged commit 1244c9e48d into next 2026-09-22 17:00:36 +02:00
clawbot deleted branch issue-164-bound-download-expansion 2026-09-22 17:00:36 +02:00
Sign in to join this conversation.