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) #

0.7.2
{
  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.