You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Harden checkpoint mechanics: missing upload targets and unsafe filenames
Review follow-ups on the checkpoint upload/commit pipeline.
1. Withhold the commit when the server omits a blob's upload target.
`upload_gathered_snapshot` already anticipates a short `upload-snapshot`
response and only warns; the target-less blob then reached `upload_entry`
and was recorded as `EntryStatus::Skipped`. The checkpoint gate rejected
only `Failed`, so the attempt committed a silently smaller object set and
made it the selected checkpoint — discarding a previously complete one.
`Skipped` conflated two very different causes. Split out
`EntryStatus::NoTarget` for "server returned no presigned target" and
treat it as fatal for a checkpoint attempt, leaving `Skipped` to mean only
the deliberate `MAX_SNAPSHOT_FILES_PER_RUN` cap. Both still surface in the
manifest as `skipped` so rehydration consumers keep a stable status
vocabulary; the distinguishing detail stays in `error`. The legacy
end-of-run path is unchanged in behavior.
2. Sanitize agent-controlled filenames before they reach storage names.
`gather_repo` sanitized its filename component but `gather_file` used the
raw basename, which flows into `checkpoint_<generation>__<logical_name>`.
`__` is documented as the reserved separator and the charset as
`[A-Za-z0-9._-]`, but nothing enforced that for the filename half — and
basenames come from agent-created files. `a__b.txt`, or
`checkpoint_1700000000000-0__evil.txt`, produced an ambiguous storage
name that either fails the server's existence check at commit time
(losing the whole checkpoint) or lands under a different generation.
`sanitize_name_component` now collapses to the server charset and squashes
`_` runs so `__` can never appear; `gather_file` and
`sanitize_filename_component` both route through it, and `storage_name`
debug-asserts the invariant.
3. Smaller fixes.
- `CheckpointGeneration::from_validated` now actually validates (debug
assertion) instead of only claiming to in its name and docs.
- Move the "pending commit" log after the outcome check so it no longer
fires for attempts that failed to allocate targets.
- Correct the `CheckpointResult::Failed { generation: None }` doc: an
external timeout also reports `None` after a generation was minted, so
`None` must not be read as "nothing landed in storage".
Tests: commit is withheld when the server omits a blob target (chunked so
the truncation hits a blob rather than the always-last manifest);
sanitization never yields `__` or out-of-charset bytes; and an end-to-end
guard that a hostile basename cannot produce an ambiguous storage name.
Co-Authored-By: Oz <oz-agent@warp.dev>
0 commit comments