Skip to content

Policy

A policy defines what your backend accepts before the file is stored or processed. It is your first line of defense.

Common guards

  • Extension allowlist: only accept known‑good extensions (e.g. .zip, .png, .jpg, .pdf).
  • Max size: cap individual file size (e.g. 25 MB).
  • MIME sniff: prefer server‑side magic‑byte detection over client‑provided MIME.
  • (Optional) ZIP inspection: limit total entries, total uncompressed size, depth, and reject suspicious paths.

Minimal example (pseudo‑API)

ts
// policy.ts — example structure; adapt to your real helpers
export const basicPolicy = {
  rules: [
    ['allowExtensions', ['.zip', '.png', '.jpg', '.jpeg', '.pdf']],
    ['maxBytes', 25 * 1024 * 1024],
    ['sniffMime', true]
  ]
}

You then pass this policy into your adapter/middleware (Express/Koa/Fastify/Next.js).
See the Express example: /docs/quickstart-express.

Best practices

  • Allowlist, not blocklist: attackers constantly invent new bad types; it’s safer to only allow what you truly need.
  • Constrain archives: cap number of entries, total uncompressed bytes, nesting depth; reject paths with .. or absolute paths.
  • Verify MIME via magic bytes when possible; treat application/octet-stream with caution.
  • Stream when feasible: avoid buffering very large files in memory without limits.
  • Log decisions (allow/deny + reason) for auditability; return clear 4xx errors to clients.

Error handling

Return a 400 with a short reason when a policy fails. Keep internals (filenames, paths) out of public messages, but include enough context in your server logs.

ts
// express error handler example
app.use((err, req, res, next) => {
  if (err && err.name === 'PompelmiError') {
    return res.status(400).json({ ok: false, reason: err.message })
  }
  next(err)
})