@blamejs/core 0.8.42 → 0.8.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +93 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
package/lib/fdx.js
CHANGED
|
@@ -1,7 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* b.fdx
|
|
4
|
-
*
|
|
3
|
+
* @module b.fdx
|
|
4
|
+
* @nav Compliance
|
|
5
|
+
* @title FDX
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Financial Data Exchange (FDX) US open-banking standard —
|
|
9
|
+
* consent records, SHA3 transaction hashes, data-recipient
|
|
10
|
+
* registry.
|
|
11
|
+
*
|
|
12
|
+
* CFPB §1033 (12 CFR §1033.121-461, final rule 2024-10-22) gives
|
|
13
|
+
* US consumers the right to authorize a third party to access
|
|
14
|
+
* their financial data through a covered data provider's
|
|
15
|
+
* developer interface. FDX (https://financialdataexchange.org)
|
|
16
|
+
* is the industry-standard schema + protocol the CFPB rule
|
|
17
|
+
* effectively codifies (FDX 6.0+ aligns with the §1033 final
|
|
18
|
+
* rule). Compliance deadline 2026-04-01 already past for $250B+
|
|
19
|
+
* asset-size banks.
|
|
20
|
+
*
|
|
21
|
+
* The framework can't be the operator's authorization server,
|
|
22
|
+
* resource server, or FDX-data origin — those are the operator's
|
|
23
|
+
* core banking system. What it can do: bind the operator's auth
|
|
24
|
+
* server to the FAPI 2.0 profile (which §1033.351 effectively
|
|
25
|
+
* requires), validate FDX response shapes, emit §1033-shape
|
|
26
|
+
* audit events on every authorized data access, and generate
|
|
27
|
+
* the §1033.401(b) consent receipt.
|
|
28
|
+
*
|
|
29
|
+
* @card
|
|
30
|
+
* Financial Data Exchange (FDX) US open-banking standard — consent records, SHA3 transaction hashes, data-recipient registry.
|
|
31
|
+
*/
|
|
32
|
+
/*
|
|
33
|
+
* Original prose retained:
|
|
5
34
|
*
|
|
6
35
|
* CFPB §1033 (12 CFR §1033.121-461, final rule 2024-10-22) gives US
|
|
7
36
|
* consumers the right to authorize a third party to access their
|
|
@@ -95,6 +124,42 @@ var FDX_SCHEMAS = {
|
|
|
95
124
|
},
|
|
96
125
|
};
|
|
97
126
|
|
|
127
|
+
/**
|
|
128
|
+
* @primitive b.fdx.bind
|
|
129
|
+
* @signature b.fdx.bind(opts)
|
|
130
|
+
* @since 0.8.0
|
|
131
|
+
* @status stable
|
|
132
|
+
* @compliance fdx, fapi2
|
|
133
|
+
* @related b.fdx.validateResponse, b.fdx.consentReceipt, b.fapi2.assertOAuthConfig
|
|
134
|
+
*
|
|
135
|
+
* Bind an operator's authorization-server config to the FDX 6.0
|
|
136
|
+
* data-sharing surface and the FAPI 2.0 security profile. Refuses
|
|
137
|
+
* unknown FDX resources, refuses missing issuer/jwksUri, and runs
|
|
138
|
+
* `b.fapi2.assertOAuthConfig` on the supplied FAPI opts so a
|
|
139
|
+
* non-conformant deployment fails at boot. Returns a handle with
|
|
140
|
+
* `schemaValidator(resource, body)` and `consent.receipt(opts)`.
|
|
141
|
+
*
|
|
142
|
+
* @opts
|
|
143
|
+
* authServer: {
|
|
144
|
+
* issuer: string, // required, non-empty
|
|
145
|
+
* jwksUri: string, // required, non-empty
|
|
146
|
+
* fapi2: { pkce, dpop?, mtls?, par },
|
|
147
|
+
* },
|
|
148
|
+
* resources: ["accounts" | "transactions" | "statements" |
|
|
149
|
+
* "payment-networks" | "rewards" | "tax-forms"],
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* var fdx = b.fdx.bind({
|
|
153
|
+
* authServer: {
|
|
154
|
+
* issuer: "https://auth.example-bank.com",
|
|
155
|
+
* jwksUri: "https://auth.example-bank.com/.well-known/jwks.json",
|
|
156
|
+
* fapi2: { pkce: true, mtls: true, par: true },
|
|
157
|
+
* },
|
|
158
|
+
* resources: ["accounts", "transactions"],
|
|
159
|
+
* });
|
|
160
|
+
* fdx.fapi2Posture;
|
|
161
|
+
* // → "fapi-2.0"
|
|
162
|
+
*/
|
|
98
163
|
function bind(opts) {
|
|
99
164
|
if (!opts || typeof opts !== "object") {
|
|
100
165
|
throw FdxError.factory("BAD_OPTS", "fdx.bind: opts required");
|
|
@@ -150,6 +215,36 @@ function bind(opts) {
|
|
|
150
215
|
};
|
|
151
216
|
}
|
|
152
217
|
|
|
218
|
+
/**
|
|
219
|
+
* @primitive b.fdx.validateResponse
|
|
220
|
+
* @signature b.fdx.validateResponse(resourceType, body)
|
|
221
|
+
* @since 0.8.0
|
|
222
|
+
* @status stable
|
|
223
|
+
* @compliance fdx
|
|
224
|
+
* @related b.fdx.bind
|
|
225
|
+
*
|
|
226
|
+
* Validate an FDX response payload against the framework's FDX 6.0
|
|
227
|
+
* minimum-required-field schema. Accepts both envelope form
|
|
228
|
+
* (`{ accounts: [...] }`) and bare-array / single-record form.
|
|
229
|
+
* Returns `{ valid, errors }` so the operator can decide whether
|
|
230
|
+
* to refuse, redact, or pass through. Throws `FdxError` only when
|
|
231
|
+
* `resourceType` is unknown.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* var result = b.fdx.validateResponse("accounts", {
|
|
235
|
+
* accounts: [{
|
|
236
|
+
* accountId: "acct-1",
|
|
237
|
+
* accountType: "DEPOSIT",
|
|
238
|
+
* accountNumberDisplay: "****1234",
|
|
239
|
+
* currency: "USD",
|
|
240
|
+
* currentBalance: 1234.56,
|
|
241
|
+
* }],
|
|
242
|
+
* });
|
|
243
|
+
* result.valid;
|
|
244
|
+
* // → true
|
|
245
|
+
* result.errors.length;
|
|
246
|
+
* // → 0
|
|
247
|
+
*/
|
|
153
248
|
function validateResponse(resourceType, body) {
|
|
154
249
|
var schema = FDX_SCHEMAS[resourceType];
|
|
155
250
|
if (!schema) {
|
|
@@ -181,6 +276,41 @@ function validateResponse(resourceType, body) {
|
|
|
181
276
|
return { valid: errors.length === 0, errors: errors };
|
|
182
277
|
}
|
|
183
278
|
|
|
279
|
+
/**
|
|
280
|
+
* @primitive b.fdx.consentReceipt
|
|
281
|
+
* @signature b.fdx.consentReceipt(opts)
|
|
282
|
+
* @since 0.8.0
|
|
283
|
+
* @status stable
|
|
284
|
+
* @compliance fdx
|
|
285
|
+
* @related b.fdx.bind
|
|
286
|
+
*
|
|
287
|
+
* Mint the §1033.401(b) consent receipt the authorization server
|
|
288
|
+
* gives the consumer at authorization time. Required fields:
|
|
289
|
+
* dataProvider, consumerRef, thirdParty, revocationUrl, scopes.
|
|
290
|
+
* Optional `durationMs` defaults to 52 weeks. Emits
|
|
291
|
+
* `fdx.consent_receipt_issued` to the audit chain so the regulator
|
|
292
|
+
* sees a record per receipt.
|
|
293
|
+
*
|
|
294
|
+
* @opts
|
|
295
|
+
* dataProvider: string, // issuer / data provider name
|
|
296
|
+
* consumerRef: string, // operator-side consumer identifier
|
|
297
|
+
* thirdParty: string, // recipient name
|
|
298
|
+
* revocationUrl: string, // public revocation endpoint
|
|
299
|
+
* scopes: [string, ...], // data scopes (account ids, resources)
|
|
300
|
+
* durationMs: number, // optional; defaults to 52 weeks
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* var receipt = b.fdx.consentReceipt({
|
|
304
|
+
* dataProvider: "https://auth.example-bank.com",
|
|
305
|
+
* consumerRef: "consumer-42",
|
|
306
|
+
* thirdParty: "Aggregator Inc.",
|
|
307
|
+
* revocationUrl: "https://example-bank.com/consent/revoke/abc",
|
|
308
|
+
* scopes: ["accounts", "transactions"],
|
|
309
|
+
* durationMs: 365 * 86400 * 1000,
|
|
310
|
+
* });
|
|
311
|
+
* receipt.type;
|
|
312
|
+
* // → "fdx.consent-receipt"
|
|
313
|
+
*/
|
|
184
314
|
function consentReceipt(opts) {
|
|
185
315
|
if (!opts || typeof opts !== "object") {
|
|
186
316
|
throw FdxError.factory("BAD_OPTS", "fdx.consentReceipt: opts required");
|
package/lib/file-type.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.fileType
|
|
4
|
+
* @nav Validation
|
|
5
|
+
* @title File Type
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Magic-byte content-type detection independent of the declared
|
|
9
|
+
* MIME. The Content-Type header on a multipart upload is supplied
|
|
10
|
+
* by the client; a hostile uploader can label a polyglot HTML
|
|
11
|
+
* payload as `image/png` and the header alone won't catch it.
|
|
12
|
+
* This primitive inspects the leading bytes against a hardcoded
|
|
13
|
+
* signature registry (PNG / JPEG / GIF / WEBP / AVIF / HEIC / PDF /
|
|
14
|
+
* OOXML / CFB / ZIP / RAR / 7Z / TAR / GZIP / BZ2 / XZ / MP3 / MP4
|
|
15
|
+
* / WEBM / PE / ELF / Mach-O) and returns the actual format.
|
|
16
|
+
*
|
|
17
|
+
* `detect(buf)` returns `null` rather than throwing on bad input
|
|
18
|
+
* (saved-for-later analysis often runs against partial reads);
|
|
19
|
+
* `assertOneOf(buf, allowlist)` throws `FileTypeError` when the
|
|
20
|
+
* detected format is not in the operator-supplied allowlist.
|
|
21
|
+
* Allowlist entries match against `mime` ("image/png"), short
|
|
22
|
+
* `name` ("png"), or `category` ("image") — operators pin the
|
|
23
|
+
* tightest level the use case allows.
|
|
24
|
+
*
|
|
25
|
+
* Out of scope: content disarm (CDR), polyglot detection, and
|
|
26
|
+
* filename-extension validation. Operators with disarm requirements
|
|
27
|
+
* reach for a sandbox like dangerzone or vmray; filename extensions
|
|
28
|
+
* live behind `b.guardFilename`. Operators extending the registry
|
|
29
|
+
* pass `opts.extra` to `detect` — extras come first, letting an
|
|
30
|
+
* operator override a built-in entry without forking.
|
|
31
|
+
*
|
|
32
|
+
* @card
|
|
33
|
+
* Magic-byte content-type detection independent of the declared MIME.
|
|
34
|
+
*/
|
|
2
35
|
/**
|
|
3
36
|
* file-type — magic-byte content detection.
|
|
4
37
|
*
|
|
@@ -206,6 +239,32 @@ function _entryMatches(entry, buf) {
|
|
|
206
239
|
return true;
|
|
207
240
|
}
|
|
208
241
|
|
|
242
|
+
/**
|
|
243
|
+
* @primitive b.fileType.detect
|
|
244
|
+
* @signature b.fileType.detect(buf, opts?)
|
|
245
|
+
* @since 0.1.0
|
|
246
|
+
* @related b.fileType.assertOneOf, b.fileUpload.create
|
|
247
|
+
*
|
|
248
|
+
* Inspects the leading bytes of `buf` against the signature registry
|
|
249
|
+
* and returns `{ mime, extension, category, name }` for the first
|
|
250
|
+
* matching entry, or `null` when no signature matches (or `buf` is
|
|
251
|
+
* empty, or not a Buffer/Uint8Array). OOXML entries (`docx`/`xlsx`/
|
|
252
|
+
* `pptx`) sit before generic ZIP so well-formed Office files win
|
|
253
|
+
* before the bare ZIP shape; operators extending the registry via
|
|
254
|
+
* `opts.extra` get their entries scanned first so they can override
|
|
255
|
+
* built-ins without forking.
|
|
256
|
+
*
|
|
257
|
+
* @opts
|
|
258
|
+
* extra: Array<{ name, mime, extension, category, offset, magic, extra? }>,
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* var fs = require("node:fs");
|
|
262
|
+
* var buf = fs.readFileSync("photo.png");
|
|
263
|
+
* b.fileType.detect(buf);
|
|
264
|
+
* // → { mime: "image/png", extension: "png", category: "image", name: "png" }
|
|
265
|
+
*
|
|
266
|
+
* b.fileType.detect(Buffer.from("")); // → null
|
|
267
|
+
*/
|
|
209
268
|
function detect(buf, opts) {
|
|
210
269
|
if (!Buffer.isBuffer(buf)) {
|
|
211
270
|
if (buf instanceof Uint8Array) buf = Buffer.from(buf);
|
|
@@ -227,6 +286,34 @@ function detect(buf, opts) {
|
|
|
227
286
|
return null;
|
|
228
287
|
}
|
|
229
288
|
|
|
289
|
+
/**
|
|
290
|
+
* @primitive b.fileType.assertOneOf
|
|
291
|
+
* @signature b.fileType.assertOneOf(buf, allowlist, opts?)
|
|
292
|
+
* @since 0.1.0
|
|
293
|
+
* @related b.fileType.detect, b.fileUpload.create
|
|
294
|
+
*
|
|
295
|
+
* Detects the format of `buf` and throws `FileTypeError` when the
|
|
296
|
+
* result is not in `allowlist`. Allowlist entries match by `mime`
|
|
297
|
+
* ("image/png"), short `name` ("png"), or `category` ("image") so
|
|
298
|
+
* operators pin the tightest level the use case allows. Empty
|
|
299
|
+
* buffers throw `EMPTY` unless `opts.allowEmpty: true`. Unrecognized
|
|
300
|
+
* magic bytes throw `UNKNOWN_TYPE` — the framework refuses to fall
|
|
301
|
+
* back to the advertised header MIME because the entire purpose of
|
|
302
|
+
* this primitive is mistrusting that header.
|
|
303
|
+
*
|
|
304
|
+
* @opts
|
|
305
|
+
* allowEmpty: boolean, // default false
|
|
306
|
+
* extra: Array<{ name, mime, extension, category, offset, magic, extra? }>,
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* var fs = require("node:fs");
|
|
310
|
+
* var buf = fs.readFileSync("photo.png");
|
|
311
|
+
* var detected = b.fileType.assertOneOf(buf, ["image/png", "image/jpeg"]);
|
|
312
|
+
* // → { mime: "image/png", extension: "png", category: "image", name: "png" }
|
|
313
|
+
*
|
|
314
|
+
* // Category-level allowlist (any image format)
|
|
315
|
+
* b.fileType.assertOneOf(buf, ["image"]);
|
|
316
|
+
*/
|
|
230
317
|
function assertOneOf(buf, allowlist, opts) {
|
|
231
318
|
opts = opts || {};
|
|
232
319
|
if (!Buffer.isBuffer(buf) && !(buf instanceof Uint8Array)) {
|
package/lib/file-upload.js
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.fileUpload
|
|
4
|
+
* @nav HTTP
|
|
5
|
+
* @title File Upload
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Streaming multipart upload with content-safety guards wired on
|
|
9
|
+
* by default. Init / acceptChunk / finalize lifecycle: operator
|
|
10
|
+
* calls `init` to allocate per-upload staging, streams chunks via
|
|
11
|
+
* `acceptChunk` (each carrying its own SHA3-512 hex), then calls
|
|
12
|
+
* `finalize` with a manifest so the framework can verify per-chunk
|
|
13
|
+
* + total hash, sniff magic bytes against an `allowedFileTypes`
|
|
14
|
+
* allowlist, and hand off to the operator's `onFinalize` (buffer
|
|
15
|
+
* for small uploads, Readable stream above `maxStreamReassemblyBytes`).
|
|
16
|
+
*
|
|
17
|
+
* Default-on safety: `b.guardAll.byExtension({ profile: "strict" })`
|
|
18
|
+
* for content gating and `b.guardFilename.gate({ profile: "strict" })`
|
|
19
|
+
* for filename gating. Operators opt out via `contentSafety: null`
|
|
20
|
+
* / `filenameSafety: null` (audited at create time so a security
|
|
21
|
+
* review can find the disabled-on-deploy rows). Per-chunk hooks
|
|
22
|
+
* (`onChunk`) are the integration point for virus scanners and
|
|
23
|
+
* schema-shape checks; rejecting from the hook surfaces as a
|
|
24
|
+
* permanent `FileUploadError`.
|
|
25
|
+
*
|
|
26
|
+
* Quotas: `maxFileBytes`, `maxChunkBytes`, `maxStagingBytes`,
|
|
27
|
+
* `maxActiveUploadsPerActor`, `maxChunks`, `incompleteTtlMs`,
|
|
28
|
+
* `maxIdleMs`. `purgeIncomplete()` reclaims TTL'd / idle staging
|
|
29
|
+
* directories — operators wire it to `b.scheduler` for a cron-shaped
|
|
30
|
+
* sweep. Permission scopes (`fileUpload.init` / `accept` / `finalize`
|
|
31
|
+
* / `status` / `list` / `cancel`) are checked through `b.permissions`
|
|
32
|
+
* when wired.
|
|
33
|
+
*
|
|
34
|
+
* @card
|
|
35
|
+
* Streaming multipart upload with content-safety guards wired on by default.
|
|
36
|
+
*/
|
|
2
37
|
/**
|
|
3
38
|
* b.fileUpload — chunked file upload primitive.
|
|
4
39
|
*
|
|
@@ -282,6 +317,64 @@ function _validateCreateOpts(opts) {
|
|
|
282
317
|
}
|
|
283
318
|
}
|
|
284
319
|
|
|
320
|
+
/**
|
|
321
|
+
* @primitive b.fileUpload.create
|
|
322
|
+
* @signature b.fileUpload.create(opts)
|
|
323
|
+
* @since 0.7.2
|
|
324
|
+
* @related b.fileType.detect, b.fileType.assertOneOf
|
|
325
|
+
*
|
|
326
|
+
* Builds an upload manager bound to `opts.stagingDir`. The returned
|
|
327
|
+
* object exposes `init`, `acceptChunk`, `finalize`, `status`, `list`,
|
|
328
|
+
* `cancelUpload`, `purgeIncomplete`, and `close`. Uploads are written
|
|
329
|
+
* chunk-per-file under a per-upload directory (mode 0o700); finalize
|
|
330
|
+
* walks the manifest in order, verifies per-chunk + total SHA3-512,
|
|
331
|
+
* runs the magic-byte allowlist (when `allowedFileTypes` is set), and
|
|
332
|
+
* hands the assembled buffer (or a stream above `maxStreamReassemblyBytes`)
|
|
333
|
+
* to the operator's `onFinalize`.
|
|
334
|
+
*
|
|
335
|
+
* Per-chunk and per-upload audits flow through the wired `audit` and
|
|
336
|
+
* `observability` instances. Quota refusals, hash mismatches, MIME-claim
|
|
337
|
+
* disagreement, filename-safety refusal, and content-safety refusal all
|
|
338
|
+
* throw `FileUploadError` with `permanent: true` — no retry succeeds.
|
|
339
|
+
*
|
|
340
|
+
* @opts
|
|
341
|
+
* stagingDir: string, // absolute path; created mode 0o700 if missing
|
|
342
|
+
* maxFileBytes: number, // default 2 GiB
|
|
343
|
+
* maxChunkBytes: number, // default 8 MiB
|
|
344
|
+
* maxStreamReassemblyBytes: number, // above this finalize streams; default 64 MiB
|
|
345
|
+
* maxStagingBytes: number, // default 50 GiB
|
|
346
|
+
* maxActiveUploadsPerActor: number, // default 16
|
|
347
|
+
* maxChunks: number, // default 16384
|
|
348
|
+
* incompleteTtlMs: number, // since createdAt; default 24h
|
|
349
|
+
* maxIdleMs: number, // since lastChunkAt; default 30m
|
|
350
|
+
* allowedFileTypes: string[], // MIME allowlist; "image/*" wildcard supported
|
|
351
|
+
* audit: b.audit,
|
|
352
|
+
* observability: b.observability,
|
|
353
|
+
* permissions: b.permissions, // optional; gates init/accept/finalize/status/list/cancel
|
|
354
|
+
* fileType: b.fileType, // required when allowedFileTypes is non-empty
|
|
355
|
+
* contentSafety: Object | null, // ext→gate map; null = audited opt-out; undefined = b.guardAll.byExtension({ profile: "strict" })
|
|
356
|
+
* filenameSafety: Object | null, // gate; null = audited opt-out; undefined = b.guardFilename.gate({ profile: "strict" })
|
|
357
|
+
* onChunk: async function (info), // optional per-chunk hook
|
|
358
|
+
* onFinalize: async function (info), // operator decides final storage
|
|
359
|
+
* clock: function () → number, // test-fixture clock; default Date.now
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* var uploads = b.fileUpload.create({
|
|
363
|
+
* stagingDir: "/var/lib/myapp/uploads",
|
|
364
|
+
* maxFileBytes: C.BYTES.gib(2),
|
|
365
|
+
* allowedFileTypes: ["image/png", "image/jpeg", "application/pdf"],
|
|
366
|
+
* fileType: b.fileType,
|
|
367
|
+
* audit: b.audit,
|
|
368
|
+
* observability: b.observability,
|
|
369
|
+
* onFinalize: async function (info) {
|
|
370
|
+
* // → info.body / info.stream → operator's storage layer
|
|
371
|
+
* return { ok: true, sha3: info.sha3, size: info.size };
|
|
372
|
+
* },
|
|
373
|
+
* });
|
|
374
|
+
*
|
|
375
|
+
* await uploads.init({ uploadId: "u-1", actor: { id: "ada" }, metadata: { filename: "photo.png" } });
|
|
376
|
+
* // → { uploadId: "u-1", createdAt: 1762560000000, expiresAt: 1762646400000 }
|
|
377
|
+
*/
|
|
285
378
|
function create(opts) {
|
|
286
379
|
_validateCreateOpts(opts);
|
|
287
380
|
var cfg = validateOpts.applyDefaults(opts, DEFAULTS);
|
package/lib/flag.js
CHANGED
|
@@ -1,29 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* b.flag
|
|
4
|
-
*
|
|
3
|
+
* @module b.flag
|
|
4
|
+
* @nav Tools
|
|
5
|
+
* @title Flag
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Feature-flag primitive shaped to the OpenFeature specification
|
|
9
|
+
* (https://openfeature.dev/specification/). Per-tenant overrides
|
|
10
|
+
* ride on the targeting context (`{ targetingKey, environment,
|
|
11
|
+
* ...attrs }`); every flip of a flag value at the provider layer
|
|
12
|
+
* audits via `flag.evaluated` / `flag.evaluation.error` on the
|
|
13
|
+
* framework audit chain so operators see the change without scraping
|
|
14
|
+
* provider state. Kill-switch semantics fall out of the same
|
|
15
|
+
* resolution chain: a provider that returns `value: false` for a
|
|
16
|
+
* targeting key disables the gated path immediately — no client
|
|
17
|
+
* restart, no cache flush.
|
|
15
18
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
19
|
+
* Validation policy:
|
|
20
|
+
* - `create()` throws on bad opts (provider missing
|
|
21
|
+
* `.evaluate(flagKey, ctx)`, hooks not function-shaped, etc.).
|
|
22
|
+
* - The hot path (`getBoolean` / `getString` / `getNumber` /
|
|
23
|
+
* `getObject` / `getValue`) NEVER throws — a provider that
|
|
24
|
+
* errors emits `flag.evaluation.error` and the call returns
|
|
25
|
+
* the operator-supplied default. A request can't be taken down
|
|
26
|
+
* by a misconfigured flag.
|
|
18
27
|
*
|
|
19
|
-
* flag.middleware(
|
|
20
|
-
*
|
|
28
|
+
* Composability: `flag.middleware({ userKey })` attaches a
|
|
29
|
+
* per-request `req.flag` accessor that bakes the request's
|
|
30
|
+
* targeting context in, so handlers call `req.flag.getBoolean(key)`
|
|
31
|
+
* without threading context through every site. Multiple providers
|
|
32
|
+
* can be stacked via `opts.providers` or `addProvider`/`removeProvider`
|
|
33
|
+
* — first non-`flag_not_found` response wins, so a local-file
|
|
34
|
+
* override beats the central provider for break-glass scenarios.
|
|
21
35
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* operator-supplied default + emit `flag.evaluation.error` on the
|
|
25
|
-
* audit chain so the operator sees the problem without taking down
|
|
26
|
-
* the request.
|
|
36
|
+
* @card
|
|
37
|
+
* Feature-flag primitive shaped to the OpenFeature specification (https://openfeature.dev/specification/).
|
|
27
38
|
*/
|
|
28
39
|
|
|
29
40
|
var validateOpts = require("./validate-opts");
|
|
@@ -60,6 +71,57 @@ function _validateHooks(rawHooks) {
|
|
|
60
71
|
return out;
|
|
61
72
|
}
|
|
62
73
|
|
|
74
|
+
/**
|
|
75
|
+
* @primitive b.flag.create
|
|
76
|
+
* @signature b.flag.create(opts)
|
|
77
|
+
* @since 0.6.0
|
|
78
|
+
* @status stable
|
|
79
|
+
* @related b.audit.safeEmit
|
|
80
|
+
*
|
|
81
|
+
* Build an OpenFeature-shaped flag client backed by one or more
|
|
82
|
+
* providers. The returned object exposes typed getters
|
|
83
|
+
* (`getBoolean` / `getString` / `getNumber` / `getObject` / `getValue`),
|
|
84
|
+
* a richer `getDetails(flagKey, ctx)` (returns
|
|
85
|
+
* `{ value, variant, reason, metadata }`), batch helpers (`getValues` /
|
|
86
|
+
* `getDetailsAll`), provider mutation (`addProvider` / `removeProvider`),
|
|
87
|
+
* and `middleware({ userKey })` for per-request binding.
|
|
88
|
+
*
|
|
89
|
+
* Throws `FlagError` at config time on a missing provider, hooks not
|
|
90
|
+
* shaped as `{ before?, after?, error?, finally? }` functions, or a
|
|
91
|
+
* provider lacking `.evaluate(flagKey, ctx)`.
|
|
92
|
+
*
|
|
93
|
+
* @opts
|
|
94
|
+
* provider: { evaluate: (flagKey, ctx) => result, kind?: string, list?: () => [string] },
|
|
95
|
+
* providers: [provider], // additional providers (first non-not-found wins)
|
|
96
|
+
* defaultEvaluationContext: { targetingKey?: string, [attr: string]: any },
|
|
97
|
+
* audit: boolean, // emit flag.evaluated / flag.evaluation.error; default true
|
|
98
|
+
* errorHandler: ({ flagKey, err, ctx }) => void,
|
|
99
|
+
* hooks: { before?: fn, after?: fn, error?: fn, finally?: fn }, // OpenFeature hook stages
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* var provider = {
|
|
103
|
+
* kind: "memory",
|
|
104
|
+
* evaluate: function (flagKey, ctx) {
|
|
105
|
+
* if (flagKey === "new-checkout-flow") {
|
|
106
|
+
* return { value: ctx.targetingKey === "user-42", variant: "on", reason: "TARGETING_MATCH" };
|
|
107
|
+
* }
|
|
108
|
+
* return { reason: "flag_not_found" };
|
|
109
|
+
* },
|
|
110
|
+
* };
|
|
111
|
+
*
|
|
112
|
+
* var flag = b.flag.create({
|
|
113
|
+
* provider: provider,
|
|
114
|
+
* defaultEvaluationContext: { environment: "production" },
|
|
115
|
+
* });
|
|
116
|
+
*
|
|
117
|
+
* flag.getBoolean("new-checkout-flow", { targetingKey: "user-42" }); // → true
|
|
118
|
+
* flag.getBoolean("new-checkout-flow", { targetingKey: "user-99" }); // → false
|
|
119
|
+
* flag.getString ("missing-key", { targetingKey: "u" }, "fallback"); // → "fallback"
|
|
120
|
+
*
|
|
121
|
+
* // Per-request binding via middleware:
|
|
122
|
+
* // app.use(flag.middleware({ userKey: "id" }));
|
|
123
|
+
* // // handler: if (req.flag.getBoolean("new-checkout-flow")) { ... }
|
|
124
|
+
*/
|
|
63
125
|
function create(opts) {
|
|
64
126
|
opts = opts || {};
|
|
65
127
|
validateOpts(opts, [
|