Static Serve

Serving a file off disk is the one place where a request path becomes a filesystem path, so the handler resolves every request under its root and refuses anything that leaves it, whatever the path spells.

The bytes are sniffed and gated before they are sent. With no contentSafety option the handler takes the map b.guardAll.byExtension returns at the strict profile, so a file someone managed to write into the served directory does not become a delivered payload. Opting out is possible and is recorded in the audit trail with the reason.

That map covers the extensions the guard family has a content guard for: HTML and XHTML, XML, SVG, Markdown, plain text, JSON and JSON-LD, YAML, CSV, mail formats and the archive formats. An extension outside it, .js, .css and images other than SVG among them, is served without a content check, because there is no guard for those bytes to run. Supply a contentSafety entry for an extension that needs one.

Caching, conditional requests and byte ranges are answered from one metadata cache per file, which also supplies the Subresource Integrity digest integrity() hands a template. That digest is the file's. Where a contentSafety gate is configured to sanitize, the response carries the gate's bytes and their own digest instead, which each response reports in its X-Integrity header.

b.staticServe.attachmentDisposition(filename) #

stable0.20.32

Build the Content-Disposition header that serves a file as a download under a name a stranger chose. Returns the RFC 6266 section 4.3 pairing: a plain filename with the non-ASCII folded away for readers that predate the extension, and a filename* ext-value carrying the real name.

The ext-value is percent-escaped against RFC 8187 section 3.2.1 attr-char, which is not the set encodeURIComponent leaves alone: that function emits ', (, ) and * raw, and the apostrophe is the delimiter the ext-value is parsed on, so a name holding one reads back truncated at it.

A name carrying CR, LF or NUL, or an empty one, is served as download.

b.staticServe.attachmentDisposition("report (final).pdf");
// → attachment; filename="report (final).pdf"; filename*=UTF-8''report%20%28final%29.pdf

b.staticServe.integrity(absPath) #

stable0.1.63

Resolve with one file's Subresource Integrity value, as sha384-, ready to put in the integrity attribute of a script or link tag.

The digest comes from the same metadata cache the handler serves from, so a template asking for it does not re-hash a file the handler has already hashed.

It is the digest of the file on disk. A handler configured with a contentSafety gate whose action is sanitize serves the gate's bytes instead, and those hash differently, so an integrity attribute built from this value makes the browser reject the response. Every response carries the digest of the bytes it actually sent in its X-Integrity header, which is where to read the value when a gate may rewrite an asset. With no gate, or with a gate whose action is serve, audit-only or refuse, the served bytes are the file's and this value is the one the response carries.

An empty or non-string path raises static/bad-opt, and a path with no file behind it raises static/not-found, since an SRI attribute built from a missing file blocks the asset in the browser with nothing to explain it.

var b = require("@blamejs/core");
var sri = await b.staticServe.integrity("/srv/app/public/app.js");
sri;                         // → "sha384-…"

b.staticServe.create(opts) #

stable0.4.0
{
  root:              string,    // required — the directory served, resolved once
  mountPath:         string,    // default "" — the prefix stripped before resolution
  indexFile:         string,    // default "index.html"; null serves no directory index
  hashedPathPattern: RegExp,    // default: fingerprinted-asset shape, served immutable
  defaultMaxAge:     number,    // seconds — default 3600
  contentTypes:      object,    // extension → media type, over the built-in table
  contentSafety:     object,    // { ".ext": gate }; null opts out, undefined is default-on
  allowedFileTypes:  [string],  // sniffed types admitted; empty means no sniffing gate
  fileType:          object,    // b.fileType handle, required when allowedFileTypes is set
  permissions:       object,    // per-path authorization hook
  cache:             object,    // b.cache handle for stat + digest memoization
  audit:             b.audit,
  mountType:         string,    // "curated" (default) or "user-content"; anything else raises
                                // static/bad-opt. "user-content" is what makes
                                // forceAttachmentForNonText default on, so a mount serving
                                // uploads sends non-text as a download unless that is set
                                // explicitly
  contentSafetyDisabledReason: string,  // recorded as `reason` on the
                                // staticserve.content_safety.disabled audit event raised when
                                // contentSafety is turned off; defaults to
                                // "operator-explicit-opt-out", so the trail says the screen was
                                // disabled either way and this names why
  acceptRanges:      boolean,   // default true — sends Accept-Ranges and honors a Range request
  maxRangeBytes:     number,    // default 67108864 (64 MiB); a longer range is refused.
                                // Infinity serves any length
  maxBytesPerActorPerWindowMs:  number,  // default 0 (off) — per-actor byte budget inside
                                // bandwidthWindowMs
  maxBytesAllActorsPerWindowMs: number,  // default 0 (off) — the same budget across all actors
  bandwidthWindowMs: number,    // default 60000 — the window both byte budgets are counted in
  maxConcurrentDownloadsPerActor: number,  // default 0 (off) — in-flight responses one actor
                                // may hold
  maxIdleMs:         number,    // default 120000 — a response making no progress for this long
                                // is dropped
  auditSuccess:      boolean,   // default true — emit staticServe.serve.success per response
  auditFailures:     boolean,   // default true — emit staticServe.serve.failure per refusal
  forceAttachmentForNonText: boolean,  // sends non-text as a download; defaults on when
                                // mountType is "user-content", off otherwise
  safeAttachmentForRiskyMimes: boolean,  // default false — sends a risky media type as an
                                // attachment rather than inline
  safeRenderSvg:     boolean,   // default true — serves SVG inline; false sends it as a download
  safeRenderPdf:     boolean,   // default false — PDF is sent as a download unless this is set
  retention:         object,    // { isServable(absPath, ctx) } consulted per request; a false
                                // answer refuses the file
  revokeStore:       object,    // { isRevoked(key), revoke(key), unrevoke(key) } backing
                                // handle.revoke / handle.unrevoke
  observability:     b.observability,
  onServe:           async function ({ req, res, absPath, urlPath, size, sendBytes, range, headers, actor }),
                                // after the response headers are decided; a throw is audited as
                                // reason "onServe_threw"
  onError:           function ({ req, res, urlPath, absPath, status, code, actor }),
                                // every refusal, including the ones answered before the file is read
}

Build a static-file handler for one directory. Returns an Express-style (req, res, next) function that resolves a request path under root, confines it to that directory, sniffs and gates the bytes, and answers with caching validators and range support.

Content safety is wired on by default: with no contentSafety option the handler uses the map b.guardAll.byExtension({ profile: "strict" }) returns. That map holds the extensions the guard family has a content guard for, so an extension outside it, .js, .css and images other than SVG among them, is served without a byte check. contentSafety takes a map of extension to gate for anything else that needs one, and null opts out entirely, recording the reason in the audit trail.

app.use("/assets", b.staticServe.create({
  root:      "./public",
  mountPath: "/assets",
}));

Last updated 2026-10-12T14:21:56.879Z by seeder.