@blamejs/core 0.7.4 → 0.7.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +423 -395
- package/README.md +150 -149
- package/bin/blamejs.js +0 -0
- package/index.js +308 -284
- package/lib/api-key.js +660 -663
- package/lib/api-snapshot.js +338 -338
- package/lib/app-shutdown.js +385 -385
- package/lib/app.js +365 -365
- package/lib/archive.js +250 -250
- package/lib/atomic-file.js +544 -544
- package/lib/audit-chain.js +177 -177
- package/lib/audit-sign.js +344 -344
- package/lib/audit-tools.js +677 -677
- package/lib/audit.js +766 -766
- package/lib/auth/jwt.js +311 -311
- package/lib/auth/lockout.js +436 -436
- package/lib/auth/oauth.js +721 -721
- package/lib/auth/passkey.js +181 -181
- package/lib/auth/password.js +594 -594
- package/lib/backup/bundle.js +217 -217
- package/lib/backup/crypto.js +176 -176
- package/lib/backup/index.js +515 -515
- package/lib/backup/manifest.js +282 -282
- package/lib/break-glass.js +1338 -1338
- package/lib/bundler.js +441 -441
- package/lib/cache-redis.js +256 -256
- package/lib/cache.js +1206 -1206
- package/lib/canonical-json.js +115 -115
- package/lib/chain-writer.js +234 -234
- package/lib/cli-helpers.js +206 -206
- package/lib/cli.js +2334 -2334
- package/lib/cluster-provider-db.js +317 -317
- package/lib/cluster-storage.js +226 -226
- package/lib/cluster.js +703 -703
- package/lib/codepoint-class.js +196 -0
- package/lib/config-drift.js +301 -301
- package/lib/consent.js +222 -222
- package/lib/constants.js +191 -191
- package/lib/cookies.js +315 -315
- package/lib/credential-hash.js +322 -322
- package/lib/crypto.js +266 -266
- package/lib/csv.js +275 -286
- package/lib/db-declare-row-policy.js +267 -267
- package/lib/db-declare-view.js +420 -421
- package/lib/db-query.js +406 -406
- package/lib/db-schema.js +319 -319
- package/lib/db.js +1288 -1288
- package/lib/deprecate.js +222 -222
- package/lib/dev.js +335 -335
- package/lib/dual-control.js +473 -473
- package/lib/error-page.js +420 -420
- package/lib/external-db-migrate.js +441 -441
- package/lib/external-db.js +1061 -1061
- package/lib/file-type.js +273 -273
- package/lib/file-upload.js +213 -10
- package/lib/forms.js +422 -422
- package/lib/framework-error.js +293 -215
- package/lib/framework-schema.js +717 -717
- package/lib/gate-contract.js +971 -0
- package/lib/guard-all.js +405 -0
- package/lib/guard-archive.js +739 -0
- package/lib/guard-csv.js +816 -0
- package/lib/guard-email.js +744 -0
- package/lib/guard-filename.js +724 -0
- package/lib/guard-html.js +976 -0
- package/lib/guard-json.js +729 -0
- package/lib/guard-markdown.js +586 -0
- package/lib/guard-svg.js +976 -0
- package/lib/guard-xml.js +405 -0
- package/lib/guard-yaml.js +529 -0
- package/lib/handlers.js +350 -350
- package/lib/http-client-cookie-jar.js +508 -508
- package/lib/http-client.js +1195 -1195
- package/lib/i18n.js +878 -878
- package/lib/jobs.js +185 -185
- package/lib/log-stream-cloudwatch.js +369 -369
- package/lib/log-stream-local.js +146 -146
- package/lib/log-stream-otlp-grpc.js +410 -410
- package/lib/log-stream-otlp.js +286 -286
- package/lib/log-stream-syslog.js +302 -302
- package/lib/log-stream-webhook.js +199 -199
- package/lib/log-stream.js +330 -330
- package/lib/log.js +500 -500
- package/lib/mail-bounce.js +528 -528
- package/lib/mail-dkim.js +369 -362
- package/lib/mail.js +981 -962
- package/lib/metrics.js +683 -683
- package/lib/middleware/api-encrypt.js +936 -936
- package/lib/middleware/attach-user.js +157 -157
- package/lib/middleware/body-parser.js +1170 -1091
- package/lib/middleware/bot-guard.js +178 -178
- package/lib/middleware/compression.js +452 -452
- package/lib/middleware/cors.js +314 -314
- package/lib/middleware/csp-nonce.js +348 -348
- package/lib/middleware/csrf-protect.js +316 -316
- package/lib/middleware/db-role-for.js +264 -264
- package/lib/middleware/health.js +392 -392
- package/lib/middleware/index.js +79 -79
- package/lib/middleware/rate-limit.js +358 -358
- package/lib/middleware/request-id.js +61 -61
- package/lib/middleware/request-log.js +168 -168
- package/lib/middleware/require-auth.js +104 -104
- package/lib/middleware/security-headers.js +116 -116
- package/lib/middleware/sse.js +166 -166
- package/lib/migrations.js +383 -383
- package/lib/mtls-ca.js +518 -518
- package/lib/mtls-engine-default.js +481 -481
- package/lib/network-dns.js +632 -632
- package/lib/network-heartbeat.js +290 -290
- package/lib/network-nts.js +574 -574
- package/lib/network-proxy.js +265 -265
- package/lib/network-tls.js +328 -328
- package/lib/network.js +233 -233
- package/lib/notify.js +612 -612
- package/lib/ntp-check.js +229 -229
- package/lib/numeric-bounds.js +111 -91
- package/lib/object-store/azure-blob-bucket-ops.js +349 -349
- package/lib/object-store/azure-blob.js +488 -488
- package/lib/object-store/gcs-bucket-ops.js +351 -351
- package/lib/object-store/gcs.js +519 -519
- package/lib/object-store/http-put.js +153 -153
- package/lib/object-store/index.js +197 -197
- package/lib/object-store/sigv4-bucket-ops.js +1092 -1092
- package/lib/object-store/sigv4.js +903 -903
- package/lib/observability.js +151 -151
- package/lib/otel-export.js +269 -269
- package/lib/pagination.js +464 -464
- package/lib/parsers/index.js +80 -80
- package/lib/parsers/safe-env.js +642 -642
- package/lib/parsers/safe-ini.js +292 -292
- package/lib/parsers/safe-toml.js +784 -784
- package/lib/parsers/safe-xml.js +390 -390
- package/lib/parsers/safe-yaml.js +1015 -1015
- package/lib/permissions.js +708 -708
- package/lib/pqc-agent.js +87 -87
- package/lib/pqc-gate.js +279 -279
- package/lib/protobuf-encoder.js +190 -190
- package/lib/protocol-dispatcher.js +161 -161
- package/lib/pubsub-redis.js +167 -167
- package/lib/pubsub.js +429 -429
- package/lib/queue-local.js +476 -476
- package/lib/queue-redis.js +745 -745
- package/lib/queue-sqs.js +319 -319
- package/lib/queue.js +695 -695
- package/lib/redis-client.js +519 -519
- package/lib/request-helpers.js +340 -340
- package/lib/restore-bundle.js +237 -237
- package/lib/restore-rollback.js +259 -259
- package/lib/restore.js +409 -409
- package/lib/retry.js +376 -376
- package/lib/router.js +748 -748
- package/lib/safe-async.js +735 -735
- package/lib/safe-buffer.js +237 -237
- package/lib/safe-json.js +541 -541
- package/lib/safe-schema.js +1266 -1266
- package/lib/safe-url.js +159 -159
- package/lib/scheduler.js +706 -706
- package/lib/security-assert.js +373 -373
- package/lib/seeders.js +618 -618
- package/lib/session.js +478 -478
- package/lib/slug.js +269 -269
- package/lib/ssrf-guard.js +401 -401
- package/lib/static.js +184 -4
- package/lib/storage.js +471 -471
- package/lib/subject.js +281 -281
- package/lib/template.js +791 -791
- package/lib/testing.js +798 -798
- package/lib/time.js +310 -310
- package/lib/totp.js +302 -302
- package/lib/tracing.js +494 -494
- package/lib/uuid.js +132 -132
- package/lib/validate-opts.js +340 -319
- package/lib/vault/index.js +308 -308
- package/lib/vault/rotate.js +784 -784
- package/lib/vault/wrap.js +296 -296
- package/lib/vendor/noble-ciphers.cjs +9 -9
- package/lib/webhook.js +595 -595
- package/lib/websocket.js +1048 -1048
- package/package.json +77 -77
- package/sbom.cyclonedx.json +7 -7
package/lib/file-upload.js
CHANGED
|
@@ -155,6 +155,8 @@ var stream = require("node:stream");
|
|
|
155
155
|
var atomicFile = require("./atomic-file");
|
|
156
156
|
var C = require("./constants");
|
|
157
157
|
var crypto = require("./crypto");
|
|
158
|
+
var gateContract = require("./gate-contract");
|
|
159
|
+
var lazyRequire = require("./lazy-require");
|
|
158
160
|
var numericBounds = require("./numeric-bounds");
|
|
159
161
|
var requestHelpers = require("./request-helpers");
|
|
160
162
|
var safeBuffer = require("./safe-buffer");
|
|
@@ -162,6 +164,12 @@ var safeJson = require("./safe-json");
|
|
|
162
164
|
var validateOpts = require("./validate-opts");
|
|
163
165
|
var { FileUploadError } = require("./framework-error");
|
|
164
166
|
|
|
167
|
+
// guard-* family is wired on by default; lazy-loaded to avoid eager
|
|
168
|
+
// import cycles (guards consume framework primitives that may not be
|
|
169
|
+
// resolved at file-upload load-time).
|
|
170
|
+
var guardAll = lazyRequire(function () { return require("./guard-all"); });
|
|
171
|
+
var guardFilename = lazyRequire(function () { return require("./guard-filename"); });
|
|
172
|
+
|
|
165
173
|
var _err = FileUploadError.factory;
|
|
166
174
|
|
|
167
175
|
var DEFAULTS = Object.freeze({
|
|
@@ -213,16 +221,10 @@ function _validateCreateOpts(opts) {
|
|
|
213
221
|
}
|
|
214
222
|
validateOpts.optionalFunction(opts.onFinalize, "fileUpload.create: onFinalize", FileUploadError);
|
|
215
223
|
validateOpts.optionalFunction(opts.onChunk, "fileUpload.create: onChunk", FileUploadError);
|
|
216
|
-
numericBounds.
|
|
217
|
-
"
|
|
218
|
-
|
|
219
|
-
"fileUpload.create
|
|
220
|
-
numericBounds.requirePositiveFiniteIntIfPresent(opts.maxStreamReassemblyBytes,
|
|
221
|
-
"fileUpload.create: maxStreamReassemblyBytes", FileUploadError, "BAD_OPT");
|
|
222
|
-
numericBounds.requirePositiveFiniteIntIfPresent(opts.maxStagingBytes,
|
|
223
|
-
"fileUpload.create: maxStagingBytes", FileUploadError, "BAD_OPT");
|
|
224
|
-
numericBounds.requirePositiveFiniteIntIfPresent(opts.maxActiveUploadsPerActor,
|
|
225
|
-
"fileUpload.create: maxActiveUploadsPerActor", FileUploadError, "BAD_OPT");
|
|
224
|
+
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
225
|
+
["maxFileBytes", "maxChunkBytes", "maxStreamReassemblyBytes",
|
|
226
|
+
"maxStagingBytes", "maxActiveUploadsPerActor"],
|
|
227
|
+
"fileUpload.create", FileUploadError, "BAD_OPT");
|
|
226
228
|
numericBounds.requireNonNegativeFiniteIntIfPresent(opts.incompleteTtlMs,
|
|
227
229
|
"fileUpload.create: incompleteTtlMs", FileUploadError, "BAD_OPT");
|
|
228
230
|
numericBounds.requireNonNegativeFiniteIntIfPresent(opts.maxIdleMs,
|
|
@@ -247,6 +249,37 @@ function _validateCreateOpts(opts) {
|
|
|
247
249
|
validateOpts.optionalObjectWithMethod(opts.permissions, "check",
|
|
248
250
|
"fileUpload.create: permissions", FileUploadError, "BAD_OPT",
|
|
249
251
|
"must be a b.permissions instance (check fn)");
|
|
252
|
+
// contentSafety — extension-keyed gate map for per-extension content
|
|
253
|
+
// validation. Default behaviour: when undefined, the framework wires
|
|
254
|
+
// b.guardAll.byExtension({ profile: "strict" }) automatically so every
|
|
255
|
+
// shipped guard is ON by default. Explicit opt-out: contentSafety:
|
|
256
|
+
// null (audited at create() time so a security review can reconstruct
|
|
257
|
+
// which deploys disabled the default-on protection).
|
|
258
|
+
// Example: contentSafety: { ".csv": b.guardCsv.gate({ profile: "strict" }) }
|
|
259
|
+
if (opts.contentSafety !== undefined && opts.contentSafety !== null) {
|
|
260
|
+
validateOpts.optionalPlainObject(opts.contentSafety,
|
|
261
|
+
"fileUpload.create: contentSafety", FileUploadError, "BAD_OPT",
|
|
262
|
+
"must be a plain { ext: gate } object, null to opt out, or " +
|
|
263
|
+
"undefined for the default-on b.guardAll wiring");
|
|
264
|
+
var safetyKeys = Object.keys(opts.contentSafety);
|
|
265
|
+
for (var sk = 0; sk < safetyKeys.length; sk++) {
|
|
266
|
+
var ext = safetyKeys[sk];
|
|
267
|
+
var g = opts.contentSafety[ext];
|
|
268
|
+
if (!g || typeof g.check !== "function") {
|
|
269
|
+
throw _err("BAD_OPT",
|
|
270
|
+
"fileUpload.create: contentSafety[" + JSON.stringify(ext) +
|
|
271
|
+
"] must be a gate (b.guardCsv.gate / b.guardHtml.gate / etc.)");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// filenameSafety — single gate for filename validation. Default: on.
|
|
276
|
+
// Operator opts out with filenameSafety: null (audited).
|
|
277
|
+
if (opts.filenameSafety !== undefined && opts.filenameSafety !== null) {
|
|
278
|
+
validateOpts.optionalObjectWithMethod(opts.filenameSafety, "check",
|
|
279
|
+
"fileUpload.create: filenameSafety", FileUploadError, "BAD_OPT",
|
|
280
|
+
"must be a gate (b.guardFilename.gate(...)), null to opt out, or " +
|
|
281
|
+
"undefined for the default-on wiring");
|
|
282
|
+
}
|
|
250
283
|
}
|
|
251
284
|
|
|
252
285
|
function create(opts) {
|
|
@@ -257,6 +290,74 @@ function create(opts) {
|
|
|
257
290
|
var onChunk = opts.onChunk || null;
|
|
258
291
|
var fileType = opts.fileType || null;
|
|
259
292
|
var permissions = opts.permissions || null;
|
|
293
|
+
// ---- Default-on safety wiring ----
|
|
294
|
+
// contentSafety: undefined → wire b.guardAll.byExtension({ profile: "strict" })
|
|
295
|
+
// contentSafety: null → explicit opt-out, audit row emitted
|
|
296
|
+
// contentSafety: { ... } → use operator-supplied map
|
|
297
|
+
var contentSafety;
|
|
298
|
+
if (opts.contentSafety === undefined) {
|
|
299
|
+
// Strict profile is the security-correct default — every shipped
|
|
300
|
+
// guard's full threat catalog refused, including dangerous tags
|
|
301
|
+
// (script / style / iframe), event handlers, dangerous URL
|
|
302
|
+
// schemes, formula injection, DOCTYPE / SVGZ / animation-href
|
|
303
|
+
// hijack. Operators who need a broader content vocabulary opt up
|
|
304
|
+
// explicitly via contentSafety: b.guardAll.byExtension({
|
|
305
|
+
// profile: "balanced" | "permissive" }).
|
|
306
|
+
contentSafety = guardAll().byExtension({
|
|
307
|
+
profile: "strict",
|
|
308
|
+
audit: opts.audit,
|
|
309
|
+
observability: opts.observability,
|
|
310
|
+
});
|
|
311
|
+
} else if (opts.contentSafety === null) {
|
|
312
|
+
if (opts.audit && typeof opts.audit.safeEmit === "function") {
|
|
313
|
+
try {
|
|
314
|
+
opts.audit.safeEmit({
|
|
315
|
+
action: "fileUpload.contentSafety.disabled",
|
|
316
|
+
actor: {},
|
|
317
|
+
outcome: "success",
|
|
318
|
+
metadata: {
|
|
319
|
+
reason: opts.contentSafetyDisabledReason || "operator-explicit-opt-out",
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
} catch (_e) { /* audit best-effort */ }
|
|
323
|
+
}
|
|
324
|
+
contentSafety = null;
|
|
325
|
+
} else {
|
|
326
|
+
contentSafety = opts.contentSafety;
|
|
327
|
+
}
|
|
328
|
+
// filenameSafety: undefined → b.guardFilename.gate({ profile: "strict" })
|
|
329
|
+
// filenameSafety: null → explicit opt-out, audit row emitted
|
|
330
|
+
// filenameSafety: gate → use operator-supplied gate
|
|
331
|
+
var filenameSafety;
|
|
332
|
+
if (opts.filenameSafety === undefined) {
|
|
333
|
+
// Strict filename profile: ASCII-only, single-dot, 64-byte leaf
|
|
334
|
+
// cap, refuses every shell-exec extension (.exe / .bat / .vbs /
|
|
335
|
+
// .ps1 / .lnk / .scr / .dll / .so / .dmg / .msi / etc.). Operators
|
|
336
|
+
// accepting Unicode filenames or executable-extension artifacts
|
|
337
|
+
// opt up explicitly via filenameSafety: b.guardFilename.gate({
|
|
338
|
+
// profile: "balanced" | "permissive" }).
|
|
339
|
+
filenameSafety = guardFilename().gate({
|
|
340
|
+
profile: "strict",
|
|
341
|
+
audit: opts.audit,
|
|
342
|
+
observability: opts.observability,
|
|
343
|
+
});
|
|
344
|
+
} else if (opts.filenameSafety === null) {
|
|
345
|
+
if (opts.audit && typeof opts.audit.safeEmit === "function") {
|
|
346
|
+
try {
|
|
347
|
+
opts.audit.safeEmit({
|
|
348
|
+
action: "fileUpload.filenameSafety.disabled",
|
|
349
|
+
actor: {},
|
|
350
|
+
outcome: "success",
|
|
351
|
+
metadata: {
|
|
352
|
+
reason: opts.filenameSafetyDisabledReason || "operator-explicit-opt-out",
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
} catch (_e) { /* audit best-effort */ }
|
|
356
|
+
}
|
|
357
|
+
filenameSafety = null;
|
|
358
|
+
} else {
|
|
359
|
+
filenameSafety = opts.filenameSafety;
|
|
360
|
+
}
|
|
260
361
|
var maxFileBytes = cfg.maxFileBytes;
|
|
261
362
|
var maxChunkBytes = cfg.maxChunkBytes;
|
|
262
363
|
var maxStreamReassemblyBytes = cfg.maxStreamReassemblyBytes;
|
|
@@ -756,6 +857,108 @@ function create(opts) {
|
|
|
756
857
|
throw e;
|
|
757
858
|
}
|
|
758
859
|
|
|
860
|
+
// Content-safety gate — operator-supplied per-extension gate
|
|
861
|
+
// (b.guardCsv.gate / b.guardHtml.gate / etc.). Routes the assembled
|
|
862
|
+
// body through the gate's check() before handing to onFinalize. The
|
|
863
|
+
// decision is honored:
|
|
864
|
+
// - serve → continue with the original buffer
|
|
865
|
+
// - sanitize → continue with decision.sanitized (operator's
|
|
866
|
+
// onFinalize sees the cleaned bytes)
|
|
867
|
+
// - refuse → throw FileUploadError; operator route surfaces the
|
|
868
|
+
// rejection to the client
|
|
869
|
+
// filenameSafety — single gate that validates the filename string
|
|
870
|
+
// (path traversal / null-byte / Windows reserved names / NTFS ADS /
|
|
871
|
+
// RTLO bidi / overlong UTF-8 / shell-exec / double-extension).
|
|
872
|
+
// Runs BEFORE contentSafety because a refused filename obviates
|
|
873
|
+
// the need to validate the body.
|
|
874
|
+
var filename = (meta.metadata && meta.metadata.filename) || uploadId;
|
|
875
|
+
if (filenameSafety && typeof filenameSafety.check === "function") {
|
|
876
|
+
var fnDecision;
|
|
877
|
+
try {
|
|
878
|
+
fnDecision = await filenameSafety.check({
|
|
879
|
+
filename: filename,
|
|
880
|
+
actor: actor,
|
|
881
|
+
direction: "inbound",
|
|
882
|
+
metadata: meta.metadata,
|
|
883
|
+
});
|
|
884
|
+
} catch (fnErr) {
|
|
885
|
+
_emitObs("fileUpload.filename_safety_threw", 1);
|
|
886
|
+
_emitAudit("fileUpload.finalize_failure", {
|
|
887
|
+
actor: requestHelpers.extractActorContext(actor),
|
|
888
|
+
outcome: "failure", reason: "filename-safety-threw",
|
|
889
|
+
metadata: { uploadId: uploadId, error: fnErr && fnErr.message },
|
|
890
|
+
});
|
|
891
|
+
throw _err("FILENAME_SAFETY_THREW",
|
|
892
|
+
"fileUpload.finalize: filenameSafety gate threw: " + (fnErr && fnErr.message));
|
|
893
|
+
}
|
|
894
|
+
if (!fnDecision.ok || fnDecision.action === "refuse") {
|
|
895
|
+
_emitObs("fileUpload.filename_safety_refused", 1);
|
|
896
|
+
_emitAudit("fileUpload.finalize_failure", {
|
|
897
|
+
actor: requestHelpers.extractActorContext(actor),
|
|
898
|
+
outcome: "failure", reason: "filename-safety-refused",
|
|
899
|
+
metadata: {
|
|
900
|
+
uploadId: uploadId, filename: filename,
|
|
901
|
+
issues: gateContract.summarizeIssues(fnDecision.issues),
|
|
902
|
+
},
|
|
903
|
+
});
|
|
904
|
+
throw _err("FILENAME_SAFETY_REFUSED",
|
|
905
|
+
"fileUpload.finalize: filenameSafety refused " + JSON.stringify(filename) +
|
|
906
|
+
": " + gateContract.summarizeIssues(fnDecision.issues));
|
|
907
|
+
}
|
|
908
|
+
// sanitize: replace metadata.filename with the sanitized form so
|
|
909
|
+
// downstream code sees the cleaned name.
|
|
910
|
+
if (fnDecision.action === "sanitize" && fnDecision.sanitizedFilename) {
|
|
911
|
+
meta.metadata = Object.assign({}, meta.metadata || {},
|
|
912
|
+
{ filename: fnDecision.sanitizedFilename });
|
|
913
|
+
filename = fnDecision.sanitizedFilename;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (contentSafety) {
|
|
917
|
+
var safetyExt = path.extname(filename).toLowerCase();
|
|
918
|
+
var safetyGate = contentSafety[safetyExt];
|
|
919
|
+
if (safetyGate && typeof safetyGate.check === "function" && bodyBuffer) {
|
|
920
|
+
var safetyDecision;
|
|
921
|
+
try {
|
|
922
|
+
safetyDecision = await safetyGate.check({
|
|
923
|
+
bytes: bodyBuffer,
|
|
924
|
+
filename: filename,
|
|
925
|
+
actor: actor,
|
|
926
|
+
direction: "inbound",
|
|
927
|
+
metadata: meta.metadata,
|
|
928
|
+
});
|
|
929
|
+
} catch (gateErr) {
|
|
930
|
+
_emitObs("fileUpload.content_safety_threw", 1);
|
|
931
|
+
_emitAudit("fileUpload.finalize_failure", {
|
|
932
|
+
actor: requestHelpers.extractActorContext(actor),
|
|
933
|
+
outcome: "failure", reason: "content-safety-threw",
|
|
934
|
+
metadata: { uploadId: uploadId, error: gateErr && gateErr.message },
|
|
935
|
+
});
|
|
936
|
+
throw _err("CONTENT_SAFETY_THREW",
|
|
937
|
+
"fileUpload.finalize: contentSafety gate threw: " + (gateErr && gateErr.message));
|
|
938
|
+
}
|
|
939
|
+
if (!safetyDecision.ok || safetyDecision.action === "refuse") {
|
|
940
|
+
_emitObs("fileUpload.content_safety_refused", 1, { ext: safetyExt });
|
|
941
|
+
_emitAudit("fileUpload.finalize_failure", {
|
|
942
|
+
actor: requestHelpers.extractActorContext(actor),
|
|
943
|
+
outcome: "failure", reason: "content-safety-refused",
|
|
944
|
+
metadata: {
|
|
945
|
+
uploadId: uploadId, ext: safetyExt,
|
|
946
|
+
issues: gateContract.summarizeIssues(safetyDecision.issues),
|
|
947
|
+
},
|
|
948
|
+
});
|
|
949
|
+
throw _err("CONTENT_SAFETY_REFUSED",
|
|
950
|
+
"fileUpload.finalize: contentSafety gate refused upload (" +
|
|
951
|
+
(safetyDecision.issues || []).map(function (i) { return i.kind; }).join(", ") + ")");
|
|
952
|
+
}
|
|
953
|
+
if (safetyDecision.action === "sanitize" && safetyDecision.sanitized) {
|
|
954
|
+
// Replace the body buffer with the sanitized variant.
|
|
955
|
+
bodyBuffer = safetyDecision.sanitized;
|
|
956
|
+
// Clear the streaming alias if present — sanitized fits in memory.
|
|
957
|
+
bodyStream = null;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
759
962
|
// Hand to operator's onFinalize.
|
|
760
963
|
var rv;
|
|
761
964
|
try {
|