Skip to content

fix(#379): Security (low): names containing U+FFFD are created by every write path but hidd - #391

Merged
BeSovereign merged 1 commit into
mainfrom
fix/issue-379
Oct 19, 2026
Merged

BeSovereign merged 1 commit into
mainfrom
fix/issue-379

Conversation

@BeSovereign

Copy link
Copy Markdown
Contributor

Refs #379

Home paths now refuse U+FFFD with PATH_INVALID through a new normalizeHomePath used by resolveInRoot and the whole local backend (PUT, tus, mkdir, move/copy/rename, WebDAV, transfers, archive import, share writes), and file-request visitor and uploader names drop it, so nothing can be planted that the listing, the ZIP export or the reindex hide; cloud sources keep such names addressable (adversarial review found the global regex broke cloud reads). The web's rename, new-folder, name-rules and migration name checks refuse U+FFFD through a shared hasUnsafeChar with the reworded inline message in all three locales; DECISIONS.md amended. Reviews run: adversarial (led to the home-only scope and the name-rules/migrate checks) and test adversary (no blocking findings; added on-disk and no-request assertions).

Driver gates (measured, not claimed)

  • Suites: typecheck: 1 passed, 0 failed | lint: 1 passed, 0 failed | unit: 1171 passed, 0 failed | e2e: 581 passed, 0 failed
  • Red/green check on declared tests: server/test/replacementChar.test.ts, server/test/fileRequests.test.ts, server/test/resolveInRoot.test.ts, shared/test/batchRename.test.ts, e2e/file-actions.spec.ts, e2e/new-folder.spec.ts
red/green output
RED without the change:
r-the-name-is-che-3b3f6-og-open-Esc-creates-nothing-mobile-chrome/trace.zip

    ────────────────────────────────────────────────────────────────────────────────────────────────

  4 failed
    [desktop-chrome] › e2e/file-actions.spec.ts:98:1 › a rename to a name with U+FFFD is refused in the dialog and never sent; the list keeps the old name (#379) 
    [desktop-chrome] › e2e/new-folder.spec.ts:62:1 › the name is checked; a taken name says so and keeps the dialog open; Esc creates nothing 
    [mobile-chrome] › e2e/file-actions.spec.ts:98:1 › a rename to a name with U+FFFD is refused in the dialog and never sent; the list keeps the old name (#379) 
    [mobile-chrome] › e2e/new-folder.spec.ts:62:1 › the name is checked; a taken name says so and keeps the dialog open; Esc creates nothing 


GREEN with the change:
nks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
$ node build.mjs


Running 4 tests using 1 worker

  ✓  1 [desktop-chrome] › e2e/file-actions.spec.ts:98:1 › a rename to a name with U+FFFD is refused in the dialog and never sent; the list keeps the old name (#379) (758ms)
  ✓  2 [desktop-chrome] › e2e/new-folder.spec.ts:62:1 › the name is checked; a taken name says so and keeps the dialog open; Esc creates nothing (778ms)
  ✓  3 [mobile-chrome] › e2e/file-actions.spec.ts:98:1 › a rename to a name with U+FFFD is refused in the dialog and never sent; the list keeps the old name (#379) (682ms)
  ✓  4 [mobile-chrome] › e2e/new-folder.spec.ts:62:1 › the name is checked; a taken name says so and keeps the dialog open; Esc creates nothing (809ms)

  4 passed (5.9s)

🤖 Generated with Claude Code

Home paths now refuse U+FFFD with PATH_INVALID through a new normalizeHomePath used by resolveInRoot and the whole local backend (PUT, tus, mkdir, move/copy/rename, WebDAV, transfers, archive import, share writes), and file-request visitor and uploader names drop it, so nothing can be planted that the listing, the ZIP export or the reindex hide; cloud sources keep such names addressable (adversarial review found the global regex broke cloud reads). The web's rename, new-folder, name-rules and migration name checks refuse U+FFFD through a shared hasUnsafeChar with the reworded inline message in all three locales; DECISIONS.md amended. Reviews run: adversarial (led to the home-only scope and the name-rules/migrate checks) and test adversary (no blocking findings; added on-disk and no-request assertions).

Refs #379

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@BeSovereign

Copy link
Copy Markdown
Contributor Author

Security review (claude-fable-5-1): no blocking finding. Fix is sound: all home writes pass through normalizeHomePath via LocalBackend.abs(); lone surrogates already rejected by isWellFormed()/toWellFormed(); sanitiser strips U+FFFD after toWellFormed(); tests fail when either guard is removed. Pre-existing U+FFFD ghosts on disk are not cleaned up (restore of such a trash row now fails PATH_INVALID); copyTree still carries unaddressable legacy names along in folder copies.

@BeSovereign
BeSovereign merged commit 4e490b7 into main Oct 19, 2026
3 checks passed
@BeSovereign
BeSovereign deleted the fix/issue-379 branch October 19, 2026 20:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant