@blamejs/blamejs-shop 0.2.13 → 0.2.14
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 +2 -0
- package/README.md +1 -1
- package/lib/admin.js +616 -21
- package/lib/asset-manifest.json +3 -3
- package/lib/catalog.js +19 -0
- package/lib/vendor/MANIFEST.json +3 -3
- package/lib/vendor/blamejs/CHANGELOG.md +16 -0
- package/lib/vendor/blamejs/api-snapshot.json +6 -2
- package/lib/vendor/blamejs/lib/agent-idempotency.js +50 -10
- package/lib/vendor/blamejs/lib/agent-orchestrator.js +58 -5
- package/lib/vendor/blamejs/lib/agent-saga.js +8 -4
- package/lib/vendor/blamejs/lib/agent-tenant.js +56 -4
- package/lib/vendor/blamejs/lib/agent-trace.js +4 -3
- package/lib/vendor/blamejs/lib/ai-adverse-decision.js +1 -1
- package/lib/vendor/blamejs/lib/audit-daily-review.js +3 -3
- package/lib/vendor/blamejs/lib/audit.js +11 -8
- package/lib/vendor/blamejs/lib/calendar.js +14 -10
- package/lib/vendor/blamejs/lib/circuit-breaker.js +6 -4
- package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +2 -2
- package/lib/vendor/blamejs/lib/compliance.js +7 -9
- package/lib/vendor/blamejs/lib/cookies.js +2 -2
- package/lib/vendor/blamejs/lib/crypto-field.js +8 -3
- package/lib/vendor/blamejs/lib/data-act.js +4 -3
- package/lib/vendor/blamejs/lib/file-upload.js +18 -1
- package/lib/vendor/blamejs/lib/mail-bimi.js +3 -2
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +7 -11
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +6 -5
- package/lib/vendor/blamejs/lib/mail-crypto.js +5 -5
- package/lib/vendor/blamejs/lib/mail-store.js +3 -2
- package/lib/vendor/blamejs/lib/mcp.js +2 -3
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +10 -6
- package/lib/vendor/blamejs/lib/middleware/ai-act-disclosure.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +3 -3
- package/lib/vendor/blamejs/lib/middleware/compose-pipeline.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/csp-nonce.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/flag-context.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +2 -1
- package/lib/vendor/blamejs/lib/queue-redis.js +9 -0
- package/lib/vendor/blamejs/lib/queue.js +17 -1
- package/lib/vendor/blamejs/lib/retry.js +26 -0
- package/lib/vendor/blamejs/lib/router.js +4 -1
- package/lib/vendor/blamejs/lib/safe-decompress.js +3 -2
- package/lib/vendor/blamejs/lib/safe-icap.js +3 -2
- package/lib/vendor/blamejs/lib/safe-mime.js +1 -1
- package/lib/vendor/blamejs/lib/safe-smtp.js +1 -1
- package/lib/vendor/blamejs/lib/sec-cyber.js +1 -1
- package/lib/vendor/blamejs/lib/storage.js +20 -16
- package/lib/vendor/blamejs/lib/vault/index.js +1 -0
- package/lib/vendor/blamejs/lib/vault-aad.js +2 -2
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.13.25.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.13.26.json +31 -0
- package/lib/vendor/blamejs/release-notes/v0.13.27.json +34 -0
- package/lib/vendor/blamejs/release-notes/v0.13.28.json +30 -0
- package/lib/vendor/blamejs/release-notes/v0.13.29.json +30 -0
- package/lib/vendor/blamejs/release-notes/v0.13.30.json +30 -0
- package/lib/vendor/blamejs/release-notes/v0.13.31.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.13.32.json +34 -0
- package/lib/vendor/blamejs/test/20-db.js +30 -0
- package/lib/vendor/blamejs/test/integration/queue-redis.test.js +14 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +37 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-orchestrator.test.js +37 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-tenant.test.js +26 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-daily-review.test.js +9 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retry.test.js +25 -0
- package/package.json +1 -1
|
@@ -107,8 +107,9 @@ var MAX_EXPAND_SPAN_MS = 10 * 365 * 24 * 60 * 60 * 1000;
|
|
|
107
107
|
* @since 0.11.31
|
|
108
108
|
* @status stable
|
|
109
109
|
*
|
|
110
|
-
* Validate a JSCalendar Event / Task object's
|
|
111
|
-
* RFC 8984 §5 (Event)
|
|
110
|
+
* Validate a JSCalendar Event / Task / Note / Group object's
|
|
111
|
+
* required-field shape per RFC 8984 §5 (Event) / §6 (Task) / §7 (Note) /
|
|
112
|
+
* §1.4.4 (Group). Returns the input on success; throws
|
|
112
113
|
* `CalendarError` on refusal with a `.code` naming the specific shape
|
|
113
114
|
* rule that failed.
|
|
114
115
|
*
|
|
@@ -337,10 +338,11 @@ function validate(jsCal) {
|
|
|
337
338
|
* @since 0.11.31
|
|
338
339
|
* @status stable
|
|
339
340
|
*
|
|
340
|
-
* Parse iCalendar text (RFC 5545) via `b.safeIcal.parse` and map
|
|
341
|
-
*
|
|
342
|
-
* a single
|
|
343
|
-
* an array
|
|
341
|
+
* Parse iCalendar text (RFC 5545) via `b.safeIcal.parse` and map each
|
|
342
|
+
* VEVENT → JSCalendar Event, VTODO → Task, and VJOURNAL → Note
|
|
343
|
+
* (RFC 8984 §5 / §6 / §7). Returns a single object when the VCALENDAR
|
|
344
|
+
* holds exactly one component, or an array across all components when
|
|
345
|
+
* there are several.
|
|
344
346
|
*
|
|
345
347
|
* @opts
|
|
346
348
|
* safeIcalOpts: object, // forwarded to b.safeIcal.parse (caps, allowExperimental, etc.)
|
|
@@ -381,10 +383,12 @@ function fromIcal(text, opts) {
|
|
|
381
383
|
* @since 0.11.31
|
|
382
384
|
* @status stable
|
|
383
385
|
*
|
|
384
|
-
* Render a JSCalendar
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
386
|
+
* Render a JSCalendar object back to RFC 5545 iCalendar text — Event →
|
|
387
|
+
* VEVENT, Task → VTODO, Note → VJOURNAL, and a Group to a VCALENDAR
|
|
388
|
+
* carrying each member component. Returns a CRLF-terminated string
|
|
389
|
+
* wrapped in a `BEGIN:VCALENDAR / VERSION:2.0 /
|
|
390
|
+
* PRODID:-//blamejs//Calendar//EN / … / END:VCALENDAR` envelope per
|
|
391
|
+
* RFC 5545 §3.4.
|
|
388
392
|
*
|
|
389
393
|
* @opts
|
|
390
394
|
* prodid: string, // PRODID value to emit; default "-//blamejs//Calendar//EN"
|
|
@@ -36,8 +36,10 @@ var retryHelper = require("./retry");
|
|
|
36
36
|
* Build a circuit-breaker. Returns a CircuitBreaker instance with
|
|
37
37
|
* `wrap(fn)` (executes `fn` if the breaker is closed; throws an
|
|
38
38
|
* `Error` with `code: "CIRCUIT_OPEN"` + `isObjectStoreError: true` +
|
|
39
|
-
* `permanent: false` when open), `
|
|
40
|
-
* `onStateChange(handler)` listener registration
|
|
39
|
+
* `permanent: false` when open), `getState()`, `reset()`, and
|
|
40
|
+
* `onStateChange(handler)` listener registration (the handler, and the
|
|
41
|
+
* `onStateChange` opt, receive `{ name, from, to, at }` on every
|
|
42
|
+
* transition). Pass-through
|
|
41
43
|
* factory: identical instance shape to `b.retry.CircuitBreaker`,
|
|
42
44
|
* with the framework's `create(opts)` vocabulary.
|
|
43
45
|
*
|
|
@@ -53,8 +55,8 @@ var retryHelper = require("./retry");
|
|
|
53
55
|
* failureThreshold: number, // failures in the closed state before opening
|
|
54
56
|
* cooldownMs: number, // milliseconds the breaker stays open before probing
|
|
55
57
|
* successThreshold: number, // probe successes required to close from half-open
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
+
* onStateChange: Function, // ({ name, from, to, at }) → void; also emits the
|
|
59
|
+
* // `breaker.state.change` observability event
|
|
58
60
|
*
|
|
59
61
|
* @example
|
|
60
62
|
* var cb = b.circuitBreaker.create({
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
* The framework provides:
|
|
32
32
|
*
|
|
33
33
|
* - banner({ kind }) → builder for the standard disclosure banner
|
|
34
|
-
* - watermark({
|
|
35
|
-
* -
|
|
34
|
+
* - watermark({ mediaKind, ... }) → builder for content-marking tags
|
|
35
|
+
* - metaTags({ ... }) → meta tag pair for HTML pages
|
|
36
36
|
* - jsonLdDisclosure({ ... }) → JSON-LD <script> for structured-data emit
|
|
37
37
|
* - C2pa-stub → operator-feeds-claims pattern for
|
|
38
38
|
* C2PA Content Credentials integration
|
|
@@ -1407,14 +1407,11 @@ function postureDefault(posture, key) {
|
|
|
1407
1407
|
*
|
|
1408
1408
|
* @example
|
|
1409
1409
|
* b.compliance.posturesByDomain("privacy");
|
|
1410
|
-
* // → ["ccpa", "gdpr", "lgpd-br",
|
|
1411
|
-
* //
|
|
1410
|
+
* // → ["ccpa", "gdpr", "lgpd-br", ...] — every posture whose
|
|
1411
|
+
* // domain is "privacy" (the full set grows as regimes are added)
|
|
1412
1412
|
*
|
|
1413
1413
|
* b.compliance.posturesByDomain("health");
|
|
1414
|
-
* // → ["hipaa", "wmhmda"]
|
|
1415
|
-
*
|
|
1416
|
-
* b.compliance.posturesByDomain("payment");
|
|
1417
|
-
* // → ["pci-dss"]
|
|
1414
|
+
* // → ["hipaa", "wmhmda", ...] — every "health"-domain posture
|
|
1418
1415
|
*
|
|
1419
1416
|
* b.compliance.posturesByDomain("not-a-domain");
|
|
1420
1417
|
* // → []
|
|
@@ -1450,13 +1447,14 @@ function posturesByDomain(domain) {
|
|
|
1450
1447
|
*
|
|
1451
1448
|
* @example
|
|
1452
1449
|
* b.compliance.posturesByJurisdiction("EU");
|
|
1453
|
-
* // → ["gdpr", "dora", "nis2",
|
|
1450
|
+
* // → ["gdpr", "dora", "nis2", ...] — every EU-jurisdiction posture
|
|
1451
|
+
* // (the full set grows as regimes are added)
|
|
1454
1452
|
*
|
|
1455
1453
|
* b.compliance.posturesByJurisdiction("US");
|
|
1456
|
-
* // → ["hipaa", "soc2", "sox"]
|
|
1454
|
+
* // → ["hipaa", "soc2", "sox", ...] — every US-jurisdiction posture
|
|
1457
1455
|
*
|
|
1458
1456
|
* b.compliance.posturesByJurisdiction("US-CA");
|
|
1459
|
-
* // → ["ccpa"]
|
|
1457
|
+
* // → ["ccpa", ...] — every US-CA (California) posture
|
|
1460
1458
|
*
|
|
1461
1459
|
* b.compliance.posturesByJurisdiction("XX");
|
|
1462
1460
|
* // → []
|
|
@@ -127,8 +127,8 @@ function _scrubAttr(s) {
|
|
|
127
127
|
* `parseSafe`.
|
|
128
128
|
*
|
|
129
129
|
* @example
|
|
130
|
-
* var jar = b.cookies.parse("session=abc; theme
|
|
131
|
-
* // → { session: "abc", theme: "dark" }
|
|
130
|
+
* var jar = b.cookies.parse("session=abc; theme=dark%20mode");
|
|
131
|
+
* // → { session: "abc", theme: "dark mode" } // percent-decoded
|
|
132
132
|
*/
|
|
133
133
|
function parse(cookieHeader) {
|
|
134
134
|
var out = {};
|
|
@@ -495,9 +495,14 @@ function unsealRow(table, row) {
|
|
|
495
495
|
} catch (_e) { /* drop-silent */ }
|
|
496
496
|
unsealed = null;
|
|
497
497
|
}
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
|
|
498
|
+
// Assign unconditionally. `unsealed` already carries the right value
|
|
499
|
+
// for every branch: the plaintext on success, the original value on
|
|
500
|
+
// the not-actually-sealed pass-through (set above), and `null` on an
|
|
501
|
+
// unseal failure. The failure case MUST null the column so downstream
|
|
502
|
+
// sees "no value" rather than the attacker-crafted `vault:<…>` string
|
|
503
|
+
// (a prior `... ? unsealed : out[field]` guard silently kept the
|
|
504
|
+
// forged ciphertext on failure, defeating the documented defense).
|
|
505
|
+
out[field] = unsealed;
|
|
501
506
|
}
|
|
502
507
|
}
|
|
503
508
|
|
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
*
|
|
21
21
|
* - Art 4 §1 — let the user access "readily available product
|
|
22
22
|
* data" generated by their use of the connected product.
|
|
23
|
-
* `b.dataAct.
|
|
24
|
-
* operator-supplied data slice
|
|
23
|
+
* `b.dataAct.recordUserAccess({ productId, userId, dataSlice })`
|
|
24
|
+
* records the operator-supplied data slice for the regulator
|
|
25
|
+
* record.
|
|
25
26
|
* - Art 5 §1 — share that data with a third-party data
|
|
26
27
|
* recipient on the user's request, "without undue delay,
|
|
27
28
|
* free of charge to the user, of the same quality as is
|
|
@@ -46,7 +47,7 @@
|
|
|
46
47
|
* b.dataAct.declareProduct({ productId, dataHolder, ... })
|
|
47
48
|
* b.dataAct.recordUserAccess({ productId, userId, dataSlice, ... })
|
|
48
49
|
* b.dataAct.shareWithThirdParty({ productId, userId, recipient, scope })
|
|
49
|
-
*
|
|
50
|
+
* — refuses a DMA-gatekeeper recipient per Art 32 §1
|
|
50
51
|
* b.dataAct.recordSwitchRequest({ customerId, targetProvider, dataSlices })
|
|
51
52
|
*
|
|
52
53
|
* The framework does NOT host the connected-product data itself;
|
|
@@ -18,7 +18,14 @@
|
|
|
18
18
|
* for content gating and `b.guardFilename.gate({ profile: "strict" })`
|
|
19
19
|
* for filename gating. Operators opt out via `contentSafety: null`
|
|
20
20
|
* / `filenameSafety: null` (audited at create time so a security
|
|
21
|
-
* review can find the disabled-on-deploy rows).
|
|
21
|
+
* review can find the disabled-on-deploy rows). The byte-level
|
|
22
|
+
* content gate inspects the reassembled buffer, so it runs on uploads
|
|
23
|
+
* up to `maxStreamReassemblyBytes` (default 64 MiB); a larger upload
|
|
24
|
+
* is handed to `onFinalize` as a stream and the byte-content gate is
|
|
25
|
+
* skipped (MIME-sniff + filename gates still run, and the skip emits a
|
|
26
|
+
* `fileUpload.content_safety_skipped` warning audit). To guarantee
|
|
27
|
+
* content-gating of a type, cap `maxFileBytes` at or below
|
|
28
|
+
* `maxStreamReassemblyBytes`. Per-chunk hooks
|
|
22
29
|
* (`onChunk`) are the integration point for virus scanners and
|
|
23
30
|
* schema-shape checks; rejecting from the hook surfaces as a
|
|
24
31
|
* permanent `FileUploadError`.
|
|
@@ -1076,6 +1083,16 @@ function create(opts) {
|
|
|
1076
1083
|
// Clear the streaming alias if present — sanitized fits in memory.
|
|
1077
1084
|
bodyStream = null;
|
|
1078
1085
|
}
|
|
1086
|
+
} else if (safetyGate && typeof safetyGate.check === "function" && !bodyBuffer) {
|
|
1087
|
+
// A content-safety gate is configured for this extension, but the
|
|
1088
|
+
// upload streamed past maxStreamReassemblyBytes and was never
|
|
1089
|
+
// reassembled into a buffer the byte-level gate can inspect. The
|
|
1090
|
+
// MIME-sniff and filename gates still ran; the per-extension
|
|
1091
|
+
// content gate did NOT. Surface it (rather than skipping silently)
|
|
1092
|
+
// via an observability counter so operators can alert, lower
|
|
1093
|
+
// maxStreamReassemblyBytes, or cap maxFileBytes to force
|
|
1094
|
+
// content-gating of this type.
|
|
1095
|
+
_emitObs("fileUpload.content_safety_skipped_streamed", 1, { ext: safetyExt });
|
|
1079
1096
|
}
|
|
1080
1097
|
}
|
|
1081
1098
|
|
|
@@ -34,8 +34,9 @@
|
|
|
34
34
|
* subjectAltName URI matches the BIMI domain, and confirms the
|
|
35
35
|
* cert carries the BIMI mark-verification policy OID
|
|
36
36
|
* (1.3.6.1.5.5.7.3.31). The verified mark is returned as
|
|
37
|
-
* { svg, evidenceDocument } pulled from RFC 3709
|
|
38
|
-
* when present
|
|
37
|
+
* { svg, evidenceDocument } — `svg` pulled from the RFC 3709
|
|
38
|
+
* logotype extension when present, `evidenceDocument` echoed from the
|
|
39
|
+
* operator-supplied opts.evidenceDocument.
|
|
39
40
|
*
|
|
40
41
|
* `validateTinyPsSvg` enforces the AuthIndicators-WG Tiny PS subset:
|
|
41
42
|
* single root <svg>, version="1.2", baseProfile="tiny-ps", viewBox
|
|
@@ -57,19 +57,15 @@
|
|
|
57
57
|
* to a known operator key rather than trusting any key that
|
|
58
58
|
* happens to match the signature.
|
|
59
59
|
*
|
|
60
|
-
*
|
|
60
|
+
* Now live (promoted to the stable top-level surface in v0.11.32):
|
|
61
61
|
* - In-process encrypt + decrypt (Message Encrypted Session Key +
|
|
62
62
|
* Symmetrically Encrypted Integrity Protected Data packets,
|
|
63
|
-
* RFC 9580 §5.1 / §5.13)
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* substrate
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* on-each-side across two patches. Cheap escape hatch (pre-
|
|
70
|
-
* v0.10.14): operators wire a third-party OpenPGP library in
|
|
71
|
-
* their own consumer code and call this module's sign() /
|
|
72
|
-
* verify() on the resulting cleartext blob.
|
|
63
|
+
* RFC 9580 §5.1 / §5.13) as `b.mail.crypto.pgp.encrypt` /
|
|
64
|
+
* `.decrypt`, and WKD key discovery (draft-koch-openpgp-webkey-
|
|
65
|
+
* service) as `b.mail.crypto.pgp.wkd` — all on the same `b.cms`
|
|
66
|
+
* substrate that backs S/MIME sign/verify.
|
|
67
|
+
*
|
|
68
|
+
* Deferred (with the documented condition for opting in):
|
|
73
69
|
* - v6 signature packets (RFC 9580 §5.2.3, packet version 6 with
|
|
74
70
|
* SHA2-512 fingerprints and salted hashes). Defer condition: v6
|
|
75
71
|
* is not yet emitted by GnuPG 2.4 LTS or by Sequoia stable, so
|
|
@@ -670,14 +670,15 @@ function _wrapBase64(s) {
|
|
|
670
670
|
* Operator-side cert preflight that lights up at boot: refuses
|
|
671
671
|
* SHA-1 / MD5 signatures, RSA keys < 2048 bits, MD2 / MD5 / SHA-1
|
|
672
672
|
* as the certificate-signature algorithm. Returns the parsed cert
|
|
673
|
-
* shape
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
* the cert is outside its
|
|
673
|
+
* shape: the full subject / issuer DN strings, the validity window,
|
|
674
|
+
* the signature algorithm (name + OID), the key type, and the SHA-256
|
|
675
|
+
* fingerprint. Throws `mail-crypto/smime/bad-cert` on any of the above;
|
|
676
|
+
* throws `mail-crypto/smime/expired-cert` if the cert is outside its
|
|
677
|
+
* validity window.
|
|
677
678
|
*
|
|
678
679
|
* @example
|
|
679
680
|
* var info = b.mail.crypto.smime.checkCert({ certPem: pem });
|
|
680
|
-
* // → {
|
|
681
|
+
* // → { subject, issuer, validFrom, validTo, sigAlgName, sigAlgOid, keyType, fingerprint256 }
|
|
681
682
|
*/
|
|
682
683
|
function checkCert(opts) {
|
|
683
684
|
opts = validateOpts.requireObject(opts, "mail.crypto.smime.checkCert",
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
* (pub-alg 1, EMSA-PKCS1-v1_5 + SHA-256, 2048-bit floor per
|
|
23
23
|
* RFC 8301).
|
|
24
24
|
* - `b.mail.crypto.smime` — S/MIME 4.0 per RFC 8551 with CMS
|
|
25
|
-
* SignedData per RFC 5652.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* the
|
|
29
|
-
*
|
|
25
|
+
* SignedData per RFC 5652. Surface: sign() + verify() + verifyAll()
|
|
26
|
+
* (built on the `b.cms` substrate — digest recompute + timing-safe
|
|
27
|
+
* compare + PQC signature verify + X.509 chain walk) plus
|
|
28
|
+
* checkCert(), the operator-side preflight that refuses SHA-1 /
|
|
29
|
+
* MD5 / < 2048-bit RSA certs at boot.
|
|
30
30
|
*
|
|
31
31
|
* Both sub-namespaces share `MailCryptoError` (FrameworkError
|
|
32
32
|
* subclass via defineClass with alwaysPermanent: true) so operator
|
|
@@ -90,8 +90,9 @@ var DEFAULT_FOLDERS = Object.freeze([
|
|
|
90
90
|
* @related b.safeMime, b.guardMessageId, b.cryptoField
|
|
91
91
|
*
|
|
92
92
|
* Build a mail-store handle. Returns an object with `appendMessage` /
|
|
93
|
-
* `fetchByObjectId` / `
|
|
94
|
-
* `
|
|
93
|
+
* `fetchByObjectId` / `search` / `queryByModseq` / `setFlags` /
|
|
94
|
+
* `createFolder` / `listFolders` / `threadFor` / `quota` /
|
|
95
|
+
* `moveMessages` / `setLegalHold` / `hardExpunge`.
|
|
95
96
|
*
|
|
96
97
|
* @opts
|
|
97
98
|
* backend: object, // required — sqlite-shaped { prepare(sql) → { run, get, all }, transaction(fn) }
|
|
@@ -399,10 +399,10 @@ function serverGuard(opts) {
|
|
|
399
399
|
* exfiltration endpoints. The framework's defense:
|
|
400
400
|
*
|
|
401
401
|
* - Strip / refuse executable HTML (`<script>` / `<iframe>` /
|
|
402
|
-
* `javascript:` URLs)
|
|
402
|
+
* `javascript:` URLs) via built-in dangerous-HTML detection
|
|
403
403
|
* - Refuse known prompt-injection markers ("ignore previous
|
|
404
404
|
* instructions", "system: you are now ...", role-claim prefixes)
|
|
405
|
-
*
|
|
405
|
+
* via a built-in injection-marker matcher
|
|
406
406
|
* - Cap text length so a tool can't blow the host's context window
|
|
407
407
|
* out from under it
|
|
408
408
|
* - Refuse content with `image_url` / `audio_url` / `resource_link`
|
|
@@ -418,7 +418,6 @@ function serverGuard(opts) {
|
|
|
418
418
|
* posture?: "refuse" | "sanitize" | "audit-only", // default "refuse"
|
|
419
419
|
* maxTextBytes?: number, // default 64 KiB per content block
|
|
420
420
|
* allowedHosts?: string[], // for image/audio/resource_link refs
|
|
421
|
-
* classifyInput?: fn(text)→{verdict, score} | null, // default b.ai.input.classify
|
|
422
421
|
* }
|
|
423
422
|
*
|
|
424
423
|
* @example
|
|
@@ -53,16 +53,20 @@ var AgeGateError = defineClass("AgeGateError", { alwaysPermanent: true });
|
|
|
53
53
|
* Classifies the request against an operator-supplied age predicate
|
|
54
54
|
* and applies COPPA / UK Children's Code / California AADC defaults
|
|
55
55
|
* (no-store cache, no-referrer, X-Privacy-Posture header) for
|
|
56
|
-
* below-threshold + unknown-age requests.
|
|
57
|
-
*
|
|
58
|
-
* record
|
|
59
|
-
*
|
|
56
|
+
* below-threshold + unknown-age requests. `requireAge` is the hard
|
|
57
|
+
* legal floor: a request classified below threshold without a
|
|
58
|
+
* parental-consent record is refused with HTTP 451. It is evaluated
|
|
59
|
+
* within the consent classification, so it takes effect only when
|
|
60
|
+
* `consentRequired` is also set (that is what classifies a request as
|
|
61
|
+
* below-threshold); `requireAge` alone, with `consentRequired: null`,
|
|
62
|
+
* never classifies a request as below-threshold and so the 451 never
|
|
63
|
+
* fires. Every classification decision is audited.
|
|
60
64
|
*
|
|
61
65
|
* @opts
|
|
62
66
|
* {
|
|
63
67
|
* getAge: function(req): number|null, // required
|
|
64
|
-
* requireAge: number|null, // 451
|
|
65
|
-
* consentRequired: number|null, // require consent below
|
|
68
|
+
* requireAge: number|null, // 451 floor; requires consentRequired set
|
|
69
|
+
* consentRequired: number|null, // require consent below; enables below-threshold classification
|
|
66
70
|
* hasParentalConsent: function(req): boolean,
|
|
67
71
|
* skipPaths: string[],
|
|
68
72
|
* errorMessage: string,
|
|
@@ -60,7 +60,7 @@ var audit = lazyRequire(function () { return require("../audit"); });
|
|
|
60
60
|
*
|
|
61
61
|
* @opts
|
|
62
62
|
* {
|
|
63
|
-
* kind: "ai-interaction"|"
|
|
63
|
+
* kind: "ai-interaction"|"ai-generated-content"|"emotion-recognition"|"biometric-categorisation"|"deep-fake"|"ai-text-public-interest",
|
|
64
64
|
* deployerName: string,
|
|
65
65
|
* policyUri: string,
|
|
66
66
|
* mode: "header"|"html", // default "header"
|
|
@@ -250,11 +250,11 @@ function _writeRejection(res, code, body) {
|
|
|
250
250
|
*
|
|
251
251
|
* @opts
|
|
252
252
|
* {
|
|
253
|
-
* keypair: { publicKey,
|
|
253
|
+
* keypair: { publicKey, privateKey, ecPublicKey, ecPrivateKey },
|
|
254
254
|
* keypairs: [...] // multi-key rotation set; first = active
|
|
255
255
|
* replayWindowMs: number,
|
|
256
256
|
* pruneIntervalMs: number,
|
|
257
|
-
* nonceStore: {
|
|
257
|
+
* nonceStore: { checkAndInsert, purgeExpired, close },
|
|
258
258
|
* exemptPaths: string[],
|
|
259
259
|
* contentTypes: string[], // default ["application/json"]
|
|
260
260
|
* audit: boolean,
|
|
@@ -270,7 +270,7 @@ function _writeRejection(res, code, body) {
|
|
|
270
270
|
* @example
|
|
271
271
|
* var b = require("@blamejs/core");
|
|
272
272
|
* var app = b.router.create();
|
|
273
|
-
* var kp = b.crypto.
|
|
273
|
+
* var kp = b.crypto.generateEncryptionKeyPair();
|
|
274
274
|
* app.use(b.middleware.apiEncrypt({
|
|
275
275
|
* keypair: kp,
|
|
276
276
|
* replayWindowMs: 30000,
|
|
@@ -104,7 +104,7 @@ var CANONICAL_POSITIONS = Object.freeze({
|
|
|
104
104
|
/**
|
|
105
105
|
* @primitive b.middleware.composePipeline
|
|
106
106
|
* @signature b.middleware.composePipeline(entries, opts?)
|
|
107
|
-
* @since 0.9.
|
|
107
|
+
* @since 0.9.43
|
|
108
108
|
* @status stable
|
|
109
109
|
* @related b.middleware.requestId, b.middleware.requireAuth, b.middleware.idempotencyKey
|
|
110
110
|
*
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* browser refuses to execute them. This middleware closes the gap:
|
|
9
9
|
*
|
|
10
10
|
* 1. Generate a fresh random nonce per request (base64, default
|
|
11
|
-
* 16 bytes /
|
|
11
|
+
* 16 bytes / 24 chars).
|
|
12
12
|
* 2. Attach it to `req.cspNonce` (handler-readable) AND
|
|
13
13
|
* `res.locals.cspNonce` (template-data-readable — render.js
|
|
14
14
|
* auto-merges res.locals into template data).
|
|
@@ -233,7 +233,7 @@ function _injectNonce(cspHeader, nonce, directives, strictDynamic) {
|
|
|
233
233
|
* Per-request CSP nonce + render integration. Constructed via
|
|
234
234
|
* `b.middleware.cspNonce(opts)`; the resulting middleware has the
|
|
235
235
|
* `(req, res, next)` shape shown above. Generates a fresh
|
|
236
|
-
* random nonce (16 bytes /
|
|
236
|
+
* random nonce (16 bytes / 24 chars base64 by default), attaches it
|
|
237
237
|
* to `req.cspNonce` and `res.locals.cspNonce` (auto-merged into
|
|
238
238
|
* template data), and patches the existing Content-Security-Policy
|
|
239
239
|
* header to append `'nonce-XYZ'` to the configured directives
|
|
@@ -34,7 +34,7 @@ var contextMod = lazyRequire(function () { return require("../flag-evaluation-co
|
|
|
34
34
|
* @primitive b.middleware.flagContext
|
|
35
35
|
* @signature b.middleware.flagContext(opts)
|
|
36
36
|
* @since 0.1.0
|
|
37
|
-
* @related b.
|
|
37
|
+
* @related b.flag.create
|
|
38
38
|
*
|
|
39
39
|
* Extracts an OpenFeature evaluation context onto `req.flagCtx` so
|
|
40
40
|
* downstream handlers and multiple flag clients read a consistent
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
* when set, prefersJson()=false rejections
|
|
31
31
|
* produce 302 instead of 401 text/plain)
|
|
32
32
|
* prefersJson: function (optional override; defaults to checking
|
|
33
|
-
* Accept / X-Requested-With
|
|
33
|
+
* Accept / X-Requested-With — NOT
|
|
34
|
+
* Content-Type, see the note above)
|
|
34
35
|
* errorMessage: 'Authentication required.'
|
|
35
36
|
* audit: true (emit auth.required.denied on reject)
|
|
36
37
|
* }
|
|
@@ -582,6 +582,15 @@ function create(opts) {
|
|
|
582
582
|
async function fail(jobId, errorMessage, retryDelayMs) {
|
|
583
583
|
await _ensureConnected();
|
|
584
584
|
var nowMs = Date.now();
|
|
585
|
+
// b.queue.consume passes the object form `{ retryDelayMs }` (matching
|
|
586
|
+
// the queue-local backend); accept it as well as a bare-number third
|
|
587
|
+
// arg. Without this the object failed the `typeof === "number"` test
|
|
588
|
+
// below and the delay was forced to 0, so the documented exponential
|
|
589
|
+
// backoff was silently discarded and a failing job re-leased
|
|
590
|
+
// immediately on the redis backend (retry storm).
|
|
591
|
+
if (retryDelayMs && typeof retryDelayMs === "object") {
|
|
592
|
+
retryDelayMs = retryDelayMs.retryDelayMs;
|
|
593
|
+
}
|
|
585
594
|
if (typeof retryDelayMs !== "number" || !isFinite(retryDelayMs) || retryDelayMs < 0) {
|
|
586
595
|
retryDelayMs = 0;
|
|
587
596
|
}
|
|
@@ -17,6 +17,18 @@
|
|
|
17
17
|
* and `nats` are listed as deferred and surface a clear error if
|
|
18
18
|
* selected.
|
|
19
19
|
*
|
|
20
|
+
* `local` and `redis` are driven by the generic `b.queue.consume`
|
|
21
|
+
* loop and the lifecycle below (framework-side leasing, deterministic
|
|
22
|
+
* backoff, DLQ, and the sweep timer). `sqs` is an SQS-native adapter
|
|
23
|
+
* with a different model: `complete` / `fail` delete or re-deliver by
|
|
24
|
+
* the message's `receiptHandle` (returned by `lease()`, threaded back
|
|
25
|
+
* by the caller), and DLQ + visibility-expiry are handled server-side
|
|
26
|
+
* by the SQS queue's RedrivePolicy — so `sqs` is driven directly
|
|
27
|
+
* (lease → handle → complete/fail), not by `b.queue.consume`, and it
|
|
28
|
+
* does not use the framework DLQ / sweep described below. See
|
|
29
|
+
* `lib/queue-sqs.js` for its action map and the features that require
|
|
30
|
+
* operator wiring.
|
|
31
|
+
*
|
|
20
32
|
* Job lifecycle:
|
|
21
33
|
* enqueued (status='pending', availableAt set by delaySeconds)
|
|
22
34
|
* ↓ availableAt reached + consumer leases
|
|
@@ -271,7 +283,11 @@ function enqueue(queueName, payload, opts) {
|
|
|
271
283
|
* through `handler(job, ctx)`. Handler resolution marks the job
|
|
272
284
|
* `done`; rejection bumps the attempt counter and either re-pends
|
|
273
285
|
* with deterministic exponential backoff (1s base, 5min cap, no
|
|
274
|
-
* jitter) or routes to the DLQ when `attempts >= maxAttempts`.
|
|
286
|
+
* jitter) or routes to the DLQ when `attempts >= maxAttempts`. This
|
|
287
|
+
* loop drives the `local` and `redis` backends; the `sqs` backend uses
|
|
288
|
+
* SQS-native receipt-handle complete/fail and server-side redrive and
|
|
289
|
+
* is driven directly rather than by this consumer (see the module
|
|
290
|
+
* intro).
|
|
275
291
|
*
|
|
276
292
|
* Returns a consumer state handle whose `.cancel()` aborts the poll
|
|
277
293
|
* loop immediately (without waiting for the next `pollIntervalMs`
|
|
@@ -187,6 +187,10 @@ function _validateBreakerOpts(name, opts) {
|
|
|
187
187
|
throw new TypeError("retry.CircuitBreaker: successThreshold must be a positive integer, got " +
|
|
188
188
|
typeof opts.successThreshold + " " + JSON.stringify(opts.successThreshold));
|
|
189
189
|
}
|
|
190
|
+
if (opts.onStateChange != null && typeof opts.onStateChange !== "function") {
|
|
191
|
+
throw new TypeError("retry.CircuitBreaker: onStateChange must be a function, got " +
|
|
192
|
+
typeof opts.onStateChange);
|
|
193
|
+
}
|
|
190
194
|
}
|
|
191
195
|
|
|
192
196
|
// ---- Public surface ----
|
|
@@ -381,6 +385,19 @@ class CircuitBreaker {
|
|
|
381
385
|
this.consecutiveFailures = 0;
|
|
382
386
|
this.consecutiveSuccesses = 0;
|
|
383
387
|
this.openedAt = 0;
|
|
388
|
+
this._stateListeners = [];
|
|
389
|
+
if (typeof merged.onStateChange === "function") this._stateListeners.push(merged.onStateChange);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Register a state-change listener. Called with { name, from, to, at }
|
|
393
|
+
// on every transition (same payload the constructor's onStateChange
|
|
394
|
+
// opt receives). Returns this for chaining.
|
|
395
|
+
onStateChange(handler) {
|
|
396
|
+
if (typeof handler !== "function") {
|
|
397
|
+
throw new TypeError("retry.CircuitBreaker.onStateChange: handler must be a function, got " + typeof handler);
|
|
398
|
+
}
|
|
399
|
+
this._stateListeners.push(handler);
|
|
400
|
+
return this;
|
|
384
401
|
}
|
|
385
402
|
|
|
386
403
|
// Wrap an async function. The breaker observes outcomes and may fail-fast.
|
|
@@ -415,7 +432,16 @@ class CircuitBreaker {
|
|
|
415
432
|
_transition(from, to) {
|
|
416
433
|
if (from === to) return;
|
|
417
434
|
this.state = to;
|
|
435
|
+
var at = Date.now();
|
|
418
436
|
_emitEvent("breaker.state.change", 1, { name: this.name, from: from, to: to });
|
|
437
|
+
if (this._stateListeners.length > 0) {
|
|
438
|
+
var payload = { name: this.name, from: from, to: to, at: at };
|
|
439
|
+
for (var i = 0; i < this._stateListeners.length; i++) {
|
|
440
|
+
// Best-effort: a throwing listener must not derail the breaker's
|
|
441
|
+
// own state machine (the transition has already been applied).
|
|
442
|
+
try { this._stateListeners[i](payload); } catch (_e) { /* drop-silent */ }
|
|
443
|
+
}
|
|
444
|
+
}
|
|
419
445
|
}
|
|
420
446
|
|
|
421
447
|
_onSuccess() {
|
|
@@ -1229,7 +1229,10 @@ function serveStatic(dir) {
|
|
|
1229
1229
|
var rel = req.pathname;
|
|
1230
1230
|
if (rel.includes("\0")) return next();
|
|
1231
1231
|
var filePath = nodePath.resolve(nodePath.join(root, rel));
|
|
1232
|
-
|
|
1232
|
+
// Anchor on `root + sep` (not a bare prefix) so a sibling directory
|
|
1233
|
+
// that shares the root's name as a prefix — e.g. root `/srv/public`
|
|
1234
|
+
// vs `/srv/public-evil` — cannot satisfy the containment check.
|
|
1235
|
+
if (filePath !== root && !filePath.startsWith(root + nodePath.sep)) return next();
|
|
1233
1236
|
if (!nodeFs.existsSync(filePath) || nodeFs.statSync(filePath).isDirectory()) return next();
|
|
1234
1237
|
|
|
1235
1238
|
var ext = nodePath.extname(filePath).toLowerCase();
|
|
@@ -37,8 +37,9 @@
|
|
|
37
37
|
* - Any algorithm not in the allowlist (including operator-typo'd).
|
|
38
38
|
*
|
|
39
39
|
* Refusal posture:
|
|
40
|
-
* - `safe-decompress/
|
|
41
|
-
* (zlib's own `maxOutputLength`
|
|
40
|
+
* - `safe-decompress/decompress-failed` — bomb-by-absolute-size
|
|
41
|
+
* (zlib's own `maxOutputLength` refuses before alloc; the throw is
|
|
42
|
+
* caught and surfaced under this code)
|
|
42
43
|
* - `safe-decompress/ratio-exceeded` — expansion > `maxRatio`
|
|
43
44
|
* (zlib accepted the bytes; our post-decompress ratio check
|
|
44
45
|
* refuses, freeing the bytes immediately)
|
|
@@ -37,8 +37,9 @@
|
|
|
37
37
|
* `\r\n`; intermediaries that accept bare-LF then desync against
|
|
38
38
|
* this parser).
|
|
39
39
|
* - **Status-code allowlist** — only `100` / `200` / `204` / `400`
|
|
40
|
-
* / `403` / 5xx are honored. RFC 3507
|
|
41
|
-
* the legal ICAP response codes; an
|
|
40
|
+
* / `403` / `404` / `405` / `408` / 5xx are honored. RFC 3507
|
|
41
|
+
* §4.3.3 enumerates these as the legal ICAP response codes; an
|
|
42
|
+
* unexpected `1xx` continuation
|
|
42
43
|
* or `3xx` redirect is refused because it's a classic header-
|
|
43
44
|
* injection class (attacker smuggles `ICAP/1.0 100 X-Inject:`
|
|
44
45
|
* through a permissive proxy).
|
|
@@ -125,7 +125,7 @@ var DEFAULT_TRANSFER_ENCODINGS = Object.freeze([
|
|
|
125
125
|
* maxBodyBytes: number, // default 25 MiB
|
|
126
126
|
* maxMessageBytes: number, // default 50 MiB
|
|
127
127
|
* charsetAllowlist: string[], // default UTF-8 / US-ASCII / common legacy 8-bit
|
|
128
|
-
* transferEncodingAllowlist: string[], // default 7bit/8bit/
|
|
128
|
+
* transferEncodingAllowlist: string[], // default 7bit/8bit/quoted-printable/base64 (binary is opt-in, RFC 3030 BINARYMIME)
|
|
129
129
|
*
|
|
130
130
|
* @example
|
|
131
131
|
* var msg = b.safeMime.parse(messageBuffer);
|
|
@@ -61,7 +61,7 @@ var SafeSmtpError = defineClass("SafeSmtpError", { alwaysPermanent: true });
|
|
|
61
61
|
* @example
|
|
62
62
|
* var body = Buffer.from("Hello world.\r\n.\r\n");
|
|
63
63
|
* b.safeSmtp.findDotTerminator(body);
|
|
64
|
-
* // →
|
|
64
|
+
* // → 12 (index of \r in the terminating \r\n.\r\n)
|
|
65
65
|
*
|
|
66
66
|
* b.safeSmtp.findDotTerminator(Buffer.from("incomplete body"));
|
|
67
67
|
* // → -1
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
*
|
|
33
33
|
* Public API:
|
|
34
34
|
*
|
|
35
|
-
* b.secCyber.eightKArtifact(opts) -> { artifact, deadline,
|
|
35
|
+
* b.secCyber.eightKArtifact(opts) -> { artifact, deadline, deadlineBusinessDays }
|
|
36
36
|
* opts:
|
|
37
37
|
* incidentId: operator-supplied incident reference (string).
|
|
38
38
|
* registrant: { name, cik, filer }
|