safepart
v1.1.0
Published
A secure, TypeScript-first multipart/form-data middleware for Express — a drop-in-friendly replacement for multer with safe defaults and no DoS footguns.
Maintainers
Readme
safepart
A secure, TypeScript-first multipart/form-data middleware for Express — built as a drop-in-friendly replacement for multer.
Why not just use multer?
multer is the de facto standard for file uploads in Express because nothing else fits Express's middleware model as cleanly. But it has shipped several real DoS vulnerabilities in 2026 alone:
- Deeply nested multipart field names with no depth/count limits, forcing excessive memory/CPU allocation.
- Malformed requests causing stack overflows.
- Dropped connections during upload leaking memory via unclosed streams.
And every limit in multer (fileSize, files, fields, parts, etc.) is unset by default — you only get protection if you remember to configure it yourself.
safepart fixes this by design, not by patching:
- Every limit is finite by default (see Default limits). There is no "unbounded" mode to forget to turn off.
- No nested-object field parsing. Field names are always flat
string -> stringpairs, which eliminates the entire class of nested-object DoS bugs by construction — there's no recursive structure to attack. - Every code path drains its stream. Rejected, filtered-out, and errored file parts are always resumed/drained before the middleware rejects, so busboy never stalls mid-parse and the underlying socket never leaks.
- Disk storage never trusts client input for filenames.
diskStoragewrites to a random filename by default;originalnameis never used to build a path unless you explicitly opt in. - TypeScript-first. Full type definitions ship in the package, no
@typespackage needed.
Install
npm install safepartQuick start
import express from "express";
import safepart, { isSafePartError } from "safepart";
const upload = safepart(); // memoryStorage by default
const app = express();
app.post("/avatar", upload.single("avatar"), (req, res) => {
// req.file.buffer, req.file.originalname, req.file.mimetype, req.file.size
res.json({ received: req.file?.originalname });
});
app.use((err, req, res, next) => {
if (isSafePartError(err)) {
return res.status(400).json({ code: err.code, field: err.field });
}
next(err);
});API
safepart(options?)
safepart({
storage: memoryStorage() | diskStorage({...}), // default: memoryStorage()
limits: { fileSize, files, fieldNameSize, fieldSize, fields, parts },
fileFilter: (req, file, callback) => void,
});Returns an object with:
.single(fieldname)— one file, exposed asreq.file.array(fieldname, maxCount?)— multiple files under one field, exposed asreq.files(array).fields([{ name, maxCount? }, ...])— multiple named fields, exposed asreq.files(object keyed by field name).none()— reject any file part; only parses text fields intoreq.body.any()— accept files under any field name (use sparingly; prefer explicit fields)
Non-multipart requests pass straight through to next() untouched.
Default limits
| Limit | Default |
| --------------- | --------- |
| fileSize | 10 MiB |
| files | 10 |
| fieldNameSize | 200 bytes |
| fieldSize | 1 MiB |
| fields | 100 |
| parts | 200 |
Override any of them via limits. There is intentionally no way to set a limit to "unlimited."
Errors
Every rejection is a SafePartError with a stable code:
LIMIT_FILE_SIZE, LIMIT_FILE_COUNT, LIMIT_FIELD_KEY, LIMIT_FIELD_VALUE, LIMIT_FIELD_COUNT, LIMIT_PART_COUNT, LIMIT_UNEXPECTED_FILE, MALFORMED_REQUEST.
Use isSafePartError(err) to narrow the type in an Express error-handling middleware.
Storage engines
import { memoryStorage, diskStorage } from "safepart";
memoryStorage(); // buffers each file into req.file.buffer
diskStorage({
destination: "/var/uploads", // string or (req, file) => string | Promise<string>
filename: (req, file) => `${file.fieldname}-${Date.now()}`, // optional; random hex by default
});Migrating from multer
The .single() / .array() / .fields() / .none() / .any() API surface matches multer intentionally. In most apps, swapping the import and adjusting error handling to check err.code (instead of err instanceof multer.MulterError plus string-matching err.code) is enough.
License
MIT
