fix: enforce maxFileSize/maxTotalFileSize on octet-stream uploads - #1113
Open
spokodev wants to merge 3 commits into
Open
fix: enforce maxFileSize/maxTotalFileSize on octet-stream uploads#1113spokodev wants to merge 3 commits into
spokodev wants to merge 3 commits into
Conversation
The octet-stream upload path wrote every chunk to disk without checking the documented maxFileSize/maxTotalFileSize limits, unlike the multipart path in _handlePart. Accumulate per-file and total sizes and abort via _error with the existing FormidableError codes when a cap is exceeded, mirroring the multipart implementation. The over-limit file is removed through the shared _error cleanup, so no partial bytes remain on disk.
Comment on lines
+4
to
+5
| import * as errors from "../FormidableError.js"; | ||
| import FormidableError from "../FormidableError.js"; |
There was a problem hiding this comment.
The two separate import statements can be merged into a single combined import, which is the pattern used throughout the codebase (e.g.
import FormidableError, * as errors from "./FormidableError.js" in Formidable.js).
Suggested change
| import * as errors from "../FormidableError.js"; | |
| import FormidableError from "../FormidableError.js"; | |
| import FormidableError, * as errors from "../FormidableError.js"; |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The octet-stream upload path does not enforce the documented
maxFileSize/maxTotalFileSizelimits, unlike the multipart path.Steps to reproduce
POST a 256KB body with
Content-Type: application/octet-streamto a form configured withmaxFileSize: 1024:ACTUAL:
errisundefined, one file is returned with size 262144, and the over-sized file is committed to disk viafile.end().EXPECTED:
err.code === 1016(biggerThanMaxFileSize), no file returned, and nothing left on disk. This matches how the multipart path already behaves.Root cause
The octet-stream plugin's
_parser.on("data", ...)handler insrc/plugins/octetstream.jswrites each chunk straight to the file with no size check, and it bypasses_handlePart()insrc/Formidable.jswhere the multipart size caps are enforced. As a result a raw octet-stream body of any size is accepted regardless of the configured limits.The README documents
maxFileSizeandmaxTotalFileSizeas limiting each file and the batch respectively, with defaults, and does not exempt octet-stream.Fix
Accumulate the per-file and running total sizes before each write and abort via
this._error(...)with the existingFormidableErrorcodesbiggerThanMaxFileSize/biggerThanTotalMaxFileSizewhen a cap is exceeded, mirroring_handlePart. In-limit uploads are unaffected. Because the octet-stream file is tracked inopenedFiles, the shared_errorcleanup callsfile.destroy(), which unlinks the partial file, so no bytes remain on disk (the same cleanup the multipart path relies on).Authority
CWE-770 (allocation without limits), plus the library's own documented contract and its multipart implementation, which enforces exactly these caps.
Tests
Added an integration case in
test/integration/octet-stream.test.js: a 256KB octet-stream body withmaxFileSize: 1024must be rejected with code 1016 and return no files. Verified it fails on the current source (the over-sized upload is accepted witherrnull) and passes with the fix, with the tmp directory left empty afterwards.Suite status: 92 passed / 3 skipped across 14 jest suites, and 11/11 node tests.
Greptile Summary
This PR closes a gap where
application/octet-streamuploads bypassed themaxFileSize/maxTotalFileSizelimits that the multipart path already enforces. The fix adds per-chunk size accounting in the octetstream plugin'sdatahandler, erroring out with the existingFormidableErrorcodes before any write occurs and relying on the shared_error()→file.destroy()cleanup path to unlink any partially-written file.src/plugins/octetstream.js: AccumulatesfileSizeandthis._totalFileSizebefore eachfile.write()call; aborts with error code 1016 (biggerThanMaxFileSize) or 1009 (biggerThanTotalMaxFileSize) when a limit is exceeded, mirroring the guard already present inFormidable._handlePart.test/integration/octet-stream.test.js: Adds an integration test confirming that a 256 KB body sent withmaxFileSize: 1024is rejected with error code 1016 and leaves no files in the result set.Confidence Score: 5/5
file.write()call, so no bytes reach disk once a limit fires.Formidable.write()already guards subsequent chunks afterthis.erroris set, making the handler's earlyreturnredundant but harmless._error()is idempotent,file.destroy()is invoked via the existingopenedFilescleanup loop, and theendfnguard (if (this.error) return) ensures the parser's "end" event — and thusfile.end()and the "file" emit — are never reached after an error. The integration test reproduces the documented failure scenario and confirms the fix.Important Files Changed
maxFileSizeandmaxTotalFileSizechecks before eachfile.write()call, using the correctFormidableErrorcodes and mirroring the cleanup path already used by_handlePart. Logic is sound:Formidable.write()guards subsequent chunks after an error, and_error()is idempotent, so neither double-write nor double-error is possible.maxFileSize: 1024. Exercises thebiggerThanMaxFileSizepath; thebiggerThanTotalMaxFileSize(1009) path added to the source remains untested, but that thread was already resolved by the maintainer.Sequence Diagram
sequenceDiagram participant Client participant Pipe as pipe (Transform) participant FJ as Formidable.write() participant Parser as OctetStreamParser participant DH as data handler participant File Client->>Pipe: chunk N Pipe->>FJ: datafn(buffer) FJ->>FJ: if (this.error) return ← guard after first error FJ->>Parser: _parser.write(buffer) [sync PassThrough] Parser->>DH: emit("data", buffer) DH->>DH: "fileSize += buffer.length" DH->>DH: "_totalFileSize += buffer.length" alt "fileSize > maxFileSize" DH->>FJ: _error(FormidableError 1016) FJ->>File: file.destroy() [unlinks partial file] FJ-->>Client: "emits "error" → callback(err, fields, {})" DH->>DH: return (no write) else "_totalFileSize > maxTotalFileSize" DH->>FJ: _error(FormidableError 1009) FJ->>File: file.destroy() FJ-->>Client: "emits "error" → callback(err, fields, {})" DH->>DH: return (no write) else within limits DH->>FJ: this.pause() DH->>File: file.write(buffer, cb) File-->>DH: cb() → this.resume() endReviews (3): Last reviewed commit: "Merge branch 'master' into fix/octetstre..." | Re-trigger Greptile