@blamejs/core 0.15.12 → 0.15.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/index.js +2 -0
- package/lib/a2a-tasks.js +45 -29
- package/lib/acme.js +6 -5
- package/lib/agent-event-bus.js +18 -4
- package/lib/agent-idempotency.js +7 -6
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-snapshot.js +32 -2
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/ai-capability.js +1 -6
- package/lib/ai-content-detect.js +1 -3
- package/lib/ai-dp.js +1 -5
- package/lib/ai-frontier-protocol.js +1 -1
- package/lib/ai-input.js +2 -2
- package/lib/ai-model-manifest.js +1 -1
- package/lib/ai-output.js +16 -7
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +7 -1
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-gz.js +9 -0
- package/lib/archive-read.js +14 -24
- package/lib/archive-tar-read.js +56 -24
- package/lib/archive.js +6 -12
- package/lib/arg-parser.js +7 -6
- package/lib/asn1-der.js +70 -22
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +312 -33
- package/lib/audit-chain.js +183 -54
- package/lib/audit-daily-review.js +36 -16
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +258 -0
- package/lib/audit-tools.js +108 -23
- package/lib/audit.js +91 -2
- package/lib/auth/access-lock.js +7 -29
- package/lib/auth/bot-challenge.js +1 -1
- package/lib/auth/ciba.js +43 -4
- package/lib/auth/dpop.js +29 -36
- package/lib/auth/fido-mds3.js +14 -16
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +15 -17
- package/lib/auth/lockout.js +24 -27
- 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 +4 -4
- package/lib/auth/password.js +8 -2
- package/lib/auth/saml.js +70 -40
- 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 +25 -5
- package/lib/auth/status-list.js +21 -9
- package/lib/auth/step-up.js +15 -13
- package/lib/auth-bot-challenge.js +27 -39
- package/lib/backup/bundle.js +18 -20
- package/lib/backup/crypto.js +23 -8
- package/lib/backup/index.js +55 -67
- package/lib/backup/manifest.js +7 -1
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/break-glass.js +41 -22
- package/lib/cache.js +7 -18
- package/lib/cbor.js +36 -12
- package/lib/cdn-cache-control.js +15 -12
- package/lib/cert.js +8 -13
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +10 -2
- package/lib/client-hints.js +7 -9
- package/lib/cloud-events.js +43 -33
- package/lib/cluster-storage.js +9 -41
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +84 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +2 -12
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +11 -21
- package/lib/compliance.js +3 -10
- package/lib/config-drift.js +24 -20
- package/lib/content-credentials.js +11 -8
- package/lib/content-digest.js +21 -12
- package/lib/cookies.js +15 -13
- package/lib/cose.js +39 -11
- package/lib/cra-report.js +1 -11
- package/lib/crdt.js +2 -1
- package/lib/crypto-field.js +34 -33
- package/lib/crypto.js +138 -7
- package/lib/csp.js +239 -3
- package/lib/daemon.js +46 -42
- package/lib/data-act.js +46 -13
- package/lib/db-file-lifecycle.js +14 -6
- package/lib/db-query.js +75 -49
- package/lib/db.js +66 -27
- 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/dora.js +43 -13
- package/lib/dr-runbook.js +1 -1
- package/lib/dsa.js +2 -1
- package/lib/dsr.js +30 -20
- package/lib/early-hints.js +19 -0
- package/lib/eat.js +5 -1
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +74 -30
- package/lib/fda-21cfr11.js +42 -13
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/flag-providers.js +6 -2
- package/lib/forms.js +1 -1
- 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 +911 -39
- package/lib/gdpr-ropa.js +22 -22
- package/lib/graphql-federation.js +18 -5
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +3 -2
- 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 +17 -14
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +2 -2
- 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 +74 -128
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +9 -10
- package/lib/guard-jmap.js +1 -1
- package/lib/guard-json.js +101 -109
- 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 +3 -2
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +6 -5
- 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 +13 -14
- 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 +9 -12
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +103 -149
- 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/html-balance.js +7 -3
- package/lib/http-client-cache.js +5 -12
- package/lib/http-client-cookie-jar.js +35 -16
- package/lib/http-client.js +233 -74
- package/lib/http-message-signature.js +3 -2
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/iab-tcf.js +3 -2
- package/lib/importmap-integrity.js +41 -1
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +12 -27
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/json-patch.js +1 -1
- package/lib/json-path.js +24 -3
- package/lib/jtd.js +2 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +30 -23
- 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/log.js +2 -2
- package/lib/mail-agent.js +2 -2
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +4 -3
- package/lib/mail-auth.js +43 -69
- package/lib/mail-bimi.js +35 -55
- package/lib/mail-bounce.js +3 -3
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-crypto-smime.js +71 -6
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +15 -14
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +17 -30
- package/lib/mail-helo.js +4 -7
- package/lib/mail-journal.js +13 -9
- package/lib/mail-mdn.js +12 -7
- package/lib/mail-rbl.js +7 -12
- package/lib/mail-scan.js +5 -7
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +39 -52
- 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-registry.js +1 -1
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +98 -2
- package/lib/mail-spam-score.js +9 -12
- package/lib/mail-store-fts.js +1 -1
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +28 -13
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +78 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +7 -5
- package/lib/mdoc.js +36 -14
- package/lib/metrics.js +46 -33
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +13 -24
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +41 -52
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +28 -28
- package/lib/middleware/clear-site-data.js +5 -1
- package/lib/middleware/compression.js +9 -0
- package/lib/middleware/cors.js +32 -23
- package/lib/middleware/csrf-protect.js +73 -23
- package/lib/middleware/daily-byte-quota.js +12 -30
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +35 -4
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/network-allowlist.js +61 -30
- package/lib/middleware/rate-limit.js +45 -44
- package/lib/middleware/scim-server.js +5 -3
- package/lib/middleware/security-headers.js +33 -7
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/speculation-rules.js +6 -3
- package/lib/middleware/tus-upload.js +14 -29
- 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 +125 -41
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +98 -7
- package/lib/network-dns.js +9 -2
- package/lib/network-dnssec.js +18 -17
- package/lib/network-heartbeat.js +3 -2
- package/lib/network-smtp-policy.js +52 -46
- package/lib/network-tls.js +67 -47
- package/lib/network-tsig.js +2 -2
- package/lib/nis2-report.js +2 -12
- package/lib/nist-crosswalk.js +1 -1
- package/lib/nonce-store.js +81 -3
- package/lib/ntp-check.js +28 -0
- package/lib/numeric-bounds.js +31 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +6 -47
- package/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/object-store/gcs.js +14 -75
- package/lib/object-store/http-put.js +2 -7
- package/lib/object-store/http-request.js +157 -0
- package/lib/object-store/local.js +37 -17
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +10 -79
- package/lib/observability-otlp-exporter.js +29 -29
- 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 +54 -41
- 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 +3 -3
- package/lib/parsers/safe-yaml.js +28 -9
- 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/queue-local.js +10 -3
- package/lib/redact.js +9 -4
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +334 -29
- package/lib/resource-access-lock.js +3 -3
- package/lib/restore-bundle.js +46 -18
- package/lib/restore-rollback.js +10 -4
- package/lib/restore.js +24 -22
- package/lib/retention.js +23 -9
- package/lib/router.js +17 -4
- 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 +14 -28
- package/lib/safe-icap.js +1 -1
- package/lib/safe-json.js +51 -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-sieve.js +1 -1
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +13 -27
- package/lib/sandbox-worker.js +15 -2
- package/lib/sandbox.js +4 -3
- package/lib/scheduler.js +22 -13
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +4 -11
- package/lib/self-update-standalone-verifier.js +16 -0
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +219 -7
- package/lib/sql.js +228 -81
- package/lib/sse.js +6 -10
- package/lib/static.js +201 -111
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +275 -16
- package/lib/template.js +7 -5
- package/lib/tenant-quota.js +53 -38
- package/lib/tsa.js +16 -17
- package/lib/validate-opts.js +154 -8
- package/lib/vault/index.js +5 -0
- package/lib/vault/passphrase-ops.js +22 -26
- package/lib/vault/passphrase-source.js +8 -3
- package/lib/vault/rotate.js +13 -18
- package/lib/vault/seal-pem-file.js +34 -49
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +3 -8
- package/lib/vendor/MANIFEST.json +10 -10
- package/lib/vendor/public-suffix-list.dat +23 -10
- package/lib/vendor/public-suffix-list.data.js +5498 -5494
- 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 +59 -19
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +8 -4
- package/lib/worm.js +3 -4
- package/lib/ws-client.js +65 -56
- package/lib/x509-chain.js +44 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/ai-output.js
CHANGED
|
@@ -59,14 +59,18 @@ var NEUTRALIZED_URL = "about:blank#blocked";
|
|
|
59
59
|
// matched (we only need the URL to gate it). Reference-style definitions
|
|
60
60
|
// ([id]: url) are caught by the third pattern so EchoLeak reference-link
|
|
61
61
|
// payloads don't slip past.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
// The `d` (hasIndices) flag is required so _rewriteUrls can splice at the
|
|
63
|
+
// captured URL's EXACT offset — when a markdown alt text equals its target URL
|
|
64
|
+
// (``), locating the URL by m[0].indexOf would hit the alt-text copy and
|
|
65
|
+
// leave the real exfiltration target intact (EchoLeak bypass).
|
|
66
|
+
var MD_IMAGE_RE = /!\[[^\]]{0,2048}\]\(\s{0,256}([^)\s]+)/dg;
|
|
67
|
+
var MD_LINK_RE = /(?<!!)\[[^\]]{0,2048}\]\(\s{0,256}([^)\s]+)/dg;
|
|
68
|
+
var MD_REF_RE = /^[ \t]{0,3}\[[^\]]+\]:\s*(\S+)/dgm;
|
|
65
69
|
// HTML src= / href= attribute URL extractor — the guardHtml pass already
|
|
66
70
|
// strips dangerous markup, but a surviving same-origin-looking src that
|
|
67
71
|
// points at an internal / metadata host must still be neutralized for
|
|
68
72
|
// the auto-fetch exfiltration class.
|
|
69
|
-
var HTML_URL_ATTR_RE = /\b(?:src|href)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^"'>\s]+))/
|
|
73
|
+
var HTML_URL_ATTR_RE = /\b(?:src|href)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^"'>\s]+))/dgi;
|
|
70
74
|
|
|
71
75
|
// SQL-shaped fragment signal. Composes safe-sql's reserved-word stance:
|
|
72
76
|
// a leading SQL keyword followed by a clause keyword is the executable
|
|
@@ -143,11 +147,16 @@ function _rewriteUrls(text, re, onUrl) {
|
|
|
143
147
|
var last = 0;
|
|
144
148
|
var m;
|
|
145
149
|
while ((m = re.exec(text)) !== null) {
|
|
146
|
-
var
|
|
147
|
-
|
|
150
|
+
var g = m[1] !== undefined ? 1 : (m[2] !== undefined ? 2 : (m[3] !== undefined ? 3 : 0));
|
|
151
|
+
var url = g ? m[g] : null;
|
|
152
|
+
if (!url) { if (re.lastIndex === m.index) re.lastIndex += 1; continue; }
|
|
148
153
|
var replacement = onUrl(url);
|
|
149
154
|
if (replacement !== null && replacement !== url) {
|
|
150
|
-
|
|
155
|
+
// Splice at the captured group's EXACT offset (hasIndices), never
|
|
156
|
+
// m[0].indexOf(url) — when a markdown alt text equals its target URL the
|
|
157
|
+
// indexOf would hit the alt copy and leave the real URL intact (EchoLeak).
|
|
158
|
+
var span = m.indices && m.indices[g];
|
|
159
|
+
var idx = span ? span[0] : (m.index + m[0].lastIndexOf(url));
|
|
151
160
|
out += text.slice(last, idx) + replacement;
|
|
152
161
|
last = idx + url.length;
|
|
153
162
|
}
|
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/api-snapshot.js
CHANGED
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
*/
|
|
52
52
|
|
|
53
53
|
var nodeFs = require("node:fs");
|
|
54
|
+
var atomicFile = require("./atomic-file");
|
|
54
55
|
var numericBounds = require("./numeric-bounds");
|
|
55
56
|
var safeJson = require("./safe-json");
|
|
56
57
|
var { FrameworkError } = require("./framework-error");
|
|
@@ -199,7 +200,9 @@ function write(snapshot, filePath) {
|
|
|
199
200
|
createdAt: snapshot.createdAt,
|
|
200
201
|
exports: snapshot.exports,
|
|
201
202
|
};
|
|
202
|
-
|
|
203
|
+
// Atomic, symlink-refusing write (0o644 — the snapshot is a public,
|
|
204
|
+
// committed artifact). writeSync stages into a no-follow exclusive temp.
|
|
205
|
+
atomicFile.writeSync(filePath, JSON.stringify(canonical, null, 2) + "\n", { fileMode: 0o644 });
|
|
203
206
|
return filePath;
|
|
204
207
|
}
|
|
205
208
|
|
package/lib/app-shutdown.js
CHANGED
|
@@ -511,6 +511,7 @@ function standardPhases(components) {
|
|
|
511
511
|
// IS the lock.
|
|
512
512
|
var nodeFs = require("node:fs");
|
|
513
513
|
var nodePath = require("node:path");
|
|
514
|
+
var atomicFile = require("./atomic-file");
|
|
514
515
|
|
|
515
516
|
/**
|
|
516
517
|
* @primitive b.appShutdown.pidLock
|
|
@@ -552,7 +553,12 @@ function pidLock(lockPath) {
|
|
|
552
553
|
|
|
553
554
|
function _readExisting() {
|
|
554
555
|
try {
|
|
555
|
-
|
|
556
|
+
// fd-safe + capped + symlink-refusing read: a PID lockfile is never a
|
|
557
|
+
// legitimate symlink (unlike a k8s/certbot secret mount), so refuseSymlink
|
|
558
|
+
// is safe here and stops a planted symlink/oversized file from redirecting
|
|
559
|
+
// or OOM-ing the read. Any throw (symlink/too-large/enoent) → null, the
|
|
560
|
+
// existing "no live lock" semantic.
|
|
561
|
+
var raw = atomicFile.fdSafeReadSync(lockPath, { maxBytes: C.BYTES.kib(1), refuseSymlink: true, encoding: "utf8" });
|
|
556
562
|
var pid = parseInt(String(raw).trim(), 10);
|
|
557
563
|
return isFinite(pid) && pid > 0 ? pid : null;
|
|
558
564
|
} catch (_e) { return null; }
|
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-gz.js
CHANGED
|
@@ -149,6 +149,15 @@ function readGz(adapter, opts) {
|
|
|
149
149
|
throw new ArchiveGzError("archive-gz/empty-input",
|
|
150
150
|
"read.gz: adapter reports empty payload");
|
|
151
151
|
}
|
|
152
|
+
// Cap the raw COMPRESSED read before allocating — `adapter.range(0, size)`
|
|
153
|
+
// does Buffer.allocUnsafe(size) of an fstat/HEAD-reported length, which a
|
|
154
|
+
// hostile .gz (or an objectStore/HTTP source advertising a huge size) can
|
|
155
|
+
// drive to OOM. maxOutputBytes bounds the DECOMPRESSED output only; this
|
|
156
|
+
// bounds the compressed read too, matching archive-tar-read's _collectAdapterBytes.
|
|
157
|
+
if (typeof maxOutputBytes === "number" && size > maxOutputBytes) {
|
|
158
|
+
throw new ArchiveGzError("archive-gz/source-too-large",
|
|
159
|
+
"read.gz: random-access source size=" + size + " exceeds the read cap " + maxOutputBytes);
|
|
160
|
+
}
|
|
152
161
|
return adapter.range(0, size);
|
|
153
162
|
}
|
|
154
163
|
if (adapter.kind === "trusted-sequential") {
|
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
|
|
@@ -920,13 +908,15 @@ function zip(adapter, opts) {
|
|
|
920
908
|
"cumulative uncompressed=" + totalDecompressed +
|
|
921
909
|
" exceeds maxTotalDecompressedBytes during extract");
|
|
922
910
|
}
|
|
923
|
-
// Write entry to disk.
|
|
924
|
-
//
|
|
925
|
-
//
|
|
926
|
-
//
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
911
|
+
// Write entry to disk atomically. The previous hand-rolled form
|
|
912
|
+
// staged into a PREDICTABLE temp name (resolvedPath +
|
|
913
|
+
// ".__blamejs-archive-read-tmp__") via a plain writeFileSync, so a
|
|
914
|
+
// symlink pre-planted at that exact path would be followed (CWE-59
|
|
915
|
+
// arbitrary write outside the extract dir). writeSync stages into a
|
|
916
|
+
// CSPRNG temp opened O_EXCL | O_NOFOLLOW, then renames — a partial
|
|
917
|
+
// write during inflate also never leaves a half-file at the canonical
|
|
918
|
+
// name. The pre-existence check above keeps the rename non-clobbering.
|
|
919
|
+
atomicFile.writeSync(resolvedPath, body);
|
|
930
920
|
written.push({ name: entry.name, bytesWritten: body.length, path: resolvedPath });
|
|
931
921
|
bytesExtracted += body.length;
|
|
932
922
|
}
|
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) {
|
|
@@ -110,13 +99,43 @@ function _parsePaxRecords(buf) {
|
|
|
110
99
|
return out;
|
|
111
100
|
}
|
|
112
101
|
|
|
113
|
-
|
|
102
|
+
// A PAX `size` attribute is an attacker-controlled ASCII string. `parseInt` of
|
|
103
|
+
// a malformed value ("", "abc", "1e9") yields NaN, which then SILENTLY bypasses
|
|
104
|
+
// the entry-size bomb check (`NaN > maxEntryDecompressedBytes` is false) AND
|
|
105
|
+
// desyncs the block walker (`Math.ceil(NaN / BLOCK_SIZE)` is NaN, so `pos`
|
|
106
|
+
// advances by NaN). Reject anything that is not a plain non-negative integer.
|
|
107
|
+
function _paxSize(raw) {
|
|
108
|
+
var s = String(raw).trim();
|
|
109
|
+
var n = parseInt(s, 10);
|
|
110
|
+
// Round-trip: a clean non-negative integer survives parseInt → String
|
|
111
|
+
// unchanged. This rejects "", "abc" (NaN), "1e9", "100abc" (parseInt stops
|
|
112
|
+
// early), negatives, leading zeros, and astronomically long strings that
|
|
113
|
+
// overflow to Infinity (String(Infinity) !== s) — every shape that would
|
|
114
|
+
// otherwise yield NaN/garbage and bypass the bomb check + desync the walker.
|
|
115
|
+
if (!Number.isFinite(n) || n < 0 || String(n) !== s) {
|
|
116
|
+
throw new TarError("archive-tar/bad-pax-size",
|
|
117
|
+
"PAX size attribute " + JSON.stringify(raw) + " is not a non-negative integer");
|
|
118
|
+
}
|
|
119
|
+
return n;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function _collectAdapterBytes(adapter, maxBytes) {
|
|
114
123
|
if (adapter.kind === "random-access") {
|
|
115
124
|
var size = adapter.size;
|
|
116
125
|
if (size == null && typeof adapter.resolveSize === "function") {
|
|
117
126
|
size = await adapter.resolveSize();
|
|
118
127
|
}
|
|
119
|
-
if (typeof size !== "number" || size
|
|
128
|
+
if (typeof size !== "number" || !Number.isFinite(size) || size <= 0) return Buffer.alloc(0);
|
|
129
|
+
// Cap the random-access read the same way the trusted-sequential branch
|
|
130
|
+
// caps its collector — a multi-GiB adapter.range(0, size) is an OOM lever
|
|
131
|
+
// (the size comes from the adapter, e.g. an on-disk file's stat or an
|
|
132
|
+
// operator-supplied length). The default ceiling is the bomb policy's
|
|
133
|
+
// total-decompressed cap.
|
|
134
|
+
if (typeof maxBytes === "number" && size > maxBytes) {
|
|
135
|
+
throw new TarError("archive-tar/source-too-large",
|
|
136
|
+
"read.tar: random-access source size=" + size +
|
|
137
|
+
" exceeds the read cap " + maxBytes);
|
|
138
|
+
}
|
|
120
139
|
return adapter.range(0, size);
|
|
121
140
|
}
|
|
122
141
|
if (adapter.kind === "trusted-sequential") {
|
|
@@ -183,7 +202,7 @@ function tar(adapter, opts) {
|
|
|
183
202
|
var entryTypePolicy = _normalizeEntryTypePolicy(opts.entryTypePolicy);
|
|
184
203
|
|
|
185
204
|
async function _walk() {
|
|
186
|
-
var bytes = await _collectAdapterBytes(adapter);
|
|
205
|
+
var bytes = await _collectAdapterBytes(adapter, bombPolicy.maxTotalDecompressedBytes);
|
|
187
206
|
if (bytes.length === 0) return { entries: [], bytes: bytes };
|
|
188
207
|
var pos = 0;
|
|
189
208
|
var entries = [];
|
|
@@ -201,6 +220,16 @@ function tar(adapter, opts) {
|
|
|
201
220
|
zeroBlockCount = 0;
|
|
202
221
|
var hdr = _parseHeader(block);
|
|
203
222
|
if (hdr.typeflag === TF_PAX_EXTENDED || hdr.typeflag === TF_PAX_GLOBAL) {
|
|
223
|
+
// The per-entry bomb cap below (line ~252) is AFTER the PAX `continue`,
|
|
224
|
+
// so a PAX header body (its size is the attacker-controlled ustar octal
|
|
225
|
+
// field, up to ~8 GiB) escaped it — a multi-hundred-MiB UTF-8 string +
|
|
226
|
+
// record Object materialization above the cap operators set. Legitimate
|
|
227
|
+
// PAX/global bodies are tiny, so bound them by the same per-entry cap.
|
|
228
|
+
if (hdr.size > bombPolicy.maxEntryDecompressedBytes) {
|
|
229
|
+
throw new TarError("archive-tar/entry-too-large",
|
|
230
|
+
"pax header body size=" + hdr.size + " exceeds maxEntryDecompressedBytes=" +
|
|
231
|
+
bombPolicy.maxEntryDecompressedBytes);
|
|
232
|
+
}
|
|
204
233
|
var bodyEnd = pos + Math.ceil(hdr.size / BLOCK_SIZE) * BLOCK_SIZE;
|
|
205
234
|
if (bodyEnd > bytes.length) {
|
|
206
235
|
throw new TarError("archive-tar/truncated-entry",
|
|
@@ -217,12 +246,12 @@ function tar(adapter, opts) {
|
|
|
217
246
|
}
|
|
218
247
|
if (globalPax) {
|
|
219
248
|
if (globalPax.path) hdr.name = globalPax.path;
|
|
220
|
-
if (globalPax.size) hdr.size =
|
|
249
|
+
if (globalPax.size) hdr.size = _paxSize(globalPax.size);
|
|
221
250
|
if (globalPax.linkpath) hdr.linkname = globalPax.linkpath;
|
|
222
251
|
}
|
|
223
252
|
if (pendingPax) {
|
|
224
253
|
if (pendingPax.path) hdr.name = pendingPax.path;
|
|
225
|
-
if (pendingPax.size) hdr.size =
|
|
254
|
+
if (pendingPax.size) hdr.size = _paxSize(pendingPax.size);
|
|
226
255
|
if (pendingPax.linkpath) hdr.linkname = pendingPax.linkpath;
|
|
227
256
|
pendingPax = null;
|
|
228
257
|
}
|
|
@@ -427,9 +456,12 @@ function tar(adapter, opts) {
|
|
|
427
456
|
"cumulative uncompressed=" + totalDecompressed +
|
|
428
457
|
" exceeds maxTotalDecompressedBytes during extract");
|
|
429
458
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
459
|
+
// Atomic, symlink-refusing write. The previous hand-rolled form staged
|
|
460
|
+
// into a PREDICTABLE temp name (resolvedPath +
|
|
461
|
+
// ".__blamejs-archive-tar-tmp__") via a plain writeFileSync, so a
|
|
462
|
+
// symlink pre-planted at that exact path would be followed (CWE-59).
|
|
463
|
+
// writeSync uses a CSPRNG temp opened O_EXCL | O_NOFOLLOW + rename.
|
|
464
|
+
atomicFile.writeSync(resolvedPath, body);
|
|
433
465
|
written.push({ name: entry.name, bytesWritten: body.length, path: resolvedPath });
|
|
434
466
|
bytesExtracted += body.length;
|
|
435
467
|
}
|
package/lib/archive.js
CHANGED
|
@@ -52,12 +52,13 @@
|
|
|
52
52
|
* ZIP archive creation primitive.
|
|
53
53
|
*/
|
|
54
54
|
var zlib = require("node:zlib");
|
|
55
|
-
var nodeFs = require("node:fs");
|
|
56
55
|
var nodeCrypto = require("node:crypto");
|
|
57
56
|
var nodeStream = require("node:stream");
|
|
58
57
|
var streamPromises = require("node:stream/promises");
|
|
59
58
|
var C = require("./constants");
|
|
60
59
|
var { defineClass } = require("./framework-error");
|
|
60
|
+
var auditEmit = require("./audit-emit");
|
|
61
|
+
var atomicFile = require("./atomic-file");
|
|
61
62
|
|
|
62
63
|
var ArchiveError = defineClass("ArchiveError", { alwaysPermanent: true });
|
|
63
64
|
|
|
@@ -477,20 +478,13 @@ function zip() {
|
|
|
477
478
|
|
|
478
479
|
function writeTo(filepath) {
|
|
479
480
|
var buf = toBuffer();
|
|
480
|
-
|
|
481
|
+
// Atomic, symlink-refusing write (a bare writeFileSync follows a symlink
|
|
482
|
+
// planted at filepath and can leave a torn archive on a crash).
|
|
483
|
+
atomicFile.writeSync(filepath, buf, { fileMode: 0o600 });
|
|
481
484
|
return buf.length;
|
|
482
485
|
}
|
|
483
486
|
|
|
484
|
-
|
|
485
|
-
if (!opts || !opts.audit || typeof opts.audit.safeEmit !== "function") return;
|
|
486
|
-
try {
|
|
487
|
-
opts.audit.safeEmit({
|
|
488
|
-
action: action,
|
|
489
|
-
outcome: outcome,
|
|
490
|
-
metadata: metadata,
|
|
491
|
-
});
|
|
492
|
-
} catch (_e) { /* drop-silent — audit sinks must never crash the producer */ }
|
|
493
|
-
}
|
|
487
|
+
var _emitAudit = auditEmit.emitToSink; // operator-sink audit emit (opts.audit)
|
|
494
488
|
|
|
495
489
|
function _writeChunk(writable, chunk) {
|
|
496
490
|
return new Promise(function (resolve, reject) {
|
package/lib/arg-parser.js
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
38
|
var { defineClass } = require("./framework-error");
|
|
39
|
+
var pick = require("./pick");
|
|
39
40
|
|
|
40
41
|
var ArgParserError = defineClass("ArgParserError", { alwaysPermanent: true });
|
|
41
42
|
|
|
@@ -44,8 +45,8 @@ var SUPPORTED_TYPES = ["string", "number", "boolean", "list"];
|
|
|
44
45
|
// Names operators can never use for a flag. Prototype-pollution defense:
|
|
45
46
|
// even though the parser internally uses Object.create(null), downstream
|
|
46
47
|
// callers spreading parsed.flags into a normal object would otherwise
|
|
47
|
-
// overwrite the prototype.
|
|
48
|
-
|
|
48
|
+
// overwrite the prototype. The forbidden-name set is the framework's one
|
|
49
|
+
// prototype-pollution key guard — pick.isPoisonedKey.
|
|
49
50
|
|
|
50
51
|
function _isPlainNonEmpty(s) {
|
|
51
52
|
return typeof s === "string" && s.length > 0;
|
|
@@ -56,7 +57,7 @@ function _validateFlagName(name) {
|
|
|
56
57
|
throw new ArgParserError("arg-parser/flag-name-invalid",
|
|
57
58
|
"flag name must be a non-empty string");
|
|
58
59
|
}
|
|
59
|
-
if (
|
|
60
|
+
if (pick.isPoisonedKey(name)) {
|
|
60
61
|
throw new ArgParserError("arg-parser/flag-name-forbidden",
|
|
61
62
|
"flag name '" + name + "' is reserved");
|
|
62
63
|
}
|
|
@@ -298,7 +299,7 @@ function _consumeFlags(index, tokens) {
|
|
|
298
299
|
|
|
299
300
|
if (tok.indexOf("--") === 0) {
|
|
300
301
|
var rest = tok.slice(2);
|
|
301
|
-
if (
|
|
302
|
+
if (pick.isPoisonedKey(rest.split("=")[0])) {
|
|
302
303
|
throw new ArgParserError("arg-parser/argv-forbidden-name",
|
|
303
304
|
"flag '--" + rest.split("=")[0] + "' is reserved");
|
|
304
305
|
}
|
|
@@ -672,14 +673,14 @@ function parseRaw(argv) {
|
|
|
672
673
|
} else {
|
|
673
674
|
val = true;
|
|
674
675
|
}
|
|
675
|
-
if (
|
|
676
|
+
if (pick.isPoisonedKey(name)) {
|
|
676
677
|
throw new ArgParserError("arg-parser/argv-forbidden-name",
|
|
677
678
|
"flag '--" + name + "' is reserved");
|
|
678
679
|
}
|
|
679
680
|
flags[name] = val;
|
|
680
681
|
} else if (tok.indexOf("-") === 0 && tok.length === 2) {
|
|
681
682
|
var s = tok.slice(1);
|
|
682
|
-
if (
|
|
683
|
+
if (pick.isPoisonedKey(s)) {
|
|
683
684
|
throw new ArgParserError("arg-parser/argv-forbidden-name",
|
|
684
685
|
"flag '-" + s + "' is reserved");
|
|
685
686
|
}
|