@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/guard-svg.js
CHANGED
|
@@ -107,10 +107,11 @@
|
|
|
107
107
|
*/
|
|
108
108
|
|
|
109
109
|
var codepointClass = require("./codepoint-class");
|
|
110
|
+
var markupTokenizer = require("./markup-tokenizer");
|
|
111
|
+
var markupEscape = require("./markup-escape").markupEscape;
|
|
110
112
|
var lazyRequire = require("./lazy-require");
|
|
111
113
|
var gateContract = require("./gate-contract");
|
|
112
114
|
var C = require("./constants");
|
|
113
|
-
var numericBounds = require("./numeric-bounds");
|
|
114
115
|
var safeUrl = require("./safe-url");
|
|
115
116
|
var { GuardSvgError } = require("./framework-error");
|
|
116
117
|
|
|
@@ -118,7 +119,6 @@ var observability = lazyRequire(function () { return require("./observability");
|
|
|
118
119
|
void observability;
|
|
119
120
|
|
|
120
121
|
var _err = GuardSvgError.factory;
|
|
121
|
-
var HEX_RADIX = 16; // base-16 radix, not byte size
|
|
122
122
|
|
|
123
123
|
// ---- Codepoint catalog (shared via lib/codepoint-class) ----
|
|
124
124
|
|
|
@@ -197,12 +197,10 @@ var URL_ATTRS = Object.freeze([
|
|
|
197
197
|
"background", "poster", "icon",
|
|
198
198
|
]);
|
|
199
199
|
|
|
200
|
-
var SAFE_SCHEMES =
|
|
200
|
+
var SAFE_SCHEMES = gateContract.SAFE_URL_SCHEMES;
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
"file", "mhtml", "jar", "intent", "view-source", "feed", "data",
|
|
205
|
-
]);
|
|
202
|
+
// Markup-attribute scheme denylist — the shared XSS / dangerous-resource set.
|
|
203
|
+
var DANGEROUS_SCHEMES = gateContract.DANGEROUS_URL_SCHEMES;
|
|
206
204
|
|
|
207
205
|
var CSS_DANGEROUS_PATTERNS = Object.freeze([
|
|
208
206
|
/expression\s*\(/i,
|
|
@@ -305,68 +303,14 @@ var PROFILES = Object.freeze({
|
|
|
305
303
|
},
|
|
306
304
|
});
|
|
307
305
|
|
|
308
|
-
var DEFAULTS =
|
|
309
|
-
mode: "enforce",
|
|
306
|
+
var DEFAULTS = gateContract.strictDefaults(PROFILES, {
|
|
310
307
|
maxRuntimeMs: C.TIME.seconds(30),
|
|
311
|
-
}));
|
|
312
|
-
|
|
313
|
-
var COMPLIANCE_POSTURES = Object.freeze({
|
|
314
|
-
"hipaa": {
|
|
315
|
-
allowedTags: STRICT_ALLOWED_TAGS,
|
|
316
|
-
bidiPolicy: "reject",
|
|
317
|
-
controlPolicy: "reject",
|
|
318
|
-
nullBytePolicy: "reject",
|
|
319
|
-
cssPolicy: "reject",
|
|
320
|
-
doctypePolicy: "reject",
|
|
321
|
-
allowExternalRefs: false,
|
|
322
|
-
allowAnimation: false,
|
|
323
|
-
forensicSnippetBytes: C.BYTES.bytes(256),
|
|
324
|
-
},
|
|
325
|
-
"pci-dss": {
|
|
326
|
-
allowedTags: STRICT_ALLOWED_TAGS,
|
|
327
|
-
bidiPolicy: "reject",
|
|
328
|
-
controlPolicy: "reject",
|
|
329
|
-
nullBytePolicy: "reject",
|
|
330
|
-
cssPolicy: "reject",
|
|
331
|
-
doctypePolicy: "reject",
|
|
332
|
-
allowExternalRefs: false,
|
|
333
|
-
allowAnimation: false,
|
|
334
|
-
urlSchemes: SAFE_SCHEMES,
|
|
335
|
-
forensicSnippetBytes: C.BYTES.bytes(256),
|
|
336
|
-
},
|
|
337
|
-
"gdpr": {
|
|
338
|
-
allowedTags: BALANCED_ALLOWED_TAGS,
|
|
339
|
-
bidiPolicy: "strip",
|
|
340
|
-
controlPolicy: "strip",
|
|
341
|
-
cssPolicy: "strip",
|
|
342
|
-
doctypePolicy: "reject",
|
|
343
|
-
allowAnimation: false,
|
|
344
|
-
forensicSnippetBytes: C.BYTES.bytes(128),
|
|
345
|
-
},
|
|
346
|
-
"soc2": {
|
|
347
|
-
allowedTags: STRICT_ALLOWED_TAGS,
|
|
348
|
-
bidiPolicy: "reject",
|
|
349
|
-
controlPolicy: "reject",
|
|
350
|
-
nullBytePolicy: "reject",
|
|
351
|
-
cssPolicy: "reject",
|
|
352
|
-
doctypePolicy: "reject",
|
|
353
|
-
allowExternalRefs: false,
|
|
354
|
-
allowAnimation: false,
|
|
355
|
-
forensicSnippetBytes: C.BYTES.bytes(512),
|
|
356
|
-
},
|
|
357
308
|
});
|
|
358
309
|
|
|
310
|
+
var COMPLIANCE_POSTURES = gateContract.compliancePostures(PROFILES, { base: 256 });
|
|
311
|
+
|
|
359
312
|
// ---- Internal helpers ----
|
|
360
313
|
|
|
361
|
-
function _resolveOpts(opts) {
|
|
362
|
-
return gateContract.resolveProfileAndPosture(opts, {
|
|
363
|
-
profiles: PROFILES,
|
|
364
|
-
compliancePostures: COMPLIANCE_POSTURES,
|
|
365
|
-
defaults: DEFAULTS,
|
|
366
|
-
errorClass: GuardSvgError,
|
|
367
|
-
errCodePrefix: "svg",
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
314
|
|
|
371
315
|
// HTML5 named-entity ASCII subset — same shape as guard-html.
|
|
372
316
|
// Browsers honor these inside URL contexts; without decoding them,
|
|
@@ -384,12 +328,9 @@ var SVG_NAMED_ENTITY_ASCII = {
|
|
|
384
328
|
|
|
385
329
|
function _extractScheme(rawUrl) {
|
|
386
330
|
var s = String(rawUrl || "").trim();
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
s = s.replace(/&#(\d+);/g, function (_m, d) {
|
|
391
|
-
return String.fromCharCode(parseInt(d, 10));
|
|
392
|
-
});
|
|
331
|
+
// Numeric entities (hex/decimal, semicolon OPTIONAL) via the shared decoder
|
|
332
|
+
// so guard-html / guard-svg / guard-markdown can't drift (see codepoint-class).
|
|
333
|
+
s = codepointClass.decodeNumericEntities(s);
|
|
393
334
|
s = s.replace(/&([A-Za-z][A-Za-z0-9]+);/g, function (m, name) {
|
|
394
335
|
if (Object.prototype.hasOwnProperty.call(SVG_NAMED_ENTITY_ASCII, name)) {
|
|
395
336
|
return SVG_NAMED_ENTITY_ASCII[name];
|
|
@@ -436,9 +377,10 @@ function _isSvgz(input) {
|
|
|
436
377
|
|
|
437
378
|
function _tokenize(input, maxBytes) {
|
|
438
379
|
var s = String(input || "");
|
|
439
|
-
|
|
380
|
+
var nb = Buffer.byteLength(s, "utf8");
|
|
381
|
+
if (nb > maxBytes) {
|
|
440
382
|
throw _err("svg.too-large",
|
|
441
|
-
"input " +
|
|
383
|
+
"input " + nb + " bytes exceeds maxBytes " + maxBytes);
|
|
442
384
|
}
|
|
443
385
|
var tokens = [];
|
|
444
386
|
var len = s.length;
|
|
@@ -455,8 +397,11 @@ function _tokenize(input, maxBytes) {
|
|
|
455
397
|
}
|
|
456
398
|
|
|
457
399
|
if (s.startsWith("<!--", lt)) {
|
|
458
|
-
|
|
459
|
-
|
|
400
|
+
// WHATWG comment end ("--!>" + abrupt "<!-->" / "<!--->"), not only
|
|
401
|
+
// "-->", so the comment boundary matches a browser parsing inline SVG
|
|
402
|
+
// and no element is smuggled past the sanitizer (mXSS differential).
|
|
403
|
+
var endC = markupTokenizer.htmlCommentEnd(s, lt);
|
|
404
|
+
if (endC === -1) endC = len;
|
|
460
405
|
tokens.push({ type: "comment", raw: s.slice(lt, endC), start: lt, end: endC });
|
|
461
406
|
pos = endC; continue;
|
|
462
407
|
}
|
|
@@ -508,28 +453,16 @@ function _tokenize(input, maxBytes) {
|
|
|
508
453
|
pos = endE; continue;
|
|
509
454
|
}
|
|
510
455
|
|
|
511
|
-
var pp = lt + 1;
|
|
512
|
-
var inQuote = "";
|
|
513
|
-
while (pp < len) {
|
|
514
|
-
var ch = s.charAt(pp);
|
|
515
|
-
if (inQuote) { if (ch === inQuote) inQuote = ""; }
|
|
516
|
-
else {
|
|
517
|
-
if (ch === '"' || ch === "'") inQuote = ch;
|
|
518
|
-
else if (ch === ">") break;
|
|
519
|
-
}
|
|
520
|
-
pp += 1;
|
|
521
|
-
}
|
|
456
|
+
var pp = markupTokenizer.scanToTagEnd(s, lt + 1, len);
|
|
522
457
|
var endT = pp < len ? pp + 1 : len;
|
|
523
458
|
var raw = s.slice(lt, endT);
|
|
524
459
|
var inner = raw.slice(1, raw.charAt(raw.length - 1) === ">" ? raw.length - 1 : raw.length);
|
|
525
460
|
var selfClosing = inner.endsWith("/");
|
|
526
461
|
if (selfClosing) inner = inner.slice(0, inner.length - 1);
|
|
527
462
|
|
|
528
|
-
var
|
|
529
|
-
var tagName =
|
|
530
|
-
var
|
|
531
|
-
|
|
532
|
-
var attrs = _parseAttrs(attrSrc);
|
|
463
|
+
var svgParts = markupTokenizer.splitTagNameAttrs(inner, /^([A-Za-z][A-Za-z0-9:_-]*)/);
|
|
464
|
+
var tagName = svgParts.tagName;
|
|
465
|
+
var attrs = _parseAttrs(svgParts.attrSrc);
|
|
533
466
|
tokens.push({
|
|
534
467
|
type: "tag", name: tagName, attrs: attrs,
|
|
535
468
|
raw: raw, start: lt, end: endT, selfClosing: selfClosing,
|
|
@@ -576,6 +509,10 @@ function _parseAttrs(src) {
|
|
|
576
509
|
// ---- Detection pass ----
|
|
577
510
|
|
|
578
511
|
function _detectIssues(input, opts) {
|
|
512
|
+
if (typeof input !== "string" && !Buffer.isBuffer(input)) {
|
|
513
|
+
return [{ kind: "bad-input", severity: "high",
|
|
514
|
+
snippet: "input is not string or Buffer" }];
|
|
515
|
+
}
|
|
579
516
|
if (_isSvgz(input)) {
|
|
580
517
|
return [{
|
|
581
518
|
kind: "svgz-compressed", severity: "critical", ruleId: "svg.svgz",
|
|
@@ -585,7 +522,7 @@ function _detectIssues(input, opts) {
|
|
|
585
522
|
}
|
|
586
523
|
|
|
587
524
|
var s = typeof input === "string" ? input : Buffer.from(input).toString("utf8");
|
|
588
|
-
var issues = codepointClass.detectCharThreats(s, opts, "svg");
|
|
525
|
+
var issues = codepointClass.detectCharThreats(s, opts, "svg", "warn");
|
|
589
526
|
|
|
590
527
|
var tokens;
|
|
591
528
|
try { tokens = _tokenize(s, opts.maxBytes); }
|
|
@@ -708,7 +645,7 @@ function _detectIssues(input, opts) {
|
|
|
708
645
|
for (var ai = 0; ai < attrs.length; ai += 1) {
|
|
709
646
|
var a = attrs[ai];
|
|
710
647
|
var an = a.name.toLowerCase();
|
|
711
|
-
if (a.value && a.value
|
|
648
|
+
if (a.value && Buffer.byteLength(a.value, "utf8") > opts.maxAttrValueBytes) {
|
|
712
649
|
issues.push({
|
|
713
650
|
kind: "attr-value-too-large", severity: "high",
|
|
714
651
|
ruleId: "svg.attr-size",
|
|
@@ -806,9 +743,10 @@ function _sanitize(input, opts) {
|
|
|
806
743
|
throw _err("svg.svgz", "compressed SVGZ payload — operator must ungzip before sanitize");
|
|
807
744
|
}
|
|
808
745
|
var s = typeof input === "string" ? input : Buffer.from(input).toString("utf8");
|
|
809
|
-
|
|
746
|
+
var nb = Buffer.byteLength(s, "utf8");
|
|
747
|
+
if (nb > opts.maxBytes) {
|
|
810
748
|
throw _err("svg.too-large",
|
|
811
|
-
"input " +
|
|
749
|
+
"input " + nb + " bytes exceeds maxBytes " + opts.maxBytes);
|
|
812
750
|
}
|
|
813
751
|
codepointClass.assertNoCharThreats(s, opts, _err, "svg");
|
|
814
752
|
|
|
@@ -873,7 +811,7 @@ function _sanitize(input, opts) {
|
|
|
873
811
|
var a = attrs[ai];
|
|
874
812
|
var an = a.name.toLowerCase();
|
|
875
813
|
if (EVENT_HANDLER_RE.test(an)) continue; // allow:regex-no-length-cap — `an` is a tokenized attribute name, bounded
|
|
876
|
-
if (a.value && a.value
|
|
814
|
+
if (a.value && Buffer.byteLength(a.value, "utf8") > opts.maxAttrValueBytes) continue;
|
|
877
815
|
if (URL_ATTRS.indexOf(an) !== -1) {
|
|
878
816
|
var scheme = _extractScheme(a.value);
|
|
879
817
|
var fragment = _isFragmentRef(a.value);
|
|
@@ -893,9 +831,7 @@ function _sanitize(input, opts) {
|
|
|
893
831
|
!fragment && !opts.allowExternalRefs) continue;
|
|
894
832
|
}
|
|
895
833
|
if (an === "style" && _isCssDangerous(a.value)) continue;
|
|
896
|
-
attrParts.push(an + "=\"" + a.value
|
|
897
|
-
.replace(/&/g, "&").replace(/</g, "<")
|
|
898
|
-
.replace(/>/g, ">").replace(/"/g, """) + "\"");
|
|
834
|
+
attrParts.push(an + "=\"" + markupEscape(a.value) + "\"");
|
|
899
835
|
}
|
|
900
836
|
var open = "<" + tok.name + (attrParts.length ? " " + attrParts.join(" ") : "") +
|
|
901
837
|
(tok.selfClosing ? "/>" : ">");
|
|
@@ -951,16 +887,12 @@ function _sanitize(input, opts) {
|
|
|
951
887
|
* clean.ok; // → true
|
|
952
888
|
* clean.issues.length; // → 0
|
|
953
889
|
*/
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
var bad = gateContract.badInputResultIfNotStringOrBuffer(input);
|
|
961
|
-
if (bad) return bad;
|
|
962
|
-
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
963
|
-
}
|
|
890
|
+
// validate is assembled by gateContract.defineGuard from `detect`
|
|
891
|
+
// (_detectIssues) below — `validate(input, opts) = aggregateIssues(detect(
|
|
892
|
+
// input, resolveOpts(opts)))`, with the maxBytes/maxElementCount/maxUseDepth
|
|
893
|
+
// caps declared via `intOpts`. Non-string/non-Buffer input returns a single
|
|
894
|
+
// `svg.bad-input` issue from _detectIssues (never throws). The @primitive
|
|
895
|
+
// block above documents the resulting public ABI.
|
|
964
896
|
|
|
965
897
|
/**
|
|
966
898
|
* @primitive b.guardSvg.sanitize
|
|
@@ -1003,7 +935,7 @@ function validate(input, opts) {
|
|
|
1003
935
|
* /onload/.test(clean); // → false
|
|
1004
936
|
*/
|
|
1005
937
|
function sanitize(input, opts) {
|
|
1006
|
-
opts =
|
|
938
|
+
opts = _guard.resolveOpts(opts);
|
|
1007
939
|
if (typeof input !== "string" && !Buffer.isBuffer(input)) {
|
|
1008
940
|
throw _err("svg.bad-input", "sanitize requires string or Buffer input");
|
|
1009
941
|
}
|
|
@@ -1055,42 +987,57 @@ function sanitize(input, opts) {
|
|
|
1055
987
|
* });
|
|
1056
988
|
* refuse.action; // → "refuse"
|
|
1057
989
|
*/
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
990
|
+
// Disposition of each svg finding = what the operator's policy for that class
|
|
991
|
+
// selected. CSS injection / DOCTYPE / CDATA / processing-instruction and the
|
|
992
|
+
// bidi / null / control char threats follow their policies (sanitize under
|
|
993
|
+
// `strip`, refuse under `reject`, audit under `audit`). The always-dangerous
|
|
994
|
+
// classes (dangerous / animation tag, event handler, animation target,
|
|
995
|
+
// dangerous URL scheme, external ref, entity declaration) refuse; a gzipped
|
|
996
|
+
// SVGZ payload refuses (it must never reach the text sanitizer); a
|
|
997
|
+
// non-allowlisted but benign tag / scheme sanitizes; structural caps and a
|
|
998
|
+
// tokenizer failure refuse. Exhaustive over every kind _detectIssues emits.
|
|
999
|
+
function _gateDispositionFor(issue, opts) {
|
|
1000
|
+
var shared = gateContract.charThreatDisposition(issue, opts);
|
|
1001
|
+
if (shared) return shared;
|
|
1002
|
+
switch (issue.kind) {
|
|
1003
|
+
case "css-injection": return gateContract.policyDisposition(opts.cssPolicy);
|
|
1004
|
+
case "doctype": return gateContract.policyDisposition(opts.doctypePolicy);
|
|
1005
|
+
case "cdata": return gateContract.policyDisposition(opts.cdataPolicy);
|
|
1006
|
+
case "processing-instruction": return gateContract.policyDisposition(opts.processingInstrPolicy);
|
|
1007
|
+
case "non-allowlisted-tag":
|
|
1008
|
+
case "non-allowlisted-url-scheme": return "sanitize";
|
|
1009
|
+
case "svgz-compressed":
|
|
1010
|
+
case "entity-declaration":
|
|
1011
|
+
case "dangerous-tag":
|
|
1012
|
+
case "event-handler":
|
|
1013
|
+
case "animation-target":
|
|
1014
|
+
case "dangerous-url-scheme":
|
|
1015
|
+
case "external-ref": return "refuse";
|
|
1016
|
+
case "tokenize-failed":
|
|
1017
|
+
case "element-count-cap":
|
|
1018
|
+
case "attr-count-cap":
|
|
1019
|
+
case "use-depth-cap":
|
|
1020
|
+
case "attr-value-too-large":
|
|
1021
|
+
case "bad-input": return "refuse";
|
|
1022
|
+
default: return null;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1077
1025
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
});
|
|
1026
|
+
function gate(opts) {
|
|
1027
|
+
opts = _guard.resolveOpts(opts);
|
|
1028
|
+
return gateContract.buildContentGate({
|
|
1029
|
+
name: opts.name || "guardSvg:" + (opts.profile || "default"),
|
|
1030
|
+
opts: opts,
|
|
1031
|
+
validate: module.exports.validate,
|
|
1032
|
+
dispositionFor: _gateDispositionFor,
|
|
1033
|
+
// SVG reads the RAW bytes (SVGZ gzip detection needs the byte signature).
|
|
1034
|
+
// A gzipped SVGZ must not be fed to the text sanitizer → it is refuse-
|
|
1035
|
+
// disposition, and sanitizeBlockingKinds is a second backstop that skips
|
|
1036
|
+
// the sanitize attempt for it.
|
|
1037
|
+
ctxField: "bytes",
|
|
1038
|
+
sanitizeBlockingKinds: ["svgz-compressed"],
|
|
1039
|
+
produceSanitized: function (bytes, o) { return sanitize(bytes, o); },
|
|
1040
|
+
});
|
|
1094
1041
|
}
|
|
1095
1042
|
|
|
1096
1043
|
// buildProfile / compliancePosture / loadRulePack are assembled by
|
|
@@ -1114,10 +1061,15 @@ var INTEGRATION_FIXTURES = Object.freeze({
|
|
|
1114
1061
|
// Assembled from the gate-contract guard factory: error class, registry
|
|
1115
1062
|
// exports (NAME / KIND / MIME_TYPES / EXTENSIONS / INTEGRATION_FIXTURES),
|
|
1116
1063
|
// buildProfile / compliancePosture / loadRulePack wiring, plus the
|
|
1117
|
-
// per-guard inspection surface
|
|
1118
|
-
//
|
|
1119
|
-
//
|
|
1120
|
-
|
|
1064
|
+
// per-guard inspection surface and the SVG tag / scheme tables passed
|
|
1065
|
+
// through verbatim. `validate` is generated from `detect` (_detectIssues)
|
|
1066
|
+
// with the int caps declared via `intOpts`. The bespoke `sanitize` and
|
|
1067
|
+
// `gate` carry SVG's tag/attr strip-reserialize chain — which drops
|
|
1068
|
+
// dangerous tags rather than throwing on them — and the SVGZ refuse
|
|
1069
|
+
// unchanged; neither reduces to the dynamic
|
|
1070
|
+
// detect→throwOnRefusalSeverity→transform path (same-severity findings
|
|
1071
|
+
// split throw-vs-strip), so both stay bespoke.
|
|
1072
|
+
var _guard = module.exports = gateContract.defineGuard({
|
|
1121
1073
|
name: "svg",
|
|
1122
1074
|
kind: "content",
|
|
1123
1075
|
errorClass: GuardSvgError,
|
|
@@ -1127,10 +1079,12 @@ module.exports = gateContract.defineGuard({
|
|
|
1127
1079
|
mimeTypes: ["image/svg+xml"],
|
|
1128
1080
|
extensions: [".svg", ".svgz"],
|
|
1129
1081
|
integrationFixtures: INTEGRATION_FIXTURES,
|
|
1130
|
-
|
|
1082
|
+
detect: _detectIssues,
|
|
1083
|
+
intOpts: ["maxBytes", "maxElementCount", "maxUseDepth"],
|
|
1131
1084
|
sanitize: sanitize,
|
|
1132
1085
|
gate: gate,
|
|
1133
1086
|
extra: {
|
|
1087
|
+
_gateDispositionForTest: _gateDispositionFor,
|
|
1134
1088
|
DANGEROUS_TAGS: DANGEROUS_TAGS,
|
|
1135
1089
|
ANIMATION_TAGS: ANIMATION_TAGS,
|
|
1136
1090
|
ANIMATION_SAFE_TARGETS: ANIMATION_SAFE_TARGETS,
|
package/lib/guard-template.js
CHANGED
|
@@ -50,11 +50,9 @@
|
|
|
50
50
|
* Server-Side Template Injection (SSTI) content-safety guard — refuses user-supplied strings that contain template-engine syntax BEFORE they're rendered.
|
|
51
51
|
*/
|
|
52
52
|
|
|
53
|
-
var codepointClass = require("./codepoint-class");
|
|
54
53
|
var lazyRequire = require("./lazy-require");
|
|
55
54
|
var gateContract = require("./gate-contract");
|
|
56
55
|
var C = require("./constants");
|
|
57
|
-
var numericBounds = require("./numeric-bounds");
|
|
58
56
|
var { GuardTemplateError } = require("./framework-error");
|
|
59
57
|
|
|
60
58
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
@@ -74,10 +72,7 @@ var VELOCITY_DIR_RE = /#(?:set|if|else|elseif|end|foreach|parse|include|stop)\b/
|
|
|
74
72
|
|
|
75
73
|
var PROFILES = Object.freeze({
|
|
76
74
|
"strict": {
|
|
77
|
-
|
|
78
|
-
controlPolicy: "reject",
|
|
79
|
-
nullBytePolicy: "reject",
|
|
80
|
-
zeroWidthPolicy: "reject",
|
|
75
|
+
...gateContract.CHAR_THREATS_REJECT_ALL,
|
|
81
76
|
jinjaPolicy: "reject",
|
|
82
77
|
erbPolicy: "reject",
|
|
83
78
|
pugPolicy: "reject",
|
|
@@ -87,10 +82,7 @@ var PROFILES = Object.freeze({
|
|
|
87
82
|
maxRuntimeMs: C.TIME.seconds(2),
|
|
88
83
|
},
|
|
89
84
|
"balanced": {
|
|
90
|
-
|
|
91
|
-
controlPolicy: "reject",
|
|
92
|
-
nullBytePolicy: "reject",
|
|
93
|
-
zeroWidthPolicy: "reject",
|
|
85
|
+
...gateContract.CHAR_THREATS_REJECT_ALL,
|
|
94
86
|
jinjaPolicy: "reject", // SSTI class — refused at every profile
|
|
95
87
|
erbPolicy: "reject",
|
|
96
88
|
pugPolicy: "reject",
|
|
@@ -100,10 +92,7 @@ var PROFILES = Object.freeze({
|
|
|
100
92
|
maxRuntimeMs: C.TIME.seconds(2),
|
|
101
93
|
},
|
|
102
94
|
"permissive": {
|
|
103
|
-
|
|
104
|
-
controlPolicy: "reject", // controls refused at every profile
|
|
105
|
-
nullBytePolicy: "reject", // null refused at every profile
|
|
106
|
-
zeroWidthPolicy: "reject", // zero-width refused at every profile
|
|
95
|
+
...gateContract.CHAR_THREATS_REJECT_ALL,
|
|
107
96
|
jinjaPolicy: "reject", // SSTI class refused at every profile
|
|
108
97
|
erbPolicy: "reject", // SSTI class refused at every profile
|
|
109
98
|
pugPolicy: "reject", // SSTI class refused at every profile
|
|
@@ -114,51 +103,14 @@ var PROFILES = Object.freeze({
|
|
|
114
103
|
},
|
|
115
104
|
});
|
|
116
105
|
|
|
117
|
-
var DEFAULTS =
|
|
118
|
-
mode: "enforce",
|
|
119
|
-
}));
|
|
106
|
+
var DEFAULTS = gateContract.strictDefaults(PROFILES);
|
|
120
107
|
|
|
121
|
-
var COMPLIANCE_POSTURES =
|
|
122
|
-
"hipaa": Object.assign({}, PROFILES["strict"], {
|
|
123
|
-
forensicSnippetBytes: C.BYTES.bytes(512),
|
|
124
|
-
}),
|
|
125
|
-
"pci-dss": Object.assign({}, PROFILES["strict"], {
|
|
126
|
-
forensicSnippetBytes: C.BYTES.bytes(512),
|
|
127
|
-
}),
|
|
128
|
-
"gdpr": Object.assign({}, PROFILES["balanced"], {
|
|
129
|
-
forensicSnippetBytes: C.BYTES.bytes(256),
|
|
130
|
-
}),
|
|
131
|
-
"soc2": Object.assign({}, PROFILES["strict"], {
|
|
132
|
-
forensicSnippetBytes: C.BYTES.bytes(1024),
|
|
133
|
-
}),
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
function _resolveOpts(opts) {
|
|
137
|
-
return gateContract.resolveProfileAndPosture(opts, {
|
|
138
|
-
profiles: PROFILES,
|
|
139
|
-
compliancePostures: COMPLIANCE_POSTURES,
|
|
140
|
-
defaults: DEFAULTS,
|
|
141
|
-
errorClass: GuardTemplateError,
|
|
142
|
-
errCodePrefix: "template",
|
|
143
|
-
});
|
|
144
|
-
}
|
|
108
|
+
var COMPLIANCE_POSTURES = gateContract.compliancePostures(PROFILES, { base: 512 });
|
|
145
109
|
|
|
146
110
|
function _detectIssues(input, opts) {
|
|
147
|
-
var
|
|
148
|
-
if (
|
|
149
|
-
|
|
150
|
-
ruleId: "template.bad-input",
|
|
151
|
-
snippet: "template input is not a string" }];
|
|
152
|
-
}
|
|
153
|
-
if (input.length === 0) return [];
|
|
154
|
-
if (Buffer.byteLength(input, "utf8") > opts.maxBytes) {
|
|
155
|
-
return [{ kind: "input-cap", severity: "high",
|
|
156
|
-
ruleId: "template.input-cap",
|
|
157
|
-
snippet: "template input exceeds maxBytes " + opts.maxBytes }];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
var charThreats = codepointClass.detectCharThreats(input, opts, "template");
|
|
161
|
-
for (var ci = 0; ci < charThreats.length; ci += 1) issues.push(charThreats[ci]);
|
|
111
|
+
var pre = gateContract.detectStringInput(input, opts, { name: "template", noun: "template input", emptyMode: "ok", cap: { bytes: opts.maxBytes, kind: "input-cap", snippet: "template input exceeds maxBytes " + opts.maxBytes } });
|
|
112
|
+
if (pre.done) return pre.issues;
|
|
113
|
+
var issues = pre.issues;
|
|
162
114
|
|
|
163
115
|
if (opts.jinjaPolicy !== "allow") {
|
|
164
116
|
if (JINJA_EXPR_RE.test(input)) { // allow:regex-no-length-cap — input bounded by maxBytes
|
|
@@ -258,13 +210,10 @@ function _detectIssues(input, opts) {
|
|
|
258
210
|
* hostile.ok; // → false
|
|
259
211
|
* hostile.issues.some(function (i) { return i.kind === "jinja-expression"; }); // → true
|
|
260
212
|
*/
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
"guardTemplate.validate", GuardTemplateError, "template.bad-opt");
|
|
266
|
-
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
267
|
-
}
|
|
213
|
+
// validate is assembled by gateContract.defineGuard from `detect`
|
|
214
|
+
// (_detectIssues) below — `validate(input, opts) = aggregateIssues(detect(
|
|
215
|
+
// input, resolveOpts(opts)))`, with the maxBytes cap declared via `intOpts`.
|
|
216
|
+
// The @primitive block above documents the resulting public ABI.
|
|
268
217
|
|
|
269
218
|
/**
|
|
270
219
|
* @primitive b.guardTemplate.sanitize
|
|
@@ -305,13 +254,12 @@ function validate(input, opts) {
|
|
|
305
254
|
* e.code; // → "template.jinja-expression"
|
|
306
255
|
* }
|
|
307
256
|
*/
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
gateContract.throwOnRefusalSeverity(issues, { errorClass: GuardTemplateError, codePrefix: "template" });
|
|
257
|
+
// _sanitizeTransform — the guard-specific normalize applied by defineGuard's
|
|
258
|
+
// generated sanitize AFTER resolve → detect → throw-on-refusal. Input is an
|
|
259
|
+
// already-validated string at this point (a non-string refuses upstream).
|
|
260
|
+
// Template input cannot be repaired safely, so the transform is pass-through:
|
|
261
|
+
// clean input is returned verbatim once no high/critical issue fired.
|
|
262
|
+
function _sanitizeTransform(input) {
|
|
315
263
|
return input;
|
|
316
264
|
}
|
|
317
265
|
|
|
@@ -323,14 +271,8 @@ function sanitize(input, opts) {
|
|
|
323
271
|
// @abiTemplate (defineGuard) blocks in gate-contract.js, instantiated per
|
|
324
272
|
// guard by the page generator.
|
|
325
273
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
benignBytes: Buffer.from("Hello world", "utf8"),
|
|
329
|
-
hostileBytes: Buffer.from("Hello {{7*7}}", "utf8"),
|
|
330
|
-
benignIdentifier: "Hello world",
|
|
331
|
-
// Hostile: Jinja-shape SSTI probe.
|
|
332
|
-
hostileIdentifier: "Hello {{7*7}}",
|
|
333
|
-
});
|
|
274
|
+
// Hostile: Jinja-shape SSTI probe.
|
|
275
|
+
var INTEGRATION_FIXTURES = gateContract.identifierFixtures("Hello world", "Hello {{7*7}}");
|
|
334
276
|
|
|
335
277
|
// Assembled from the gate-contract guard factory: error class, registry
|
|
336
278
|
// exports (NAME / KIND / INTEGRATION_FIXTURES), the default gate, buildProfile
|
|
@@ -347,7 +289,8 @@ module.exports = gateContract.defineGuard({
|
|
|
347
289
|
defaults: DEFAULTS,
|
|
348
290
|
postures: COMPLIANCE_POSTURES,
|
|
349
291
|
integrationFixtures: INTEGRATION_FIXTURES,
|
|
350
|
-
|
|
351
|
-
|
|
292
|
+
detect: _detectIssues,
|
|
293
|
+
sanitizeTransform: _sanitizeTransform,
|
|
294
|
+
intOpts: ["maxBytes"],
|
|
352
295
|
ctxFields: ["identifier", "text"],
|
|
353
296
|
});
|
package/lib/guard-tenant-id.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
var { defineClass } = require("./framework-error");
|
|
26
26
|
var gateContract = require("./gate-contract");
|
|
27
|
+
var codepointClass = require("./codepoint-class");
|
|
27
28
|
|
|
28
29
|
var GuardTenantIdError = defineClass("GuardTenantIdError", { alwaysPermanent: true });
|
|
29
30
|
|
|
@@ -93,7 +94,7 @@ function validate(tenantId, opts) {
|
|
|
93
94
|
throw new GuardTenantIdError("tenant-id/non-ascii",
|
|
94
95
|
"guardTenantId.validate: non-ASCII codepoint at offset " + i);
|
|
95
96
|
}
|
|
96
|
-
if (c
|
|
97
|
+
if (codepointClass.isForbiddenControlChar(c, { forbidTab: true }) || c === 0x2F || c === 0x5C) { // C0/DEL/slash/backslash
|
|
97
98
|
throw new GuardTenantIdError("tenant-id/bad-char",
|
|
98
99
|
"guardTenantId.validate: forbidden char 0x" + c.toString(16) + " at offset " + i);
|
|
99
100
|
}
|