@blamejs/core 0.8.42 → 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 +93 -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
package/lib/credential-hash.js
CHANGED
|
@@ -1,73 +1,44 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* b.credentialHash
|
|
3
|
+
* @module b.credentialHash
|
|
4
|
+
* @nav Identity
|
|
5
|
+
* @title Credential Hash
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Derive a deterministic, verifiable hash for credential lookup
|
|
9
|
+
* (API-key secret, shared bearer token, webhook signing key) without
|
|
10
|
+
* storing the credential itself. The default is an Argon2id-style
|
|
11
|
+
* fingerprint over a SHAKE256 MAC — same chassis the password
|
|
12
|
+
* primitive uses, but tuned for high-entropy machine-generated
|
|
13
|
+
* secrets where memory-hard work is unnecessary.
|
|
7
14
|
*
|
|
8
|
-
*
|
|
9
|
-
* byte 1: <algorithm ID>
|
|
10
|
-
* bytes 2..N: algorithm-specific payload
|
|
15
|
+
* Rows persist a base64 envelope:
|
|
11
16
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* surface via `needsRehash()` and the next successful verify rotates
|
|
16
|
-
* them transparently — same pattern as `b.auth.password.needsRehash`.
|
|
17
|
+
* byte 0: 0xC1 (CREDENTIAL_MAGIC)
|
|
18
|
+
* byte 1: algorithm ID (0x01 SHAKE256 | 0x02 Argon2id)
|
|
19
|
+
* bytes 2-N: algorithm-specific payload
|
|
17
20
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
21
|
+
* `verify` dispatches on the algorithm byte so old rows remain
|
|
22
|
+
* verifiable regardless of what `ACTIVE.CRED_HASH` is today. When a
|
|
23
|
+
* new algorithm becomes the framework default, existing rows surface
|
|
24
|
+
* via `needsRehash()` and the next successful verify rotates them
|
|
25
|
+
* transparently — same pattern as `b.auth.password.needsRehash`.
|
|
20
26
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* await db.update({ credentialHash: await b.credentialHash.hash(secretBytes) });
|
|
29
|
-
* }
|
|
30
|
-
*
|
|
31
|
-
* Active algorithm: SHAKE256 (0x01). Suitable for high-entropy random
|
|
32
|
-
* secrets (≥ 128 bits) — fast verify (microseconds), brute-force
|
|
33
|
-
* infeasible at the entropy level the framework generates. SHAKE256
|
|
34
|
-
* is an XOF: the envelope payload length drives the digest size, so
|
|
35
|
-
* a future operator can request a 96-byte (or 32-byte) digest without
|
|
36
|
-
* a primitive change — the same algorithm ID covers all output sizes.
|
|
37
|
-
* Operators with low-entropy or operator-supplied secrets should pin
|
|
38
|
-
* Argon2id (0x02) per-registry: `hash(s, { algo: "argon2id" })`.
|
|
39
|
-
*
|
|
40
|
-
* Why SHAKE256 over SHA3-512 as the active:
|
|
41
|
-
* - SHAKE256 is an extensible-output function (XOF). The envelope
|
|
42
|
-
* payload's actual byte length tells the verify path how many
|
|
43
|
-
* bytes to recompute. Changing digest size = no algo rotation.
|
|
44
|
-
* - Same family as the framework KDF (`crypto.kdf`), so one
|
|
45
|
-
* primitive does double duty.
|
|
46
|
-
* - SHA-3 family fixed-size mode locks the byte count at 64 — the
|
|
47
|
-
* moment we want a different size, we'd have to rotate algos.
|
|
48
|
-
*
|
|
49
|
-
* Why not Argon2id by default for api-key:
|
|
50
|
-
* - Argon2id at framework defaults costs ~250ms per verify call.
|
|
51
|
-
* For request-path verification that's a real latency hit.
|
|
52
|
-
* SHAKE256 is microseconds.
|
|
53
|
-
* - For ≥128-bit random secrets, the memory-hard property buys
|
|
54
|
-
* nothing — brute force is infeasible regardless of hash.
|
|
55
|
-
*
|
|
56
|
-
* Why the envelope still matters with SHAKE256 as active:
|
|
57
|
-
* - Algorithm agility — when SHA-3 family ever shows weakness, or
|
|
58
|
-
* a stronger XOF lands, ACTIVE.CRED_HASH rotates with no need
|
|
59
|
-
* to re-issue every credential. Old rows verify under their
|
|
60
|
-
* stored algo byte; new rows use the active.
|
|
61
|
-
* - Transparent rehash via needsRehash() drains old algos at the
|
|
62
|
-
* pace of organic verify traffic.
|
|
27
|
+
* Active algorithm: SHAKE256 (0x01). Suitable for high-entropy random
|
|
28
|
+
* secrets (>= 128 bits) — verify is microseconds, brute force is
|
|
29
|
+
* infeasible at the entropy level the framework generates. SHAKE256
|
|
30
|
+
* is an XOF: the envelope payload length drives the digest size, so
|
|
31
|
+
* a future operator can request a 96-byte (or 32-byte) digest with no
|
|
32
|
+
* algorithm rotation. Operators with low-entropy or operator-supplied
|
|
33
|
+
* secrets pin Argon2id per-registry via `{ algo: "argon2id" }`.
|
|
63
34
|
*
|
|
64
|
-
*
|
|
35
|
+
* Validation tiers:
|
|
36
|
+
* - hash() opts and secret shape — throw at call site (config-time)
|
|
37
|
+
* - verify() malformed envelope or unknown algo ID — return false
|
|
38
|
+
* - inspect() malformed envelope — return null
|
|
65
39
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* - verify() envelope shape unparsable → return false (tolerant read)
|
|
69
|
-
* - verify() unknown algo ID → return false (tolerant read)
|
|
70
|
-
* - inspect() bad envelope → return null (tolerant read)
|
|
40
|
+
* @card
|
|
41
|
+
* Derive a deterministic, verifiable hash for credential lookup (API-key secret, shared bearer token, webhook signing key) without storing the credential itself.
|
|
71
42
|
*/
|
|
72
43
|
|
|
73
44
|
var crypto = require("./crypto");
|
|
@@ -196,6 +167,37 @@ function _decodeEnvelope(env) {
|
|
|
196
167
|
|
|
197
168
|
// ---- Public surface ----
|
|
198
169
|
|
|
170
|
+
/**
|
|
171
|
+
* @primitive b.credentialHash.hash
|
|
172
|
+
* @signature b.credentialHash.hash(secret, opts?)
|
|
173
|
+
* @since 0.2.28
|
|
174
|
+
* @status stable
|
|
175
|
+
* @compliance pci-dss, soc2, hipaa
|
|
176
|
+
* @related b.credentialHash.verify, b.credentialHash.needsRehash, b.auth.password.hash
|
|
177
|
+
*
|
|
178
|
+
* Hash a credential secret into a base64 envelope ready for storage in
|
|
179
|
+
* a `credentialHash` column. Default algorithm is SHAKE256 with a
|
|
180
|
+
* 128-byte output; pass `{ algo: "argon2id" }` for low-entropy or
|
|
181
|
+
* operator-supplied secrets. Throws on a non-string-or-Buffer secret,
|
|
182
|
+
* an unknown algorithm, a non-object `params`, or a SHAKE256 length
|
|
183
|
+
* below the 16-byte (128-bit) collision-space floor.
|
|
184
|
+
*
|
|
185
|
+
* @opts
|
|
186
|
+
* algo: "shake256" | "argon2id",
|
|
187
|
+
* params: {
|
|
188
|
+
* length: number, // SHAKE256 output bytes (default 128)
|
|
189
|
+
* ... // Argon2id m / t / p forwarded to b.auth.password
|
|
190
|
+
* },
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* var token = b.crypto.generateToken(); // 32 random bytes, base64url
|
|
194
|
+
* var env = await b.credentialHash.hash(token);
|
|
195
|
+
* // → "wQE..." (base64 envelope)
|
|
196
|
+
*
|
|
197
|
+
* // Operator-supplied (low-entropy) secret pins Argon2id:
|
|
198
|
+
* var humanEnv = await b.credentialHash.hash("partner-shared-key", { algo: "argon2id" });
|
|
199
|
+
* // → "wQI..." (base64 envelope, algo byte 0x02)
|
|
200
|
+
*/
|
|
199
201
|
async function hash(secret, opts) {
|
|
200
202
|
_validateSecret(secret);
|
|
201
203
|
_validateOpts(opts);
|
|
@@ -228,6 +230,29 @@ async function hash(secret, opts) {
|
|
|
228
230
|
"credential-hash/unsupported");
|
|
229
231
|
}
|
|
230
232
|
|
|
233
|
+
/**
|
|
234
|
+
* @primitive b.credentialHash.verify
|
|
235
|
+
* @signature b.credentialHash.verify(secret, envelope)
|
|
236
|
+
* @since 0.2.28
|
|
237
|
+
* @status stable
|
|
238
|
+
* @compliance pci-dss, soc2, hipaa
|
|
239
|
+
* @related b.credentialHash.hash, b.credentialHash.needsRehash
|
|
240
|
+
*
|
|
241
|
+
* Constant-time check that `secret` matches the stored envelope.
|
|
242
|
+
* Tolerant read: malformed envelope / unknown algorithm / payload
|
|
243
|
+
* shorter than 16 bytes returns `false` without throwing, so callers
|
|
244
|
+
* write a single `if (!await verify(...))` branch without try/catch
|
|
245
|
+
* ceremony. Emits `credentialHash.verify` observability events with
|
|
246
|
+
* outcome + reason for SIEM dashboards.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* var ok = await b.credentialHash.verify(presented, row.credentialHash);
|
|
250
|
+
* if (!ok) {
|
|
251
|
+
* res.statusCode = 401;
|
|
252
|
+
* return res.end();
|
|
253
|
+
* }
|
|
254
|
+
* // → true / false
|
|
255
|
+
*/
|
|
231
256
|
async function verify(secret, envelope) {
|
|
232
257
|
// Tolerant read: any malformed envelope → false. Lets operators write
|
|
233
258
|
// if (!await ch.verify(s, row.hash)) return res.status(401);
|
|
@@ -285,6 +310,26 @@ async function verify(secret, envelope) {
|
|
|
285
310
|
return false;
|
|
286
311
|
}
|
|
287
312
|
|
|
313
|
+
/**
|
|
314
|
+
* @primitive b.credentialHash.inspect
|
|
315
|
+
* @signature b.credentialHash.inspect(envelope)
|
|
316
|
+
* @since 0.2.28
|
|
317
|
+
* @status stable
|
|
318
|
+
* @related b.credentialHash.needsRehash
|
|
319
|
+
*
|
|
320
|
+
* Decode the envelope's algorithm byte and payload length without
|
|
321
|
+
* verifying the secret. Returns `null` for any malformed envelope
|
|
322
|
+
* (missing magic byte, unknown algorithm, truncated). Used by
|
|
323
|
+
* operator dashboards to count rows-by-algorithm during a rotation
|
|
324
|
+
* window.
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* var info = b.credentialHash.inspect(row.credentialHash);
|
|
328
|
+
* if (info && info.algoName === "shake256" && info.payloadBytes < 64) {
|
|
329
|
+
* console.warn("legacy SHAKE256 row, will be rotated on next verify");
|
|
330
|
+
* }
|
|
331
|
+
* // → { algoId: 0x01, algoName: "shake256", payloadBytes: 128 }
|
|
332
|
+
*/
|
|
288
333
|
function inspect(envelope) {
|
|
289
334
|
var decoded = _decodeEnvelope(envelope);
|
|
290
335
|
if (!decoded) return null;
|
|
@@ -295,6 +340,33 @@ function inspect(envelope) {
|
|
|
295
340
|
};
|
|
296
341
|
}
|
|
297
342
|
|
|
343
|
+
/**
|
|
344
|
+
* @primitive b.credentialHash.needsRehash
|
|
345
|
+
* @signature b.credentialHash.needsRehash(envelope, opts?)
|
|
346
|
+
* @since 0.2.28
|
|
347
|
+
* @status stable
|
|
348
|
+
* @related b.credentialHash.hash, b.credentialHash.verify, b.auth.password.needsRehash
|
|
349
|
+
*
|
|
350
|
+
* Returns `true` when the stored envelope was produced under an
|
|
351
|
+
* algorithm or parameter set that no longer matches the framework
|
|
352
|
+
* default. Operators wrap a successful `verify` with this and re-issue
|
|
353
|
+
* the credential transparently — same shape as `b.auth.password`.
|
|
354
|
+
* Argon2id rows defer the parameter-lag check to the password
|
|
355
|
+
* primitive's own `needsRehash` so the threshold lives in one place.
|
|
356
|
+
*
|
|
357
|
+
* @opts
|
|
358
|
+
* algo: "shake256" | "argon2id", // pin the comparison target
|
|
359
|
+
* params: object, // Argon2id m / t / p targets
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* if (await b.credentialHash.verify(secret, row.credentialHash)) {
|
|
363
|
+
* if (b.credentialHash.needsRehash(row.credentialHash)) {
|
|
364
|
+
* var fresh = await b.credentialHash.hash(secret);
|
|
365
|
+
* db.from("apiKeys").where({ _id: row._id }).update({ credentialHash: fresh });
|
|
366
|
+
* }
|
|
367
|
+
* }
|
|
368
|
+
* // → true / false
|
|
369
|
+
*/
|
|
298
370
|
function needsRehash(envelope, opts) {
|
|
299
371
|
var decoded = _decodeEnvelope(envelope);
|
|
300
372
|
if (!decoded) return true; // unrecognized → migrate aggressively
|