sporades 0.9.42 → 0.9.43
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/CHANGES.md +22 -0
- package/bin/sporades-host-helper.js +1 -1
- package/bin/sporades.js +183 -20
- package/dist/cli/cli-version.d.ts +1 -1
- package/dist/cli/cli-version.js +1 -1
- package/dist/endpoint-error.d.ts +7 -0
- package/dist/endpoint-error.d.ts.map +1 -0
- package/dist/endpoint-error.js +42 -0
- package/dist/endpoint-error.js.map +1 -0
- package/dist/file-ingress-refusal.d.ts +4 -0
- package/dist/file-ingress-refusal.d.ts.map +1 -0
- package/dist/file-ingress-refusal.js +64 -0
- package/dist/file-ingress-refusal.js.map +1 -0
- package/dist/generated-source-manifest.json +12 -2
- package/dist/http-runtime.d.ts.map +1 -1
- package/dist/http-runtime.js +19 -3
- package/dist/http-runtime.js.map +1 -1
- package/dist/server-runtime-source.d.ts.map +1 -1
- package/dist/server-runtime-source.js +97 -16
- package/dist/server-runtime-source.js.map +1 -1
- package/dist/server.d.ts +8 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3 -0
- package/dist/server.js.map +1 -1
- package/package.json +6 -3
- package/src/types/server.d.ts +6 -1
package/CHANGES.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## v0.9.43 - 2026-11-01
|
|
4
|
+
|
|
5
|
+
Changes since v0.9.42.
|
|
6
|
+
|
|
7
|
+
### 🚀 Features
|
|
8
|
+
|
|
9
|
+
- Allow registry propagation before verifying npm releases (#324) (15bdab53).
|
|
10
|
+
|
|
11
|
+
### 🐛 Bug Fixes
|
|
12
|
+
|
|
13
|
+
- Fix dependency audit vulnerabilities (#328) (cd70c8de).
|
|
14
|
+
|
|
15
|
+
### 🔧 Improvements
|
|
16
|
+
|
|
17
|
+
- Regenerate dist and bin [skip ci] (de2841c9).
|
|
18
|
+
- Regenerate dist and bin [skip ci] (57a49c25).
|
|
19
|
+
|
|
20
|
+
### 📝 Documentation
|
|
21
|
+
|
|
22
|
+
- Preserve endpoint security on rollback and record ingress refusals (#327) (1a7e2185).
|
|
23
|
+
- Trigger npm releases from labelled Release issues (#325) (83a9c292).
|
|
24
|
+
|
|
3
25
|
## v0.9.42 - 2026-10-30
|
|
4
26
|
|
|
5
27
|
Changes since v0.9.41.
|
|
@@ -46189,7 +46189,7 @@ var mutationExecution = new MutationExecutionStorage();
|
|
|
46189
46189
|
init_cli_support();
|
|
46190
46190
|
|
|
46191
46191
|
// src/cli/cli-version.ts
|
|
46192
|
-
var CLI_VERSION = "0.9.
|
|
46192
|
+
var CLI_VERSION = "0.9.43";
|
|
46193
46193
|
|
|
46194
46194
|
// src/cli/schedule-inspection-envelope.ts
|
|
46195
46195
|
var SCHEDULE_DIAGNOSTIC_FIELDS = /* @__PURE__ */ new Set([
|
package/bin/sporades.js
CHANGED
|
@@ -95491,6 +95491,38 @@ function flushTeamSecurityEvents(database, context2, options = {}) {
|
|
|
95491
95491
|
}
|
|
95492
95492
|
}
|
|
95493
95493
|
|
|
95494
|
+
// src/endpoint-error.ts
|
|
95495
|
+
function endpointError(headers) {
|
|
95496
|
+
const invalid4 = () => new Error("Invalid endpoint error headers.");
|
|
95497
|
+
if (!headers || typeof headers !== "object" || Array.isArray(headers) || ![Object.prototype, null].includes(Object.getPrototypeOf(headers))) throw invalid4();
|
|
95498
|
+
const result = {};
|
|
95499
|
+
let size = 0;
|
|
95500
|
+
for (const key of Reflect.ownKeys(headers)) {
|
|
95501
|
+
if (typeof key !== "string") throw invalid4();
|
|
95502
|
+
const name2 = key.toLowerCase();
|
|
95503
|
+
if (!["cache-control", "pragma", "x-robots-tag", "content-security-policy", "referrer-policy"].includes(name2) || Object.hasOwn(result, name2)) throw invalid4();
|
|
95504
|
+
const property = Object.getOwnPropertyDescriptor(headers, key);
|
|
95505
|
+
const value = property?.value;
|
|
95506
|
+
if (!property || !Object.hasOwn(property, "value") || typeof value !== "string" || !value.trim() || /[^\x20-\x7e]/.test(value)) throw invalid4();
|
|
95507
|
+
size += name2.length + value.length;
|
|
95508
|
+
if (size > 8192) throw invalid4();
|
|
95509
|
+
result[name2] = value;
|
|
95510
|
+
}
|
|
95511
|
+
const error = new Error("Endpoint handler failed.");
|
|
95512
|
+
Object.defineProperty(error, Symbol.for("sporades.endpoint.errorHeaders"), { value: Object.freeze(result) });
|
|
95513
|
+
return error;
|
|
95514
|
+
}
|
|
95515
|
+
function endpointErrorResponseHeaders(error) {
|
|
95516
|
+
try {
|
|
95517
|
+
const descriptor = error && typeof error === "object" ? Object.getOwnPropertyDescriptor(error, Symbol.for("sporades.endpoint.errorHeaders")) : void 0;
|
|
95518
|
+
if (!descriptor || !Object.hasOwn(descriptor, "value")) return {};
|
|
95519
|
+
const checked = endpointError(descriptor.value);
|
|
95520
|
+
return checked[Symbol.for("sporades.endpoint.errorHeaders")];
|
|
95521
|
+
} catch {
|
|
95522
|
+
return {};
|
|
95523
|
+
}
|
|
95524
|
+
}
|
|
95525
|
+
|
|
95494
95526
|
// src/http-runtime.ts
|
|
95495
95527
|
import { ServerResponse } from "node:http";
|
|
95496
95528
|
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
@@ -129726,8 +129758,9 @@ function prepareHttpSecurity(database, request, response, admitPreflight) {
|
|
|
129726
129758
|
response.writeHead = ((statusCode, statusMessageOrHeaders, maybeHeaders) => {
|
|
129727
129759
|
const statusMessage = typeof statusMessageOrHeaders === "string" ? statusMessageOrHeaders : void 0;
|
|
129728
129760
|
const inputHeaders = statusMessage ? maybeHeaders : typeof statusMessageOrHeaders === "string" ? {} : statusMessageOrHeaders;
|
|
129761
|
+
const supplied = sanitizeResponseHeaders({ ...response.getHeaders?.() ?? {}, ...inputHeaders });
|
|
129729
129762
|
const headers = {
|
|
129730
|
-
...
|
|
129763
|
+
...supplied,
|
|
129731
129764
|
"x-content-type-options": "nosniff",
|
|
129732
129765
|
"referrer-policy": "no-referrer",
|
|
129733
129766
|
"x-frame-options": "DENY",
|
|
@@ -129736,6 +129769,11 @@ function prepareHttpSecurity(database, request, response, admitPreflight) {
|
|
|
129736
129769
|
[policy.csp.header]: serializeCspDirectives(directives),
|
|
129737
129770
|
...policy.hsts ? { "strict-transport-security": policy.hsts } : {}
|
|
129738
129771
|
};
|
|
129772
|
+
const suppliedCsp = supplied[policy.csp.header];
|
|
129773
|
+
if (suppliedCsp) headers[policy.csp.header] = [
|
|
129774
|
+
...Array.isArray(suppliedCsp) ? suppliedCsp.map(String) : [String(suppliedCsp)],
|
|
129775
|
+
serializeCspDirectives(directives)
|
|
129776
|
+
];
|
|
129739
129777
|
if (isGuardedAttachmentHttpResponse(response)) {
|
|
129740
129778
|
delete headers["content-security-policy-report-only"];
|
|
129741
129779
|
delete headers["access-control-allow-origin"];
|
|
@@ -130015,7 +130053,7 @@ function sanitizeResponseHeaders(headers) {
|
|
|
130015
130053
|
[...entries].filter(([name2]) => {
|
|
130016
130054
|
const normalized = String(name2).toLowerCase();
|
|
130017
130055
|
return normalized !== "x-powered-by" && normalized !== "server";
|
|
130018
|
-
})
|
|
130056
|
+
}).map(([name2, value]) => [String(name2).toLowerCase(), value])
|
|
130019
130057
|
);
|
|
130020
130058
|
}
|
|
130021
130059
|
async function handleFileHttpRoute(database, request, response, websocketHub = null) {
|
|
@@ -130282,7 +130320,7 @@ function mergeEndpointResponseHeaders(handlerHeaders, runtimeHeaders) {
|
|
|
130282
130320
|
return { ...headers, ...runtimeHeaders };
|
|
130283
130321
|
}
|
|
130284
130322
|
function writeEndpointError(response, error) {
|
|
130285
|
-
const headers = { "content-type": "application/json; charset=utf-8" };
|
|
130323
|
+
const headers = { ...endpointErrorResponseHeaders(error), "content-type": "application/json; charset=utf-8" };
|
|
130286
130324
|
if (error?.code === "UNAUTHENTICATED" && error?.sporadesAccessKeyFailure) {
|
|
130287
130325
|
headers["www-authenticate"] = error?.sporadesAccessKeyFailure === "invalid" && error?.sporadesAccessKeyReason !== "missing" ? 'Bearer realm="sporades", error="invalid_token"' : 'Bearer realm="sporades"';
|
|
130288
130326
|
}
|
|
@@ -130290,6 +130328,10 @@ function writeEndpointError(response, error) {
|
|
|
130290
130328
|
headers["cache-control"] = "no-store";
|
|
130291
130329
|
headers.pragma = "no-cache";
|
|
130292
130330
|
}
|
|
130331
|
+
if (String(response.getHeader?.("cache-control") ?? "").includes("no-store")) {
|
|
130332
|
+
headers["cache-control"] = response.getHeader("cache-control");
|
|
130333
|
+
headers.pragma = "no-cache";
|
|
130334
|
+
}
|
|
130293
130335
|
const multipartDetails = safeMultipartLimitDetails(error);
|
|
130294
130336
|
response.writeHead(endpointErrorStatus(error), headers);
|
|
130295
130337
|
response.end(
|
|
@@ -130314,6 +130356,7 @@ function safeMultipartLimitDetails(error) {
|
|
|
130314
130356
|
return { partType: details.partType, limitKind: details.limitKind, limit: details.limit };
|
|
130315
130357
|
}
|
|
130316
130358
|
function endpointErrorStatus(error) {
|
|
130359
|
+
if (error?.code === "FILE_INGRESS_REFUSAL_FAILED") return 503;
|
|
130317
130360
|
if (error?.code === "UNAUTHENTICATED") return 401;
|
|
130318
130361
|
if (error?.code === "FORBIDDEN") return 403;
|
|
130319
130362
|
if (error?.code === "RATE_LIMITED") return 429;
|
|
@@ -142565,6 +142608,7 @@ function privilegedOnlyTableAcl() {
|
|
|
142565
142608
|
}
|
|
142566
142609
|
function serverRuntimeModuleSource() {
|
|
142567
142610
|
return `export { escapeHtml, html } from "sporades:html";
|
|
142611
|
+
export const endpointError = ${endpointError.toString()};
|
|
142568
142612
|
const validateEndpointSessionDeclaration = ${validateEndpointSessionDeclaration.toString()};
|
|
142569
142613
|
const AUTH_REQUIREMENTS = Symbol.for("sporades.auth.requirements");
|
|
142570
142614
|
const ATOMIC_STRIPE_EVENT_DEFINITION = Symbol.for("sporades.stripeEvent.atomicDefinition");
|
|
@@ -145115,6 +145159,49 @@ function restartPolicyStatus(mode, overrides2 = {}) {
|
|
|
145115
145159
|
};
|
|
145116
145160
|
}
|
|
145117
145161
|
|
|
145162
|
+
// src/file-ingress-refusal.ts
|
|
145163
|
+
function snapshotIngressDecision(value) {
|
|
145164
|
+
let nodes = 0;
|
|
145165
|
+
let bytes = 0;
|
|
145166
|
+
const invalid4 = () => {
|
|
145167
|
+
throw new Error("Invalid File ingress decision.");
|
|
145168
|
+
};
|
|
145169
|
+
const visit = (input, depth2) => {
|
|
145170
|
+
if (++nodes > 1024 || depth2 > 16) return invalid4();
|
|
145171
|
+
if (input === null || typeof input === "boolean") return input;
|
|
145172
|
+
if (typeof input === "number") return Number.isFinite(input) ? input : invalid4();
|
|
145173
|
+
if (typeof input === "string") {
|
|
145174
|
+
bytes += Buffer.byteLength(input, "utf8");
|
|
145175
|
+
return bytes <= 8192 ? input : invalid4();
|
|
145176
|
+
}
|
|
145177
|
+
if (!input || typeof input !== "object" || !Array.isArray(input) && ![Object.prototype, null].includes(Object.getPrototypeOf(input))) return invalid4();
|
|
145178
|
+
const output = Array.isArray(input) ? [] : /* @__PURE__ */ Object.create(null);
|
|
145179
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
145180
|
+
if (Array.isArray(input) && key === "length") continue;
|
|
145181
|
+
if (typeof key !== "string") return invalid4();
|
|
145182
|
+
bytes += Buffer.byteLength(key, "utf8");
|
|
145183
|
+
if (bytes > 8192) return invalid4();
|
|
145184
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
145185
|
+
if (!descriptor || !Object.hasOwn(descriptor, "value") || !descriptor.enumerable) return invalid4();
|
|
145186
|
+
if (depth2 === 0 && (key === "allowFiles" || key === "refusalJob") && descriptor.value === void 0) continue;
|
|
145187
|
+
if (Array.isArray(input) && (!/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= 1024)) return invalid4();
|
|
145188
|
+
Object.defineProperty(output, key, { value: visit(descriptor.value, depth2 + 1), enumerable: true });
|
|
145189
|
+
}
|
|
145190
|
+
if (Array.isArray(input) && (input.length > 1024 || Object.keys(output).length !== input.length)) return invalid4();
|
|
145191
|
+
return Object.freeze(output);
|
|
145192
|
+
};
|
|
145193
|
+
const result = visit(value, 0);
|
|
145194
|
+
if (Buffer.byteLength(JSON.stringify(result) ?? "", "utf8") > 8192) invalid4();
|
|
145195
|
+
return result;
|
|
145196
|
+
}
|
|
145197
|
+
function ingressRefusalJob(decision) {
|
|
145198
|
+
if (!decision || decision.allow !== false || Object.keys(decision).some((key) => !["allow", "refusalJob"].includes(key))) throw new Error("Invalid File ingress refusal.");
|
|
145199
|
+
if (!Object.hasOwn(decision, "refusalJob")) return null;
|
|
145200
|
+
const job = decision.refusalJob;
|
|
145201
|
+
if (!job || typeof job !== "object" || Array.isArray(job) || Object.keys(job).length !== 3 || Object.keys(job).some((key) => !["name", "payload", "idempotencyKey"].includes(key)) || typeof job.name !== "string" || !/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(job.name) || typeof job.idempotencyKey !== "string" || !job.idempotencyKey || Buffer.byteLength(job.idempotencyKey, "utf8") > 256 || /[\x00-\x1f\x7f]/.test(job.idempotencyKey) || !Object.hasOwn(job, "payload") || Buffer.byteLength(JSON.stringify(job.payload), "utf8") > 4096) throw new Error("Invalid File ingress refusal.");
|
|
145202
|
+
return job;
|
|
145203
|
+
}
|
|
145204
|
+
|
|
145118
145205
|
// src/services-context.ts
|
|
145119
145206
|
import { inspect } from "node:util";
|
|
145120
145207
|
var hidden = { toJSON: { value: () => void 0 }, [inspect.custom]: { value: () => "[Capsule services]" } };
|
|
@@ -151471,25 +151558,47 @@ function multipartAdmissionFilePermission(decision, principalMode = false) {
|
|
|
151471
151558
|
if (keys.some((key) => key !== "allow" && key !== "allowFiles" && !(principalMode && key === "principal")) || !allow || !Object.prototype.hasOwnProperty.call(allow, "value") || allow.value !== true || principalMode && (!principal || !Object.prototype.hasOwnProperty.call(principal, "value")) || allowFiles && (!Object.prototype.hasOwnProperty.call(allowFiles, "value") || allowFiles.value !== void 0 && typeof allowFiles.value !== "boolean")) throw multipartAdmissionDenied();
|
|
151472
151559
|
return allowFiles?.value !== false;
|
|
151473
151560
|
}
|
|
151474
|
-
async function admitCapsuleIngressPrincipal(database, endpoint, endpointRequest,
|
|
151561
|
+
async function admitCapsuleIngressPrincipal(database, endpoint, endpointRequest, requestSignal) {
|
|
151562
|
+
const signal = requestSignal ? AbortSignal.any([requestSignal, AbortSignal.timeout(5e3)]) : AbortSignal.timeout(5e3);
|
|
151475
151563
|
const definition = database.fileIngressDefinition;
|
|
151476
151564
|
if (!definition) throw commandError("Unauthenticated.", "Provide valid ingress authority and retry.", "UNAUTHENTICATED");
|
|
151477
|
-
|
|
151478
|
-
|
|
151479
|
-
|
|
151480
|
-
|
|
151481
|
-
|
|
151482
|
-
|
|
151483
|
-
|
|
151484
|
-
|
|
151485
|
-
|
|
151486
|
-
|
|
151487
|
-
|
|
151565
|
+
let decision;
|
|
151566
|
+
try {
|
|
151567
|
+
decision = await database.adapter.withTransaction((transaction) => withTrustedRead(database, {
|
|
151568
|
+
transaction,
|
|
151569
|
+
purpose: "files.ingress-admission",
|
|
151570
|
+
subject: { method: endpoint.options.method, path: endpoint.options.path },
|
|
151571
|
+
signal
|
|
151572
|
+
}, async (db, assertActive) => {
|
|
151573
|
+
const request = Object.freeze({ method: endpointRequest.method, path: endpointRequest.path, headers: Object.freeze({ ...endpointRequest.headers }), query: Object.freeze({ ...endpointRequest.query }) });
|
|
151574
|
+
const result = await raceIngressAdmission(() => definition.admit(Object.freeze({
|
|
151575
|
+
db,
|
|
151576
|
+
app: database.app,
|
|
151577
|
+
env: database.serverEnv,
|
|
151578
|
+
signal,
|
|
151579
|
+
request,
|
|
151580
|
+
rateLimit: (key2, options) => {
|
|
151581
|
+
assertActive();
|
|
151582
|
+
return database.capsuleRateLimiter.consume(key2, options);
|
|
151583
|
+
}
|
|
151584
|
+
}), request), signal);
|
|
151488
151585
|
assertActive();
|
|
151489
|
-
return
|
|
151490
|
-
},
|
|
151491
|
-
|
|
151492
|
-
}
|
|
151586
|
+
return snapshotIngressDecision(result);
|
|
151587
|
+
}), { signal });
|
|
151588
|
+
if (signal?.aborted) throw new Error("Aborted");
|
|
151589
|
+
} catch {
|
|
151590
|
+
throw capsuleIngressDenied();
|
|
151591
|
+
}
|
|
151592
|
+
if (decision?.allow === false) {
|
|
151593
|
+
let refusal;
|
|
151594
|
+
try {
|
|
151595
|
+
refusal = ingressRefusalJob(decision);
|
|
151596
|
+
} catch {
|
|
151597
|
+
throw capsuleIngressDenied();
|
|
151598
|
+
}
|
|
151599
|
+
if (refusal) await enqueueCapsuleIngressRefusal(database, endpointRequest, refusal, requestSignal);
|
|
151600
|
+
throw capsuleIngressDenied();
|
|
151601
|
+
}
|
|
151493
151602
|
let allowFiles;
|
|
151494
151603
|
try {
|
|
151495
151604
|
allowFiles = multipartAdmissionFilePermission(decision, true);
|
|
@@ -151510,6 +151619,58 @@ async function admitCapsuleIngressPrincipal(database, endpoint, endpointRequest,
|
|
|
151510
151619
|
}
|
|
151511
151620
|
return Object.freeze({ allowFiles, authority: Object.freeze({ kind: "capsule-principal", namespace, key, keyDigest: createHash15("sha256").update(`${namespace}\0${key}`, "utf8").digest("hex"), ownerId: database.capsuleIngressOwnerId }) });
|
|
151512
151621
|
}
|
|
151622
|
+
async function raceIngressAdmission(evaluate, signal) {
|
|
151623
|
+
if (signal.aborted) throw capsuleIngressDenied();
|
|
151624
|
+
let removeAbort = () => {
|
|
151625
|
+
};
|
|
151626
|
+
try {
|
|
151627
|
+
const abort = new Promise((_2, reject) => {
|
|
151628
|
+
const onAbort = () => reject(capsuleIngressDenied());
|
|
151629
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
151630
|
+
removeAbort = () => signal.removeEventListener("abort", onAbort);
|
|
151631
|
+
});
|
|
151632
|
+
return await Promise.race([Promise.resolve().then(evaluate), abort]);
|
|
151633
|
+
} finally {
|
|
151634
|
+
removeAbort();
|
|
151635
|
+
}
|
|
151636
|
+
}
|
|
151637
|
+
function capsuleIngressDenied() {
|
|
151638
|
+
return commandError("Unauthenticated.", "Provide valid ingress authority and retry.", "UNAUTHENTICATED");
|
|
151639
|
+
}
|
|
151640
|
+
async function enqueueCapsuleIngressRefusal(database, request, refusal, signal) {
|
|
151641
|
+
let context2;
|
|
151642
|
+
const failure2 = () => commandError("File ingress refusal could not be recorded.", "Retry the request later.", "FILE_INGRESS_REFUSAL_FAILED");
|
|
151643
|
+
try {
|
|
151644
|
+
if (signal?.aborted || !database.jobs?.some((job) => job.name === refusal.name) || refusal.name.toLowerCase().startsWith(RESERVED_JOB_NAME_PREFIX)) throw failure2();
|
|
151645
|
+
const idempotencyKey = "file-ingress-refusal:" + createHash15("sha256").update(JSON.stringify([request.method, request.path, refusal.name, refusal.idempotencyKey]), "utf8").digest("hex");
|
|
151646
|
+
await database.adapter.withTransaction(async (adapter) => {
|
|
151647
|
+
const scoped = createTransactionDatabase(database, adapter);
|
|
151648
|
+
const checks = Symbol.for("sporades.database.transactionBeforeCommitChecks");
|
|
151649
|
+
(adapter[checks] ??= []).push(() => {
|
|
151650
|
+
if (signal?.aborted) throw failure2();
|
|
151651
|
+
});
|
|
151652
|
+
let failed = false;
|
|
151653
|
+
try {
|
|
151654
|
+
if (signal?.aborted) throw failure2();
|
|
151655
|
+
context2 = createMutationContext(scoped, { userId: database.capsuleIngressOwnerId, displayName: "File ingress", email: null, picture: null, isAuthenticated: false, isGuest: false, provider: "file-ingress" }, { ordinaryCredential: false });
|
|
151656
|
+
await context2.privileged.run(
|
|
151657
|
+
{ operation: "files.ingress-refusal", targetResourceKind: "job-queue", signal },
|
|
151658
|
+
(privileged) => privileged.jobs.enqueue(refusal.name, refusal.payload, { idempotencyKey })
|
|
151659
|
+
);
|
|
151660
|
+
if (signal?.aborted) throw failure2();
|
|
151661
|
+
} catch (error) {
|
|
151662
|
+
failed = true;
|
|
151663
|
+
throw error;
|
|
151664
|
+
} finally {
|
|
151665
|
+
await cleanupTransactionHandler(scoped, context2, failed);
|
|
151666
|
+
}
|
|
151667
|
+
}, { signal });
|
|
151668
|
+
await dispatchPendingJobs(context2);
|
|
151669
|
+
} catch {
|
|
151670
|
+
dropPendingJobDispatch(context2);
|
|
151671
|
+
throw failure2();
|
|
151672
|
+
}
|
|
151673
|
+
}
|
|
151513
151674
|
var endpointMultipartAdmissionTimeoutMs = 5e3;
|
|
151514
151675
|
function multipartAdmissionDenied() {
|
|
151515
151676
|
return commandError("Multipart request was not admitted.", "Check the request conditions and retry.", "MULTIPART_ADMISSION_DENIED");
|
|
@@ -151632,8 +151793,10 @@ async function runEndpoint(database, endpoint, requestUrl, request, requestPath
|
|
|
151632
151793
|
let allowFiles = true;
|
|
151633
151794
|
if (claimAuthority === "capsule-principal") {
|
|
151634
151795
|
const endpointSignal = request.__sporadesEndpointSignal ?? request.signal;
|
|
151796
|
+
request.__sporadesCapsuleIngressAdmissionDenied = true;
|
|
151635
151797
|
const admission = await admitCapsuleIngressPrincipal(database, endpoint, endpointRequest, endpointSignal);
|
|
151636
151798
|
if (endpointSignal?.aborted) throw commandError("Unauthenticated.", "Provide valid ingress authority and retry.", "UNAUTHENTICATED");
|
|
151799
|
+
delete request.__sporadesCapsuleIngressAdmissionDenied;
|
|
151637
151800
|
ingressAuthority = admission.authority;
|
|
151638
151801
|
allowFiles = admission.allowFiles;
|
|
151639
151802
|
} else {
|
|
@@ -164411,7 +164574,7 @@ jobs:
|
|
|
164411
164574
|
}
|
|
164412
164575
|
|
|
164413
164576
|
// src/cli/cli-version.ts
|
|
164414
|
-
var CLI_VERSION = "0.9.
|
|
164577
|
+
var CLI_VERSION = "0.9.43";
|
|
164415
164578
|
|
|
164416
164579
|
// src/cli/sporades.ts
|
|
164417
164580
|
var containerSecretResolutions = /* @__PURE__ */ new WeakMap();
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.9.
|
|
1
|
+
export declare const CLI_VERSION = "0.9.43";
|
|
2
2
|
//# sourceMappingURL=cli-version.d.ts.map
|
package/dist/cli/cli-version.js
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** The only response headers available on a thrown endpoint failure. */
|
|
2
|
+
export type EndpointErrorHeaders = Readonly<Partial<Record<"Cache-Control" | "Pragma" | "X-Robots-Tag" | "Content-Security-Policy" | "Referrer-Policy" | "cache-control" | "pragma" | "x-robots-tag" | "content-security-policy" | "referrer-policy", string>>>;
|
|
3
|
+
/** Self-contained so the public helper and generated Capsule helper use identical validation. */
|
|
4
|
+
export declare function endpointError(headers: EndpointErrorHeaders): Error;
|
|
5
|
+
/** Revalidate the public helper's snapshot at the response boundary, including forged errors. */
|
|
6
|
+
export declare function endpointErrorResponseHeaders(error: unknown): Record<string, string>;
|
|
7
|
+
//# sourceMappingURL=endpoint-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-error.d.ts","sourceRoot":"","sources":["../src/endpoint-error.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CACxD,eAAe,GAAG,QAAQ,GAAG,cAAc,GAAG,yBAAyB,GAAG,iBAAiB,GAC3F,eAAe,GAAG,QAAQ,GAAG,cAAc,GAAG,yBAAyB,GAAG,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;AAEzG,iGAAiG;AACjG,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,KAAK,CAqBlE;AAED,iGAAiG;AACjG,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOnF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Self-contained so the public helper and generated Capsule helper use identical validation. */
|
|
2
|
+
export function endpointError(headers) {
|
|
3
|
+
const invalid = () => new Error("Invalid endpoint error headers.");
|
|
4
|
+
if (!headers || typeof headers !== "object" || Array.isArray(headers)
|
|
5
|
+
|| ![Object.prototype, null].includes(Object.getPrototypeOf(headers)))
|
|
6
|
+
throw invalid();
|
|
7
|
+
const result = {};
|
|
8
|
+
let size = 0;
|
|
9
|
+
for (const key of Reflect.ownKeys(headers)) {
|
|
10
|
+
if (typeof key !== "string")
|
|
11
|
+
throw invalid();
|
|
12
|
+
const name = key.toLowerCase();
|
|
13
|
+
if (!["cache-control", "pragma", "x-robots-tag", "content-security-policy", "referrer-policy"].includes(name) || Object.hasOwn(result, name))
|
|
14
|
+
throw invalid();
|
|
15
|
+
const property = Object.getOwnPropertyDescriptor(headers, key);
|
|
16
|
+
const value = property?.value;
|
|
17
|
+
// ASCII only, with a total 8 KiB budget; never invoke accessors or coerce values.
|
|
18
|
+
if (!property || !Object.hasOwn(property, "value") || typeof value !== "string" || !value.trim() || /[^\x20-\x7e]/.test(value))
|
|
19
|
+
throw invalid();
|
|
20
|
+
size += name.length + value.length;
|
|
21
|
+
if (size > 8192)
|
|
22
|
+
throw invalid();
|
|
23
|
+
result[name] = value;
|
|
24
|
+
}
|
|
25
|
+
const error = new Error("Endpoint handler failed.");
|
|
26
|
+
Object.defineProperty(error, Symbol.for("sporades.endpoint.errorHeaders"), { value: Object.freeze(result) });
|
|
27
|
+
return error;
|
|
28
|
+
}
|
|
29
|
+
/** Revalidate the public helper's snapshot at the response boundary, including forged errors. */
|
|
30
|
+
export function endpointErrorResponseHeaders(error) {
|
|
31
|
+
try {
|
|
32
|
+
const descriptor = error && typeof error === "object" ? Object.getOwnPropertyDescriptor(error, Symbol.for("sporades.endpoint.errorHeaders")) : undefined;
|
|
33
|
+
if (!descriptor || !Object.hasOwn(descriptor, "value"))
|
|
34
|
+
return {};
|
|
35
|
+
const checked = endpointError(descriptor.value);
|
|
36
|
+
return checked[Symbol.for("sporades.endpoint.errorHeaders")];
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=endpoint-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-error.js","sourceRoot":"","sources":["../src/endpoint-error.ts"],"names":[],"mappings":"AAKA,iGAAiG;AACjG,MAAM,UAAU,aAAa,CAAC,OAA6B;IACzD,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACnE,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;WAChE,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QAAE,MAAM,OAAO,EAAE,CAAC;IACzF,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,MAAM,OAAO,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QAC/B,IAAI,CAAC,CAAC,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,yBAAyB,EAAE,iBAAiB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC;YAAE,MAAM,OAAO,EAAE,CAAC;QAC9J,MAAM,QAAQ,GAAG,MAAM,CAAC,wBAAwB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC/D,MAAM,KAAK,GAAG,QAAQ,EAAE,KAAK,CAAC;QAC9B,kFAAkF;QAClF,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,MAAM,OAAO,EAAE,CAAC;QAChJ,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QACnC,IAAI,IAAI,GAAG,IAAI;YAAE,MAAM,OAAO,EAAE,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACvB,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IACpD,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,gCAAgC,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC7G,OAAO,KAAK,CAAC;AACf,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,4BAA4B,CAAC,KAAc;IACzD,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACzJ,IAAI,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAClE,MAAM,OAAO,GAAG,aAAa,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAChD,OAAQ,OAAe,CAAC,MAAM,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC,CAAC;IACxE,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Snapshot untrusted policy output without getters/toJSON or retained mutable references. */
|
|
2
|
+
export declare function snapshotIngressDecision(value: unknown): any;
|
|
3
|
+
export declare function ingressRefusalJob(decision: any): any;
|
|
4
|
+
//# sourceMappingURL=file-ingress-refusal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-ingress-refusal.d.ts","sourceRoot":"","sources":["../src/file-ingress-refusal.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,GAAG,CAkC3D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,GAAG,OAW9C"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Snapshot untrusted policy output without getters/toJSON or retained mutable references. */
|
|
2
|
+
export function snapshotIngressDecision(value) {
|
|
3
|
+
let nodes = 0;
|
|
4
|
+
let bytes = 0;
|
|
5
|
+
const invalid = () => { throw new Error("Invalid File ingress decision."); };
|
|
6
|
+
const visit = (input, depth) => {
|
|
7
|
+
if (++nodes > 1024 || depth > 16)
|
|
8
|
+
return invalid();
|
|
9
|
+
if (input === null || typeof input === "boolean")
|
|
10
|
+
return input;
|
|
11
|
+
if (typeof input === "number")
|
|
12
|
+
return Number.isFinite(input) ? input : invalid();
|
|
13
|
+
if (typeof input === "string") {
|
|
14
|
+
bytes += Buffer.byteLength(input, "utf8");
|
|
15
|
+
return bytes <= 8192 ? input : invalid();
|
|
16
|
+
}
|
|
17
|
+
if (!input || typeof input !== "object" || (!Array.isArray(input) && ![Object.prototype, null].includes(Object.getPrototypeOf(input))))
|
|
18
|
+
return invalid();
|
|
19
|
+
const output = Array.isArray(input) ? [] : Object.create(null);
|
|
20
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
21
|
+
if (Array.isArray(input) && key === "length")
|
|
22
|
+
continue;
|
|
23
|
+
if (typeof key !== "string")
|
|
24
|
+
return invalid();
|
|
25
|
+
bytes += Buffer.byteLength(key, "utf8");
|
|
26
|
+
if (bytes > 8192)
|
|
27
|
+
return invalid();
|
|
28
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
29
|
+
if (!descriptor || !Object.hasOwn(descriptor, "value") || !descriptor.enumerable)
|
|
30
|
+
return invalid();
|
|
31
|
+
// Preserve the established optional allowFiles contract while keeping
|
|
32
|
+
// refusal payloads strict JSON. Explicitly undefined optional fields
|
|
33
|
+
// have the same meaning as omission, only at the decision root.
|
|
34
|
+
if (depth === 0 && (key === "allowFiles" || key === "refusalJob") && descriptor.value === undefined)
|
|
35
|
+
continue;
|
|
36
|
+
if (Array.isArray(input) && (!/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= 1024))
|
|
37
|
+
return invalid();
|
|
38
|
+
Object.defineProperty(output, key, { value: visit(descriptor.value, depth + 1), enumerable: true });
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(input) && (input.length > 1024 || Object.keys(output).length !== input.length))
|
|
41
|
+
return invalid();
|
|
42
|
+
return Object.freeze(output);
|
|
43
|
+
};
|
|
44
|
+
const result = visit(value, 0);
|
|
45
|
+
if (Buffer.byteLength(JSON.stringify(result) ?? "", "utf8") > 8192)
|
|
46
|
+
invalid();
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
export function ingressRefusalJob(decision) {
|
|
50
|
+
if (!decision || decision.allow !== false || Object.keys(decision).some(key => !["allow", "refusalJob"].includes(key)))
|
|
51
|
+
throw new Error("Invalid File ingress refusal.");
|
|
52
|
+
if (!Object.hasOwn(decision, "refusalJob"))
|
|
53
|
+
return null;
|
|
54
|
+
const job = decision.refusalJob;
|
|
55
|
+
if (!job || typeof job !== "object" || Array.isArray(job) || Object.keys(job).length !== 3
|
|
56
|
+
|| Object.keys(job).some(key => !["name", "payload", "idempotencyKey"].includes(key))
|
|
57
|
+
|| typeof job.name !== "string" || !/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(job.name)
|
|
58
|
+
|| typeof job.idempotencyKey !== "string" || !job.idempotencyKey || Buffer.byteLength(job.idempotencyKey, "utf8") > 256
|
|
59
|
+
|| /[\x00-\x1f\x7f]/.test(job.idempotencyKey)
|
|
60
|
+
|| !Object.hasOwn(job, "payload") || Buffer.byteLength(JSON.stringify(job.payload), "utf8") > 4096)
|
|
61
|
+
throw new Error("Invalid File ingress refusal.");
|
|
62
|
+
return job;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=file-ingress-refusal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-ingress-refusal.js","sourceRoot":"","sources":["../src/file-ingress-refusal.ts"],"names":[],"mappings":"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"}
|
|
@@ -80,8 +80,10 @@
|
|
|
80
80
|
"src/dev-clamav-sidecar.ts",
|
|
81
81
|
"src/email-events-config.ts",
|
|
82
82
|
"src/email-events-runtime.ts",
|
|
83
|
+
"src/endpoint-error.ts",
|
|
83
84
|
"src/endpoint-file-response.ts",
|
|
84
85
|
"src/endpoint-session.ts",
|
|
86
|
+
"src/file-ingress-refusal.ts",
|
|
85
87
|
"src/file-ingress-runtime.ts",
|
|
86
88
|
"src/file-policy.ts",
|
|
87
89
|
"src/file-storage-runtime.ts",
|
|
@@ -158,7 +160,7 @@
|
|
|
158
160
|
"tsconfig.json",
|
|
159
161
|
"tsconfig.runtime.json"
|
|
160
162
|
],
|
|
161
|
-
"digest": "
|
|
163
|
+
"digest": "e705886424917bce68edceb78f8f45184c1055abfd7699836596e6e4e364a7e5"
|
|
162
164
|
},
|
|
163
165
|
"outputs": {
|
|
164
166
|
"files": [
|
|
@@ -448,6 +450,10 @@
|
|
|
448
450
|
"dist/email-events-runtime.d.ts.map",
|
|
449
451
|
"dist/email-events-runtime.js",
|
|
450
452
|
"dist/email-events-runtime.js.map",
|
|
453
|
+
"dist/endpoint-error.d.ts",
|
|
454
|
+
"dist/endpoint-error.d.ts.map",
|
|
455
|
+
"dist/endpoint-error.js",
|
|
456
|
+
"dist/endpoint-error.js.map",
|
|
451
457
|
"dist/endpoint-file-response.d.ts",
|
|
452
458
|
"dist/endpoint-file-response.d.ts.map",
|
|
453
459
|
"dist/endpoint-file-response.js",
|
|
@@ -456,6 +462,10 @@
|
|
|
456
462
|
"dist/endpoint-session.d.ts.map",
|
|
457
463
|
"dist/endpoint-session.js",
|
|
458
464
|
"dist/endpoint-session.js.map",
|
|
465
|
+
"dist/file-ingress-refusal.d.ts",
|
|
466
|
+
"dist/file-ingress-refusal.d.ts.map",
|
|
467
|
+
"dist/file-ingress-refusal.js",
|
|
468
|
+
"dist/file-ingress-refusal.js.map",
|
|
459
469
|
"dist/file-ingress-runtime.d.ts",
|
|
460
470
|
"dist/file-ingress-runtime.d.ts.map",
|
|
461
471
|
"dist/file-ingress-runtime.js",
|
|
@@ -706,6 +716,6 @@
|
|
|
706
716
|
"dist/user-preferences-runtime.js.map",
|
|
707
717
|
"monitoring/trace/inventory-contract.mjs"
|
|
708
718
|
],
|
|
709
|
-
"digest": "
|
|
719
|
+
"digest": "2beff51c6581530f9cc54446a881f896db899362e19c06d4d883e0fbaf54a9ff"
|
|
710
720
|
}
|
|
711
721
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-runtime.d.ts","sourceRoot":"","sources":["../src/http-runtime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http-runtime.d.ts","sourceRoot":"","sources":["../src/http-runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/http-runtime.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { endpointErrorResponseHeaders } from "./endpoint-error.js";
|
|
1
2
|
// The Capsule runtime's HTTP and security policy domain: the CORS and CSP posture every response
|
|
2
3
|
// carries, the origin and host-header validation behind it, the request body reader and its size
|
|
3
4
|
// limit, the generic response writers, and the two routes the runtime owns outright — the health
|
|
@@ -405,8 +406,9 @@ export function prepareHttpSecurity(database, request, response, admitPreflight)
|
|
|
405
406
|
response.writeHead = ((statusCode, statusMessageOrHeaders, maybeHeaders) => {
|
|
406
407
|
const statusMessage = typeof statusMessageOrHeaders === "string" ? statusMessageOrHeaders : undefined;
|
|
407
408
|
const inputHeaders = statusMessage ? maybeHeaders : typeof statusMessageOrHeaders === "string" ? {} : statusMessageOrHeaders;
|
|
409
|
+
const supplied = sanitizeResponseHeaders({ ...(response.getHeaders?.() ?? {}), ...inputHeaders });
|
|
408
410
|
const headers = {
|
|
409
|
-
...
|
|
411
|
+
...supplied,
|
|
410
412
|
"x-content-type-options": "nosniff",
|
|
411
413
|
"referrer-policy": "no-referrer",
|
|
412
414
|
"x-frame-options": "DENY",
|
|
@@ -415,6 +417,14 @@ export function prepareHttpSecurity(database, request, response, admitPreflight)
|
|
|
415
417
|
[policy.csp.header]: serializeCspDirectives(directives),
|
|
416
418
|
...(policy.hsts ? { "strict-transport-security": policy.hsts } : {}),
|
|
417
419
|
};
|
|
420
|
+
// Multiple CSP policies are enforced independently (intersection), never merged
|
|
421
|
+
// by directive: neither the endpoint nor the platform can weaken the other.
|
|
422
|
+
const suppliedCsp = supplied[policy.csp.header];
|
|
423
|
+
if (suppliedCsp)
|
|
424
|
+
headers[policy.csp.header] = [
|
|
425
|
+
...(Array.isArray(suppliedCsp) ? suppliedCsp.map(String) : [String(suppliedCsp)]),
|
|
426
|
+
serializeCspDirectives(directives),
|
|
427
|
+
];
|
|
418
428
|
if (isGuardedAttachmentHttpResponse(response)) {
|
|
419
429
|
delete headers["content-security-policy-report-only"];
|
|
420
430
|
delete headers["access-control-allow-origin"];
|
|
@@ -731,7 +741,7 @@ function sanitizeResponseHeaders(headers) {
|
|
|
731
741
|
return Object.fromEntries([...entries].filter(([name]) => {
|
|
732
742
|
const normalized = String(name).toLowerCase();
|
|
733
743
|
return normalized !== "x-powered-by" && normalized !== "server";
|
|
734
|
-
}));
|
|
744
|
+
}).map(([name, value]) => [String(name).toLowerCase(), value]));
|
|
735
745
|
}
|
|
736
746
|
export async function handleFileHttpRoute(database, request, response, websocketHub = null) {
|
|
737
747
|
const target = requestTarget(request);
|
|
@@ -1013,7 +1023,7 @@ function mergeEndpointResponseHeaders(handlerHeaders, runtimeHeaders) {
|
|
|
1013
1023
|
return { ...headers, ...runtimeHeaders };
|
|
1014
1024
|
}
|
|
1015
1025
|
export function writeEndpointError(response, error) {
|
|
1016
|
-
const headers = { "content-type": "application/json; charset=utf-8" };
|
|
1026
|
+
const headers = { ...endpointErrorResponseHeaders(error), "content-type": "application/json; charset=utf-8" };
|
|
1017
1027
|
if (error?.code === "UNAUTHENTICATED" && error?.sporadesAccessKeyFailure) {
|
|
1018
1028
|
headers["www-authenticate"] = error?.sporadesAccessKeyFailure === "invalid" && error?.sporadesAccessKeyReason !== "missing"
|
|
1019
1029
|
? 'Bearer realm="sporades", error="invalid_token"'
|
|
@@ -1023,6 +1033,10 @@ export function writeEndpointError(response, error) {
|
|
|
1023
1033
|
headers["cache-control"] = "no-store";
|
|
1024
1034
|
headers.pragma = "no-cache";
|
|
1025
1035
|
}
|
|
1036
|
+
if (String(response.getHeader?.("cache-control") ?? "").includes("no-store")) {
|
|
1037
|
+
headers["cache-control"] = response.getHeader("cache-control");
|
|
1038
|
+
headers.pragma = "no-cache";
|
|
1039
|
+
}
|
|
1026
1040
|
const multipartDetails = safeMultipartLimitDetails(error);
|
|
1027
1041
|
response.writeHead(endpointErrorStatus(error), headers);
|
|
1028
1042
|
response.end(`${JSON.stringify({
|
|
@@ -1058,6 +1072,8 @@ function safeMultipartLimitDetails(error) {
|
|
|
1058
1072
|
return { partType: details.partType, limitKind: details.limitKind, limit: details.limit };
|
|
1059
1073
|
}
|
|
1060
1074
|
function endpointErrorStatus(error) {
|
|
1075
|
+
if (error?.code === "FILE_INGRESS_REFUSAL_FAILED")
|
|
1076
|
+
return 503;
|
|
1061
1077
|
if (error?.code === "UNAUTHENTICATED")
|
|
1062
1078
|
return 401;
|
|
1063
1079
|
if (error?.code === "FORBIDDEN")
|