@blamejs/core 0.8.43 → 0.8.49
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 +92 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
|
@@ -1,56 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* b.contentCredentials
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* effective 2026-08-02, require providers of generative AI systems
|
|
8
|
-
* to embed a latent (machine-readable) provenance disclosure in
|
|
9
|
-
* every AI-generated image / video / audio asset distributed in
|
|
10
|
-
* California. The disclosure MUST carry:
|
|
11
|
-
*
|
|
12
|
-
* - Provider name
|
|
13
|
-
* - System (model) identifier + version
|
|
14
|
-
* - Content timestamp (when generated)
|
|
15
|
-
* - Unique content ID
|
|
16
|
-
*
|
|
17
|
-
* SB-942 specifically cites C2PA (Coalition for Content Provenance
|
|
18
|
-
* and Authenticity) as an acceptable disclosure format. C2PA 2.1+
|
|
19
|
-
* manifests carry signed assertions with the same fields.
|
|
20
|
-
*
|
|
21
|
-
* The framework can't embed the manifest into image/video/audio
|
|
22
|
-
* bytes directly (that requires format-specific muxers — JPEG XMP /
|
|
23
|
-
* PNG iTXt / MP4 ContentBoxes / etc. that vary per codec). What it
|
|
24
|
-
* CAN do:
|
|
25
|
-
*
|
|
26
|
-
* - Build a C2PA-shaped manifest carrying the required fields.
|
|
27
|
-
* - Sign the manifest with the framework's audit-sign keypair
|
|
28
|
-
* (ML-DSA-87 — or operator-supplied SigStore key).
|
|
29
|
-
* - Emit a tamper-evident audit row recording the disclosure.
|
|
30
|
-
* - Validate inbound manifests presented by upstream content
|
|
31
|
-
* pipelines (the receiver side of the same chain).
|
|
32
|
-
*
|
|
33
|
-
* Operator workflow:
|
|
3
|
+
* @module b.contentCredentials
|
|
4
|
+
* @featured true
|
|
5
|
+
* @nav AI
|
|
6
|
+
* @title Content Credentials
|
|
34
7
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* // operator hands `signed.manifest` to their muxer for embedding
|
|
8
|
+
* @intro
|
|
9
|
+
* C2PA 2.1 content provenance — sign assets with a manifest
|
|
10
|
+
* declaring origin, edits, AI involvement.
|
|
11
|
+
*
|
|
12
|
+
* California SB-942 (Cal. Bus. & Prof. Code §22757) + AB-853,
|
|
13
|
+
* effective 2026-08-02, require generative-AI providers to embed a
|
|
14
|
+
* latent disclosure carrying provider name, system identifier,
|
|
15
|
+
* system version, content timestamp, and a unique content ID in
|
|
16
|
+
* every AI-generated image / video / audio asset distributed in
|
|
17
|
+
* California. SB-942 names C2PA as an acceptable format.
|
|
46
18
|
*
|
|
47
|
-
*
|
|
19
|
+
* The framework can't push bytes into format-specific muxers (JPEG
|
|
20
|
+
* XMP / PNG iTXt / MP4 boxes vary per codec). What it does ship:
|
|
21
|
+
* build a C2PA-shaped manifest with the SB-942 required fields,
|
|
22
|
+
* sign it with the audit-sign keypair (ML-DSA-87 by default),
|
|
23
|
+
* record a tamper-evident audit row, and verify inbound manifests
|
|
24
|
+
* on the receive side. Operators hand the signed manifest to their
|
|
25
|
+
* format-specific embedder.
|
|
48
26
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* contentCredentials.verify(envelope, publicKeyPem) -> { valid, claims }
|
|
52
|
-
* contentCredentials.required(opts) -> array of missing-field errors
|
|
53
|
-
* (returns [] when the operator's input satisfies SB-942 minimums)
|
|
27
|
+
* @card
|
|
28
|
+
* C2PA 2.1 content provenance — sign assets with a manifest declaring origin, edits, AI involvement.
|
|
54
29
|
*/
|
|
55
30
|
|
|
56
31
|
var crypto = require("./crypto");
|
|
@@ -112,6 +87,44 @@ function _validateBuildOpts(opts) {
|
|
|
112
87
|
}
|
|
113
88
|
}
|
|
114
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @primitive b.contentCredentials.build
|
|
92
|
+
* @signature b.contentCredentials.build(opts)
|
|
93
|
+
* @since 0.8.44
|
|
94
|
+
* @related b.contentCredentials.sign, b.contentCredentials.verify, b.contentCredentials.required
|
|
95
|
+
*
|
|
96
|
+
* Build an unsigned C2PA 2.1-shaped manifest carrying the SB-942
|
|
97
|
+
* §22757(a) required fields (provider, system, system version,
|
|
98
|
+
* content ID) plus optional content type, SHA3-512 digest, and a
|
|
99
|
+
* visible-disclosure string. Returns a frozen object so downstream
|
|
100
|
+
* code can't mutate the claims before signing. `generatedAt`
|
|
101
|
+
* defaults to `Date.now()` so the manifest carries a real timestamp
|
|
102
|
+
* unless the operator pins one for testing.
|
|
103
|
+
*
|
|
104
|
+
* @opts
|
|
105
|
+
* provider: string, // e.g. "Acme AI Inc."
|
|
106
|
+
* providerContact: string, // optional contact URL
|
|
107
|
+
* system: string, // model id, e.g. "acme-image-v3"
|
|
108
|
+
* systemVersion: string, // semver
|
|
109
|
+
* contentId: string, // unique per asset
|
|
110
|
+
* contentType: string, // IANA media type (optional)
|
|
111
|
+
* contentSha3: string, // SHA3-512 hex (optional)
|
|
112
|
+
* generatedAt: number, // ms epoch (optional)
|
|
113
|
+
* visibleDisclosure: string, // operator display text (optional)
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* var manifest = b.contentCredentials.build({
|
|
117
|
+
* provider: "Acme AI Inc.",
|
|
118
|
+
* system: "acme-image-v3",
|
|
119
|
+
* systemVersion: "3.2.1",
|
|
120
|
+
* contentId: "img-2026-05-08-abc123",
|
|
121
|
+
* contentType: "image/png",
|
|
122
|
+
* generatedAt: Date.UTC(2026, 4, 8),
|
|
123
|
+
* });
|
|
124
|
+
* manifest.aiGenerated; // → true
|
|
125
|
+
* manifest.system.id; // → "acme-image-v3"
|
|
126
|
+
* manifest.content.id; // → "img-2026-05-08-abc123"
|
|
127
|
+
*/
|
|
115
128
|
function build(opts) {
|
|
116
129
|
_validateBuildOpts(opts);
|
|
117
130
|
var generatedAt = typeof opts.generatedAt === "number" ? opts.generatedAt : Date.now();
|
|
@@ -141,6 +154,36 @@ function build(opts) {
|
|
|
141
154
|
return Object.freeze(manifest);
|
|
142
155
|
}
|
|
143
156
|
|
|
157
|
+
/**
|
|
158
|
+
* @primitive b.contentCredentials.required
|
|
159
|
+
* @signature b.contentCredentials.required(opts)
|
|
160
|
+
* @since 0.8.44
|
|
161
|
+
* @related b.contentCredentials.build, b.contentCredentials.verify
|
|
162
|
+
*
|
|
163
|
+
* Pre-flight check that returns the list of SB-942 §22757(a) fields
|
|
164
|
+
* missing from a candidate input — useful for operator UIs that
|
|
165
|
+
* surface "what's needed before we can disclose" without round-
|
|
166
|
+
* tripping through `build` and catching the throw. Returns `[]`
|
|
167
|
+
* when every required field is present and non-empty.
|
|
168
|
+
*
|
|
169
|
+
* @opts
|
|
170
|
+
* provider: string, // required
|
|
171
|
+
* system: string, // required
|
|
172
|
+
* systemVersion: string, // required
|
|
173
|
+
* contentId: string, // required
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* b.contentCredentials.required({
|
|
177
|
+
* provider: "Acme AI Inc.",
|
|
178
|
+
* system: "acme-image-v3",
|
|
179
|
+
* systemVersion: "3.2.1",
|
|
180
|
+
* contentId: "img-001",
|
|
181
|
+
* });
|
|
182
|
+
* // → []
|
|
183
|
+
*
|
|
184
|
+
* b.contentCredentials.required({ provider: "Acme AI Inc." });
|
|
185
|
+
* // → ["missing-system", "missing-systemVersion", "missing-contentId"]
|
|
186
|
+
*/
|
|
144
187
|
function required(opts) {
|
|
145
188
|
var errors = [];
|
|
146
189
|
if (!opts || typeof opts !== "object") return ["opts-required"];
|
|
@@ -152,6 +195,36 @@ function required(opts) {
|
|
|
152
195
|
return errors;
|
|
153
196
|
}
|
|
154
197
|
|
|
198
|
+
/**
|
|
199
|
+
* @primitive b.contentCredentials.sign
|
|
200
|
+
* @signature b.contentCredentials.sign(manifest, opts)
|
|
201
|
+
* @since 0.8.44
|
|
202
|
+
* @related b.contentCredentials.build, b.contentCredentials.verify, b.crypto.sign
|
|
203
|
+
*
|
|
204
|
+
* Canonicalize the manifest (RFC 8785 JCS via `b.canonicalJson`) and
|
|
205
|
+
* sign it with `b.crypto.sign` using the operator's private-key PEM
|
|
206
|
+
* — typically the ML-DSA-87 audit-sign keypair. Returns an envelope
|
|
207
|
+
* with the original manifest plus the base64-encoded signature.
|
|
208
|
+
* Audits the disclosure under `contentcredentials.signed` unless the
|
|
209
|
+
* caller passes `audit:false`.
|
|
210
|
+
*
|
|
211
|
+
* @opts
|
|
212
|
+
* privateKeyPem: string, // PEM-encoded signing key
|
|
213
|
+
* audit: boolean, // default true
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* var pair = b.crypto.generateSigningKeyPair("ml-dsa-87");
|
|
217
|
+
* var manifest = b.contentCredentials.build({
|
|
218
|
+
* provider: "Acme AI Inc.",
|
|
219
|
+
* system: "acme-image-v3",
|
|
220
|
+
* systemVersion: "3.2.1",
|
|
221
|
+
* contentId: "img-2026-05-08-abc123",
|
|
222
|
+
* });
|
|
223
|
+
* var envelope = b.contentCredentials.sign(manifest, {
|
|
224
|
+
* privateKeyPem: pair.privateKey,
|
|
225
|
+
* });
|
|
226
|
+
* typeof envelope.signature; // → "string"
|
|
227
|
+
*/
|
|
155
228
|
function sign(manifest, opts) {
|
|
156
229
|
opts = opts || {};
|
|
157
230
|
if (!manifest || typeof manifest !== "object") {
|
|
@@ -180,6 +253,38 @@ function sign(manifest, opts) {
|
|
|
180
253
|
};
|
|
181
254
|
}
|
|
182
255
|
|
|
256
|
+
/**
|
|
257
|
+
* @primitive b.contentCredentials.verify
|
|
258
|
+
* @signature b.contentCredentials.verify(envelope, publicKeyPem, opts)
|
|
259
|
+
* @since 0.8.44
|
|
260
|
+
* @related b.contentCredentials.sign, b.contentCredentials.build, b.crypto.verify
|
|
261
|
+
*
|
|
262
|
+
* Verify a signed envelope produced by `sign`. Re-canonicalizes the
|
|
263
|
+
* manifest, checks the signature with `b.crypto.verify` against the
|
|
264
|
+
* operator-supplied public-key PEM, and re-runs the SB-942 required-
|
|
265
|
+
* field presence check on the verified claims so a manifest with a
|
|
266
|
+
* valid signature but missing fields fails closed. Never throws —
|
|
267
|
+
* returns `{ valid, claims, reason }`. Audits successful
|
|
268
|
+
* verifications under `contentcredentials.verified` unless
|
|
269
|
+
* `audit:false`.
|
|
270
|
+
*
|
|
271
|
+
* @opts
|
|
272
|
+
* audit: boolean, // default true
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* var pair = b.crypto.generateSigningKeyPair("ml-dsa-87");
|
|
276
|
+
* var manifest = b.contentCredentials.build({
|
|
277
|
+
* provider: "Acme AI Inc.",
|
|
278
|
+
* system: "acme-image-v3",
|
|
279
|
+
* systemVersion: "3.2.1",
|
|
280
|
+
* contentId: "img-001",
|
|
281
|
+
* });
|
|
282
|
+
* var envelope = b.contentCredentials.sign(manifest, {
|
|
283
|
+
* privateKeyPem: pair.privateKey,
|
|
284
|
+
* });
|
|
285
|
+
* var result = b.contentCredentials.verify(envelope, pair.publicKey);
|
|
286
|
+
* result.valid; // → true
|
|
287
|
+
*/
|
|
183
288
|
function verify(envelope, publicKeyPem, opts) {
|
|
184
289
|
opts = opts || {};
|
|
185
290
|
if (!envelope || typeof envelope !== "object" || !envelope.manifest || !envelope.signature) {
|
package/lib/cookies.js
CHANGED
|
@@ -1,55 +1,50 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.cookies
|
|
4
|
+
* @nav HTTP
|
|
5
|
+
* @title Cookies
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* RFC 6265 cookie plumbing — parse, serialize, and sealed (vault-
|
|
9
|
+
* gated) cookies in one primitive. Replaces the ad-hoc Set-Cookie
|
|
10
|
+
* strings that used to live in middleware and route handlers.
|
|
9
11
|
*
|
|
10
|
-
*
|
|
12
|
+
* Two surfaces:
|
|
11
13
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
+
* 1. Module-level (stateless): `b.cookies.parse` /
|
|
15
|
+
* `b.cookies.serialize` / `b.cookies.parseSafe`. Useful in test
|
|
16
|
+
* fixtures and code paths that don't have a vault wired.
|
|
14
17
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
18
|
+
* 2. Instance (`b.cookies.create`): bound defaults for cookie
|
|
19
|
+
* attributes, a wired vault for sealed reads/writes, and
|
|
20
|
+
* request/response helpers (`read` / `write` / `clear` /
|
|
21
|
+
* `writeSealed` / `readSealed`).
|
|
17
22
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* secure: true, // default true; HTTPS expected
|
|
23
|
-
* sameSite: "Lax",
|
|
24
|
-
* path: "/",
|
|
25
|
-
* maxAge: 7 * 86400, // seconds
|
|
26
|
-
* },
|
|
27
|
-
* });
|
|
23
|
+
* Defaults mirror modern browser expectations: HttpOnly on,
|
|
24
|
+
* Secure on, SameSite=Lax, Path=/. Operators developing locally
|
|
25
|
+
* over plain http opt out of Secure explicitly so the production
|
|
26
|
+
* posture isn't silently weakened.
|
|
28
27
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* Cookie-prefix invariants from RFC 6265bis §4.1.3 are enforced at
|
|
29
|
+
* serialize time: `__Secure-*` requires Secure; `__Host-*` requires
|
|
30
|
+
* Secure + Path=/ + no Domain. Operator typos (`__Host-` cookie
|
|
31
|
+
* without Path=/) throw at the source instead of silently failing
|
|
32
|
+
* on the browser side.
|
|
32
33
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* Header-injection defense: cookie names are RFC 6265 tokens; values
|
|
35
|
+
* reject CRLF / NUL / semicolon / comma pre-encoding, then percent-
|
|
36
|
+
* encode on write and percent-decode on read. Domain / Path
|
|
37
|
+
* attributes are CRLF/NUL-scrubbed before they reach Set-Cookie.
|
|
36
38
|
*
|
|
37
|
-
* cookies
|
|
38
|
-
*
|
|
39
|
+
* Sealed cookies wrap the value in a `vault.seal` envelope: without
|
|
40
|
+
* the framework's vault key no client can hand-craft a valid value,
|
|
41
|
+
* so curl-with-arbitrary-cookies (or any tool that hasn't been
|
|
42
|
+
* through the framework's crypto flow) can't reach a sealed-cookie-
|
|
43
|
+
* gated endpoint. The vault prefix is stripped on write and re-added
|
|
44
|
+
* on read so the cookie carries only the compact base64 envelope.
|
|
39
45
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* valid cookie value, so the API is unreachable via curl-with-arbitrary-
|
|
43
|
-
* cookies or any tool that hasn't been through the framework's crypto
|
|
44
|
-
* flow. The vault prefix is stripped on write and re-added on read so
|
|
45
|
-
* the cookie carries only the base64 envelope.
|
|
46
|
-
*
|
|
47
|
-
* Defense in serialize/parse:
|
|
48
|
-
* - Cookie name must be a valid token (no CTLs, no separator chars).
|
|
49
|
-
* - Cookie value must not contain CRLF, semicolon, or comma.
|
|
50
|
-
* - Value is percent-encoded on write, percent-decoded on read.
|
|
51
|
-
* - Domain / Path are CRLF-stripped to defeat header injection
|
|
52
|
-
* attempts via operator-controlled but improperly-escaped inputs.
|
|
46
|
+
* @card
|
|
47
|
+
* RFC 6265 cookie plumbing — parse, serialize, and sealed (vault- gated) cookies in one primitive.
|
|
53
48
|
*/
|
|
54
49
|
|
|
55
50
|
var C = require("./constants");
|
|
@@ -116,6 +111,25 @@ function _scrubAttr(s) {
|
|
|
116
111
|
|
|
117
112
|
}
|
|
118
113
|
|
|
114
|
+
/**
|
|
115
|
+
* @primitive b.cookies.parse
|
|
116
|
+
* @signature b.cookies.parse(cookieHeader)
|
|
117
|
+
* @since 0.1.72
|
|
118
|
+
* @status stable
|
|
119
|
+
* @related b.cookies.parseSafe, b.cookies.serialize, b.cookies.create
|
|
120
|
+
*
|
|
121
|
+
* Lenient RFC 6265 Cookie-header parser. Returns a plain object
|
|
122
|
+
* `{ name: value }` with last-write-wins semantics (matching every
|
|
123
|
+
* browser). Surrounding double-quotes are stripped per §5.2 and
|
|
124
|
+
* values are percent-decoded; malformed pairs are silently dropped
|
|
125
|
+
* because that's how browsers behave. For the threat-detecting
|
|
126
|
+
* variant that surfaces issues instead of dropping silently, use
|
|
127
|
+
* `parseSafe`.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* var jar = b.cookies.parse("session=abc; theme=%22dark%22");
|
|
131
|
+
* // → { session: "abc", theme: "dark" }
|
|
132
|
+
*/
|
|
119
133
|
function parse(cookieHeader) {
|
|
120
134
|
var out = {};
|
|
121
135
|
if (typeof cookieHeader !== "string" || cookieHeader.length === 0) return out;
|
|
@@ -140,6 +154,43 @@ function parse(cookieHeader) {
|
|
|
140
154
|
return out;
|
|
141
155
|
}
|
|
142
156
|
|
|
157
|
+
/**
|
|
158
|
+
* @primitive b.cookies.serialize
|
|
159
|
+
* @signature b.cookies.serialize(name, value, attrs)
|
|
160
|
+
* @since 0.1.72
|
|
161
|
+
* @status stable
|
|
162
|
+
* @related b.cookies.parse, b.cookies.create
|
|
163
|
+
*
|
|
164
|
+
* Build a single Set-Cookie header value from a name, value, and
|
|
165
|
+
* attributes object. Validates the name as an RFC 6265 token, the
|
|
166
|
+
* value against CRLF / NUL / `;` / `,`, and enforces the `__Secure-`
|
|
167
|
+
* / `__Host-` prefix invariants from RFC 6265bis §4.1.3. SameSite
|
|
168
|
+
* is normalized to `Strict` / `Lax` / `None`, and SameSite=None
|
|
169
|
+
* implicitly turns Secure on so browsers don't silently drop the
|
|
170
|
+
* cookie. Throws `CookieError` on any invariant break — the operator
|
|
171
|
+
* sees the typo at the call site, not as a silently-missing cookie.
|
|
172
|
+
*
|
|
173
|
+
* @opts
|
|
174
|
+
* maxAge: 3600, // integer seconds; emits `Max-Age=`
|
|
175
|
+
* expires: new Date(), // Date or parseable date string
|
|
176
|
+
* domain: "example.com",
|
|
177
|
+
* path: "/",
|
|
178
|
+
* httpOnly: true,
|
|
179
|
+
* secure: true,
|
|
180
|
+
* sameSite: "Lax", // "Strict" / "Lax" / "None"
|
|
181
|
+
* partitioned: false, // CHIPS partitioning
|
|
182
|
+
* priority: "Medium", // "Low" / "Medium" / "High"
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* var header = b.cookies.serialize("__Host-sid", "abc", {
|
|
186
|
+
* httpOnly: true,
|
|
187
|
+
* secure: true,
|
|
188
|
+
* sameSite: "Lax",
|
|
189
|
+
* path: "/",
|
|
190
|
+
* maxAge: 3600,
|
|
191
|
+
* });
|
|
192
|
+
* // → "__Host-sid=abc; Max-Age=3600; Path=/; HttpOnly; SameSite=Lax; Secure"
|
|
193
|
+
*/
|
|
143
194
|
function serialize(name, value, attrs) {
|
|
144
195
|
_validateName(name);
|
|
145
196
|
_validateValue(value);
|
|
@@ -269,6 +320,40 @@ function _readCookieFromReq(req, name) {
|
|
|
269
320
|
return Object.prototype.hasOwnProperty.call(jar, name) ? jar[name] : null;
|
|
270
321
|
}
|
|
271
322
|
|
|
323
|
+
/**
|
|
324
|
+
* @primitive b.cookies.create
|
|
325
|
+
* @signature b.cookies.create(opts)
|
|
326
|
+
* @since 0.1.72
|
|
327
|
+
* @status stable
|
|
328
|
+
* @related b.cookies.parse, b.cookies.serialize, b.vault.seal
|
|
329
|
+
*
|
|
330
|
+
* Build a cookie helper bound to a default attribute set and an
|
|
331
|
+
* optional vault. Returned object exposes `read(req, name)`,
|
|
332
|
+
* `write(res, name, value, attrs)`, `clear(res, name, attrs)`, and
|
|
333
|
+
* (when a vault is wired) `writeSealed` / `readSealed` for vault-
|
|
334
|
+
* gated cookie values. Per-call attrs merge over the bound defaults
|
|
335
|
+
* so callers override piecewise.
|
|
336
|
+
*
|
|
337
|
+
* @opts
|
|
338
|
+
* vault: b.vault, // required for sealed* methods
|
|
339
|
+
* defaults: {
|
|
340
|
+
* httpOnly: true,
|
|
341
|
+
* secure: true,
|
|
342
|
+
* sameSite: "Lax",
|
|
343
|
+
* path: "/",
|
|
344
|
+
* maxAge: 604800, // seconds (7 days)
|
|
345
|
+
* },
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* var cookies = b.cookies.create({
|
|
349
|
+
* vault: b.vault,
|
|
350
|
+
* defaults: { httpOnly: true, secure: true, sameSite: "Lax", path: "/" },
|
|
351
|
+
* });
|
|
352
|
+
* cookies.write(res, "theme", "dark", { maxAge: 86400 });
|
|
353
|
+
* cookies.writeSealed(res, "session", "u-1");
|
|
354
|
+
* var sid = cookies.readSealed(req, "session");
|
|
355
|
+
* // → "u-1" or null
|
|
356
|
+
*/
|
|
272
357
|
function create(opts) {
|
|
273
358
|
opts = opts || {};
|
|
274
359
|
validateOpts(opts, ["vault", "defaults"], "b.cookies");
|
|
@@ -343,27 +428,34 @@ function create(opts) {
|
|
|
343
428
|
};
|
|
344
429
|
}
|
|
345
430
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
|
|
366
|
-
|
|
431
|
+
/**
|
|
432
|
+
* @primitive b.cookies.parseSafe
|
|
433
|
+
* @signature b.cookies.parseSafe(cookieHeader, opts)
|
|
434
|
+
* @since 0.7.20
|
|
435
|
+
* @status stable
|
|
436
|
+
* @related b.cookies.parse, b.middleware.cookies
|
|
437
|
+
*
|
|
438
|
+
* Threat-detecting inbound-cookie parser. Returns
|
|
439
|
+
* `{ jar, issues }` where every detected anomaly surfaces as an
|
|
440
|
+
* issue instead of being silently dropped (as the lenient `parse`
|
|
441
|
+
* does). Detected issues: oversized header / pair, duplicate cookie
|
|
442
|
+
* name (cookie-tossing class), malformed pair, CR / LF / NUL in the
|
|
443
|
+
* raw header (proxy-side injection vector), and non-string input.
|
|
444
|
+
* Issue shape: `{ kind, severity: "high" | "warn", snippet, name? }`.
|
|
445
|
+
*
|
|
446
|
+
* @opts
|
|
447
|
+
* maxHeaderBytes: 8192, // total Cookie-header byte cap
|
|
448
|
+
* maxNameBytes: 256, // per-name byte cap
|
|
449
|
+
* maxValueBytes: 4096, // per-value byte cap
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* var result = b.cookies.parseSafe("session=abc; session=evil", {
|
|
453
|
+
* maxHeaderBytes: 8192,
|
|
454
|
+
* });
|
|
455
|
+
* // → { jar: { session: "evil" },
|
|
456
|
+
* // issues: [{ kind: "duplicate-name", severity: "high",
|
|
457
|
+
* // name: "session", snippet: "..." }] }
|
|
458
|
+
*/
|
|
367
459
|
function parseSafe(cookieHeader, opts) {
|
|
368
460
|
opts = opts || {};
|
|
369
461
|
var maxHeaderBytes = opts.maxHeaderBytes || C.BYTES.kib(8);
|