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) #
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) #
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) #
{
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",
}));