File Upload
Streaming multipart upload with content-safety guards wired on by default. Init / acceptChunk / finalize lifecycle: operator calls init to allocate per-upload staging, streams chunks via acceptChunk (each carrying its own SHA3-512 hex), then calls finalize with a manifest so the framework can verify per-chunk + total hash, sniff magic bytes against an allowedFileTypes allowlist, and hand off to the operator's onFinalize (buffer for small uploads, Readable stream above maxStreamReassemblyBytes).
Default-on safety: b.guardAll.byExtension({ profile: "strict" }) for content gating and b.guardFilename.gate({ profile: "strict" }) for filename gating. Operators opt out via contentSafety: null / filenameSafety: null (audited at create time so a security review can find the disabled-on-deploy rows). The byte-level content gate inspects the reassembled buffer, so it runs on uploads up to maxStreamReassemblyBytes (default 64 MiB); a larger upload is handed to onFinalize as a stream and the byte-content gate is skipped (MIME-sniff + filename gates still run). Every skip path — the upload streamed past the reassembly cap, no gate is registered for the file's extension, or contentSafety: null disabled scanning — emits a fileUpload.content_safety_skipped audit whose reason names the cause, so a security review of the audit log can tell which uploads reached storage without a content scan and why. To guarantee content-gating of a type, cap maxFileBytes at or below maxStreamReassemblyBytes. Per-chunk hooks (onChunk) are the integration point for virus scanners and schema-shape checks; rejecting from the hook surfaces as a permanent FileUploadError.
Quotas: maxFileBytes, maxChunkBytes, maxStagingBytes, maxActiveUploadsPerActor, maxChunks, incompleteTtlMs, maxIdleMs. purgeIncomplete() reclaims TTL'd / idle staging directories — operators wire it to b.scheduler for a cron-shaped sweep. Permission scopes (fileUpload.init / accept / finalize / status / list / cancel) are checked through b.permissions when wired.
b.fileUpload.create(opts) #
{
stagingDir: string, // absolute path; created mode 0o700 if missing
maxFileBytes: number, // default 2 GiB
maxChunkBytes: number, // default 8 MiB
maxStreamReassemblyBytes: number, // above this finalize streams; default 64 MiB
maxStagingBytes: number, // default 50 GiB
maxActiveUploadsPerActor: number, // default 16
maxChunks: number, // default 16384
incompleteTtlMs: number, // since createdAt; default 24h
maxIdleMs: number, // since lastChunkAt; default 30m
allowedFileTypes: string[], // MIME allowlist; "image/*" wildcard supported
audit: b.audit,
observability: b.observability,
permissions: b.permissions, // optional; gates init/accept/finalize/status/list/cancel
allowCrossActor: boolean, // default false; admin escape hatch — bypasses per-upload ownership when the caller holds the "fileUpload.admin" scope
fileType: b.fileType, // required when allowedFileTypes is non-empty
contentSafety: Object | null, // ext→gate map; null = audited opt-out; undefined = b.guardAll.byExtension({ profile: "strict" })
filenameSafety: Object | null, // gate; null = audited opt-out; undefined = b.guardFilename.gate({ profile: "strict" })
onChunk: async function (info), // optional per-chunk hook
onFinalize: async function (info), // operator decides final storage
clock: function () → number, // test-fixture clock; default Date.now
}
Builds an upload manager bound to opts.stagingDir. The returned object exposes init, acceptChunk, finalize, status, list, cancelUpload, purgeIncomplete, and close. Uploads are written chunk-per-file under a per-upload directory (mode 0o700); finalize walks the manifest in order, verifies per-chunk + total SHA3-512, runs the magic-byte allowlist (when allowedFileTypes is set), and hands the assembled buffer (or a stream above maxStreamReassemblyBytes) to the operator's onFinalize.
Per-chunk and per-upload audits flow through the wired audit and observability instances. Quota refusals, hash mismatches, MIME-claim disagreement, filename-safety refusal, and content-safety refusal all throw FileUploadError with permanent: true — no retry succeeds.
var uploads = b.fileUpload.create({
stagingDir: "/var/lib/myapp/uploads",
maxFileBytes: C.BYTES.gib(2),
allowedFileTypes: ["image/png", "image/jpeg", "application/pdf"],
fileType: b.fileType,
audit: b.audit,
observability: b.observability,
onFinalize: async function (info) {
// → info.body / info.stream → operator's storage layer
return { ok: true, sha3: info.sha3, size: info.size };
},
});
await uploads.init({ uploadId: "u-1", actor: { id: "ada" }, metadata: { filename: "photo.png" } });
// → { uploadId: "u-1", createdAt: 1762560000000, expiresAt: 1762646400000 }
Last updated 2026-08-08T16:39:49.652Z by seeder.