npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aria-framework/uploads

v0.4.0

Published

Aria App Framework — uploads module. The safe half of accepting a file over HTTP: the multer gate bound to a CSRF check that cannot be omitted, orphan cleanup on rejected requests, magic-byte verification, and MIME presets. The destination stays the app's

Readme

@aria-framework/uploads

The safe half of accepting a file over HTTP. The destination is not here and must not be — one consumer writes attachments to a filesystem, another writes documents into a field-encrypted database column, a third writes restore archives beside the data directory. What they share is everything before the bytes land and everything after a request is rejected.

const uploads = require('@aria-framework/uploads');

const attachments = uploads.createUploader({
  dir: config.uploadsDir,
  field: 'attachments',
  maxFiles: 20,
  maxBytes: 100 * 1024 * 1024,
  allow: uploads.PRESETS.ATTACHMENTS,
  prefix: 'att-',
  csrf: csrfUtil.doubleCsrfProtection,   // REQUIRED
  handledFlag: 'attachmentsHandled'
});

router.post('/reply', requirePermission('tickets.reply'), ...attachments.accept, handler);

Three mechanisms, each of which was a comment before it was code

1. CSRF runs after multer, and must still run. The token is inside the multipart body, so nothing can verify it until multer has parsed the body — which means the global CSRF gate has to skip multipart routes, and each such route has to check CSRF itself. One consumer documented that in a comment ending "adding a path here WITHOUT that second half opens a CSRF hole". A comment is not a mechanism. Here csrf is required and accept is the pair, in order, so a route cannot take the gate without the check.

The one legitimate exception is named, not implied. A route authenticated by a Bearer token has nothing for CSRF to prevent — a browser attaches a cookie automatically, but an attacker's page cannot make it send a JWT. Forcing such a route to pass a no-op would mean writing a lie that looks exactly like the bug this module exists to stop, so instead:

csrf: uploads.CSRF_NOT_NEEDED_BEARER_AUTH

A Symbol, so a stray default or a truthiness bug cannot become it, and greppable — one search finds every route that skips, with the justification in the name. If the route is authenticated by a cookie, this is the wrong constant and the answer is a real CSRF middleware.

2. A rejected request has already written the file. By the time CSRF refuses, multer has written up to maxBytes to disk, and the handler that would have cleaned up never runs. Repeat the request and the volume the app needs to keep running fills up. Cleanup is therefore flagged on res.on('finish'), which fires either way; the handler sets handledFlag once it owns the file.

3. The upload middleware never short-circuits. It always calls next() and reports failure on req.uploadError. Calling next(err) would skip the CSRF check after it, turning a rejected upload into an unauthenticated request.

When NOT to use this

If your route's CSRF token travels in a HEADER rather than the multipart body, you do not need this module's central guarantee. One consumer posts uploads with fetch() and an x-csrf-token header, so the global CSRF gate validates it before the route runs and multer parsing the body afterwards is harmless. That arrangement needs no exemption list and no re-applied check — it is simply better, and an app that can do it should.

If you can DERIVE the type from the bytes, do that instead of validating a claimed one. The same consumer reads the magic bytes and names the type itself, cross-checking the extension for the formats magic alone cannot separate (.doc and .docx are OLE and ZIP containers). It never stores a client-supplied type at all. verify() below is the weaker design: it checks that a claim is consistent with the content, which still lets an unrecognised file through if nothing declared it.

This module is for the case where the token is inside the multipart body, where files land on disk, or where both.

verify() is separate from the gate, and after it

The gate checks what the client said; verify() checks what it sent, because magic bytes cannot be read until the file exists. Pass @aria-framework/kit's fileSniff:

const { kept, rejected } = uploads.verify(req.files, { sniff: fileSniff });

It returns both rather than throwing: three good files and one bad should keep three, and the caller decides how to say so. Rejected files are removed immediately rather than left for a sweep.

The rest

  • sweep(dir, prefix, maxAgeMs) — orphans from attempts that never completed. Only files carrying the prefix, only when older than the age; a file that is not ours is never touched.
  • hashFile(path) — sha256, for an audit trail.
  • PRESETS — named MIME allow-lists (IMAGES, DOCUMENTS, AUDIO_VIDEO, ARCHIVES, ATTACHMENTS, PAPERWORK). A list, not a policy: pass your own set freely. allow: null means "anything", which is a legitimate choice for opaque binary rather than an omission.

Notes

multer is an optional peer, required lazily with a message that names the fix. memory: true keeps bytes in RAM for a database-column destination — do not reuse a disk path's limits there; 100 MB on disk is fine and 100 MB in memory is an OOM.

The client filename never lands on disk. It is a path-traversal primitive and an extension-spoofing one, so what is stored is prefix + 16 random bytes + a sanitised extension, with originalname kept for the audit.