@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/asn1-der.js
CHANGED
|
@@ -79,13 +79,25 @@ function readNode(buf, offset) {
|
|
|
79
79
|
// High-tag-number form (multi-byte tag). Walk continuation octets
|
|
80
80
|
// (each top bit set means another follows).
|
|
81
81
|
tag = 0;
|
|
82
|
+
var tagOctets = 0;
|
|
82
83
|
while (true) {
|
|
83
84
|
if (offset + headerLen >= buf.length) {
|
|
84
85
|
throw new Asn1Error("asn1/short", "tag continuation truncated");
|
|
85
86
|
}
|
|
86
87
|
var byte = buf[offset + headerLen];
|
|
88
|
+
if (tagOctets === 0 && byte === 0x80) { // leading 0x80 = non-minimal
|
|
89
|
+
throw new Asn1Error("asn1/tag-non-minimal",
|
|
90
|
+
"high-tag-number form has a non-minimal leading 0x80 octet");
|
|
91
|
+
}
|
|
87
92
|
headerLen += 1;
|
|
88
|
-
|
|
93
|
+
tagOctets += 1;
|
|
94
|
+
// Base-128 multiplication (NOT `tag << 7`, which coerces to 32-bit
|
|
95
|
+
// signed int and overflows to a negative/wrong tag past 2^28).
|
|
96
|
+
tag = (tag * 128) + (byte & 0x7f);
|
|
97
|
+
if (tag > Number.MAX_SAFE_INTEGER) {
|
|
98
|
+
throw new Asn1Error("asn1/tag-too-large",
|
|
99
|
+
"high-tag-number exceeds the safe-integer range");
|
|
100
|
+
}
|
|
89
101
|
if ((byte & 0x80) === 0) break; // continuation bit
|
|
90
102
|
}
|
|
91
103
|
}
|
|
@@ -161,29 +173,45 @@ function readOid(node) {
|
|
|
161
173
|
if (bytes.length === 0) {
|
|
162
174
|
throw new Asn1Error("asn1/oid-empty", "OID value is empty");
|
|
163
175
|
}
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
var
|
|
170
|
-
|
|
171
|
-
var i = 1;
|
|
176
|
+
// Decode every subidentifier as a full base-128 quantity (X.690 §8.19),
|
|
177
|
+
// rejecting non-minimal leading-0x80 padding and unterminated/oversized
|
|
178
|
+
// arcs. The FIRST subidentifier may itself span continuation octets
|
|
179
|
+
// (§8.19.4); only after decoding it do we split into the first two arcs.
|
|
180
|
+
var subids = [];
|
|
181
|
+
var i = 0;
|
|
172
182
|
while (i < bytes.length) {
|
|
173
183
|
var arc = 0;
|
|
174
184
|
var j = i;
|
|
185
|
+
var done = false;
|
|
175
186
|
while (j < bytes.length) {
|
|
176
187
|
var b = bytes[j];
|
|
188
|
+
if (j === i && b === 0x80) { // leading 0x80 = non-minimal
|
|
189
|
+
throw new Asn1Error("asn1/oid-non-minimal",
|
|
190
|
+
"OID subidentifier has a non-minimal leading 0x80 octet");
|
|
191
|
+
}
|
|
177
192
|
arc = (arc * 128) + (b & 0x7f); // base-128 OID arc
|
|
193
|
+
if (arc > Number.MAX_SAFE_INTEGER) {
|
|
194
|
+
throw new Asn1Error("asn1/oid-overflow",
|
|
195
|
+
"OID subidentifier exceeds the safe-integer range");
|
|
196
|
+
}
|
|
178
197
|
j += 1;
|
|
179
|
-
if ((b & 0x80) === 0) break;
|
|
198
|
+
if ((b & 0x80) === 0) { done = true; break; } // continuation bit
|
|
180
199
|
}
|
|
181
|
-
if (
|
|
200
|
+
if (!done) {
|
|
182
201
|
throw new Asn1Error("asn1/oid-malformed", "OID arc never terminated");
|
|
183
202
|
}
|
|
184
|
-
|
|
203
|
+
subids.push(arc);
|
|
185
204
|
i = j;
|
|
186
205
|
}
|
|
206
|
+
|
|
207
|
+
// Split the first subidentifier into the first two arcs (40*X + Y).
|
|
208
|
+
var firstSubid = subids[0];
|
|
209
|
+
var first, second;
|
|
210
|
+
if (firstSubid < 40) { first = 0; second = firstSubid; }
|
|
211
|
+
else if (firstSubid < 80) { first = 1; second = firstSubid - 40; }
|
|
212
|
+
else { first = 2; second = firstSubid - 80; }
|
|
213
|
+
var arcs = [String(first), String(second)];
|
|
214
|
+
for (var k = 1; k < subids.length; k += 1) arcs.push(String(subids[k]));
|
|
187
215
|
return arcs.join(".");
|
|
188
216
|
}
|
|
189
217
|
|
|
@@ -298,24 +326,44 @@ function writeInteger(buf) {
|
|
|
298
326
|
return writeNode(TAG.INTEGER, buf);
|
|
299
327
|
}
|
|
300
328
|
|
|
329
|
+
// Append `arc` to `bytes` as a base-128 subidentifier (X.690 §8.19), most-
|
|
330
|
+
// significant octet first, every octet but the last carrying the
|
|
331
|
+
// continuation bit. Uses integer division (not 32-bit `>>> 7`) so arcs
|
|
332
|
+
// beyond 2^31 encode correctly.
|
|
333
|
+
function _writeBase128Subid(arc, bytes) {
|
|
334
|
+
if (arc === 0) { bytes.push(0); return; }
|
|
335
|
+
var stack = [];
|
|
336
|
+
while (arc > 0) {
|
|
337
|
+
stack.unshift(arc % 128); // base-128 digit
|
|
338
|
+
arc = Math.floor(arc / 128);
|
|
339
|
+
}
|
|
340
|
+
for (var j = 0; j < stack.length - 1; j += 1) stack[j] |= 0x80; // continuation bit
|
|
341
|
+
for (var k = 0; k < stack.length; k += 1) bytes.push(stack[k]);
|
|
342
|
+
}
|
|
343
|
+
|
|
301
344
|
function writeOid(dotted) {
|
|
302
345
|
// Encode dotted-decimal OID per X.690 §8.19.
|
|
303
346
|
var parts = String(dotted).split(".").map(function (s) { return parseInt(s, 10); });
|
|
304
347
|
if (parts.length < 2) {
|
|
305
348
|
throw new Asn1Error("asn1/oid-too-short", "OID needs at least 2 arcs");
|
|
306
349
|
}
|
|
307
|
-
var
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (arc === 0) { bytes.push(0); continue; }
|
|
311
|
-
var stack = [];
|
|
312
|
-
while (arc > 0) {
|
|
313
|
-
stack.unshift(arc & 0x7f); // base-128 mask
|
|
314
|
-
arc = arc >>> 7; // base-128 shift
|
|
350
|
+
for (var p = 0; p < parts.length; p += 1) {
|
|
351
|
+
if (!Number.isInteger(parts[p]) || parts[p] < 0 || parts[p] > Number.MAX_SAFE_INTEGER) {
|
|
352
|
+
throw new Asn1Error("asn1/oid-bad-arc", "OID arc " + p + " is not a non-negative integer");
|
|
315
353
|
}
|
|
316
|
-
for (var j = 0; j < stack.length - 1; j += 1) stack[j] |= 0x80; // continuation bit
|
|
317
|
-
for (var k = 0; k < stack.length; k += 1) bytes.push(stack[k]);
|
|
318
354
|
}
|
|
355
|
+
if (parts[0] > 2) {
|
|
356
|
+
throw new Asn1Error("asn1/oid-bad-arc", "OID first arc must be 0, 1, or 2");
|
|
357
|
+
}
|
|
358
|
+
if (parts[0] < 2 && parts[1] >= 40) {
|
|
359
|
+
throw new Asn1Error("asn1/oid-bad-arc",
|
|
360
|
+
"OID second arc must be < 40 when the first arc is 0 or 1");
|
|
361
|
+
}
|
|
362
|
+
var bytes = [];
|
|
363
|
+
// The first two arcs share one subidentifier (40*X + Y), which may itself
|
|
364
|
+
// need multiple base-128 octets (e.g. 2.999 -> 1079).
|
|
365
|
+
_writeBase128Subid(parts[0] * 40 + parts[1], bytes);
|
|
366
|
+
for (var i = 2; i < parts.length; i += 1) _writeBase128Subid(parts[i], bytes);
|
|
319
367
|
return writeNode(TAG.OID, Buffer.from(bytes));
|
|
320
368
|
}
|
|
321
369
|
|
package/lib/asyncapi-traits.js
CHANGED
|
@@ -109,12 +109,8 @@ function _mergeKey(key, base, overlay) {
|
|
|
109
109
|
if (typeof base === "object" && typeof overlay === "object" &&
|
|
110
110
|
!Array.isArray(base) && !Array.isArray(overlay)) {
|
|
111
111
|
var out = {};
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
for (var k2 in overlay) {
|
|
116
|
-
if (Object.prototype.hasOwnProperty.call(overlay, k2)) out[k2] = overlay[k2];
|
|
117
|
-
}
|
|
112
|
+
validateOpts.assignOwnEnumerable(out, base);
|
|
113
|
+
validateOpts.assignOwnEnumerable(out, overlay);
|
|
118
114
|
return out;
|
|
119
115
|
}
|
|
120
116
|
return overlay; // scalar overrides
|
package/lib/atomic-file.js
CHANGED
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
*/
|
|
41
41
|
var nodeFs = require("node:fs");
|
|
42
42
|
var nodePath = require("node:path");
|
|
43
|
+
var nodeStream = require("node:stream");
|
|
44
|
+
var streamPromises = require("node:stream/promises");
|
|
43
45
|
var { generateToken, sha3Hash } = require("./crypto");
|
|
44
46
|
var safeJson = require("./safe-json");
|
|
45
47
|
var C = require("./constants");
|
|
@@ -81,7 +83,11 @@ class AtomicFileError extends FrameworkError {
|
|
|
81
83
|
// primitive's standard names; jitterFactor 0.5 reproduces the original
|
|
82
84
|
// `delay * (0.5 + Math.random()/2)` range of [delay/2, delay].
|
|
83
85
|
|
|
84
|
-
|
|
86
|
+
// EACCES joins the transient set: on Windows a freshly-written file is briefly
|
|
87
|
+
// locked by AV / the search indexer / a file-sync client (Dropbox, OneDrive),
|
|
88
|
+
// surfacing as EACCES (alongside EPERM/EBUSY) on the next open/rename — the same
|
|
89
|
+
// transient contention the sync _renameWithRetry already treats as retryable.
|
|
90
|
+
var TRANSIENT_FS_ERRNOS = new Set(["EBUSY", "EAGAIN", "ENFILE", "EMFILE", "EPERM", "EACCES"]);
|
|
85
91
|
|
|
86
92
|
function _isFsRetryable(e) {
|
|
87
93
|
return e != null && TRANSIENT_FS_ERRNOS.has(e.code);
|
|
@@ -485,6 +491,146 @@ function writeSync(filepath, data, opts) {
|
|
|
485
491
|
};
|
|
486
492
|
}
|
|
487
493
|
|
|
494
|
+
/**
|
|
495
|
+
* @primitive b.atomicFile.writeStream
|
|
496
|
+
* @signature b.atomicFile.writeStream(filepath, source, opts?)
|
|
497
|
+
* @since 0.15.14
|
|
498
|
+
* @status stable
|
|
499
|
+
* @related b.atomicFile.writeSync, b.atomicFile.openNoFollowSync
|
|
500
|
+
*
|
|
501
|
+
* Streaming sibling of `writeSync` for payloads too large to buffer in
|
|
502
|
+
* memory. Pipes a Readable `source` into a sibling temp file opened with
|
|
503
|
+
* `O_EXCL | O_NOFOLLOW` (the same exclusive, symlink-refusing create
|
|
504
|
+
* every atomic write uses), fsyncs, then atomically renames over
|
|
505
|
+
* `filepath` and fsyncs the parent directory. A plain
|
|
506
|
+
* `fs.createWriteStream(filepath)` instead follows a symlink an attacker
|
|
507
|
+
* pre-planted at `filepath` (CWE-59 arbitrary write) and leaves a
|
|
508
|
+
* half-written object at the canonical name if the source aborts
|
|
509
|
+
* mid-stream — this primitive does neither: the file appears at
|
|
510
|
+
* `filepath` only after the full stream has landed and synced.
|
|
511
|
+
*
|
|
512
|
+
* Enforces a byte ceiling while streaming (`maxBytes`, default 64 MiB) so
|
|
513
|
+
* an unbounded source cannot fill the disk; the partial temp is removed
|
|
514
|
+
* on overflow or any pipeline error.
|
|
515
|
+
*
|
|
516
|
+
* @opts
|
|
517
|
+
* fileMode: 0o600, // mode applied to the temp file (and thus the renamed final)
|
|
518
|
+
* maxBytes: 64 * 1024 * 1024, // refuse + clean up once the source exceeds this many bytes
|
|
519
|
+
* signal: undefined, // optional AbortSignal forwarded to the pipeline
|
|
520
|
+
*
|
|
521
|
+
* @example
|
|
522
|
+
* await b.atomicFile.writeStream(
|
|
523
|
+
* "/var/lib/blamejs/object",
|
|
524
|
+
* incomingRequestStream,
|
|
525
|
+
* { fileMode: 0o600, maxBytes: b.C.BYTES.gib(2) }
|
|
526
|
+
* );
|
|
527
|
+
* // → { bytesWritten: 12345 }
|
|
528
|
+
*/
|
|
529
|
+
async function writeStream(filepath, source, opts) {
|
|
530
|
+
opts = Object.assign({}, DEFAULTS, opts || {});
|
|
531
|
+
if (!source || typeof source.pipe !== "function") {
|
|
532
|
+
throw new AtomicFileError(
|
|
533
|
+
"writeStream: source must be a Readable stream", "atomic-file/invalid-source");
|
|
534
|
+
}
|
|
535
|
+
var maxBytes = opts.maxBytes;
|
|
536
|
+
|
|
537
|
+
var dir = nodePath.dirname(filepath);
|
|
538
|
+
if (!nodeFs.existsSync(dir)) nodeFs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
539
|
+
|
|
540
|
+
var tmpPath = filepath + ".tmp-" + generateToken(C.BYTES.bytes(8));
|
|
541
|
+
var fd = _openExclTemp(tmpPath, opts.fileMode);
|
|
542
|
+
var fileStream = nodeFs.createWriteStream(null, { fd: fd, autoClose: false });
|
|
543
|
+
var bytesWritten = 0;
|
|
544
|
+
var renamed = false;
|
|
545
|
+
|
|
546
|
+
// Cap the stream as it flows — an unbounded source must not fill the disk.
|
|
547
|
+
var counter = new nodeStream.Transform({
|
|
548
|
+
transform: function (chunk, _enc, cb) {
|
|
549
|
+
bytesWritten += chunk.length;
|
|
550
|
+
if (typeof maxBytes === "number" && bytesWritten > maxBytes) {
|
|
551
|
+
return cb(new AtomicFileError(
|
|
552
|
+
"writeStream: source exceeds maxBytes " + maxBytes, "atomic-file/too-large"));
|
|
553
|
+
}
|
|
554
|
+
cb(null, chunk);
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
try {
|
|
559
|
+
if (opts.signal) {
|
|
560
|
+
await streamPromises.pipeline(source, counter, fileStream, { signal: opts.signal });
|
|
561
|
+
} else {
|
|
562
|
+
await streamPromises.pipeline(source, counter, fileStream);
|
|
563
|
+
}
|
|
564
|
+
_fsync(fd);
|
|
565
|
+
nodeFs.closeSync(fd);
|
|
566
|
+
fd = -1;
|
|
567
|
+
_renameWithRetry(tmpPath, filepath);
|
|
568
|
+
renamed = true;
|
|
569
|
+
_fsyncDir(dir);
|
|
570
|
+
} finally {
|
|
571
|
+
if (fd >= 0) { try { nodeFs.closeSync(fd); } catch (_e) { /* already closed? */ } }
|
|
572
|
+
if (!renamed) {
|
|
573
|
+
// Source aborted, overflowed, or the rename failed — remove the temp so
|
|
574
|
+
// no half-written object survives at the canonical name.
|
|
575
|
+
try { nodeFs.unlinkSync(tmpPath); } catch (_e) { /* may not exist */ }
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return { bytesWritten: bytesWritten };
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* @primitive b.atomicFile.writeExclSync
|
|
584
|
+
* @signature b.atomicFile.writeExclSync(filepath, data, opts?)
|
|
585
|
+
* @since 0.15.14
|
|
586
|
+
* @status stable
|
|
587
|
+
* @related b.atomicFile.writeSync, b.atomicFile.openNoFollowSync
|
|
588
|
+
*
|
|
589
|
+
* Exclusive, symlink-refusing write to `filepath` WITHOUT the atomic
|
|
590
|
+
* rename — for staged "write → fsync → verify → rename" flows where the
|
|
591
|
+
* caller must re-read and validate the written bytes before committing them
|
|
592
|
+
* over the live file (the vault seal/unseal round-trip re-reads the staged
|
|
593
|
+
* file and confirms it decrypts before renaming it into place). Clears any
|
|
594
|
+
* stale leftover at `filepath` first (an aborted prior run, or a planted
|
|
595
|
+
* symlink — `unlink` removes the LINK, never its target), then creates the
|
|
596
|
+
* file with `O_EXCL | O_NOFOLLOW`, so a symlink re-planted in the race
|
|
597
|
+
* window fails the open closed instead of being followed (CWE-59 / CWE-377).
|
|
598
|
+
* fsyncs the data before returning. For an ordinary write-and-replace use
|
|
599
|
+
* `writeSync`, which renames atomically; reach for this only when a
|
|
600
|
+
* verify-before-commit step sits between the write and the rename.
|
|
601
|
+
*
|
|
602
|
+
* @opts
|
|
603
|
+
* fileMode: 0o600, // mode applied to the created file
|
|
604
|
+
*
|
|
605
|
+
* @example
|
|
606
|
+
* b.atomicFile.writeExclSync(stagingPath, bytes, { fileMode: 0o600 });
|
|
607
|
+
* // re-read + verify stagingPath, then:
|
|
608
|
+
* b.atomicFile.renameWithRetry(stagingPath, finalPath);
|
|
609
|
+
*/
|
|
610
|
+
function writeExclSync(filepath, data, opts) {
|
|
611
|
+
opts = Object.assign({}, DEFAULTS, opts || {});
|
|
612
|
+
var buf = safeBuffer.toBuffer(data, {
|
|
613
|
+
errorClass: AtomicFileError,
|
|
614
|
+
typeCode: "atomic-file/invalid-data",
|
|
615
|
+
typeMessage: "data must be Buffer, Uint8Array, or string",
|
|
616
|
+
});
|
|
617
|
+
// Clear any stale leftover so the exclusive create can proceed; unlink
|
|
618
|
+
// removes a planted symlink itself (not its target), and the O_EXCL open
|
|
619
|
+
// then fails closed if anything re-appears at the path in the race window.
|
|
620
|
+
try { nodeFs.unlinkSync(filepath); } catch (_e) { /* nothing to clear */ }
|
|
621
|
+
var fd = _openExclTemp(filepath, opts.fileMode);
|
|
622
|
+
try {
|
|
623
|
+
var pos = 0;
|
|
624
|
+
while (pos < buf.length) {
|
|
625
|
+
pos += nodeFs.writeSync(fd, buf, pos, buf.length - pos, null);
|
|
626
|
+
}
|
|
627
|
+
_fsync(fd);
|
|
628
|
+
} finally {
|
|
629
|
+
try { nodeFs.closeSync(fd); } catch (_e) { /* already closed? */ }
|
|
630
|
+
}
|
|
631
|
+
return { bytesWritten: buf.length };
|
|
632
|
+
}
|
|
633
|
+
|
|
488
634
|
/**
|
|
489
635
|
* @primitive b.atomicFile.cleanOrphans
|
|
490
636
|
* @signature b.atomicFile.cleanOrphans(filepath, opts)
|
|
@@ -703,39 +849,94 @@ function _validateMaxBytes(maxBytes) {
|
|
|
703
849
|
}
|
|
704
850
|
}
|
|
705
851
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
852
|
+
/**
|
|
853
|
+
* @primitive b.atomicFile.fdSafeReadSync
|
|
854
|
+
* @signature b.atomicFile.fdSafeReadSync(filepath, opts?)
|
|
855
|
+
* @since 0.15.13
|
|
856
|
+
* @status stable
|
|
857
|
+
* @related b.atomicFile.readSync, b.atomicFile.read
|
|
858
|
+
*
|
|
859
|
+
* TOCTOU-safe synchronous file read (CWE-367 / js/file-system-race). Opens
|
|
860
|
+
* the path read-only, then binds every subsequent measurement — size,
|
|
861
|
+
* content, integrity — to the inode the fd holds open, so an attacker who
|
|
862
|
+
* swaps the file between stat and read can't change which bytes come back.
|
|
863
|
+
* The optional guards layer on top of that core: a byte cap (`maxBytes`),
|
|
864
|
+
* symlink refusal + inode-equality (`refuseSymlink` / `inodeCheck` — the
|
|
865
|
+
* strongest defense, for operator-writable source paths), an integrity
|
|
866
|
+
* hash (`expectedHash`, SHA3-512), and a short-read policy (throw, or
|
|
867
|
+
* slice when `allowShortRead`). Each caller maps a failure KIND to its own
|
|
868
|
+
* typed error via `errorFor`, so the message / code / audit posture stays
|
|
869
|
+
* per-domain; the default raises an `AtomicFileError`.
|
|
870
|
+
*
|
|
871
|
+
* @opts
|
|
872
|
+
* mode: number, // open mode (default 0o600; inert under O_RDONLY)
|
|
873
|
+
* maxBytes: number, // refuse a file larger than this (default: no cap)
|
|
874
|
+
* refuseSymlink: boolean, // lstat + refuse a symlink source (default: false)
|
|
875
|
+
* inodeCheck: boolean, // refuse if the fd inode != the lstat inode (needs refuseSymlink)
|
|
876
|
+
* expectedHash: string, // SHA3-512 the content must match (default: none)
|
|
877
|
+
* encoding: string, // decode to a string (default: return a Buffer)
|
|
878
|
+
* allowShortRead: boolean, // slice to the bytes read instead of throwing (default: false)
|
|
879
|
+
* withStat: boolean, // return { bytes, stat } — stat of the bound fd (mode/uid/gid/size/ino/nlink/mtimeMs), TOCTOU-free
|
|
880
|
+
* errorFor: Function, // (kind, detail) => Error|undefined; kinds: enoent / symlink / too-large / toctou / short-read / integrity
|
|
881
|
+
*
|
|
882
|
+
* @example
|
|
883
|
+
* var cfg = b.atomicFile.fdSafeReadSync("/etc/app/config.json", {
|
|
884
|
+
* maxBytes: b.constants.BYTES.mib(1),
|
|
885
|
+
* encoding: "utf8",
|
|
886
|
+
* });
|
|
887
|
+
*
|
|
888
|
+
* // Assert mode + owner on the exact inode the bytes came from (no re-stat):
|
|
889
|
+
* var r = b.atomicFile.fdSafeReadSync("/etc/app/secret", { withStat: true });
|
|
890
|
+
* if ((r.stat.mode & 0o077) !== 0) throw new Error("secret is group/other-readable");
|
|
891
|
+
* // r.bytes is the Buffer (or string under `encoding`)
|
|
892
|
+
*/
|
|
893
|
+
function fdSafeReadSync(filepath, opts) {
|
|
894
|
+
opts = opts || {};
|
|
895
|
+
var errorFor = opts.errorFor || function (kind, detail) {
|
|
896
|
+
return new AtomicFileError((detail && detail.message) || ("atomic-file: " + kind), "atomic-file/" + kind);
|
|
897
|
+
};
|
|
898
|
+
if (opts.maxBytes !== undefined) _validateMaxBytes(opts.maxBytes);
|
|
899
|
+
var mode = opts.mode === undefined ? 0o600 : opts.mode;
|
|
900
|
+
// refuseSymlink: lstat the path first and refuse a symlink source —
|
|
901
|
+
// the strongest TOCTOU posture (open() would follow the link). The
|
|
902
|
+
// fd's inode is re-checked against this lstat's inode below.
|
|
903
|
+
var lstat = null;
|
|
904
|
+
if (opts.refuseSymlink) {
|
|
905
|
+
lstat = nodeFs.lstatSync(filepath);
|
|
906
|
+
if (lstat.isSymbolicLink()) throw errorFor("symlink", { path: filepath });
|
|
907
|
+
if (opts.maxBytes !== undefined && lstat.size > opts.maxBytes) {
|
|
908
|
+
throw errorFor("too-large", { size: lstat.size, max: opts.maxBytes });
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
// The third argument pins an owner-only mode (0o600 default). The flag
|
|
912
|
+
// is read-only ("r" → O_RDONLY, no O_CREAT) so the mode is inert on
|
|
913
|
+
// disk, but specifying it keeps this open out of the insecure-temp-file
|
|
914
|
+
// class (CWE-377). ENOENT surfaces from open() rather than a pre-check;
|
|
915
|
+
// a caller's errorFor("enoent") may translate it, else it rethrows raw.
|
|
720
916
|
var fd;
|
|
721
917
|
try {
|
|
722
|
-
fd = nodeFs.openSync(filepath, "r",
|
|
918
|
+
fd = nodeFs.openSync(filepath, "r", mode);
|
|
723
919
|
} catch (openErr) {
|
|
724
920
|
if (openErr && openErr.code === "ENOENT") {
|
|
725
|
-
var
|
|
726
|
-
|
|
727
|
-
throw e;
|
|
921
|
+
var typed = errorFor("enoent", { path: filepath, cause: openErr });
|
|
922
|
+
if (typed) throw typed;
|
|
728
923
|
}
|
|
729
924
|
throw openErr;
|
|
730
925
|
}
|
|
731
926
|
var buf;
|
|
732
927
|
try {
|
|
733
928
|
var fstat = nodeFs.fstatSync(fd);
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
929
|
+
// inodeCheck: the fd must point at the same inode lstat saw — any
|
|
930
|
+
// swap between lstat and open is a TOCTOU and is refused. A file that
|
|
931
|
+
// GREW past the cap between lstat and open is the same class of swap,
|
|
932
|
+
// so under inodeCheck a post-open over-cap is reported as toctou; a
|
|
933
|
+
// plain (no-inodeCheck) reader reports an over-cap as too-large.
|
|
934
|
+
if (lstat && opts.inodeCheck) {
|
|
935
|
+
if (fstat.ino !== lstat.ino || (opts.maxBytes !== undefined && fstat.size > opts.maxBytes)) {
|
|
936
|
+
throw errorFor("toctou", { path: filepath });
|
|
937
|
+
}
|
|
938
|
+
} else if (opts.maxBytes !== undefined && fstat.size > opts.maxBytes) {
|
|
939
|
+
throw errorFor("too-large", { size: fstat.size, max: opts.maxBytes });
|
|
739
940
|
}
|
|
740
941
|
buf = Buffer.alloc(fstat.size);
|
|
741
942
|
var read = 0;
|
|
@@ -745,10 +946,8 @@ function _readSyncCore(filepath, opts) {
|
|
|
745
946
|
read += n;
|
|
746
947
|
}
|
|
747
948
|
if (read !== fstat.size) {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
"atomic-file/short-read"
|
|
751
|
-
);
|
|
949
|
+
if (opts.allowShortRead) { buf = buf.slice(0, read); }
|
|
950
|
+
else { throw errorFor("short-read", { read: read, size: fstat.size }); }
|
|
752
951
|
}
|
|
753
952
|
} finally {
|
|
754
953
|
try { nodeFs.closeSync(fd); } catch (_c) { /* close best-effort */ }
|
|
@@ -756,13 +955,61 @@ function _readSyncCore(filepath, opts) {
|
|
|
756
955
|
if (opts.expectedHash) {
|
|
757
956
|
var actual = sha3Hash(buf);
|
|
758
957
|
if (actual !== opts.expectedHash) {
|
|
759
|
-
throw
|
|
760
|
-
"integrity check failed: expected " + opts.expectedHash + " got " + actual,
|
|
761
|
-
"atomic-file/integrity"
|
|
762
|
-
);
|
|
958
|
+
throw errorFor("integrity", { expected: opts.expectedHash, actual: actual });
|
|
763
959
|
}
|
|
764
960
|
}
|
|
765
|
-
|
|
961
|
+
var content = opts.encoding ? buf.toString(opts.encoding) : buf;
|
|
962
|
+
// withStat: return the fstat of the SAME bound fd alongside the bytes, so a
|
|
963
|
+
// caller that needs the mode / owner (e.g. to assert 0o600 + owned-by-me on a
|
|
964
|
+
// secrets file) reads it TOCTOU-free — the stat describes the exact inode the
|
|
965
|
+
// bytes came from, not a re-stat that an attacker could swap underneath.
|
|
966
|
+
if (opts.withStat) {
|
|
967
|
+
return {
|
|
968
|
+
bytes: content,
|
|
969
|
+
stat: {
|
|
970
|
+
mode: fstat.mode,
|
|
971
|
+
uid: fstat.uid,
|
|
972
|
+
gid: fstat.gid,
|
|
973
|
+
size: fstat.size,
|
|
974
|
+
ino: fstat.ino,
|
|
975
|
+
nlink: fstat.nlink,
|
|
976
|
+
mtimeMs: fstat.mtimeMs,
|
|
977
|
+
},
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
return content;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Atomic-file's own reads route through fdSafeReadSync with an errorFor
|
|
984
|
+
// that reproduces this module's exact codes + messages (a pure refactor).
|
|
985
|
+
function _readSyncCore(filepath, opts) {
|
|
986
|
+
return fdSafeReadSync(filepath, {
|
|
987
|
+
mode: 0o600,
|
|
988
|
+
maxBytes: opts.maxBytes,
|
|
989
|
+
expectedHash: opts.expectedHash,
|
|
990
|
+
encoding: opts.encoding,
|
|
991
|
+
errorFor: function (kind, detail) {
|
|
992
|
+
if (kind === "enoent") {
|
|
993
|
+
var e = new AtomicFileError("file not found: " + filepath, "atomic-file/not-found");
|
|
994
|
+
e.code = "ENOENT";
|
|
995
|
+
return e;
|
|
996
|
+
}
|
|
997
|
+
if (kind === "too-large") {
|
|
998
|
+
return new AtomicFileError(
|
|
999
|
+
"file size " + detail.size + " > maxBytes " + detail.max, "atomic-file/too-large");
|
|
1000
|
+
}
|
|
1001
|
+
if (kind === "short-read") {
|
|
1002
|
+
return new AtomicFileError(
|
|
1003
|
+
"short read: " + detail.read + " of " + detail.size + " bytes", "atomic-file/short-read");
|
|
1004
|
+
}
|
|
1005
|
+
if (kind === "integrity") {
|
|
1006
|
+
return new AtomicFileError(
|
|
1007
|
+
"integrity check failed: expected " + detail.expected + " got " + detail.actual,
|
|
1008
|
+
"atomic-file/integrity");
|
|
1009
|
+
}
|
|
1010
|
+
return new AtomicFileError("atomic-file: " + kind, "atomic-file/" + kind);
|
|
1011
|
+
},
|
|
1012
|
+
});
|
|
766
1013
|
}
|
|
767
1014
|
|
|
768
1015
|
/**
|
|
@@ -1061,11 +1308,43 @@ function listDir(dir, opts) {
|
|
|
1061
1308
|
return out;
|
|
1062
1309
|
}
|
|
1063
1310
|
|
|
1311
|
+
/**
|
|
1312
|
+
* @primitive b.atomicFile.openNoFollowSync
|
|
1313
|
+
* @signature b.atomicFile.openNoFollowSync(filepath, mode?)
|
|
1314
|
+
* @since 0.15.14
|
|
1315
|
+
* @status stable
|
|
1316
|
+
* @related b.atomicFile.fdSafeReadSync, b.atomicFile.readSync
|
|
1317
|
+
*
|
|
1318
|
+
* Open a path read-only with `O_NOFOLLOW` so a symlink at the final path
|
|
1319
|
+
* component is refused (`ELOOP`) instead of followed — the streaming-read
|
|
1320
|
+
* counterpart to `fdSafeReadSync` for callers that must `fs.createReadStream`
|
|
1321
|
+
* (range serving, SRI/ETag hashing, large-object download) and cannot buffer
|
|
1322
|
+
* the whole file. Stream from the returned fd: `fs.createReadStream(path, { fd
|
|
1323
|
+
* })`. Defends a post-confinement symlink swap (CWE-22 / CWE-367) on
|
|
1324
|
+
* request-reachable static-serve and object-store read paths, where a lexical
|
|
1325
|
+
* `_assertInsideRoot` check alone leaves a swap window between the check and the
|
|
1326
|
+
* open. `O_NOFOLLOW` is POSIX-only; on platforms without it the flag is 0 (a
|
|
1327
|
+
* plain `O_RDONLY` open) — Windows symlink semantics differ and are out of
|
|
1328
|
+
* scope. Throws the raw `openSync` error (caller maps `ELOOP` / `ENOENT`).
|
|
1329
|
+
*
|
|
1330
|
+
* @example
|
|
1331
|
+
* var fd = b.atomicFile.openNoFollowSync(absPath);
|
|
1332
|
+
* var stream = fs.createReadStream(absPath, { fd: fd }); // autoClose closes fd
|
|
1333
|
+
*/
|
|
1334
|
+
function openNoFollowSync(filepath, mode) {
|
|
1335
|
+
var flags = nodeFs.constants.O_RDONLY | (nodeFs.constants.O_NOFOLLOW || 0);
|
|
1336
|
+
return nodeFs.openSync(filepath, flags, mode === undefined ? 0o600 : mode);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1064
1339
|
module.exports = {
|
|
1065
1340
|
write: write,
|
|
1066
1341
|
writeSync: writeSync,
|
|
1342
|
+
writeStream: writeStream,
|
|
1343
|
+
writeExclSync: writeExclSync,
|
|
1067
1344
|
read: read,
|
|
1068
1345
|
readSync: readSync,
|
|
1346
|
+
fdSafeReadSync: fdSafeReadSync,
|
|
1347
|
+
openNoFollowSync: openNoFollowSync,
|
|
1069
1348
|
writeJson: writeJson,
|
|
1070
1349
|
readJson: readJson,
|
|
1071
1350
|
copy: copy,
|