Repository navigation
restore: refuse a Linux name's backslash on a Windows restore - #420
Conversation
A backslash is an ordinary character in a Linux or macOS name, and Windows reads it as a separator. reroot splits a snapshot path on `/` alone and hands each segment to join, which on Windows splits on `\` too and resolves any `..` it then finds. So restore --output of a Linux snapshot on Windows wrote back\slash.txt as a folder and a file, sent a\b/c.txt and a/b/c.txt to one file, and placed a\..\..\..\Windows\x.txt at C:\Windows\x.txt, over an existing file under --overwrite. The reader's ./.. check (ADR-0101) splits on `/` too, so nothing caught it. planRestore now refuses such a name, beside putsColonInName's refusal, but asked of the source path: by then dest has already resolved the `..`. Found by the Windows restore clean-room experiment (2026-10-16), whose restorer refused the names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code review (Opus, high): 8 findings. The first three compare.py findings were confirmed by running it on Windows (Python 3.14.5): each prints IDENTICAL and exits 0.
src/lib/restore.mjs:15 — "Only through --output" is false, here and in the new doc that copies the premise: a POSIX path is win32-absolute, so a plain restore on Windows reaches NTFS too. A Linux row /home/u/a:b.txt restored without --output writes into stream b.txt of a file a and is reported restored. Same root cause as the backslash escape, and the comment tells the next reader both refusals are complete.
src/commands/restore.mjs:375 — isRefusedName's doc still says only a : is refused up front. The PR updated the header comment but not this sentence, so the fact now lives in two places and this one is stale. Point at planRestore instead of restating it.
…in reroot Review of #420 found the first fix incomplete. Without --output, restore's absolute-path guard used isAbsolute, which on Windows takes a POSIX /home/... too, on the current drive, where a \ in its names is read as a separator: the escape was still open. On Windows only a Windows-shaped path now counts as absolute, so a Linux or macOS snapshot is refused up front and pointed at --output, as the reverse already was. The backslash check moves from planRestore into reroot, which sees only the names it joins: a \ in a folder above the backed-up root no longer refuses the whole set. reroot returns undefined for a name it can't place, and planRestore refuses it under the snapshot path rather than the resolved one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
On the two findings in the review body, both fixed in 42e2fcc:
|
On Windows,
restore --outputof a Linux or macOS snapshot could write outside the output folder.A backslash is an ordinary character in a Linux name, and Windows reads it as a separator.
rerootsplits a snapshot path on/alone and hands each segment tojoin, which on Windows splits on\too and resolves any..it then finds. The reader's./..check (pathFault, ADR-0101) also splits on/, so nothing caught it. Measured on Windows,reroot+planRestorefor--output C:\restore-out:/home/u/edge/back\slash.txtC:\restore-out\edge\back\slash.txt(a folder and a file)/home/u/edge/a\..\..\..\Windows\x.txtC:\Windows\x.txt/home/u/edge/plain.txtUnder
--overwritethe escaped write replaced an existing file, anda\b/c.txtanda/b/c.txtrestored to one file, one of them lost.Two guards close it:
rerootreturns no destination for a name holding\under a Windows output, checking only the names it joins, so a\in a folder above the backed-up root doesn't matter.planRestorerefuses such a name and lists it under its snapshot path.--output, Windows'isAbsolutetook/home/…too, on the current drive, so the escape was open there as well. On Windows only a Windows-shaped path now counts as absolute (restore.mjs): a plain restore of a Linux or macOS snapshot is refused up front and pointed at--output, as the reverse already was.Refused names are listed and the run exits non-zero, as for names Windows refuses.
guide/restore.mdsays so, andguide/format.md's restorer pitfalls say why\differs from characters that simply fail. The spec date is unchanged, so the clean-room stamp still matches.Found by the Windows restore clean-room experiment (2026-10-16), whose restorer refused these names. Each new test fails with its guard disabled. Not run:
test:integration, since this is the planner and the path guard, not the S3 read path.🤖 Generated with Claude Code