Security audit follow-ups on the DeepSeek Harness provider.
- **Decompression bomb.** Every zstd frame was decoded with no output bound, so
a 16 KB crafted log expanded to ~916 MB of RSS (a 65 KB one declares 2 GB).
Each frame now decodes under a 64 MB per-call cap, and the caps chain into a
running per-file budget of MAX_SESSION_FILE_BYTES: a frame is given only the
bytes the file has left, so node throws ERR_BUFFER_TOO_LARGE without
allocating past the cap. The throw propagates out of the existing skip path,
which discards the whole file rather than counting the frames read before the
bomb, so a crafted tail cannot poison a partial total. The discovery header
read takes the same per-frame cap. Measured on a 65 KB / 2 GB bomb: 916 MB
-> 67 MB peak, zero calls emitted, one notice.
Lines are still materialized eagerly; the byte budget bounds that, and making
the read lazy would change readEventLines' contract for no further bound.
- **Usage type confusion.** Token fields were read with `?? 0` and never
type-checked, so a string or array inputTokens flowed into the global totals
and the persisted cache, where `0 + [1, 2]` becomes "01,2". They now go
through numberOrZero (copilot.ts semantics: finite, positive, else 0).
All-zero calls are still skipped.
- **Snap over-scope.** The personal-files read entry is `$HOME/.dsh/sessions`
rather than all of `$HOME/.dsh`; the provider reads nothing else.
- **Third-party notice.** scanZstdFrames is transcribed from
@deepseek-ai/dsh-session-persistence-jsonl. The published npm package is
BSD-3-Clause (Copyright (c) 2026, DeepSeek) while the monorepo source
declares MIT for the same package; THIRD_PARTY_NOTICES.md reproduces the
stricter of the two and ships via package.json `files`.