fix(path): reject Windows reserved basenames in sanitizeUntrustedFileName - #67
fix(path): reject Windows reserved basenames in sanitizeUntrustedFileName#67SebTardif wants to merge 1 commit into
Conversation
…Name Reuse the device-path reserved-name set so untrusted names like CON, NUL.txt, and COM1 fall back instead of staging as Windows devices. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
|
Codex review: needs changes before merge. Reviewed August 1, 2026, 8:15 PM ET / August 2, 2026, 00:15 UTC. ClawSweeper reviewWhat this changesThe PR changes Merge readiness⛔ Blocked by patch quality or review findings - 8 items remain This PR has a concrete P1 compatibility regression: the released filename-sanitizer contract deliberately preserves Windows reserved basenames and documents caller-side opt-in rejection, while the branch silently changes all callers to receive the fallback. The previous finding remains unresolved on the same PR head, so this should stay open for a narrow repair or explicit maintainer approval of a breaking contract change. Priority: P2 Review scores
Verification
How this fits together
flowchart LR
A[Untrusted filename] --> B[Filename sanitizer]
B --> C[Portable basename policy]
C --> D[Staging and output helpers]
D --> E[Filesystem creation or replacement]
F[Caller-specific strict policy] --> C
Decision needed
Why: The branch reverses an explicit v0.5.0 public contract and changes output for all existing callers; choosing a permanent default is product and compatibility policy, not a mechanical security repair. Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Copy recommended automerge instructionTechnical reviewBest possible solution: Keep the existing narrow sanitizer default and its documented caller-side strict pattern; if the package should offer reserved-name rejection, add it as an explicitly chosen opt-in API or separately approved major compatibility change. Do we have a high-confidence way to reproduce the issue? Yes for the compatibility regression: on current Is this the best way to solve the issue? No; unconditionally changing the default is not the best path because the released contract deliberately leaves this policy to callers. Preserve that default or add an explicitly opt-in strict API after maintainer direction. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against ab933820c089. LabelsLabel changes:
Label justifications:
EvidenceAcceptance criteria:
What I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (18 earlier review cycles; latest 8 shown)
|
|
Maintainer-side reproduction supports CLOSE for the current proposal. On current On this PR's exact head ( That is not a bug repair within the existing contract; it is a global compatibility-policy change for every sanitizer and staging/output caller. The existing documented caller-side opt-in is the appropriate boundary unless a separately approved API/default redesign is undertaken. The branch is also stale/dirty against current No contributor revision is requested: the recommendation is to close this implementation and retain the documented default. |
|
Closing because the proposed default conflicts with the documented |
|
The contract discussion here led to an approved redesign. It has now been implemented fresh on top of current Commit: 5ca756d Review PR: #83 Thanks @SebTardif for prompting the contract discussion and the original portability idea. |
What Problem This Solves
Fixes an issue where consumers using
sanitizeUntrustedFileName()(via staging, external output, and sibling-temp helpers) would accept Windows reserved device basenames such asCON,NUL.txt, orCOM1from untrusted input. On Windows those names open devices rather than ordinary files, which breaks portable name sanitization already used for C0/C1 and Windows-invalid characters on every host.Why This Change Was Made
Reuse the reserved-name set and normalization already used by the device-path read guards (
isWindowsReservedDeviceBaseName) insidesanitizeUntrustedFileName(). Reserved basenames (including extensions and trailing spaces/dots) return the caller-supplied fallback. Non-goals: changing path resolution or read guards; only the untrusted basename sanitizer.User Impact
Callers that pass reserved basenames now receive the same fallback they already get for empty,
., or..names. Valid names such asconsole.txtandnull.pdfare unchanged. No public API signature changes.Evidence
Red / green
pnpm exec vitest run test/filename.test.tsfailed withexpected 'CON' to be 'fallback.bin'before the production change.pnpm check(598 passed, 22 skipped).Runtime sample (built
dist/)Checklist
CHANGELOG.mdupdated when release-relevantRelated
Real behavior proof
Behavior or issue addressed: Untrusted basenames that are Windows reserved devices must not pass through
sanitizeUntrustedFileName; they must fall back.Real environment tested: macOS arm64, Node from repo toolchain,
@openclaw/fs-safebranchfix/windows-reserved-basenamesafterpnpm build.Exact steps or command run after this patch:
Evidence after fix: reserved cases map to
fallback.bin; non-reservedconsole.txtkept; full check green.Observed result after fix:
sanitizeUntrustedFileName('CON', 'fallback.bin') === 'fallback.bin'and siblings; staging helpers inherit the safer basename policy.What was not tested: Live Windows NTFS create of reserved names (behavior is documented OS semantics; this PR blocks the name before open).