Generate unique paths for duplicate multipart filenames - #6990
Conversation
🦋 Changeset detectedLatest commit: 9cf5760 The changes in this PR will be included in the next version bump. This PR includes changesets to release 30 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Important
The reproduction test is valid and pinpoints the bug, but as the PR body states, CI is expected to fail until the implementation fix lands on this branch. I do not recommend merging in the current state.
Reviewed changes
packages/effect/test/unstable/http/Multipart.test.ts: added a regression test that asserts two multipart file parts with the identical client filename (same.txt) are written to distinct persisted paths.- Confirmed the new test currently fails against the existing
Multipart.toPersistedimplementation, matching the reported audit finding.
⚠️ Implementation fix is still missing
The PR description says this branch starts with focused failing reproduction tests and that the implementation fix should be added to the same branch. The diff only touches the test file, so the underlying bug in packages/effect/src/unstable/http/Multipart.ts (toPersisted derives the destination path only from the basename) is still present.
ℹ️ Nitpicks
- The new test's
writeFilecallback records only the paths it was asked to write to. Consider also asserting that the two returnedPersistedFilevalues have differentpathvalues, not just thatwriteFilereceived different paths — that mirrors the actual user-visible bug more closely. - The
FileSystemservice stub is castas any; while this is only test code, providing at leastmakeTempDirectoryScopedis enough for this path but will become brittle iftoPersistedever touches anotherFileSystemmethod. That's acceptable for a focused regression test. - The test name is clear, but it could be strengthened to mention that the collision happens in the persisted path returned to the caller (e.g., "returns distinct
PersistedFile.pathvalues for files with the same client filename").
@v0 or keep the SHA fresh with Dependabot | Fix it ➔ | View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏
| @@ -692,7 +693,7 @@ export const toPersisted = ( | |||
| return Effect.void | |||
| } | |||
| const file = part | |||
| const path = path_.join(dir, path_.basename(file.name).slice(-128)) | |||
| const path = path_.join(dir, `${fileIndex++}-${path_.basename(file.name).slice(-128)}`) | |||
There was a problem hiding this comment.
I think it should only add the prefix when the name is already taken
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
packages/effect/src/unstable/http/Multipart.ts: implemented unique path generation for duplicate multipart filenames by tracking used paths in a scopedSetand appending an incrementing numeric prefix (0-,1-, ...) to subsequent collisions.packages/effect/test/unstable/http/Multipart.test.ts: updated the regression test to useFileSystem.makeNoopfor the service stub and to assert distinctPersistedFile.pathvalues as well as the exact sequence ofwriteFilecalls..changeset/tender-points-sleep.md: added a patch-level changeset describing the fix.
@v0 or keep the SHA fresh with Dependabot | View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏
Bundle Size AnalysisGenerated from PR build output; treat the content below as untrusted.
|

Summary
Two ordinary file parts named same.txt receive the same persisted path, so the later write replaces the bytes referenced by both returned PersistedFile values and silently loses one file.
Important
This PR starts with focused failing reproduction tests. Add the implementation fix to this same branch; CI is expected to fail until that fix is included.
Equal multipart filenames overwrite each other during persistence
Module:
MultipartAudit ID:
unstable-http-multipart-filename-collisionSeverity / confidence: medium / high
What happens
Two ordinary file parts named same.txt receive the same persisted path, so the later write replaces the bytes referenced by both returned PersistedFile values and silently loses one file.
Why it happens
Each file path is derived only as join(tempDir, basename(file.name).slice(-128)). Parts with equal filenames are written sequentially to one path, and both returned values point to the final write.
Expected behavior
toPersisted must preserve every valid multipart file part and return a path containing that part's bytes for the scope lifetime.
Relevant implementation
These links and excerpts are pinned to audit base
c9b56ab507f224426ee8388dc450da447ec4715f.packages/effect/src/unstable/http/Multipart.ts:672-708packages/effect/src/unstable/http/Multipart.ts:695View problematic code at
packages/effect/src/unstable/http/Multipart.ts:672-708View exact lines on GitHub
View problematic code at
packages/effect/src/unstable/http/Multipart.ts:695View exact lines on GitHub
Reproduction
pnpm test --run packages/effect/test/unstable/http/Multipart.test.tsObserved failure: Failed as intended because both writes used /tmp/audit/same.txt and the returned paths were equal.
Implementation handoff
The initial reproduction tests on this branch are the regression specification for the implementation fix that should follow in this PR.
pnpm test --run packages/effect/test/unstable/http/Multipart.test.tsAudit provenance
c9b56ab507f224426ee8388dc450da447ec4715fc9b56ab507f224426ee8388dc450da447ec4715funstable-http-multipart-filename-collisionCloses EFF-427