@blamejs/core 0.8.43 → 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 +92 -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/guard-csv.js
CHANGED
|
@@ -1,56 +1,57 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* -
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* -
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* -
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* -
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
3
|
+
* @module b.guardCsv
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Csv
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* CSV content-safety guard — defends against the broader threat
|
|
9
|
+
* catalog operators face when emitting or accepting CSVs sourced from
|
|
10
|
+
* user input. `b.csv.parse` / `b.csv.stringify` handle RFC 4180
|
|
11
|
+
* shape; this module layers the security catalog on top.
|
|
12
|
+
*
|
|
13
|
+
* CSV-injection / formula-trigger defense: spreadsheet evaluators
|
|
14
|
+
* (Excel / LibreOffice / Google Sheets) treat any cell beginning with
|
|
15
|
+
* `=`, `+`, `-`, `@`, TAB, CR, LF, or `|` as a formula — including
|
|
16
|
+
* exfiltration vectors like `=WEBSERVICE(...)`, `=HYPERLINK(...)`,
|
|
17
|
+
* `=IMPORTXML(...)`. Full-width variants (U+FF1D `=`, U+FF0B `+`,
|
|
18
|
+
* U+FF0D `-`, U+FF20 `@`) are caught alongside the ASCII triggers
|
|
19
|
+
* per the OWASP locale catalog. Five mitigation modes apply:
|
|
20
|
+
* `prefix-tab` (OWASP-recommended, prepends TAB so the evaluator
|
|
21
|
+
* treats the cell as text), `prefix-quote` (legacy `'` prefix),
|
|
22
|
+
* `wrap-with-quotes-and-prefix` (email-attachment posture),
|
|
23
|
+
* `reject` (throw), `allowlist` (only documented safe functions
|
|
24
|
+
* like SUM / AVERAGE pass through unprefixed).
|
|
25
|
+
*
|
|
26
|
+
* Unicode bidi/zero-width strip: CVE-2021-42574 Trojan Source bidi
|
|
27
|
+
* overrides (U+202A-202E, U+2066-2069) are rejected or stripped
|
|
28
|
+
* per profile; zero-width characters (ZWSP / ZWNJ / ZWJ / WJ / SHY)
|
|
29
|
+
* always strip. Leading bidi/zero-width prefixes are stripped before
|
|
30
|
+
* the formula scan so a cell beginning with U+200B`=SUM(...)` cannot
|
|
31
|
+
* slip past the start-anchor check.
|
|
32
|
+
*
|
|
33
|
+
* CSV-bomb caps: per-cell (`maxCellBytes`, default 64 KiB), total
|
|
34
|
+
* (`maxTotalBytes`, default 1 GiB), row count (`maxRows`, default
|
|
35
|
+
* ~1 M), column count (`maxColumns`, default 1024), and a sanitize
|
|
36
|
+
* amplification ratio (`sanitizeAmplificationCap`, default 1.5x)
|
|
37
|
+
* that refuses pathological re-quote expansions.
|
|
38
|
+
*
|
|
39
|
+
* Doubled-quote escape is delegated to `b.csv.stringify` — every
|
|
40
|
+
* cell value containing the delimiter, the quote char, CR, or LF
|
|
41
|
+
* is wrapped in quotes with embedded quotes doubled per RFC 4180.
|
|
42
|
+
*
|
|
43
|
+
* Profiles: `strict` / `balanced` / `permissive` /
|
|
44
|
+
* `email-attachment`. Compliance postures: `hipaa` / `pci-dss` /
|
|
45
|
+
* `gdpr` / `soc2`. Operators select via `{ profile: "strict" }` or
|
|
46
|
+
* `{ compliance: "hipaa" }`; postures overlay on top of the
|
|
47
|
+
* profile baseline.
|
|
48
|
+
*
|
|
49
|
+
* Threat-detection regex literals are composed programmatically
|
|
50
|
+
* from numeric codepoint ranges so the source file stays pure
|
|
51
|
+
* ASCII — never embeds the attack characters themselves.
|
|
52
|
+
*
|
|
53
|
+
* @card
|
|
54
|
+
* CSV content-safety guard — defends against the broader threat catalog operators face when emitting or accepting CSVs sourced from user input.
|
|
54
55
|
*/
|
|
55
56
|
|
|
56
57
|
var codepointClass = require("./codepoint-class");
|
|
@@ -449,6 +450,54 @@ function _resolveOpts(opts) {
|
|
|
449
450
|
|
|
450
451
|
// ---- Cell-level escape with full threat application ----
|
|
451
452
|
|
|
453
|
+
/**
|
|
454
|
+
* @primitive b.guardCsv.escapeCell
|
|
455
|
+
* @signature b.guardCsv.escapeCell(value, opts?)
|
|
456
|
+
* @since 0.7.5
|
|
457
|
+
* @status stable
|
|
458
|
+
* @related b.guardCsv.serialize, b.guardCsv.gate, b.csv.stringify
|
|
459
|
+
*
|
|
460
|
+
* Apply the full guard-csv threat catalog to a single cell value:
|
|
461
|
+
* formula-prefix mitigation, null-byte / C0-control / bidi handling,
|
|
462
|
+
* trailing-whitespace policy, numeric-precision policy, and BigInt
|
|
463
|
+
* disposition. Returns the safe string form. Throws `GuardCsvError`
|
|
464
|
+
* when a `reject` policy fires (formula-trigger under
|
|
465
|
+
* `formulaInjectionPolicy: "reject"`, control char under
|
|
466
|
+
* `controlCharPolicy: "reject"`, etc.) or when the cell exceeds
|
|
467
|
+
* `maxCellBytes`.
|
|
468
|
+
*
|
|
469
|
+
* Used internally by `b.guardCsv.serialize` per cell; exposed
|
|
470
|
+
* directly for operators that emit CSV through their own writer
|
|
471
|
+
* (streaming exports, third-party libraries) and only need the
|
|
472
|
+
* per-cell defense.
|
|
473
|
+
*
|
|
474
|
+
* @opts
|
|
475
|
+
* formulaInjectionPolicy: "prefix-tab"|"prefix-quote"|"wrap-with-quotes-and-prefix"|"reject"|"allowlist",
|
|
476
|
+
* formulasAllowlist: string[], // when policy === "allowlist"
|
|
477
|
+
* bidiCharPolicy: "reject"|"strip"|"audit"|"allow",
|
|
478
|
+
* controlCharPolicy: "reject"|"strip"|"allow",
|
|
479
|
+
* nullByteHandling: "reject"|"strip"|"allow",
|
|
480
|
+
* trailingWhitespacePolicy: "trim"|"preserve"|"reject",
|
|
481
|
+
* numericPrecisionPolicy: "decimal-string-above-safe-int"|"scientific"|"reject-bigint",
|
|
482
|
+
* maxCellBytes: number, // default 65536 (64 KiB)
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* var safe = b.guardCsv.escapeCell("=cmd|x", { formulaInjectionPolicy: "prefix-tab" });
|
|
486
|
+
* safe; // → "\t=cmd|x"
|
|
487
|
+
*
|
|
488
|
+
* // Reject mode throws GuardCsvError instead of disarming.
|
|
489
|
+
* try {
|
|
490
|
+
* b.guardCsv.escapeCell("+1234567", { formulaInjectionPolicy: "reject" });
|
|
491
|
+
* } catch (e) {
|
|
492
|
+
* e.code; // → "csv.formula-injection"
|
|
493
|
+
* }
|
|
494
|
+
*
|
|
495
|
+
* // Numeric precision: above MAX_SAFE_INTEGER, write as decimal string.
|
|
496
|
+
* var huge = b.guardCsv.escapeCell(9007199254740993, {
|
|
497
|
+
* numericPrecisionPolicy: "decimal-string-above-safe-int",
|
|
498
|
+
* });
|
|
499
|
+
* huge; // → "9007199254740993"
|
|
500
|
+
*/
|
|
452
501
|
function escapeCell(value, opts) {
|
|
453
502
|
opts = Object.assign({}, DEFAULTS, opts || {});
|
|
454
503
|
var str = value == null ? "" : String(value);
|
|
@@ -516,6 +565,41 @@ function escapeCell(value, opts) {
|
|
|
516
565
|
|
|
517
566
|
// ---- Schema-bound serializer ----
|
|
518
567
|
|
|
568
|
+
/**
|
|
569
|
+
* @primitive b.guardCsv.schema
|
|
570
|
+
* @signature b.guardCsv.schema(spec)
|
|
571
|
+
* @since 0.7.5
|
|
572
|
+
* @status stable
|
|
573
|
+
* @related b.guardCsv.serialize, b.guardCsv.validate
|
|
574
|
+
*
|
|
575
|
+
* Build a schema-bound serializer/validator pair. Each row's column
|
|
576
|
+
* values are checked against the column's `type` (`"string"` /
|
|
577
|
+
* `"number"` / `"boolean"`), optional `regex`, optional `min` / `max`
|
|
578
|
+
* (for numbers), and `nullable` flag before the row reaches
|
|
579
|
+
* `serialize`. Type / range / regex / null violations throw
|
|
580
|
+
* `GuardCsvError` with codes `csv.schema-type` / `csv.schema-range`
|
|
581
|
+
* / `csv.schema-regex` / `csv.schema-null` and the offending row
|
|
582
|
+
* index — operators get the failing-row coordinates without parsing
|
|
583
|
+
* the error string.
|
|
584
|
+
*
|
|
585
|
+
* Returns `{ serialize, validate, columns }`. The returned
|
|
586
|
+
* `serialize` accepts the same opts as `b.guardCsv.serialize` and
|
|
587
|
+
* applies the column ordering automatically.
|
|
588
|
+
*
|
|
589
|
+
* @example
|
|
590
|
+
* var bound = b.guardCsv.schema({
|
|
591
|
+
* columns: [
|
|
592
|
+
* { name: "email", type: "string", regex: /^[^@]+@[^@]+$/ },
|
|
593
|
+
* { name: "age", type: "number", min: 0, max: 150, nullable: true },
|
|
594
|
+
* ],
|
|
595
|
+
* });
|
|
596
|
+
*
|
|
597
|
+
* var out = bound.serialize([
|
|
598
|
+
* { email: "alice@example.com", age: 30 },
|
|
599
|
+
* { email: "bob@example.com", age: null },
|
|
600
|
+
* ], { profile: "strict" });
|
|
601
|
+
* out.indexOf("alice@example.com") !== -1; // → true
|
|
602
|
+
*/
|
|
519
603
|
function schema(spec) {
|
|
520
604
|
validateOpts.requireObject(spec, "guardCsv.schema", GuardCsvError);
|
|
521
605
|
if (!Array.isArray(spec.columns)) {
|
|
@@ -588,6 +672,52 @@ function schema(spec) {
|
|
|
588
672
|
|
|
589
673
|
// ---- Module-level entry points ----
|
|
590
674
|
|
|
675
|
+
/**
|
|
676
|
+
* @primitive b.guardCsv.serialize
|
|
677
|
+
* @signature b.guardCsv.serialize(rows, opts?)
|
|
678
|
+
* @since 0.7.5
|
|
679
|
+
* @status stable
|
|
680
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
681
|
+
* @related b.guardCsv.escapeCell, b.guardCsv.gate, b.csv.stringify
|
|
682
|
+
*
|
|
683
|
+
* Emit RFC 4180 CSV from `rows` (array of objects or array of
|
|
684
|
+
* arrays) with the full guard-csv threat catalog applied per cell
|
|
685
|
+
* — formula-prefix mitigation, bidi/null/control handling,
|
|
686
|
+
* trailing-whitespace policy, numeric-precision policy. Doubled-
|
|
687
|
+
* quote escape is delegated to `b.csv.stringify`. Caps enforced:
|
|
688
|
+
* `maxRows`, `maxCellBytes`, `maxColumns`, `maxTotalBytes` (each
|
|
689
|
+
* a positive finite integer; passing `Infinity` throws).
|
|
690
|
+
*
|
|
691
|
+
* When `piiPolicy: "redact"` is set and an `opts.redact` instance
|
|
692
|
+
* is passed (typically `b.redact.create(...)`), every emitted
|
|
693
|
+
* string cell is run through `redact.string(...)` before
|
|
694
|
+
* stringification. The HIPAA / PCI-DSS / GDPR postures default
|
|
695
|
+
* `piiPolicy` to `"redact"`.
|
|
696
|
+
*
|
|
697
|
+
* @opts
|
|
698
|
+
* profile: "strict"|"balanced"|"permissive"|"email-attachment",
|
|
699
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
700
|
+
* headers: string[]|false, // explicit column order; false suppresses header row
|
|
701
|
+
* delimiter: string, // default ","
|
|
702
|
+
* lineEnding: string, // default "\r\n"
|
|
703
|
+
* bomPrefix: boolean, // prepend U+FEFF (Excel-friendly)
|
|
704
|
+
* maxRows: number, // default 1048576
|
|
705
|
+
* maxCellBytes: number, // default 65536
|
|
706
|
+
* maxColumns: number, // default 1024
|
|
707
|
+
* maxTotalBytes: number, // default 1073741824 (1 GiB)
|
|
708
|
+
* piiPolicy: "preserve"|"redact",
|
|
709
|
+
* redact: b.redact instance, // required when piiPolicy === "redact"
|
|
710
|
+
*
|
|
711
|
+
* @example
|
|
712
|
+
* var out = b.guardCsv.serialize([
|
|
713
|
+
* { name: "alice", note: "=WEBSERVICE(\"http://x\")" },
|
|
714
|
+
* { name: "bob", note: "ok" },
|
|
715
|
+
* ], { profile: "strict" });
|
|
716
|
+
*
|
|
717
|
+
* // Formula trigger disarmed with a leading TAB per OWASP guidance:
|
|
718
|
+
* out.indexOf("\t=WEBSERVICE") !== -1; // → true
|
|
719
|
+
* out.indexOf("\r\n") !== -1; // → true
|
|
720
|
+
*/
|
|
591
721
|
function serialize(rows, opts) {
|
|
592
722
|
opts = _resolveOpts(opts);
|
|
593
723
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -663,11 +793,89 @@ function serialize(rows, opts) {
|
|
|
663
793
|
return out;
|
|
664
794
|
}
|
|
665
795
|
|
|
796
|
+
/**
|
|
797
|
+
* @primitive b.guardCsv.validate
|
|
798
|
+
* @signature b.guardCsv.validate(input, opts?)
|
|
799
|
+
* @since 0.7.5
|
|
800
|
+
* @status stable
|
|
801
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
802
|
+
* @related b.guardCsv.sanitize, b.guardCsv.gate
|
|
803
|
+
*
|
|
804
|
+
* Inspect `input` (string or Buffer of CSV text) and return
|
|
805
|
+
* `{ ok, issues, summary }`. Each issue carries `{ kind, severity,
|
|
806
|
+
* ruleId, location, snippet }` with severity in
|
|
807
|
+
* `"warn"|"high"|"critical"`. Detected: BOM mid-stream, Unicode
|
|
808
|
+
* bidi override (CVE-2021-42574), C0 control char, null byte,
|
|
809
|
+
* homoglyph, zero-width char, formula-prefix cell (bidi/zero-width
|
|
810
|
+
* leading prefix is stripped before the scan), dangerous-function
|
|
811
|
+
* denylist hit, mixed line endings (when `dialectPolicy: "strict"`).
|
|
812
|
+
* Pure inspection — never mutates input or throws.
|
|
813
|
+
*
|
|
814
|
+
* @opts
|
|
815
|
+
* profile: "strict"|"balanced"|"permissive"|"email-attachment",
|
|
816
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
817
|
+
* bidiCharPolicy: "reject"|"strip"|"audit"|"allow",
|
|
818
|
+
* controlCharPolicy: "reject"|"strip"|"allow",
|
|
819
|
+
* nullByteHandling: "reject"|"strip"|"allow",
|
|
820
|
+
* homoglyphPolicy: "audit"|"strip"|"allow",
|
|
821
|
+
* formulaInjectionPolicy: "prefix-tab"|"prefix-quote"|"wrap-with-quotes-and-prefix"|"reject"|"audit-only"|"allow",
|
|
822
|
+
* dangerousFunctions: string[],
|
|
823
|
+
* dialectPolicy: "strict"|"permissive",
|
|
824
|
+
*
|
|
825
|
+
* @example
|
|
826
|
+
* var rv = b.guardCsv.validate("name,formula\r\nalice,=WEBSERVICE(\"x\")\r\n", {
|
|
827
|
+
* profile: "strict",
|
|
828
|
+
* });
|
|
829
|
+
* rv.ok; // → false
|
|
830
|
+
* rv.issues.some(function (i) { return i.kind === "dangerous-function"; }); // → true
|
|
831
|
+
*/
|
|
666
832
|
function validate(input, opts) {
|
|
667
833
|
opts = _resolveOpts(opts);
|
|
668
834
|
return gateContract.runIssueValidator(input, opts, _detectIssues);
|
|
669
835
|
}
|
|
670
836
|
|
|
837
|
+
/**
|
|
838
|
+
* @primitive b.guardCsv.sanitize
|
|
839
|
+
* @signature b.guardCsv.sanitize(input, opts?)
|
|
840
|
+
* @since 0.7.5
|
|
841
|
+
* @status stable
|
|
842
|
+
* @related b.guardCsv.validate, b.guardCsv.gate
|
|
843
|
+
*
|
|
844
|
+
* Best-effort cleanup of `input` (string or Buffer): strips leading
|
|
845
|
+
* BOM (when `bomPrefix: false`), bidi override chars (when
|
|
846
|
+
* `bidiCharPolicy: "strip"`), C0 control chars (when
|
|
847
|
+
* `controlCharPolicy: "strip"`), null bytes (when
|
|
848
|
+
* `nullByteHandling: "strip"`), zero-width chars (always), and
|
|
849
|
+
* trailing whitespace per `trailingWhitespacePolicy`. Refuses
|
|
850
|
+
* pathological expansion: when the sanitized output exceeds
|
|
851
|
+
* `sanitizeAmplificationCap` (default 1.5x) the function throws
|
|
852
|
+
* `GuardCsvError("csv.sanitize-amplified")` — sanitize is a
|
|
853
|
+
* shrinking operation by contract, never a growing one.
|
|
854
|
+
*
|
|
855
|
+
* Note: sanitize does NOT prepend formula-trigger mitigations to
|
|
856
|
+
* cells (that's `b.guardCsv.serialize` / `b.guardCsv.escapeCell`'s
|
|
857
|
+
* job, applied during emission). Use the `gate` action chain for
|
|
858
|
+
* accept-side defense — it sanitizes, re-parses, and re-serializes
|
|
859
|
+
* with the formula mitigation baked in.
|
|
860
|
+
*
|
|
861
|
+
* @opts
|
|
862
|
+
* profile: "strict"|"balanced"|"permissive"|"email-attachment",
|
|
863
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
864
|
+
* bidiCharPolicy: "reject"|"strip"|"audit"|"allow",
|
|
865
|
+
* controlCharPolicy: "reject"|"strip"|"allow",
|
|
866
|
+
* nullByteHandling: "reject"|"strip"|"allow",
|
|
867
|
+
* homoglyphPolicy: "audit"|"strip"|"allow",
|
|
868
|
+
* trailingWhitespacePolicy: "trim"|"preserve"|"reject",
|
|
869
|
+
* sanitizeAmplificationCap: number, // default 1.5
|
|
870
|
+
*
|
|
871
|
+
* @example
|
|
872
|
+
* // Build hostile input programmatically so the source stays ASCII.
|
|
873
|
+
* var ZWSP = String.fromCharCode(0x200B);
|
|
874
|
+
* var clean = b.guardCsv.sanitize("name,note\r\nalice,hi" + ZWSP + "\r\n", {
|
|
875
|
+
* profile: "balanced",
|
|
876
|
+
* });
|
|
877
|
+
* clean.indexOf(ZWSP) === -1; // → true
|
|
878
|
+
*/
|
|
671
879
|
function sanitize(input, opts) {
|
|
672
880
|
opts = _resolveOpts(opts);
|
|
673
881
|
var text = typeof input === "string"
|
|
@@ -686,6 +894,29 @@ function sanitize(input, opts) {
|
|
|
686
894
|
return sanitized;
|
|
687
895
|
}
|
|
688
896
|
|
|
897
|
+
/**
|
|
898
|
+
* @primitive b.guardCsv.detect
|
|
899
|
+
* @signature b.guardCsv.detect(input)
|
|
900
|
+
* @since 0.7.5
|
|
901
|
+
* @status stable
|
|
902
|
+
* @related b.guardCsv.validate, b.csv.parse
|
|
903
|
+
*
|
|
904
|
+
* Sniff dialect heuristics from `input` (string or Buffer): most-
|
|
905
|
+
* frequent delimiter on the first line (`","`, `";"`, `"\t"`,
|
|
906
|
+
* `"|"`), dominant line-ending, header presence (first line starts
|
|
907
|
+
* with an ASCII letter), encoding hint (`"utf-8"` vs `"utf-8-sig"`
|
|
908
|
+
* when a leading BOM is present), and a single-pass `dialect`
|
|
909
|
+
* verdict (`"consistent"` vs `"mixed"` line endings). Returns a
|
|
910
|
+
* confidence score in `[0, 1]`. Pure inspection.
|
|
911
|
+
*
|
|
912
|
+
* @example
|
|
913
|
+
* var d = b.guardCsv.detect("name,age\r\nalice,30\r\nbob,40\r\n");
|
|
914
|
+
* d.delimiter; // → ","
|
|
915
|
+
* d.lineEnding; // → "\r\n"
|
|
916
|
+
* d.hasHeader; // → true
|
|
917
|
+
* d.encoding; // → "utf-8"
|
|
918
|
+
* d.dialect; // → "consistent"
|
|
919
|
+
*/
|
|
689
920
|
function detect(input) {
|
|
690
921
|
var text = typeof input === "string"
|
|
691
922
|
? input
|
|
@@ -724,6 +955,51 @@ function detect(input) {
|
|
|
724
955
|
|
|
725
956
|
// ---- Gate factory (b.gateContract shape) ----
|
|
726
957
|
|
|
958
|
+
/**
|
|
959
|
+
* @primitive b.guardCsv.gate
|
|
960
|
+
* @signature b.guardCsv.gate(opts?)
|
|
961
|
+
* @since 0.7.5
|
|
962
|
+
* @status stable
|
|
963
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
964
|
+
* @related b.guardCsv.validate, b.guardCsv.sanitize, b.staticServe.create, b.fileUpload.create
|
|
965
|
+
*
|
|
966
|
+
* Build a `b.gateContract` gate suitable for plugging into
|
|
967
|
+
* `b.staticServe({ contentSafety: { ".csv": gate } })`,
|
|
968
|
+
* `b.fileUpload({ contentSafety: { "text/csv": gate } })`,
|
|
969
|
+
* `b.mail`, or `b.objectStore`. Action chain on validation:
|
|
970
|
+
* `serve` (no issues) → `audit-only` (warn-only issues) →
|
|
971
|
+
* `sanitize` (critical/high but no `reject` policy active —
|
|
972
|
+
* sanitize, re-parse, re-serialize so formula mitigation lands)
|
|
973
|
+
* → `refuse` (critical/high under any `reject` policy, or when
|
|
974
|
+
* sanitize fails / amplifies past cap).
|
|
975
|
+
*
|
|
976
|
+
* Operator extensibility: pass `operatorRules: [{ id, severity,
|
|
977
|
+
* detect: fn(ctx)→boolean, reason }]` to inject custom detectors
|
|
978
|
+
* alongside the built-in catalog. Rules run best-effort — a
|
|
979
|
+
* throwing detector is silently skipped (the framework cannot
|
|
980
|
+
* crash a request because an operator rule mishandled bytes).
|
|
981
|
+
*
|
|
982
|
+
* @opts
|
|
983
|
+
* profile: "strict"|"balanced"|"permissive"|"email-attachment",
|
|
984
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
985
|
+
* name: string, // gate identity for audit / observability
|
|
986
|
+
* operatorRules: [{ id: string, severity: "warn"|"high"|"critical",
|
|
987
|
+
* detect: function, reason: string }],
|
|
988
|
+
*
|
|
989
|
+
* @example
|
|
990
|
+
* var csvGate = b.guardCsv.gate({ profile: "strict" });
|
|
991
|
+
*
|
|
992
|
+
* // Wire into staticServe so every served .csv runs through the gate.
|
|
993
|
+
* var serve = b.staticServe.create({
|
|
994
|
+
* root: "/var/data",
|
|
995
|
+
* contentSafety: { ".csv": csvGate },
|
|
996
|
+
* });
|
|
997
|
+
*
|
|
998
|
+
* // Direct invocation for an upload pipeline:
|
|
999
|
+
* var hostile = Buffer.from("name,formula\r\nalice,=cmd|x\r\n", "utf8");
|
|
1000
|
+
* var verdict = await csvGate.check({ bytes: hostile });
|
|
1001
|
+
* verdict.action; // → "refuse"
|
|
1002
|
+
*/
|
|
727
1003
|
function gate(opts) {
|
|
728
1004
|
opts = _resolveOpts(opts);
|
|
729
1005
|
return gateContract.buildGuardGate(
|
|
@@ -784,13 +1060,83 @@ function gate(opts) {
|
|
|
784
1060
|
});
|
|
785
1061
|
}
|
|
786
1062
|
|
|
1063
|
+
/**
|
|
1064
|
+
* @primitive b.guardCsv.buildProfile
|
|
1065
|
+
* @signature b.guardCsv.buildProfile(opts)
|
|
1066
|
+
* @since 0.7.5
|
|
1067
|
+
* @status stable
|
|
1068
|
+
* @related b.guardCsv.gate, b.guardCsv.compliancePosture
|
|
1069
|
+
*
|
|
1070
|
+
* Compose a derived profile from one or more named bases plus
|
|
1071
|
+
* inline overrides. `opts.extends` is a profile name (`"strict"`
|
|
1072
|
+
* / `"balanced"` / `"permissive"` / `"email-attachment"`) or an
|
|
1073
|
+
* array of names; later entries shadow earlier ones. Inline
|
|
1074
|
+
* `opts` keys win last. Used to keep operator-defined profiles
|
|
1075
|
+
* traceable to a baseline rather than re-typing every key.
|
|
1076
|
+
*
|
|
1077
|
+
* @opts
|
|
1078
|
+
* extends: string|string[], // base profile name(s) to compose
|
|
1079
|
+
* ...: any guard-csv key, // inline override of resolved keys
|
|
1080
|
+
*
|
|
1081
|
+
* @example
|
|
1082
|
+
* var custom = b.guardCsv.buildProfile({
|
|
1083
|
+
* extends: "strict",
|
|
1084
|
+
* trailingWhitespacePolicy: "preserve",
|
|
1085
|
+
* bomPrefix: true,
|
|
1086
|
+
* });
|
|
1087
|
+
* custom.formulaInjectionPolicy; // → "prefix-tab"
|
|
1088
|
+
* custom.bomPrefix; // → true
|
|
1089
|
+
*/
|
|
787
1090
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
788
1091
|
|
|
1092
|
+
/**
|
|
1093
|
+
* @primitive b.guardCsv.compliancePosture
|
|
1094
|
+
* @signature b.guardCsv.compliancePosture(name)
|
|
1095
|
+
* @since 0.7.5
|
|
1096
|
+
* @status stable
|
|
1097
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
1098
|
+
* @related b.guardCsv.gate, b.guardCsv.buildProfile
|
|
1099
|
+
*
|
|
1100
|
+
* Look up a compliance-posture overlay by name (`"hipaa"` /
|
|
1101
|
+
* `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
|
|
1102
|
+
* the posture object — the caller may mutate freely. Throws
|
|
1103
|
+
* `GuardCsvError("csv.bad-posture")` on unknown name.
|
|
1104
|
+
*
|
|
1105
|
+
* @example
|
|
1106
|
+
* var posture = b.guardCsv.compliancePosture("hipaa");
|
|
1107
|
+
* posture.piiPolicy; // → "redact"
|
|
1108
|
+
* posture.bidiCharPolicy; // → "reject"
|
|
1109
|
+
*/
|
|
789
1110
|
function compliancePosture(name) {
|
|
790
1111
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES, _err, "csv");
|
|
791
1112
|
}
|
|
792
1113
|
|
|
793
1114
|
var _csvRulePacks = gateContract.makeRulePackLoader(GuardCsvError, "csv");
|
|
1115
|
+
/**
|
|
1116
|
+
* @primitive b.guardCsv.loadRulePack
|
|
1117
|
+
* @signature b.guardCsv.loadRulePack(pack)
|
|
1118
|
+
* @since 0.7.5
|
|
1119
|
+
* @status stable
|
|
1120
|
+
* @related b.guardCsv.gate
|
|
1121
|
+
*
|
|
1122
|
+
* Register an operator-supplied rule pack with the guard-csv
|
|
1123
|
+
* registry. The pack is identified by `pack.id` (non-empty
|
|
1124
|
+
* string) and stored for later inspection / dispatch by gates
|
|
1125
|
+
* that opt in via `opts.rulePackId`. Returns the pack object
|
|
1126
|
+
* unchanged on success; throws `GuardCsvError("csv.bad-opt")`
|
|
1127
|
+
* when `pack` is missing or `pack.id` is not a non-empty string.
|
|
1128
|
+
*
|
|
1129
|
+
* @example
|
|
1130
|
+
* var pack = b.guardCsv.loadRulePack({
|
|
1131
|
+
* id: "pii-extra",
|
|
1132
|
+
* rules: [
|
|
1133
|
+
* { id: "ssn-cell", severity: "critical",
|
|
1134
|
+
* detect: function (cell) { return /^\d{3}-\d{2}-\d{4}$/.test(cell); },
|
|
1135
|
+
* reason: "US SSN-shaped value in CSV cell" },
|
|
1136
|
+
* ],
|
|
1137
|
+
* });
|
|
1138
|
+
* pack.id; // → "pii-extra"
|
|
1139
|
+
*/
|
|
794
1140
|
var loadRulePack = _csvRulePacks.load;
|
|
795
1141
|
|
|
796
1142
|
module.exports = {
|