@blamejs/core 0.15.12 → 0.15.13
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/index.js +2 -0
- package/lib/a2a-tasks.js +7 -23
- package/lib/acme.js +6 -5
- package/lib/agent-event-bus.js +5 -4
- package/lib/agent-idempotency.js +2 -5
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-capability.js +1 -6
- package/lib/ai-dp.js +1 -5
- package/lib/ai-input.js +2 -2
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-read.js +5 -17
- package/lib/archive-tar-read.js +5 -16
- package/lib/archive.js +2 -10
- package/lib/arg-parser.js +7 -6
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +108 -31
- package/lib/audit-chain.js +133 -53
- package/lib/audit-daily-review.js +24 -14
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +257 -0
- package/lib/audit.js +84 -0
- package/lib/auth/access-lock.js +5 -27
- package/lib/auth/dpop.js +23 -35
- package/lib/auth/fido-mds3.js +9 -15
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +13 -15
- package/lib/auth/lockout.js +6 -25
- package/lib/auth/oauth.js +67 -45
- package/lib/auth/oid4vci.js +55 -32
- package/lib/auth/oid4vp.js +3 -2
- package/lib/auth/openid-federation.js +6 -6
- package/lib/auth/passkey.js +3 -3
- package/lib/auth/password.js +7 -1
- package/lib/auth/saml.js +37 -27
- package/lib/auth/sd-jwt-vc-holder.js +2 -14
- package/lib/auth/sd-jwt-vc-issuer.js +2 -14
- package/lib/auth/sd-jwt-vc.js +1 -1
- package/lib/auth/status-list.js +7 -7
- package/lib/auth/step-up.js +6 -12
- package/lib/auth-bot-challenge.js +6 -37
- package/lib/backup/bundle.js +11 -18
- package/lib/backup/index.js +14 -47
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/cache.js +7 -18
- package/lib/cbor.js +2 -1
- package/lib/cdn-cache-control.js +8 -9
- package/lib/cert.js +3 -10
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +5 -1
- package/lib/client-hints.js +7 -9
- package/lib/cloud-events.js +40 -31
- package/lib/cluster-storage.js +2 -38
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +67 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +1 -11
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +2 -14
- package/lib/compliance.js +2 -9
- package/lib/config-drift.js +2 -12
- package/lib/content-digest.js +10 -8
- package/lib/cookies.js +5 -11
- package/lib/cose.js +19 -11
- package/lib/cra-report.js +1 -11
- package/lib/crypto-field.js +5 -10
- package/lib/crypto.js +120 -3
- package/lib/csp.js +235 -3
- package/lib/daemon.js +42 -41
- package/lib/data-act.js +19 -9
- package/lib/db-query.js +6 -40
- package/lib/db.js +34 -12
- package/lib/dbsc.js +5 -6
- package/lib/ddl-change-control.js +18 -14
- package/lib/deprecate.js +4 -5
- package/lib/did.js +6 -2
- package/lib/dsr.js +8 -12
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +14 -26
- package/lib/fda-21cfr11.js +12 -8
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/framework-error.js +12 -0
- package/lib/framework-schema.js +19 -0
- package/lib/fsm.js +7 -12
- package/lib/gate-contract.js +869 -38
- package/lib/gdpr-ropa.js +4 -13
- package/lib/graphql-federation.js +1 -1
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +1 -0
- package/lib/guard-archive.js +9 -30
- package/lib/guard-auth.js +23 -80
- package/lib/guard-cidr.js +20 -96
- package/lib/guard-csv.js +135 -196
- package/lib/guard-domain.js +23 -106
- package/lib/guard-dsn.js +16 -13
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +1 -1
- package/lib/guard-event-bus-topic.js +2 -1
- package/lib/guard-filename.js +12 -60
- package/lib/guard-graphql.js +28 -75
- package/lib/guard-html-wcag.js +15 -2
- package/lib/guard-html.js +65 -117
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +8 -9
- package/lib/guard-json.js +87 -103
- package/lib/guard-jsonpath.js +20 -88
- package/lib/guard-jwt.js +32 -114
- package/lib/guard-list-id.js +2 -7
- package/lib/guard-list-unsubscribe.js +2 -7
- package/lib/guard-mail-compose.js +5 -6
- package/lib/guard-mail-move.js +2 -1
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +5 -4
- package/lib/guard-markdown.js +76 -110
- package/lib/guard-message-id.js +5 -6
- package/lib/guard-mime.js +20 -99
- package/lib/guard-oauth.js +19 -73
- package/lib/guard-pdf.js +65 -72
- package/lib/guard-pop3-command.js +12 -13
- package/lib/guard-posture-chain.js +2 -1
- package/lib/guard-regex.js +24 -99
- package/lib/guard-saga-config.js +2 -1
- package/lib/guard-shell.js +22 -87
- package/lib/guard-smtp-command.js +8 -11
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +95 -140
- package/lib/guard-template.js +23 -80
- package/lib/guard-tenant-id.js +2 -1
- package/lib/guard-text.js +592 -0
- package/lib/guard-time.js +27 -95
- package/lib/guard-uuid.js +21 -93
- package/lib/guard-xml.js +76 -106
- package/lib/guard-yaml.js +24 -60
- package/lib/http-client-cache.js +5 -12
- package/lib/http-client-cookie-jar.js +2 -4
- package/lib/http-client.js +8 -21
- package/lib/http-message-signature.js +3 -2
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +3 -21
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +6 -15
- package/lib/log-stream-cloudwatch.js +44 -112
- package/lib/log-stream-otlp-grpc.js +17 -14
- package/lib/log-stream-otlp.js +16 -80
- package/lib/log-stream-webhook.js +20 -92
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +3 -2
- package/lib/mail-auth.js +40 -66
- package/lib/mail-bimi.js +19 -39
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +6 -9
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +15 -26
- package/lib/mail-helo.js +2 -3
- package/lib/mail-journal.js +2 -1
- package/lib/mail-mdn.js +4 -3
- package/lib/mail-rbl.js +5 -8
- package/lib/mail-scan.js +2 -2
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +35 -51
- package/lib/mail-server-managesieve.js +29 -83
- package/lib/mail-server-mx.js +33 -74
- package/lib/mail-server-net.js +177 -0
- package/lib/mail-server-pop3.js +30 -83
- package/lib/mail-server-rate-limit.js +30 -6
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +89 -0
- package/lib/mail-spam-score.js +7 -8
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +6 -11
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +54 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +1 -1
- package/lib/mdoc.js +11 -12
- package/lib/metrics.js +32 -30
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +11 -22
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +31 -48
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +2 -10
- package/lib/middleware/csrf-protect.js +13 -2
- package/lib/middleware/daily-byte-quota.js +6 -26
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +7 -0
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/rate-limit.js +20 -28
- package/lib/middleware/scim-server.js +3 -2
- package/lib/middleware/security-headers.js +9 -1
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/tus-upload.js +12 -27
- package/lib/middleware/web-app-manifest.js +2 -7
- package/lib/migrations.js +4 -13
- package/lib/mime-parse.js +34 -17
- package/lib/module-loader.js +44 -0
- package/lib/money.js +105 -0
- package/lib/mtls-ca.js +116 -36
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +97 -6
- package/lib/network-dnssec.js +16 -16
- package/lib/network-heartbeat.js +3 -2
- package/lib/network-smtp-policy.js +29 -41
- package/lib/network-tls.js +40 -42
- package/lib/nis2-report.js +1 -11
- package/lib/nonce-store.js +81 -3
- package/lib/numeric-bounds.js +22 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +5 -45
- package/lib/object-store/gcs.js +8 -71
- package/lib/object-store/http-put.js +1 -5
- package/lib/object-store/http-request.js +128 -0
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +9 -77
- package/lib/observability-otlp-exporter.js +9 -25
- package/lib/observability.js +95 -0
- package/lib/openapi-paths-builder.js +7 -3
- package/lib/otel-export.js +7 -10
- package/lib/outbox.js +43 -37
- package/lib/pagination.js +11 -15
- package/lib/parsers/safe-env.js +5 -4
- package/lib/parsers/safe-ini.js +10 -4
- package/lib/parsers/safe-toml.js +11 -9
- package/lib/parsers/safe-xml.js +2 -2
- package/lib/parsers/safe-yaml.js +7 -6
- package/lib/pick.js +63 -5
- package/lib/pipl-cn.js +69 -59
- package/lib/privacy-pass.js +8 -6
- package/lib/problem-details.js +2 -5
- package/lib/pubsub.js +4 -8
- package/lib/redact.js +2 -1
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +133 -6
- package/lib/restore.js +5 -22
- package/lib/retention.js +3 -5
- package/lib/safe-async.js +457 -0
- package/lib/safe-buffer.js +137 -6
- package/lib/safe-decompress.js +2 -1
- package/lib/safe-dns.js +2 -1
- package/lib/safe-ical.js +12 -26
- package/lib/safe-json.js +7 -8
- package/lib/safe-jsonpath.js +6 -5
- package/lib/safe-mime.js +25 -29
- package/lib/safe-redirect.js +2 -5
- package/lib/safe-schema.js +7 -6
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +12 -26
- package/lib/sandbox-worker.js +9 -2
- package/lib/sandbox.js +3 -2
- package/lib/scheduler.js +5 -12
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +4 -11
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +194 -6
- package/lib/sql.js +225 -78
- package/lib/sse.js +6 -10
- package/lib/static.js +136 -98
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +275 -16
- package/lib/tenant-quota.js +2 -20
- package/lib/tsa.js +11 -15
- package/lib/validate-opts.js +154 -8
- package/lib/vault/seal-pem-file.js +30 -48
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +2 -7
- package/lib/vex.js +35 -13
- package/lib/web-push-vapid.js +23 -20
- package/lib/webhook-dispatcher.js +706 -0
- package/lib/webhook.js +43 -18
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +7 -3
- package/lib/worm.js +2 -3
- package/lib/ws-client.js +8 -10
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.15.x
|
|
10
10
|
|
|
11
|
+
- v0.15.13 (2026-06-19) — **Consolidates a large set of duplicated, security-sensitive code paths — injected-dependency validation, positive-integer option caps, TOCTOU-safe file reads, markup escaping, audit emission, structured-field parsing, and untrusted-JWK import — onto shared hardened primitives, so a fix or guard now applies everywhere at once; adds a TOCTOU-safe file reader; and makes several configuration errors throw typed framework errors instead of a bare Error.** Across the framework, logic that had been hand-rolled in many places (and had quietly drifted between copies) is now single-sourced through shared primitives. The practical effect for operators is consistency: the strongest available guard is applied everywhere a given operation happens, rather than in whichever copy happened to have it. Injected dependencies (stores, backends, vaults, db handles, query objects) are validated through one contract; operator-tunable positive-integer options (pool sizes, byte caps, timeouts, stream limits) validate through one bounds primitive that forwards the caller's non-retryable / HTTP-status flags; the open-fd → fstat → read-fully pattern that several call sites used to defend file reads against a swap-after-stat race (CWE-367) is now the b.atomicFile.fdSafeReadSync primitive, with symlink refusal, inode-equality, a byte cap, and an integrity hash available as options and each caller's exact typed error preserved; and the XML/HTML markup escapers (including the b.guardHtml / b.guardSvg sanitizer output) route through one b.markupEscape so the base & < > " chain can't drift between them. A few configuration-validation paths that threw a bare Error (httpClient.configurePool, b.db stream-limit validation) now throw the module's typed framework error. No security defaults change; this release tightens and unifies existing protections rather than adding opt-ins. **Added:** *b.atomicFile.fdSafeReadSync(filepath, opts?)* — A TOCTOU-safe synchronous file read (CWE-367 / js/file-system-race): it opens the path read-only, then binds the size, content, and integrity measurement to the inode the fd holds open, so a swap between stat and read can't change which bytes are returned. Optional guards layer on top — maxBytes (refuse an over-cap file), refuseSymlink + inodeCheck (refuse a symlink source and any inode swap, the strongest posture for operator-writable paths), expectedHash (SHA3-512), encoding (return a string), allowShortRead — and an errorFor(kind, detail) callback lets each caller keep its own typed error. The framework's own file reads (atomic-file, the TLS certificate-path loader, the sealed-PEM source reader, the backup bundle reader) all route through it. · *Shared substrate primitives for previously-duplicated logic* — A set of shared primitives now back call sites that had each re-implemented the same logic: validateOpts.requireMethods (gains a `permanent` flag) for injected-dependency contract checks; numericBounds positive-finite-int validators (gain an errorOpts { permanent, statusCode }) for tunable integer options; b.markupEscape for the & < > " markup chain; structured-field parsing helpers (parseTagList, forEachKeyValue, splitUnquoted, stripDoubleQuotes, unfoldHeaderContinuations); crypto.importPublicJwk and crypto.makeBase64UrlDecoder; safeSql.toPositional; safeBuffer.makeByteCoercer; nonceStore.enforceReplay; and the audit/observability namespaced emitters. These are mostly invisible substrate — the operator-visible benefit is that a guard or fix added to one of them now covers every caller. **Changed:** *Configuration errors throw typed framework errors* — Two configuration-validation paths that threw a bare `Error` now throw the module's typed framework error: httpClient.configurePool (bad maxSockets / maxFreeSockets / keepAliveMsecs → HttpClientError, code httpclient/bad-opts) and the b.db stream-limit validation (Query.stream → DbQueryError). The thrown error remains an Error subclass, so existing `instanceof Error` / try-catch handling is unaffected; code that matched the old free-text message should match the typed error's `code` instead. Several routed validators also report a slightly more precise message ("positive finite integer" instead of "positive integer"). **Fixed:** *Webhook deliveries retry transient transport failures instead of dead-lettering them* — A delivery that failed with a transient transport error (connection reset, timeout, 5xx) was recorded as permanently failed and dead-lettered rather than retried. The destination-safety check (SSRF / blocked-host guard) now runs in its own step and is the only permanent failure; a transport failure backs off and retries up to the configured limit. · *Webhook deliveries table creates on MySQL* — The pending-delivery index used a partial-index (WHERE) clause that MySQL does not support, so the deliveries table failed to create on a MySQL backend. The index is now dialect-aware — partial on PostgreSQL / SQLite, plain on MySQL. · *Inline webhook delivery can't be double-sent by concurrent retry processing* — An inline delivery is now claimed (status set to in-flight with a claim timestamp) before the POST, so a concurrent retry pass can't pick up and re-send the same delivery; a row left in-flight by a crash is reclaimed after the stale-claim window. · *Audit checkpoint can never be anchored against a replaced database* — close() fires a best-effort final checkpoint as the database shuts down. In a narrow close-then-reopen window that checkpoint's write could resume after a fresh database had opened and anchor the prior database's chain tip into it. checkpoint() now binds to the database it read the tip from and refuses to write (returns null, fail-closed) if that handle has since been closed or replaced — so a checkpoint is only ever written against the database it measured. **Security:** *TOCTOU-safe file reads applied uniformly* — The open-fd → fstat → read pattern that anchors a file read to one inode against a swap-after-stat race is now centralized in b.atomicFile.fdSafeReadSync, and every framework file read routes through it. The sealed-PEM source reader keeps the strongest posture (symlink refusal + inode-equality + byte cap), and those guards are now available as options to any caller — a re-introduced hand-rolled read-loop is flagged by a codebase-pattern detector. · *Injected-dependency and integer-option validation can't silently drift* — Injected stores / backends / vaults / db handles / query objects are validated through one contract (validateOpts.requireMethods), and operator-tunable positive-integer options through one bounds primitive (numericBounds), both forwarding the caller's non-retryable / HTTP-status error flags so a config error stays permanent / carries its 4xx. Inverse detectors flag any re-introduced inline check, so the validation can no longer be partially copied or weakened in one place. · *Single markup-escape chain for the HTML/SVG sanitizers* — The b.guardHtml and b.guardSvg sanitizer output escapers, the AI-Act transparency and mail HTML escapers, and the XML report escapers now share one b.markupEscape for the base & < > " chain (apostrophe form as a parameter; each caller keeps its own input coercion and any extra escapes such as guardHtml.escapeAttr's backtick / = IE-attribute-injection hardening). Byte-for-byte parity with the prior escapers was verified across an XSS-vector corpus, so the consolidation cannot have introduced an escaping gap; a re-inlined & < > " chain anywhere else is flagged by a detector. · *One hardening point for untrusted public-JWK import* — The createPublicKey-from-JWK call that imports attacker-controlled key material (DID publicKeyJwk, DNSKEY, COSE_Key, DPoP / OAuth / OIDC / DBSC proof JWKs) is centralized in b.crypto.importPublicJwk, so the untrusted-key import has a single audited home; each caller keeps its own kty/crv pre-validation and typed error. · *Linear-time Content-Security-Policy header parsing* — The CSP response-header parser split directives on a `/\s*,\s*/` regex that ran in O(n^2) on a long comma-less run of whitespace, letting a crafted header stall a worker (js/polynomial-redos). It now splits on the literal comma and trims each item, which is linear and byte-for-byte equivalent. · *Parsed INI and OpenAPI path maps can't reach Object.prototype* — The INI parser already refuses a `__proto__` / `constructor` / `prototype` section or key (it throws before any write); as defense-in-depth, every parsed node — and the OpenAPI paths map keyed by URL pattern — is now a null-prototype object, so even a future gap could only ever set an own property, never mutate Object.prototype (CWE-1321).
|
|
12
|
+
|
|
11
13
|
- v0.15.12 (2026-06-14) — **Hardens a set of defense-in-depth seams: a single-pass structured-field unescape, a constant-time content-digest member match, complete reserved-character stripping, a Trojan-Source escape on the boot logger, generic body-parse error responses, and an audit trail whenever outbound TLS certificate validation is disabled.** A sweep of low-severity but real hardening items. RFC 8941 structured-field string values (HTTP Message Signatures, Client Hints, Cache-Control) were un-escaped with two chained replaces that mis-decoded an escaped backslash adjacent to another escape; they now use one left-to-right pass that decodes each escape exactly once. The HTTP Message Signature content-digest check dropped a dead identity-replace and now matches the sha3-512 member by an exact, top-level, constant-time comparison instead of an unanchored substring scan. b.guardFilename's reserved-character strip used a non-global regex that left every separator after the first; it now strips all of them. The boot logger's TTY branch wrote raw text, bypassing the Trojan-Source / control-character escape the main logger applies — it now escapes the bidi and C0/newline control classes on every sink. The body parser no longer echoes a caught exception's detail (an fs errno + temp path, or a parse hook's thrown message) to the HTTP client — the client gets a generic status phrase while the full detail stays on the audit chain. And any outbound TLS connection that runs with peer-certificate validation disabled (an explicit operator opt-in, never a default) now emits a tls.insecure_skip_verify audit + observability event so the degraded posture is visible for compliance and incident response. **Added:** *b.structuredFields.unescapeSfStringBody(body)* — A single-pass decode of the RFC 8941 §3.3.3 quoted-string backslash escapes (the bytes between the surrounding quotes). It replaces the chained two-`.replace()` form, which is not equivalent to one decode — whichever pass runs first can rewrite a backslash the other escape sequence owns, so a lone escaped backslash decoded to two. The HTTP Message Signature, Client Hints, and Cache-Control sf-string readers now route through it. · *tls.insecure_skip_verify audit event* — b.network.tls.auditInsecureTls(meta) emits an audit + observability event at the point an outbound TLS connection honors rejectUnauthorized:false / allowInsecure. The connectWithEch, OTLP-gRPC log stream, syslog-TLS log stream, and SMTP transports all emit it when an operator disables certificate validation — parallel to the existing tls.classical_downgrade audit. No default changes; the framework never disables validation itself. **Security:** *Single-pass structured-field string unescape* — The RFC 8941 sf-string readers in HTTP Message Signatures (Signature-Input covered-component names), Client Hints, and Cache-Control directive values un-escaped with `.replace(/\\\\/g,"\\").replace(/\\"/g,'"')` — two sequential passes that mis-decode adjacent escapes (a lone escaped backslash became two). All four sites now use the single-pass b.structuredFields.unescapeSfStringBody. It is fail-closed (a mis-decoded covered-component name just fails the signature check, never bypasses it); the fix restores RFC-conformant interop with peers that legitimately escape these values. · *Constant-time, member-anchored content-digest verification* — b.crypto.httpSig.verify's covered content-digest check dropped a dead no-op replace and now parses the Content-Digest header into its top-level members and matches the sha3-512 member EXACTLY, in constant time (b.crypto.timingSafeEqual), rather than scanning for the digest text as a substring anywhere in the header. The Content-Digest header is already bound by the signature, so the substring form was not reachably exploitable; the change removes the latent ambiguity and the timing channel. · *Reserved-character filename strip removes every occurrence* — b.guardFilename's reservedCharPolicy:"strip" (the permissive profile) used a non-global regex, so only the FIRST reserved character — including path separators — was replaced and the rest leaked through. The strip is now global: every reserved character is removed. Not a traversal bypass (the unconditional security floor still throws on `..`, null bytes, NTFS ADS, UNC, overlong UTF-8), but the strip is now complete and consistent. · *Boot logger escapes control + bidi characters on every sink* — The boot-time logger's TTY branch wrote the raw message, bypassing the Trojan-Source (bidi) and control-character escapes the main logger applies — a hostile message could forge extra log lines on a terminal (CWE-117) or re-order the visible line (CVE-2021-42574). Both boot branches now escape the bidi and C0/newline control classes, matching the create() path and the logger's advertised guarantee. · *Body-parser error responses never echo internal detail* — The body-parser's terminal error path surfaced a caught exception's message verbatim to the HTTP client — a multipart filesystem error leaked the errno + temp path, and a parse hook's thrown error (which can carry secrets) was echoed back. The client now gets a curated message only for a framework-classified 4xx error and a generic status phrase otherwise; the parse-hook wrapper carries a fixed message, and full diagnostics stay on the audit chain server-side (CWE-209). The cluster leader-discovery endpoint's error body is generalized the same way.
|
|
12
14
|
|
|
13
15
|
- v0.15.11 (2026-06-14) — **Replaces a family of quadratic-time regexes that hostile input could use to stall a worker with linear scans, refuses a relocatable sealed-cell downgrade on the read side, fails closed when enabling row-level security behind a non-native driver, and scans the vendored crypto for known CVEs on every build.** Several text-handling primitives stripped trailing whitespace or extracted a mail address with a regex whose backtracking is quadratic in the input length on adversarial strings — a request body, a YAML document, a CSV cell, or a From header crafted as a long run of spaces could pin a worker's CPU. Each is now a linear character scan with identical output. The HTML-content check the MCP tool surface applies gained the vbscript: and data:text/html vectors it was missing. On the data-at-rest side, an AAD-bound (or per-row-key) column now refuses a plain, unbound vault cell on read — a relocatable envelope an attacker with write access could copy in from another row defeats the cross-row binding, so the field is nulled rather than surfaced; operators mid-migration opt back in with registerTable({ allowPlainMigration: true }). declareRowPolicy now treats row-level-security as enabled only on a value that unambiguously means true, so a non-native Postgres driver that returns the string "f" can no longer be read as "already on" and silently skip the ENABLE that protects the table's rows. Finally, because the framework's crypto is vendored rather than installed, npm audit and Dependabot never see it: every build now matches the vendored versions against the OSV vulnerability database, with a complementary Semgrep pass and workflow-file static analysis alongside. **Added:** *b.safeBuffer.indexAfterOpenTag(html, tagName)* — A linear helper that returns the offset just past a `<tag ...>` opening tag (case-insensitive), or -1 when absent or unterminated — the insertion point a response rewriter uses to splice content after <body> or <head> without a regex. It replaces the O(n^2) html.match(/<body[^>]*>/i) shape and is stricter than it: a real tag boundary is required after the name, so <bodyfoo> is not mistaken for <body>. **Security:** *Linear-time replacements across a family of quadratic regexes (ReDoS class)* — Several primitives located or stripped text with a regex whose backtracking is quadratic in V8 on adversarial input (CWE-1333): b.safeBuffer and the safe-env / safe-yaml / guard-csv parsers stripped trailing horizontal whitespace with /[ \t]+$/; b.mail extracted the address from a `Name <addr>` header with /<([^>]+)>/; the bot-disclosure and speculation-rules response middleware found the <body> insertion point with /<body[^>]*>/i; and the BIMI certificate-chain splitter walked PEM blocks with a lazy /BEGIN[\s\S]*?END/ scan. A crafted field — a long run of spaces, an unterminated bracket, a body carrying many <body starts with no closing >, a chain of BEGIN markers — could drive a worker's CPU to seconds of work. Each is now a linear scan: a shared b.safeBuffer.stripTrailingHspace (backward char walk), b.safeBuffer.indexAfterOpenTag (forward indexOf walk for the tag insertion point), a forward indexOf for address extraction, and an indexOf walk for the PEM split. Output is byte-identical (the tag-find is stricter — it no longer mistakes <bodyfoo> for <body>), and 400K-character adversarial inputs that took 8–85 seconds now complete in under 2 ms. · *MCP HTML-content check covers vbscript: and data:text/html* — The dangerous-markup check applied to text/html tool content matched <script>/<iframe>/<object>/<embed> and javascript: URLs but not the vbscript: scheme or data:text/html payloads. Both are now refused; data: URLs carrying non-HTML media (data:image/png and similar) are unaffected. · *AAD-bound columns refuse a plain sealed cell on read* — b.cryptoField.unsealRow now refuses a plain, unbound vault: envelope found on an AAD-bound (or per-row-key) column and nulls the field instead of returning it. A plain envelope carries no per-cell binding, so a writer who could place one — copied from anywhere under the same vault root — would otherwise relocate a value across rows or columns and defeat the copy-protection the AAD binding advertises. Operators migrating pre-AAD rows up to bound ciphertext opt into a bounded acceptance window with registerTable({ allowPlainMigration: true }) and clear it when migration completes. · *Row-level-security enablement fails closed on non-native drivers* — b.db.declareRowPolicy read pg_class.relrowsecurity to skip a redundant ENABLE ROW LEVEL SECURITY, but tested it with a bare truthiness check. A native pg driver returns a JS boolean; a proxy or ORM may return the string "f" for a disabled table — and "f" is truthy, so the check read it as already-enabled and silently skipped the ENABLE, leaving every row in the table unprotected while the migration reported success. RLS now counts as enabled only on a value that unambiguously means true (true, 1, or "t"/"true"/"1"/"on"/"yes"); every other shape re-issues ENABLE, a harmless no-op on an already-enabled table. · *Vendored-crypto CVE scanning, complementary SAST, and workflow static analysis in CI* — The framework ships zero npm runtime dependencies — its crypto (the noble suite, the WebAuthn server, the PKI layer) is vendored under lib/vendor/, where npm audit, Dependabot, and Socket cannot see it. Every build now generates a CycloneDX SBOM of the vendored tree (each library carrying an npm purl) and runs it through OSV-Scanner, matching the exact pinned version against the OSV vulnerability database; a published CVE or GHSA affecting a vendored version fails the build so the copy is refreshed before merge. A Semgrep pass (registry security-audit + javascript packs at ERROR severity) runs alongside CodeQL as complementary SAST, and actionlint statically checks the workflow files. All three install the OSS tool from its upstream release, matching the existing secret-scan gate's posture. **Detectors:** *Quadratic trailing-whitespace and tag-find regex detectors* — Two codebase-pattern detectors refuse reintroduction of the quadratic shapes: the /[ \t]+$/ trailing-whitespace strip (as .replace, .test, or via the named TRAILING_HSPACE_RE export) outside the linear helper that owns it, and the str.match(/<tag[^>]*>/) document-tag find that the response middleware must route through b.safeBuffer.indexAfterOpenTag. Each is proven to fire on the removed shape and stay silent on the linear replacement, so the ReDoS class cannot creep back into a new parser, guard, or response rewriter.
|
package/index.js
CHANGED
|
@@ -225,6 +225,7 @@ var guardYaml = require("./lib/guard-yaml");
|
|
|
225
225
|
var guardXml = require("./lib/guard-xml");
|
|
226
226
|
var guardMarkdown = require("./lib/guard-markdown");
|
|
227
227
|
var guardEmail = require("./lib/guard-email");
|
|
228
|
+
var guardText = require("./lib/guard-text");
|
|
228
229
|
var guardDomain = require("./lib/guard-domain");
|
|
229
230
|
var guardUuid = require("./lib/guard-uuid");
|
|
230
231
|
var guardCidr = require("./lib/guard-cidr");
|
|
@@ -609,6 +610,7 @@ module.exports = {
|
|
|
609
610
|
guardXml: guardXml,
|
|
610
611
|
guardMarkdown: guardMarkdown,
|
|
611
612
|
guardEmail: guardEmail,
|
|
613
|
+
guardText: guardText,
|
|
612
614
|
guardDomain: guardDomain,
|
|
613
615
|
guardUuid: guardUuid,
|
|
614
616
|
guardCidr: guardCidr,
|
package/lib/a2a-tasks.js
CHANGED
|
@@ -51,7 +51,7 @@ var validateOpts = require("./validate-opts");
|
|
|
51
51
|
var { defineClass } = require("./framework-error");
|
|
52
52
|
|
|
53
53
|
var httpClient = lazyRequire(function () { return require("./http-client"); });
|
|
54
|
-
var
|
|
54
|
+
var auditEmit = require("./audit-emit");
|
|
55
55
|
var C = require("./constants");
|
|
56
56
|
|
|
57
57
|
// A2aTasksError is the per-call error class — separate from A2aError
|
|
@@ -82,15 +82,7 @@ var TASK_ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
|
82
82
|
var SKILL_NAME_RE = /^[a-zA-Z][a-zA-Z0-9._-]{0,63}$/; // allow:duplicate-regex — RFC-3986-unreserved identifier shape, shared across mcp.js + mcp-tool-registry.js
|
|
83
83
|
// RFC-3986-unreserved identifier shape (length cap inside regex), not a byte count
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
audit().safeEmit({
|
|
88
|
-
action: action,
|
|
89
|
-
outcome: outcome || "success",
|
|
90
|
-
metadata: metadata,
|
|
91
|
-
});
|
|
92
|
-
} catch (_e) { /* best-effort */ }
|
|
93
|
-
}
|
|
85
|
+
var _emitAudit = auditEmit.emit;
|
|
94
86
|
|
|
95
87
|
var bCrypto = lazyRequire(function () { return require("./crypto"); });
|
|
96
88
|
|
|
@@ -515,19 +507,11 @@ function _jsonRpcError(id, code, message, data) {
|
|
|
515
507
|
}
|
|
516
508
|
|
|
517
509
|
function _readBody(req, maxBytes) {
|
|
518
|
-
return
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
sizeMessage: "a2a-tasks: request body exceeded " + maxBytes + " bytes",
|
|
524
|
-
});
|
|
525
|
-
req.on("data", function (chunk) {
|
|
526
|
-
try { collector.push(chunk); }
|
|
527
|
-
catch (capErr) { reject(capErr); }
|
|
528
|
-
});
|
|
529
|
-
req.on("end", function () { resolve(collector.result()); });
|
|
530
|
-
req.on("error", function (e) { reject(e); });
|
|
510
|
+
return safeBuffer.collectStream(req, {
|
|
511
|
+
maxBytes: maxBytes,
|
|
512
|
+
errorClass: A2aTasksError,
|
|
513
|
+
sizeCode: "a2a-tasks/body-too-large",
|
|
514
|
+
sizeMessage: "a2a-tasks: request body exceeded " + maxBytes + " bytes",
|
|
531
515
|
});
|
|
532
516
|
}
|
|
533
517
|
|
package/lib/acme.js
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
*/
|
|
43
43
|
|
|
44
44
|
var nodeCrypto = require("node:crypto");
|
|
45
|
+
var safeBuffer = require("./safe-buffer");
|
|
45
46
|
|
|
46
47
|
var C = require("./constants");
|
|
47
48
|
var asn1 = require("./asn1-der");
|
|
@@ -553,7 +554,7 @@ function create(opts) {
|
|
|
553
554
|
var id = orderOpts.identifiers[i];
|
|
554
555
|
if (!id || typeof id.type !== "string" || typeof id.value !== "string" ||
|
|
555
556
|
id.type.length === 0 || id.value.length === 0 ||
|
|
556
|
-
id.value
|
|
557
|
+
safeBuffer.byteLengthOf(id.value) > C.BYTES.bytes(255)) {
|
|
557
558
|
throw _err("acme/bad-identifier",
|
|
558
559
|
"newOrder: identifier must be { type: string, value: string<=255 }", true);
|
|
559
560
|
}
|
|
@@ -569,7 +570,7 @@ function create(opts) {
|
|
|
569
570
|
// the field. v1-defensible scope: refuse non-string + cap length so
|
|
570
571
|
// attacker-supplied profile values can't bloat the JSON payload.
|
|
571
572
|
if (typeof orderOpts.profile === "string") {
|
|
572
|
-
if (orderOpts.profile.length === 0 || orderOpts.profile
|
|
573
|
+
if (orderOpts.profile.length === 0 || safeBuffer.byteLengthOf(orderOpts.profile) > C.BYTES.bytes(64)) {
|
|
573
574
|
throw _err("acme/bad-profile",
|
|
574
575
|
"newOrder: profile name must be a non-empty string <= 64 bytes", true);
|
|
575
576
|
}
|
|
@@ -611,7 +612,7 @@ function create(opts) {
|
|
|
611
612
|
} else {
|
|
612
613
|
throw _err("acme/bad-csr", "finalize: csr must be a DER Buffer or PEM string", true);
|
|
613
614
|
}
|
|
614
|
-
if (csrDer.length === 0 || csrDer
|
|
615
|
+
if (csrDer.length === 0 || safeBuffer.byteLengthOf(csrDer) > C.BYTES.kib(64)) {
|
|
615
616
|
throw _err("acme/bad-csr",
|
|
616
617
|
"finalize: CSR DER size out of range (got " + csrDer.length + " bytes)", true);
|
|
617
618
|
}
|
|
@@ -842,7 +843,7 @@ function create(opts) {
|
|
|
842
843
|
}
|
|
843
844
|
for (var di = 0; di < opts2.domains.length; di += 1) {
|
|
844
845
|
var d = opts2.domains[di];
|
|
845
|
-
if (typeof d !== "string" || d.length === 0 || d
|
|
846
|
+
if (typeof d !== "string" || d.length === 0 || safeBuffer.byteLengthOf(d) > C.BYTES.bytes(255)) {
|
|
846
847
|
throw _err("acme/bad-csr-domain",
|
|
847
848
|
"buildCsr: domains[" + di + "] must be a non-empty string <= 255 bytes", true);
|
|
848
849
|
}
|
|
@@ -1328,7 +1329,7 @@ function create(opts) {
|
|
|
1328
1329
|
if (!opts2 || typeof opts2 !== "object" || typeof opts2.identifier !== "string" || opts2.identifier.length === 0) {
|
|
1329
1330
|
throw _err("acme/bad-identifier", "dnsAccount01ChallengeRecord: opts.identifier (host) is required", true);
|
|
1330
1331
|
}
|
|
1331
|
-
if (opts2.identifier
|
|
1332
|
+
if (safeBuffer.byteLengthOf(opts2.identifier) > C.BYTES.bytes(255)) {
|
|
1332
1333
|
throw _err("acme/bad-identifier", "dnsAccount01ChallengeRecord: identifier exceeds 255 bytes", true);
|
|
1333
1334
|
}
|
|
1334
1335
|
if (!state.accountUrl) {
|
package/lib/agent-event-bus.js
CHANGED
|
@@ -70,6 +70,7 @@ var agentAudit = require("./agent-audit");
|
|
|
70
70
|
var envelopeMac = require("./agent-envelope-mac");
|
|
71
71
|
var safeJson = require("./safe-json");
|
|
72
72
|
var bCrypto = require("./crypto");
|
|
73
|
+
var boundedMap = require("./bounded-map");
|
|
73
74
|
|
|
74
75
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
75
76
|
|
|
@@ -149,10 +150,10 @@ function create(opts) {
|
|
|
149
150
|
|
|
150
151
|
function _registerTopic(topics, name, topicOpts, auditImpl) {
|
|
151
152
|
guardEventBusTopic.validate(name);
|
|
152
|
-
|
|
153
|
+
boundedMap.requireAbsent(topics, name, function () {
|
|
153
154
|
throw new AgentEventBusError("agent-event-bus/topic-duplicate",
|
|
154
155
|
"registerTopic: '" + name + "' already registered");
|
|
155
|
-
}
|
|
156
|
+
});
|
|
156
157
|
if (!topicOpts.schema || typeof topicOpts.schema !== "object") {
|
|
157
158
|
throw new AgentEventBusError("agent-event-bus/bad-schema",
|
|
158
159
|
"registerTopic: schema required (flat key→type map)");
|
|
@@ -193,10 +194,10 @@ function _registerTopic(topics, name, topicOpts, auditImpl) {
|
|
|
193
194
|
// lifecycle traceability.
|
|
194
195
|
function _unregisterTopic(topics, name, auditImpl) {
|
|
195
196
|
guardEventBusTopic.validate(name);
|
|
196
|
-
|
|
197
|
+
boundedMap.requirePresent(topics, name, function () {
|
|
197
198
|
throw new AgentEventBusError("agent-event-bus/unknown-topic",
|
|
198
199
|
"unregisterTopic: '" + name + "' not registered");
|
|
199
|
-
}
|
|
200
|
+
});
|
|
200
201
|
topics.delete(name);
|
|
201
202
|
_safeAudit(auditImpl, "agent.event_bus.topic_unregistered", null, { name: name });
|
|
202
203
|
}
|
package/lib/agent-idempotency.js
CHANGED
|
@@ -143,11 +143,8 @@ function _ensureSealTable() {
|
|
|
143
143
|
function create(opts) {
|
|
144
144
|
opts = opts || {};
|
|
145
145
|
var store = opts.store || _inMemoryBackend(opts.maxInMemoryEntries);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
throw new AgentIdempotencyError("agent-idempotency/bad-store",
|
|
149
|
-
"create: store must expose { get, put, delete }");
|
|
150
|
-
}
|
|
146
|
+
validateOpts.requireMethods(store, ["get", "put", "delete"],
|
|
147
|
+
"create: store", AgentIdempotencyError, "agent-idempotency/bad-store");
|
|
151
148
|
var ttlMs = typeof opts.ttlMs === "number" ? opts.ttlMs : DEFAULT_TTL_MS;
|
|
152
149
|
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
153
150
|
throw new AgentIdempotencyError("agent-idempotency/bad-ttl",
|
|
@@ -157,11 +157,8 @@ var _saltedFnvBasisCache = null;
|
|
|
157
157
|
function create(opts) {
|
|
158
158
|
opts = opts || {};
|
|
159
159
|
var backend = opts.backend || _inMemoryBackend();
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
throw new AgentOrchestratorError("agent-orchestrator/bad-backend",
|
|
163
|
-
"b.agent.orchestrator.create: backend must expose { get, set, delete, list }");
|
|
164
|
-
}
|
|
160
|
+
validateOpts.requireMethods(backend, ["get", "set", "delete", "list"],
|
|
161
|
+
"b.agent.orchestrator.create: backend", AgentOrchestratorError, "agent-orchestrator/bad-backend");
|
|
165
162
|
var clusterImpl = opts.cluster || cluster();
|
|
166
163
|
var auditImpl = opts.audit || audit();
|
|
167
164
|
var permissions = opts.permissions || null;
|
package/lib/agent-saga.js
CHANGED
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
|
|
72
72
|
var lazyRequire = require("./lazy-require");
|
|
73
73
|
var { defineClass } = require("./framework-error");
|
|
74
|
+
var validateOpts = require("./validate-opts");
|
|
74
75
|
var guardSagaConfig = require("./guard-saga-config");
|
|
75
76
|
var bCrypto = require("./crypto");
|
|
76
77
|
var agentAudit = require("./agent-audit");
|
|
@@ -118,11 +119,8 @@ function create(config) {
|
|
|
118
119
|
// `agent.saga.no_state_store` surfaces the posture per call).
|
|
119
120
|
var stateStore = config.stateStore || null;
|
|
120
121
|
if (stateStore !== null) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
throw new AgentSagaError("agent-saga/bad-state-store",
|
|
124
|
-
"create: stateStore must expose { saveStep, loadResumePoint, markCompleted?, markFailed? }");
|
|
125
|
-
}
|
|
122
|
+
validateOpts.requireMethods(stateStore, ["saveStep", "loadResumePoint"],
|
|
123
|
+
"create: stateStore", AgentSagaError, "agent-saga/bad-state-store");
|
|
126
124
|
}
|
|
127
125
|
return {
|
|
128
126
|
run: function (ctx, initialState, opts) { return _run(config, auditImpl, stateStore, ctx, initialState, opts || {}); },
|
package/lib/agent-tenant.js
CHANGED
|
@@ -153,11 +153,8 @@ var TENANT_KEY_BYTES = 32;
|
|
|
153
153
|
function create(opts) {
|
|
154
154
|
opts = opts || {};
|
|
155
155
|
var backend = opts.backend || _inMemoryBackend();
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
throw new AgentTenantError("agent-tenant/bad-backend",
|
|
159
|
-
"create: backend must expose { get, set, delete, list }");
|
|
160
|
-
}
|
|
156
|
+
validateOpts.requireMethods(backend, ["get", "set", "delete", "list"],
|
|
157
|
+
"create: backend", AgentTenantError, "agent-tenant/bad-backend");
|
|
161
158
|
var auditImpl = opts.audit || audit();
|
|
162
159
|
var permissions = opts.permissions || null;
|
|
163
160
|
var ctx = {
|
|
@@ -99,25 +99,12 @@ function wrap(opts) {
|
|
|
99
99
|
var legalBasis = opts.legalBasis;
|
|
100
100
|
var decide = opts.decide;
|
|
101
101
|
var onAdverse = typeof opts.onAdverse === "function" ? opts.onAdverse : null;
|
|
102
|
-
var auditOn = opts.audit !== false;
|
|
103
102
|
var now = typeof opts.now === "function" ? opts.now : function () { return Date.now(); };
|
|
104
103
|
|
|
105
104
|
var deadlines = REGIME_DEADLINES[legalBasis] || REGIME_DEADLINES["operator-defined"];
|
|
106
105
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
audit().safeEmit({
|
|
111
|
-
action: "ai.adverse_decision." + action,
|
|
112
|
-
outcome: outcome,
|
|
113
|
-
metadata: metadata || {},
|
|
114
|
-
});
|
|
115
|
-
} catch (_e) { /* drop-silent */ }
|
|
116
|
-
}
|
|
117
|
-
function _emitMetric(verb, n, labels) {
|
|
118
|
-
try { observability().safeEvent("ai.adverse_decision." + verb, n || 1, labels || {}); }
|
|
119
|
-
catch (_e) { /* drop-silent */ }
|
|
120
|
-
}
|
|
106
|
+
var _emitAudit = audit().namespaced("ai.adverse_decision", opts.audit);
|
|
107
|
+
var _emitMetric = observability().namespaced("ai.adverse_decision");
|
|
121
108
|
|
|
122
109
|
return async function adverseDecisionDecorated(subject) {
|
|
123
110
|
if (!subject || typeof subject !== "object") {
|
package/lib/ai-capability.js
CHANGED
|
@@ -239,12 +239,7 @@ function create(opts) {
|
|
|
239
239
|
}
|
|
240
240
|
var auditOn = opts.audit !== false;
|
|
241
241
|
|
|
242
|
-
|
|
243
|
-
if (!auditOn) return;
|
|
244
|
-
try {
|
|
245
|
-
audit().safeEmit({ action: action, outcome: outcome, metadata: metadata || {} });
|
|
246
|
-
} catch (_e) { /* audit best-effort — drop-silent */ }
|
|
247
|
-
}
|
|
242
|
+
var _emitAudit = audit().namespaced(null, { audit: auditOn });
|
|
248
243
|
|
|
249
244
|
function describe(modelId) {
|
|
250
245
|
var d = registry.get(modelId);
|
package/lib/ai-dp.js
CHANGED
|
@@ -429,11 +429,7 @@ function budget(opts) {
|
|
|
429
429
|
var spentDelta = 0;
|
|
430
430
|
var rdp = RDP_ORDERS.map(function () { return 0; }); // rdp accounting
|
|
431
431
|
|
|
432
|
-
|
|
433
|
-
if (!auditOn) return;
|
|
434
|
-
try { audit().safeEmit({ action: action, outcome: outcome, metadata: metadata || {} }); }
|
|
435
|
-
catch (_e) { /* drop-silent */ }
|
|
436
|
-
}
|
|
432
|
+
var _emitAudit = audit().namespaced(null, { audit: auditOn });
|
|
437
433
|
|
|
438
434
|
function _currentEpsilon(rdpCurve) {
|
|
439
435
|
if (accounting === "basic") return spentEpsilon;
|
package/lib/ai-input.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
var C = require("./constants");
|
|
24
24
|
var numericBounds = require("./numeric-bounds");
|
|
25
25
|
var audit = require("./audit");
|
|
26
|
+
var codepointClass = require("./codepoint-class");
|
|
26
27
|
var { AiInputError } = require("./framework-error");
|
|
27
28
|
|
|
28
29
|
var SAMPLE_TRUNC = 80; // sample truncation length, not bytes
|
|
@@ -69,8 +70,7 @@ function _featuresOf(input) {
|
|
|
69
70
|
var cp = input.charCodeAt(i);
|
|
70
71
|
if ((cp >= 0x202a && cp <= 0x202e) || (cp >= 0x2066 && cp <= 0x2069)) bidi++;
|
|
71
72
|
else if (cp === 0x200b || cp === 0x200c || cp === 0x200d || cp === 0xfeff || cp === 0x2060) zw++;
|
|
72
|
-
else if (cp
|
|
73
|
-
else if (cp === 0x7f) ctrl++;
|
|
73
|
+
else if (codepointClass.isForbiddenControlChar(cp, { allowLf: true, allowCr: true })) ctrl++;
|
|
74
74
|
}
|
|
75
75
|
return {
|
|
76
76
|
length: input.length,
|
package/lib/ai-pref.js
CHANGED
|
@@ -152,13 +152,8 @@ function parseHeader(value) {
|
|
|
152
152
|
label: "aiPref.parseHeader",
|
|
153
153
|
});
|
|
154
154
|
var out = { train: null, infer: null, snippet: null, price: null };
|
|
155
|
-
var
|
|
156
|
-
|
|
157
|
-
var p = pairs[i].trim();
|
|
158
|
-
var eq = p.indexOf("=");
|
|
159
|
-
if (eq === -1) continue;
|
|
160
|
-
var k = p.slice(0, eq).trim().toLowerCase();
|
|
161
|
-
var val = p.slice(eq + 1).trim();
|
|
155
|
+
var kvps = structuredFields.parseKeyValuePieces(value.split(","));
|
|
156
|
+
structuredFields.forEachKeyValue(kvps, function (k, val) {
|
|
162
157
|
if (k === "train" && TRAIN_VALUES.indexOf(val) !== -1) out.train = val;
|
|
163
158
|
else if (k === "infer" && INFER_VALUES.indexOf(val) !== -1) out.infer = val;
|
|
164
159
|
else if (k === "snippet" && SNIPPET_VALUES.indexOf(val) !== -1) out.snippet = val;
|
|
@@ -169,7 +164,7 @@ function parseHeader(value) {
|
|
|
169
164
|
var pt = parseInt(val, 10);
|
|
170
165
|
if (isFinite(pt) && pt > 0) out.price = Object.assign({ perTokens: pt }, out.price || {});
|
|
171
166
|
}
|
|
172
|
-
}
|
|
167
|
+
});
|
|
173
168
|
return out;
|
|
174
169
|
}
|
|
175
170
|
|
package/lib/ai-quota.js
CHANGED
|
@@ -318,12 +318,7 @@ function create(opts) {
|
|
|
318
318
|
return null;
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
|
|
322
|
-
if (!auditOn) return;
|
|
323
|
-
try {
|
|
324
|
-
audit().safeEmit({ action: action, outcome: outcome, metadata: metadata || {} });
|
|
325
|
-
} catch (_e) { /* audit best-effort — drop-silent */ }
|
|
326
|
-
}
|
|
321
|
+
var _emitAudit = audit().namespaced(null, { audit: auditOn });
|
|
327
322
|
|
|
328
323
|
function _emitMetric(name, n) {
|
|
329
324
|
try { observability().safeEvent(name, n || 1, {}); }
|
|
@@ -507,14 +502,8 @@ function _validateLimitMap(map, label) {
|
|
|
507
502
|
}
|
|
508
503
|
|
|
509
504
|
function _validateStore(store) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
typeof store.add !== "function" ||
|
|
513
|
-
typeof store.get !== "function" ||
|
|
514
|
-
typeof store.reset !== "function") {
|
|
515
|
-
throw new AiQuotaError("ai-quota/bad-store",
|
|
516
|
-
"ai.quota.create: store must expose reserve / add / get / reset functions");
|
|
517
|
-
}
|
|
505
|
+
validateOpts.requireMethods(store, ["reserve", "add", "get", "reset"],
|
|
506
|
+
"ai.quota.create: store", AiQuotaError, "ai-quota/bad-store");
|
|
518
507
|
}
|
|
519
508
|
|
|
520
509
|
module.exports = {
|
package/lib/api-key.js
CHANGED
|
@@ -134,37 +134,46 @@ function _validateIdentifier(name, value) {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
function _validateCreateOpts(opts) {
|
|
137
|
-
validateOpts.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
137
|
+
validateOpts.shape(opts, {
|
|
138
|
+
namespace: function (v, l) { _validateIdentifier(l, v); },
|
|
139
|
+
prefix: function (v, l) { if (v !== undefined) _validateIdentifier(l, v); },
|
|
140
|
+
idBytes: "optional-positive-int",
|
|
141
|
+
secretBytes: "optional-positive-int",
|
|
142
|
+
trackLastUsedAt: "optional-boolean",
|
|
143
|
+
auditFailures: "optional-boolean",
|
|
144
|
+
auditSuccess: "optional-boolean",
|
|
145
|
+
purgeAfterMs: "optional-non-negative",
|
|
146
|
+
hashAlgo: function (v) {
|
|
147
|
+
if (v !== undefined) {
|
|
148
|
+
if (typeof v !== "string" || (v !== "shake256" && v !== "argon2id")) {
|
|
149
|
+
throw _err("BAD_OPT", "apiKey.create: hashAlgo must be 'shake256' or 'argon2id', got " +
|
|
150
|
+
JSON.stringify(v));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
audit: function (v) { validateOpts.auditShape(v, "apiKey.create", ApiKeyError); },
|
|
155
|
+
clock: "optional-function",
|
|
156
|
+
}, "apiKey.create", ApiKeyError);
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
function _validateIssueOpts(opts) {
|
|
158
|
-
validateOpts.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
160
|
+
validateOpts.shape(opts, {
|
|
161
|
+
ownerId: { rule: "required-string", code: "MISSING_OWNER" },
|
|
162
|
+
scopes: { rule: "optional-string-array", code: "BAD_SCOPES" },
|
|
163
|
+
metadata: { rule: "optional-plain-object", code: "BAD_METADATA" },
|
|
164
|
+
expiresAt: function (v) {
|
|
165
|
+
if (v !== undefined && v !== null) {
|
|
166
|
+
if (typeof v !== "number" || !isFinite(v) || v < 0) {
|
|
167
|
+
throw _err("BAD_OPT", "apiKey.issue: expiresAt must be a non-negative finite number (unix ms) or null");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
}, "apiKey.issue", ApiKeyError, undefined, {
|
|
172
|
+
// `req` / `context` are the audit-actor pass-through bag — forwarded
|
|
173
|
+
// verbatim to requestHelpers.resolveActorWithOverride (via _actor)
|
|
174
|
+
// to populate the issue audit's 5 W's, not validated locally.
|
|
175
|
+
allow: ["req", "context"],
|
|
176
|
+
});
|
|
168
177
|
}
|
|
169
178
|
|
|
170
179
|
// ---- Token format ----
|
package/lib/archive-adapters.js
CHANGED
|
@@ -547,10 +547,8 @@ function http(url, opts) {
|
|
|
547
547
|
* for await (var e of reader.entries()) entries.push(e);
|
|
548
548
|
*/
|
|
549
549
|
function trustedStream(readable, opts) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
"trustedStream: arg must be a Readable (or pipe/on-compatible stream)");
|
|
553
|
-
}
|
|
550
|
+
validateOpts.requireMethods(readable, ["pipe", "on"],
|
|
551
|
+
"trustedStream: arg (Readable / pipe-on-compatible stream)", AdapterError, "adapter/bad-arg");
|
|
554
552
|
if (!(readable instanceof nodeStream.Readable) && !readable.readable) {
|
|
555
553
|
// Accept stream-like duck-typed objects; many libraries return
|
|
556
554
|
// Readable-flavored bytes via Symbol.asyncIterator only.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* lib/archive-entry-policy.js — the entry-type extraction policy shared by the
|
|
4
|
+
* ZIP (archive-read) and TAR (archive-tar-read) readers.
|
|
5
|
+
*
|
|
6
|
+
* Special archive entry types — symlinks, hardlinks, device nodes, FIFOs, and
|
|
7
|
+
* sockets — are a malicious-archive vector (symlink traversal out of the
|
|
8
|
+
* extraction root, device-node creation, etc.), so every type is DENIED by
|
|
9
|
+
* default; an operator opts a type in explicitly per archive. Both readers
|
|
10
|
+
* normalized the same default the same way, so the policy + its overlay live
|
|
11
|
+
* here once rather than drifting between the two formats.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
var DEFAULT_ENTRY_TYPE_POLICY = Object.freeze({
|
|
15
|
+
symlinks: false,
|
|
16
|
+
hardlinks: false,
|
|
17
|
+
devices: false,
|
|
18
|
+
fifos: false,
|
|
19
|
+
sockets: false,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// normalize(p) — overlay an operator policy onto the all-denied default and
|
|
23
|
+
// freeze it. A falsy `p` returns the shared default object (no allocation).
|
|
24
|
+
function normalize(p) {
|
|
25
|
+
if (!p) return DEFAULT_ENTRY_TYPE_POLICY;
|
|
26
|
+
return Object.freeze(Object.assign({}, DEFAULT_ENTRY_TYPE_POLICY, p));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
DEFAULT_ENTRY_TYPE_POLICY: DEFAULT_ENTRY_TYPE_POLICY,
|
|
31
|
+
normalize: normalize,
|
|
32
|
+
};
|
package/lib/archive-read.js
CHANGED
|
@@ -54,6 +54,8 @@ var guardArchive = lazyRequire(function () { return require("./guard-archive");
|
|
|
54
54
|
var safeDecompress = lazyRequire(function () { return require("./safe-decompress"); });
|
|
55
55
|
var safeBuffer = lazyRequire(function () { return require("./safe-buffer"); });
|
|
56
56
|
var archiveAdapters = lazyRequire(function () { return require("./archive-adapters"); });
|
|
57
|
+
var archiveEntryPolicy = require("./archive-entry-policy");
|
|
58
|
+
var auditEmit = require("./audit-emit");
|
|
57
59
|
|
|
58
60
|
// ---- Wire-format constants ------------------------------------------------
|
|
59
61
|
// Aligned with the write-side `lib/archive.js`. APPNOTE.TXT § references
|
|
@@ -120,13 +122,7 @@ var DEFAULT_BOMB_POLICY = Object.freeze({
|
|
|
120
122
|
maxExpansionRatio: 100, // compressed → decompressed ratio cap
|
|
121
123
|
});
|
|
122
124
|
|
|
123
|
-
var DEFAULT_ENTRY_TYPE_POLICY =
|
|
124
|
-
symlinks: false,
|
|
125
|
-
hardlinks: false,
|
|
126
|
-
devices: false,
|
|
127
|
-
fifos: false,
|
|
128
|
-
sockets: false,
|
|
129
|
-
});
|
|
125
|
+
var DEFAULT_ENTRY_TYPE_POLICY = archiveEntryPolicy.DEFAULT_ENTRY_TYPE_POLICY;
|
|
130
126
|
|
|
131
127
|
// ---- Helpers --------------------------------------------------------------
|
|
132
128
|
|
|
@@ -629,17 +625,9 @@ function _normalizeBombPolicy(p) {
|
|
|
629
625
|
return Object.freeze(Object.assign({}, DEFAULT_BOMB_POLICY, p));
|
|
630
626
|
}
|
|
631
627
|
|
|
632
|
-
|
|
633
|
-
if (!p) return DEFAULT_ENTRY_TYPE_POLICY;
|
|
634
|
-
return Object.freeze(Object.assign({}, DEFAULT_ENTRY_TYPE_POLICY, p));
|
|
635
|
-
}
|
|
628
|
+
var _normalizeEntryTypePolicy = archiveEntryPolicy.normalize;
|
|
636
629
|
|
|
637
|
-
|
|
638
|
-
if (!opts || !opts.audit || typeof opts.audit.safeEmit !== "function") return;
|
|
639
|
-
try {
|
|
640
|
-
opts.audit.safeEmit({ action: action, outcome: outcome, metadata: metadata });
|
|
641
|
-
} catch (_e) { /* drop-silent — audit sinks must never crash the reader */ }
|
|
642
|
-
}
|
|
630
|
+
var _emitAudit = auditEmit.emitToSink; // operator-sink audit emit (opts.audit)
|
|
643
631
|
|
|
644
632
|
/**
|
|
645
633
|
* @primitive b.archive.read.zip
|
package/lib/archive-tar-read.js
CHANGED
|
@@ -14,7 +14,9 @@ var C = require("./constants");
|
|
|
14
14
|
var lazyRequire = require("./lazy-require");
|
|
15
15
|
var safeBuffer = require("./safe-buffer");
|
|
16
16
|
var archiveTar = require("./archive-tar");
|
|
17
|
+
var archiveEntryPolicy = require("./archive-entry-policy");
|
|
17
18
|
var atomicFile = require("./atomic-file");
|
|
19
|
+
var auditEmit = require("./audit-emit");
|
|
18
20
|
|
|
19
21
|
var TarError = archiveTar.TarError;
|
|
20
22
|
var _parseHeader = archiveTar._parseHeader;
|
|
@@ -45,29 +47,16 @@ var DEFAULT_BOMB_POLICY = Object.freeze({
|
|
|
45
47
|
maxExpansionRatio: 100, // tar has no compression-ratio concept, but keep field for orchestrator policy parity
|
|
46
48
|
});
|
|
47
49
|
|
|
48
|
-
var DEFAULT_ENTRY_TYPE_POLICY =
|
|
49
|
-
symlinks: false,
|
|
50
|
-
hardlinks: false,
|
|
51
|
-
devices: false,
|
|
52
|
-
fifos: false,
|
|
53
|
-
sockets: false,
|
|
54
|
-
});
|
|
50
|
+
var DEFAULT_ENTRY_TYPE_POLICY = archiveEntryPolicy.DEFAULT_ENTRY_TYPE_POLICY;
|
|
55
51
|
|
|
56
52
|
function _normalizeBombPolicy(p) {
|
|
57
53
|
if (!p) return DEFAULT_BOMB_POLICY;
|
|
58
54
|
return Object.freeze(Object.assign({}, DEFAULT_BOMB_POLICY, p));
|
|
59
55
|
}
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
if (!p) return DEFAULT_ENTRY_TYPE_POLICY;
|
|
63
|
-
return Object.freeze(Object.assign({}, DEFAULT_ENTRY_TYPE_POLICY, p));
|
|
64
|
-
}
|
|
57
|
+
var _normalizeEntryTypePolicy = archiveEntryPolicy.normalize;
|
|
65
58
|
|
|
66
|
-
|
|
67
|
-
if (!opts || !opts.audit || typeof opts.audit.safeEmit !== "function") return;
|
|
68
|
-
try { opts.audit.safeEmit({ action: action, outcome: outcome, metadata: metadata }); }
|
|
69
|
-
catch (_e) { /* drop-silent */ }
|
|
70
|
-
}
|
|
59
|
+
var _emitAudit = auditEmit.emitToSink; // operator-sink audit emit (opts.audit)
|
|
71
60
|
|
|
72
61
|
function _isZeroBlock(buf) {
|
|
73
62
|
for (var i = 0; i < BLOCK_SIZE; i += 1) {
|