@blamejs/core 0.18.47 → 0.18.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 +34 -0
- package/lib/ai-model-manifest.js +2 -2
- package/lib/audit-sign.js +3 -3
- package/lib/auth/passkey.js +1 -1
- package/lib/backup/index.js +2 -2
- package/lib/client-hints.js +1 -2
- package/lib/codepoint-class.js +6 -5
- package/lib/gate-contract.js +147 -1
- package/lib/guard-archive.js +24 -5
- package/lib/guard-auth.js +28 -0
- package/lib/guard-cidr.js +19 -0
- package/lib/guard-country.js +4 -4
- package/lib/guard-csv.js +31 -2
- package/lib/guard-domain.js +22 -0
- package/lib/guard-email.js +17 -0
- package/lib/guard-filename.js +45 -11
- package/lib/guard-graphql.js +16 -0
- package/lib/guard-html.js +17 -0
- package/lib/guard-image.js +19 -0
- package/lib/guard-json.js +23 -0
- package/lib/guard-jsonpath.js +14 -1
- package/lib/guard-jwt.js +20 -2
- package/lib/guard-markdown.js +25 -0
- package/lib/guard-mime.js +15 -5
- package/lib/guard-oauth.js +23 -0
- package/lib/guard-pdf.js +19 -1
- package/lib/guard-regex.js +31 -15
- package/lib/guard-shell.js +14 -1
- package/lib/guard-svg.js +17 -0
- package/lib/guard-template.js +14 -1
- package/lib/guard-text.js +16 -1
- package/lib/guard-time.js +20 -4
- package/lib/guard-uuid.js +28 -4
- package/lib/guard-xml.js +19 -0
- package/lib/guard-yaml.js +22 -2
- package/lib/mail-auth.js +3 -6
- package/lib/metrics.js +4 -4
- package/lib/mtls-ca.js +4 -4
- package/lib/network-dns.js +2 -2
- package/lib/session-device-binding.js +2 -2
- package/lib/vault/index.js +2 -2
- package/lib/ws-client.js +2 -2
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/guard-filename.js
CHANGED
|
@@ -399,7 +399,9 @@ function _detectIssues(input, opts) {
|
|
|
399
399
|
// by the maxBytes check at the end of this function (issues are
|
|
400
400
|
// reported all-at-once; an oversized name still gets traversal-shape
|
|
401
401
|
// detection so operators see the full failure surface).
|
|
402
|
-
|
|
402
|
+
// Unconditional, as the opts block has always said: a leaf name has no
|
|
403
|
+
// legitimate `..` component, so there is no policy value that turns this off.
|
|
404
|
+
{
|
|
403
405
|
if (_hasTraversalSegment(name) || name === "..") {
|
|
404
406
|
issues.push({
|
|
405
407
|
kind: "path-traversal", severity: "critical",
|
|
@@ -457,8 +459,10 @@ function _detectIssues(input, opts) {
|
|
|
457
459
|
}
|
|
458
460
|
}
|
|
459
461
|
|
|
460
|
-
// 6. NTFS alternate data streams — `name:stream`.
|
|
461
|
-
|
|
462
|
+
// 6. NTFS alternate data streams — `name:stream`. Unconditional: a write to
|
|
463
|
+
// `name:stream` lands on a hidden stream of the base file rather than the
|
|
464
|
+
// file the caller named, which no policy value makes safe.
|
|
465
|
+
{
|
|
462
466
|
if (_hasAdsSuffix(name) && name.charAt(0) !== "/") {
|
|
463
467
|
// Only flag when there's a `:` followed by stream-name characters
|
|
464
468
|
// and we're NOT at the start (relative path indicator).
|
|
@@ -616,11 +620,9 @@ function _sanitize(input, opts) {
|
|
|
616
620
|
if (opts.unicodeNormalization === "NFC") name = _normalizeNFC(name);
|
|
617
621
|
|
|
618
622
|
// Reject path traversal even in sanitize — there's no safe sanitization.
|
|
619
|
-
if (
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
throw _err("filename.traversal", "filename contains path-traversal sequence");
|
|
623
|
-
}
|
|
623
|
+
if (_hasTraversalSegment(name) || _hasAnyFolded(name, PERCENT_ENCODED_TRAVERSALS) ||
|
|
624
|
+
name === "." || name === "..") {
|
|
625
|
+
throw _err("filename.traversal", "filename contains path-traversal sequence");
|
|
624
626
|
}
|
|
625
627
|
if (_hasUncPrefix(name)) {
|
|
626
628
|
throw _err("filename.unc", "UNC path syntax");
|
|
@@ -659,7 +661,7 @@ function _sanitize(input, opts) {
|
|
|
659
661
|
}
|
|
660
662
|
|
|
661
663
|
// ADS detection.
|
|
662
|
-
if (
|
|
664
|
+
if (_hasAdsSuffix(name)) {
|
|
663
665
|
throw _err("filename.ntfs-ads", "filename contains NTFS alternate data stream syntax");
|
|
664
666
|
}
|
|
665
667
|
|
|
@@ -707,7 +709,9 @@ function _sanitize(input, opts) {
|
|
|
707
709
|
* traversalPolicy: "reject", // always reject
|
|
708
710
|
* reservedCharPolicy: "reject"|"strip"|"allow",
|
|
709
711
|
* reservedNamePolicy: "reject"|"audit"|"allow",
|
|
710
|
-
* adsPolicy: "reject",
|
|
712
|
+
* adsPolicy: "reject"|"allow", // reject here; "allow"
|
|
713
|
+
* // is honoured only
|
|
714
|
+
* // by verifyExtractionPath
|
|
711
715
|
* leadingTrailingPolicy: "reject"|"strip"|"allow",
|
|
712
716
|
* shellExecExtPolicy: "reject"|"audit"|"allow",
|
|
713
717
|
* pathSeparatorsPolicy: "reject"|"audit"|"allow",
|
|
@@ -1081,7 +1085,7 @@ var PATH_MAX_BYTES = 4096;
|
|
|
1081
1085
|
* @opts
|
|
1082
1086
|
* followSymlinks: boolean, // default false — symlink in the
|
|
1083
1087
|
* // resolved path refuses unless set
|
|
1084
|
-
* reservedNamePolicy: string, // "allow" opts out of the Windows
|
|
1088
|
+
* reservedNamePolicy: string, // "allow" opts out of the Windows-
|
|
1085
1089
|
* // reserved-device-name segment check
|
|
1086
1090
|
* adsPolicy: string, // "allow" opts out of the NTFS-ADS check
|
|
1087
1091
|
* leadingTrailingPolicy: string, // "allow" opts out of the trailing-dot /
|
|
@@ -1303,7 +1307,37 @@ var INTEGRATION_FIXTURES = Object.freeze({
|
|
|
1303
1307
|
// factory only supplies the error class, registry exports, buildProfile /
|
|
1304
1308
|
// compliancePosture / loadRulePack wiring, and the verifyExtractionPath /
|
|
1305
1309
|
// WIN_RESERVED_NAMES / SHELL_EXEC_EXTS extras.
|
|
1310
|
+
// Each policy's vocabulary, so a misspelling is a boot error rather than a
|
|
1311
|
+
// runtime surprise. Read leniently, a typo takes whichever branch is not the
|
|
1312
|
+
// strict one: `reservedNamePolicy: "rejct"` is not "allow", so the check runs,
|
|
1313
|
+
// and it is not "reject" either, so the finding drops to a warning.
|
|
1314
|
+
//
|
|
1315
|
+
// `traversalPolicy` and `nullBytePolicy` take one value each, as the opts block
|
|
1316
|
+
// has always said: a leaf name has no legitimate `..` component, and a NUL
|
|
1317
|
+
// truncates the name at whichever consumer reads it first. Both were reading
|
|
1318
|
+
// `"allow"` to turn the check off — a value neither of them offered, and one
|
|
1319
|
+
// the gate's floor overrode anyway, so it disabled the finding while the
|
|
1320
|
+
// refusal stood.
|
|
1321
|
+
//
|
|
1322
|
+
// `adsPolicy` keeps two, for a narrower reason: the detection is likewise a
|
|
1323
|
+
// floor, but `verifyExtractionPath` honours `"allow"` for an operator who is
|
|
1324
|
+
// deliberately extracting stream-suffixed names. `"audit"` was accepted there
|
|
1325
|
+
// and did nothing.
|
|
1326
|
+
var POLICY_ENUM = gateContract.policyVocabulary([
|
|
1327
|
+
"homoglyphPolicy", "reservedNamePolicy", "shellExecExtPolicy",
|
|
1328
|
+
], gateContract.POLICY_VALUES.rejectAuditAllow, {
|
|
1329
|
+
// No `audit-only`: the separator check suppresses its finding on the literal
|
|
1330
|
+
// "audit", so the synonym would fail a path-shaped name that "audit" accepts.
|
|
1331
|
+
pathSeparatorsPolicy: ["reject", "audit", "allow"],
|
|
1332
|
+
traversalPolicy: ["reject"],
|
|
1333
|
+
nullBytePolicy: ["reject"],
|
|
1334
|
+
adsPolicy: ["reject", "allow"],
|
|
1335
|
+
reservedCharPolicy: ["reject", "strip", "allow"],
|
|
1336
|
+
leadingTrailingPolicy: ["reject", "strip", "allow"],
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1306
1339
|
module.exports = gateContract.defineGuard({
|
|
1340
|
+
enumOpts: POLICY_ENUM,
|
|
1307
1341
|
name: "filename",
|
|
1308
1342
|
kind: "filename",
|
|
1309
1343
|
charRepair: true,
|
package/lib/guard-graphql.js
CHANGED
|
@@ -626,7 +626,23 @@ var INTEGRATION_FIXTURES = Object.freeze({
|
|
|
626
626
|
// surface (validate / sanitize / bespoke gate) passed through verbatim.
|
|
627
627
|
// The custom KIND ("graphql-request") is accepted because the bespoke
|
|
628
628
|
// gate reads its own ctx fields (ctx.graphqlRequest / ctx.gql).
|
|
629
|
+
// Each policy's vocabulary, so a misspelling is a boot error rather than a
|
|
630
|
+
// runtime surprise. Read leniently, a typo takes whichever branch is not the
|
|
631
|
+
// strict one: `introspectionPolicy: "rejct"` is not "allow", so the check
|
|
632
|
+
// runs, and it is not "reject" either, so the finding drops to a warning.
|
|
633
|
+
//
|
|
634
|
+
// `persistedQueryPolicy` opens with "require" rather than "reject" because the
|
|
635
|
+
// finding is an absence: the operator is demanding a persisted-query id, not
|
|
636
|
+
// refusing a construct that appeared.
|
|
637
|
+
var POLICY_ENUM = gateContract.policyVocabulary([
|
|
638
|
+
"introspectionPolicy", "operationNamePolicy", "batchPolicy",
|
|
639
|
+
"aliasBombPolicy", "depthPolicy", "variableShapePolicy",
|
|
640
|
+
], gateContract.POLICY_VALUES.rejectAuditAllow, {
|
|
641
|
+
persistedQueryPolicy: ["require", "audit", "audit-only", "allow"],
|
|
642
|
+
});
|
|
643
|
+
|
|
629
644
|
var _guard = module.exports = gateContract.defineGuard({
|
|
645
|
+
enumOpts: POLICY_ENUM,
|
|
630
646
|
name: "graphql",
|
|
631
647
|
kind: "graphql-request",
|
|
632
648
|
errorClass: GuardGraphqlError,
|
package/lib/guard-html.js
CHANGED
|
@@ -980,7 +980,24 @@ var INTEGRATION_FIXTURES = Object.freeze({
|
|
|
980
980
|
// (escapeText / escapeAttr / wcag + the tag/attr/scheme/clobber tables)
|
|
981
981
|
// passed through verbatim. The bespoke `gate` carries HTML's
|
|
982
982
|
// sanitize-and-reemit chain unchanged.
|
|
983
|
+
// Each policy's vocabulary, so a misspelling is a boot error rather than a
|
|
984
|
+
// runtime surprise. Read leniently, a typo takes whichever branch is not the
|
|
985
|
+
// strict one: `cssPolicy: "rejct"` is not "allow", so the check runs, and it
|
|
986
|
+
// is not "reject" either, so a dangerous style attribute drops to a warning.
|
|
987
|
+
//
|
|
988
|
+
// The opts block types these as `string` rather than naming the values; these
|
|
989
|
+
// are the values the profiles use and the code compares against.
|
|
990
|
+
//
|
|
991
|
+
// The character policies are absent on purpose — they are derived for the
|
|
992
|
+
// whole guard family, and an entry here would shadow that derivation.
|
|
993
|
+
var POLICY_ENUM = gateContract.policyVocabulary([
|
|
994
|
+
"cssPolicy", "domClobberPolicy",
|
|
995
|
+
], gateContract.POLICY_VALUES.rejectStripAuditAllow, {
|
|
996
|
+
mxssHintPolicy: gateContract.POLICY_VALUES.rejectAuditAllow,
|
|
997
|
+
});
|
|
998
|
+
|
|
983
999
|
module.exports = gateContract.defineGuard({
|
|
1000
|
+
enumOpts: POLICY_ENUM,
|
|
984
1001
|
name: "html",
|
|
985
1002
|
kind: "content",
|
|
986
1003
|
// sanitize rewrites the document, so `strip` on a character policy is an
|
package/lib/guard-image.js
CHANGED
|
@@ -161,6 +161,21 @@ var DEFAULTS = gateContract.strictDefaults(PROFILES);
|
|
|
161
161
|
// maxRuntimeMs among them, where zero means no runtime budget.
|
|
162
162
|
var INT_OPTS = ["maxBytes", "maxWidth", "maxHeight", "maxFrames"];
|
|
163
163
|
|
|
164
|
+
// Each policy's vocabulary, so a misspelling is a boot error rather than a
|
|
165
|
+
// runtime surprise. Read leniently, a typo takes whichever branch is not the
|
|
166
|
+
// strict one: `polyglotPolicy: "rejct"` is not "allow", so the check runs, and
|
|
167
|
+
// it is not "reject" either, so the finding drops to a warning.
|
|
168
|
+
//
|
|
169
|
+
// Declared up here because `gate()` below binds its own resolver and needs the
|
|
170
|
+
// same table. Given to the generated path alone, a bad value survived gate
|
|
171
|
+
// construction and surfaced as a refusal on the first image request — a deploy
|
|
172
|
+
// that boots clean and then refuses the route, which is the opposite of what
|
|
173
|
+
// checking at construction is for.
|
|
174
|
+
var POLICY_ENUM = gateContract.policyVocabulary([
|
|
175
|
+
"magicPolicy", "mismatchPolicy", "polyglotPolicy", "svgRoutingPolicy",
|
|
176
|
+
"dimensionsPolicy", "framesPolicy", "unknownMagicPolicy",
|
|
177
|
+
], gateContract.POLICY_VALUES.rejectAuditAllow);
|
|
178
|
+
|
|
164
179
|
var COMPLIANCE_POSTURES = gateContract.compliancePostures(PROFILES, { base: 256 });
|
|
165
180
|
|
|
166
181
|
function _bytesAt(buf, offset, sig) {
|
|
@@ -676,6 +691,9 @@ function gate(opts) {
|
|
|
676
691
|
// caller to identical rules.
|
|
677
692
|
intOpts: INT_OPTS,
|
|
678
693
|
nonNegativeOpts: gateContract.capKeysOf(DEFAULTS),
|
|
694
|
+
// And the same policy vocabulary, for the same reason as the limits: a
|
|
695
|
+
// door that skips it accepts a value the other doors refuse.
|
|
696
|
+
enumOpts: POLICY_ENUM,
|
|
679
697
|
});
|
|
680
698
|
return gateContract.buildGuardGate(
|
|
681
699
|
opts.name || "guardImage:" + (opts.profile || "default"),
|
|
@@ -745,6 +763,7 @@ var INTEGRATION_FIXTURES = Object.freeze({
|
|
|
745
763
|
// (operator-feeds-metadata ctx.metadata reader) is REQUIRED and carries the
|
|
746
764
|
// magic-byte / polyglot / dimension chain unchanged.
|
|
747
765
|
module.exports = gateContract.defineGuard({
|
|
766
|
+
enumOpts: POLICY_ENUM,
|
|
748
767
|
name: "image",
|
|
749
768
|
kind: "metadata",
|
|
750
769
|
errorClass: GuardImageError,
|
package/lib/guard-json.js
CHANGED
|
@@ -1083,7 +1083,30 @@ var INTEGRATION_FIXTURES = Object.freeze({
|
|
|
1083
1083
|
// per-guard inspection surface (validate / parse) and JSON extras
|
|
1084
1084
|
// (POLLUTION_KEYS, surfaced from the framework's canonical pick.POISONED_KEYS).
|
|
1085
1085
|
// The bespoke `gate` carries JSON's sanitize-reparse-reserialize chain unchanged.
|
|
1086
|
+
// Each policy's vocabulary, taken from this guard's documented opts and
|
|
1087
|
+
// confirmed against what the code compares, so a misspelling is a boot error
|
|
1088
|
+
// rather than a runtime surprise. Read leniently, a typo takes whichever branch
|
|
1089
|
+
// is not the strict one: `duplicateKeyPolicy: "rejct"` is not "allow", so the
|
|
1090
|
+
// check runs, and it is not "reject" either, so the finding drops to a warning
|
|
1091
|
+
// — the operator asked to refuse a duplicate key and silently got an audit.
|
|
1092
|
+
//
|
|
1093
|
+
// The character policies are deliberately absent: they are derived for the
|
|
1094
|
+
// whole family, and an explicit entry here would override that derivation with
|
|
1095
|
+
// a narrower copy.
|
|
1096
|
+
var POLICY_ENUM = gateContract.policyVocabulary([
|
|
1097
|
+
"duplicateKeyPolicy", "nanInfinityPolicy", "commentPolicy",
|
|
1098
|
+
"trailingCommaPolicy", "json5SyntaxPolicy", "numericPrecisionPolicy",
|
|
1099
|
+
], gateContract.POLICY_VALUES.rejectAuditAllow, {
|
|
1100
|
+
// No `audit-only`: the parse path decides whether to preserve a poison-named
|
|
1101
|
+
// key by testing for the literal "audit", so the synonym would strip
|
|
1102
|
+
// `__proto__` where "audit" keeps it — a silent difference in the parsed
|
|
1103
|
+
// data, not in a finding.
|
|
1104
|
+
pollutionPolicy: ["reject", "strip", "audit", "allow"],
|
|
1105
|
+
bomPolicy: ["reject", "strip", "allow"],
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1086
1108
|
module.exports = gateContract.defineGuard({
|
|
1109
|
+
enumOpts: POLICY_ENUM,
|
|
1087
1110
|
name: "json",
|
|
1088
1111
|
kind: "content",
|
|
1089
1112
|
charRepair: true,
|
package/lib/guard-jsonpath.js
CHANGED
|
@@ -257,7 +257,7 @@ function _detectIssues(input, opts) {
|
|
|
257
257
|
* bidiPolicy: "reject"|"audit"|"allow",
|
|
258
258
|
* controlPolicy: "reject"|"audit"|"allow",
|
|
259
259
|
* nullBytePolicy: "reject"|"audit"|"allow",
|
|
260
|
-
* zeroWidthPolicy: "reject"|"
|
|
260
|
+
* zeroWidthPolicy: "reject"|"audit"|"allow",
|
|
261
261
|
* filterExprPolicy: "reject"|"audit"|"allow",
|
|
262
262
|
* scriptExprPolicy: "reject"|"audit"|"allow",
|
|
263
263
|
* dynamicHintPolicy: "reject"|"audit"|"allow",
|
|
@@ -350,7 +350,20 @@ var INTEGRATION_FIXTURES = gateContract.identifierFixtures("$.users[*].name", "$
|
|
|
350
350
|
// surface (validate / sanitize). The gate is the factory default
|
|
351
351
|
// serve/audit-only/refuse chain, reading the path from
|
|
352
352
|
// `ctx.identifier` || `ctx.jsonpath` via `ctxFields`.
|
|
353
|
+
// Each policy's vocabulary, so a misspelling is a boot error rather than a
|
|
354
|
+
// runtime surprise. Read leniently, a typo takes whichever branch is not the
|
|
355
|
+
// strict one: `scriptExprPolicy: "rejct"` is not "allow", so the check runs,
|
|
356
|
+
// and it is not "reject" either, so the finding drops to a warning.
|
|
357
|
+
//
|
|
358
|
+
// The character policies are absent on purpose — they are derived for the
|
|
359
|
+
// whole guard family, and an entry here would shadow that derivation.
|
|
360
|
+
var POLICY_ENUM = gateContract.policyVocabulary([
|
|
361
|
+
"filterExprPolicy", "scriptExprPolicy", "dynamicHintPolicy",
|
|
362
|
+
"bracketNestingPolicy", "recursiveDescentPolicy",
|
|
363
|
+
], gateContract.POLICY_VALUES.rejectAuditAllow);
|
|
364
|
+
|
|
353
365
|
module.exports = gateContract.defineGuard({
|
|
366
|
+
enumOpts: POLICY_ENUM,
|
|
354
367
|
name: "jsonpath",
|
|
355
368
|
kind: "identifier",
|
|
356
369
|
errorClass: GuardJsonpathError,
|
package/lib/guard-jwt.js
CHANGED
|
@@ -283,8 +283,10 @@ function _detectIssues(input, opts) {
|
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
// kid path-traversal.
|
|
287
|
-
|
|
286
|
+
// kid path-traversal. Unconditional: `kid` steers key lookup, so a traversal
|
|
287
|
+
// sequence in it is critical at every profile and there is no policy value
|
|
288
|
+
// that turns the check off.
|
|
289
|
+
if (_hasKidTraversal(header.kid)) {
|
|
288
290
|
issues.push({
|
|
289
291
|
kind: "kid-traversal", severity: "critical",
|
|
290
292
|
ruleId: "jwt.kid-traversal",
|
|
@@ -578,7 +580,23 @@ var INTEGRATION_FIXTURES = gateContract.identifierFixtures(
|
|
|
578
580
|
// default gate (no bespoke `gate` passed) and the factory supplies the
|
|
579
581
|
// error class, registry exports, buildProfile / compliancePosture /
|
|
580
582
|
// loadRulePack wiring, and the kidSafe extra.
|
|
583
|
+
// Each policy's vocabulary, so a misspelling is a boot error rather than a
|
|
584
|
+
// runtime surprise. Read leniently, a typo takes whichever branch is not the
|
|
585
|
+
// strict one: `typConfusionPolicy: "rejct"` is not "allow", so the check runs,
|
|
586
|
+
// and it is not "reject" either, so the finding drops from high to warn.
|
|
587
|
+
//
|
|
588
|
+
// `algNonePolicy` and `kidTraversalPolicy` take one value each because both
|
|
589
|
+
// threats are unconditional, as their opts block has always said.
|
|
590
|
+
var POLICY_ENUM = gateContract.policyVocabulary([
|
|
591
|
+
"algAllowlistPolicy", "typConfusionPolicy", "expSanityPolicy",
|
|
592
|
+
"nbfSanityPolicy", "iatSanityPolicy", "critUnknownPolicy",
|
|
593
|
+
], gateContract.POLICY_VALUES.rejectAuditAllow, {
|
|
594
|
+
algNonePolicy: ["reject"],
|
|
595
|
+
kidTraversalPolicy: ["reject"],
|
|
596
|
+
});
|
|
597
|
+
|
|
581
598
|
module.exports = gateContract.defineGuard({
|
|
599
|
+
enumOpts: POLICY_ENUM,
|
|
582
600
|
name: "jwt",
|
|
583
601
|
kind: "identifier",
|
|
584
602
|
errorClass: GuardJwtError,
|
package/lib/guard-markdown.js
CHANGED
|
@@ -1902,7 +1902,32 @@ function render(source, opts) {
|
|
|
1902
1902
|
}
|
|
1903
1903
|
}
|
|
1904
1904
|
|
|
1905
|
+
// Each policy's vocabulary, so a misspelling is a boot error rather than a
|
|
1906
|
+
// runtime surprise. Read leniently, a typo takes whichever branch is not the
|
|
1907
|
+
// strict one: `dangerousSchemePolicy: "rejct"` is not "allow", so the check
|
|
1908
|
+
// runs, and it is not "reject" either, so a `javascript:` link drops to a
|
|
1909
|
+
// warning.
|
|
1910
|
+
//
|
|
1911
|
+
// "strip" is listed only where the opts block offers it — that is, where the
|
|
1912
|
+
// renderer can remove the construct and still produce a document. Advertising
|
|
1913
|
+
// it more widely would route a finding to a repair that never happens, which
|
|
1914
|
+
// is the shape that lets an unrepaired document through as sanitized.
|
|
1915
|
+
//
|
|
1916
|
+
// The character policies are absent on purpose — they are derived for the
|
|
1917
|
+
// whole guard family, and an entry here would shadow that derivation.
|
|
1918
|
+
var REPAIRABLE = ["dangerousTagPolicy", "dangerousSchemePolicy",
|
|
1919
|
+
"autolinkSchemePolicy", "imageSchemePolicy",
|
|
1920
|
+
"referenceLinkPolicy", "doctypePolicy", "codeFenceLangPolicy"];
|
|
1921
|
+
var REPORT_ONLY = ["rawHtmlPolicy", "htmlCommentPolicy", "frontMatterPolicy",
|
|
1922
|
+
"emphasisRunPolicy", "filePolicy"];
|
|
1923
|
+
|
|
1924
|
+
var POLICY_ENUM = gateContract.policyVocabulary(
|
|
1925
|
+
REPAIRABLE, gateContract.POLICY_VALUES.rejectStripAuditAllow,
|
|
1926
|
+
gateContract.policyVocabulary(
|
|
1927
|
+
REPORT_ONLY, gateContract.POLICY_VALUES.rejectAuditAllow));
|
|
1928
|
+
|
|
1905
1929
|
module.exports = gateContract.defineGuard({
|
|
1930
|
+
enumOpts: POLICY_ENUM,
|
|
1906
1931
|
name: "markdown",
|
|
1907
1932
|
kind: "content",
|
|
1908
1933
|
charRepair: true,
|
package/lib/guard-mime.js
CHANGED
|
@@ -328,15 +328,15 @@ function _detectIssues(input, opts) {
|
|
|
328
328
|
* @opts
|
|
329
329
|
* profile: "strict"|"balanced"|"permissive",
|
|
330
330
|
* compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
331
|
-
* bidiPolicy: "reject"|"
|
|
332
|
-
* controlPolicy: "reject"|"
|
|
333
|
-
* nullBytePolicy: "reject"|"
|
|
334
|
-
* zeroWidthPolicy: "reject"|"
|
|
331
|
+
* bidiPolicy: "reject"|"audit"|"allow",
|
|
332
|
+
* controlPolicy: "reject"|"audit"|"allow",
|
|
333
|
+
* nullBytePolicy: "reject"|"audit"|"allow",
|
|
334
|
+
* zeroWidthPolicy: "reject"|"audit"|"allow",
|
|
335
335
|
* wildcardPolicy: "reject"|"audit"|"allow",
|
|
336
336
|
* vendorTreePolicy: "reject"|"audit"|"allow",
|
|
337
337
|
* personalTreePolicy: "reject"|"audit"|"allow",
|
|
338
338
|
* unregisteredTreePolicy: "reject"|"audit"|"allow",
|
|
339
|
-
*
|
|
339
|
+
* riskyTypePolicy: "reject"|"audit"|"allow",
|
|
340
340
|
* parameterPolicy: "reject"|"audit"|"allow",
|
|
341
341
|
* maxBytes: number, // default 256 (RFC-recommended cap)
|
|
342
342
|
*
|
|
@@ -418,7 +418,17 @@ var INTEGRATION_FIXTURES = gateContract.identifierFixtures("application/json", "
|
|
|
418
418
|
// compliancePosture / loadRulePack wiring, plus the per-guard inspection
|
|
419
419
|
// surface (validate / sanitize). The gate is the factory default chain,
|
|
420
420
|
// dispatched to `ctx.identifier` / `ctx.mime` via ctxFields.
|
|
421
|
+
// Each policy's vocabulary, so a misspelling is a boot error rather than a
|
|
422
|
+
// runtime surprise. Read leniently, a typo takes whichever branch is not the
|
|
423
|
+
// strict one: `wildcardPolicy: "rejct"` is not "allow", so the check runs, and
|
|
424
|
+
// it is not "reject" either, so the finding drops from high to warn.
|
|
425
|
+
var POLICY_ENUM = gateContract.policyVocabulary([
|
|
426
|
+
"wildcardPolicy", "vendorTreePolicy", "personalTreePolicy",
|
|
427
|
+
"unregisteredTreePolicy", "riskyTypePolicy", "parameterPolicy",
|
|
428
|
+
], gateContract.POLICY_VALUES.rejectAuditAllow);
|
|
429
|
+
|
|
421
430
|
module.exports = gateContract.defineGuard({
|
|
431
|
+
enumOpts: POLICY_ENUM,
|
|
422
432
|
name: "mime",
|
|
423
433
|
kind: "identifier",
|
|
424
434
|
errorClass: GuardMimeError,
|
package/lib/guard-oauth.js
CHANGED
|
@@ -574,7 +574,30 @@ var INTEGRATION_FIXTURES = Object.freeze({
|
|
|
574
574
|
// surface (validate / sanitize / bespoke gate) passed through verbatim.
|
|
575
575
|
// The custom KIND ("oauth-flow") is accepted because the bespoke gate
|
|
576
576
|
// reads its own ctx fields (ctx.oauthFlow / ctx.flow).
|
|
577
|
+
// Each policy's vocabulary, so a misspelling is a boot error rather than a
|
|
578
|
+
// runtime surprise. Read leniently, a typo takes whichever branch is not the
|
|
579
|
+
// strict one: `statePolicy: "requre"` is not "allow", so the check runs, and
|
|
580
|
+
// it is not "require" either, so a missing state parameter drops to a warning.
|
|
581
|
+
//
|
|
582
|
+
// Several of these open with "require" rather than "reject" because the
|
|
583
|
+
// finding is an absence — no PKCE challenge, no state, no issuer on the
|
|
584
|
+
// callback — so the operator is demanding a parameter, not refusing one.
|
|
585
|
+
var POLICY_ENUM = gateContract.policyVocabulary(
|
|
586
|
+
["scopeTamperingPolicy"], gateContract.POLICY_VALUES.rejectAuditAllow, {
|
|
587
|
+
// No `audit-only`: the PKCE check tests for the literal "audit", so the
|
|
588
|
+
// synonym would take the enforcing branch and refuse a flow that plain
|
|
589
|
+
// "audit" serves with a finding. Refusing the value is the honest answer
|
|
590
|
+
// — the operator picks a spelling that means what they want.
|
|
591
|
+
pkcePolicy: ["require-s256", "require-any", "audit", "allow"],
|
|
592
|
+
statePolicy: ["require", "audit", "audit-only", "allow"],
|
|
593
|
+
redirectUriPolicy: ["require-exact-allowlist", "audit", "audit-only", "allow"],
|
|
594
|
+
responseTypePolicy: ["require-allowlist", "audit", "audit-only", "allow"],
|
|
595
|
+
issuerOnCallbackPolicy: ["require", "audit", "audit-only", "allow"],
|
|
596
|
+
codeReusePolicy: ["reject", "allow"],
|
|
597
|
+
});
|
|
598
|
+
|
|
577
599
|
var _guard = module.exports = gateContract.defineGuard({
|
|
600
|
+
enumOpts: POLICY_ENUM,
|
|
578
601
|
name: "oauth",
|
|
579
602
|
kind: "oauth-flow",
|
|
580
603
|
errorClass: GuardOauthError,
|
package/lib/guard-pdf.js
CHANGED
|
@@ -141,6 +141,20 @@ var DEFAULTS = gateContract.strictDefaults(PROFILES);
|
|
|
141
141
|
// generated validate() does. See guard-image for the full reasoning.
|
|
142
142
|
var INT_OPTS = ["maxBytes", "maxPageCount"];
|
|
143
143
|
|
|
144
|
+
// Each policy's vocabulary, so a misspelling is a boot error rather than a
|
|
145
|
+
// runtime surprise. Read leniently, a typo takes whichever branch is not the
|
|
146
|
+
// strict one: `javascriptPolicy: "rejct"` is not "allow", so the check runs,
|
|
147
|
+
// and it is not "reject" either, so an embedded script drops to a warning.
|
|
148
|
+
//
|
|
149
|
+
// Declared up here because `gate()` below binds its own resolver and needs the
|
|
150
|
+
// same table. Given to the generated path alone, a bad value survived gate
|
|
151
|
+
// construction and surfaced as a refusal on the first PDF request.
|
|
152
|
+
var POLICY_ENUM = gateContract.policyVocabulary([
|
|
153
|
+
"magicPolicy", "javascriptPolicy", "launchActionPolicy", "openActionPolicy",
|
|
154
|
+
"embeddedFilePolicy", "embeddedFileCountPolicy", "encryptedPolicy",
|
|
155
|
+
"polyglotPolicy", "pageCountPolicy",
|
|
156
|
+
], gateContract.POLICY_VALUES.rejectAuditAllow);
|
|
157
|
+
|
|
144
158
|
var COMPLIANCE_POSTURES = gateContract.compliancePostures(PROFILES, { base: 512 });
|
|
145
159
|
|
|
146
160
|
function _hasPdfMagic(buf) {
|
|
@@ -441,9 +455,12 @@ function gate(opts) {
|
|
|
441
455
|
defaults: DEFAULTS,
|
|
442
456
|
errorClass: GuardPdfError,
|
|
443
457
|
errCodePrefix: "pdf",
|
|
444
|
-
// Own resolver, own cap declaration — see guard-image.gate.
|
|
458
|
+
// Own resolver, own cap declaration — see guard-image.gate. The policy
|
|
459
|
+
// vocabulary comes with it, for the same reason: a door that skips it
|
|
460
|
+
// accepts a value the other doors refuse.
|
|
445
461
|
intOpts: INT_OPTS,
|
|
446
462
|
nonNegativeOpts: gateContract.capKeysOf(DEFAULTS),
|
|
463
|
+
enumOpts: POLICY_ENUM,
|
|
447
464
|
});
|
|
448
465
|
return gateContract.buildGuardGate(
|
|
449
466
|
opts.name || "guardPdf:" + (opts.profile || "default"),
|
|
@@ -512,6 +529,7 @@ var INTEGRATION_FIXTURES = Object.freeze({
|
|
|
512
529
|
// (operator-feeds-metadata ctx.metadata reader) is REQUIRED and carries the
|
|
513
530
|
// JavaScript / launch-action / embedded-file chain unchanged.
|
|
514
531
|
module.exports = gateContract.defineGuard({
|
|
532
|
+
enumOpts: POLICY_ENUM,
|
|
515
533
|
name: "pdf",
|
|
516
534
|
kind: "metadata",
|
|
517
535
|
errorClass: GuardPdfError,
|
package/lib/guard-regex.js
CHANGED
|
@@ -201,14 +201,15 @@ function _classUsesSetSyntax(text, from) {
|
|
|
201
201
|
// ---- pattern parsing ------------------------------------------------------
|
|
202
202
|
//
|
|
203
203
|
// Every analysis below reads a parse tree. None of them reads the pattern
|
|
204
|
-
// source. Reading source with regexes
|
|
205
|
-
//
|
|
206
|
-
// past a nested group, one
|
|
207
|
-
// the previous CHARACTER (so the `?` in `\*?`
|
|
208
|
-
// length variation it contributes
|
|
209
|
-
// `{n,m}` (so a longer bound
|
|
210
|
-
//
|
|
211
|
-
//
|
|
204
|
+
// source. Reading source with regexes puts a separate reader behind each
|
|
205
|
+
// analysis, and separate readers draw the token boundaries differently: one
|
|
206
|
+
// cannot see past a nested group, one decides whether a `?` is a quantifier by
|
|
207
|
+
// looking at the previous CHARACTER (so the `?` in `\*?` reads as a lazy marker
|
|
208
|
+
// and the length variation it contributes is lost), one caps the digits inside
|
|
209
|
+
// `{n,m}` (so a longer bound reads as no quantifier at all). Every such
|
|
210
|
+
// disagreement is a way to write a catastrophic pattern that one reader finds
|
|
211
|
+
// and another waves through, and patching them one at a time moves the edge
|
|
212
|
+
// rather than closing it.
|
|
212
213
|
//
|
|
213
214
|
// So: one tokenizer, one tree, and anything it cannot represent becomes an
|
|
214
215
|
// OPAQUE node — which every analysis treats as "cannot prove", never as
|
|
@@ -754,9 +755,9 @@ function _codePointAt(src, at, flags) {
|
|
|
754
755
|
// `k`, but `k` uppercases to `K` and never back to the Kelvin sign, so a pass
|
|
755
756
|
// starting at `K` never reaches it and two branches that both match it were
|
|
756
757
|
// proven disjoint. Which characters an engine treats as equal under `i` is a
|
|
757
|
-
// rule the language states, so the rule is applied
|
|
758
|
-
//
|
|
759
|
-
//
|
|
758
|
+
// rule the language states, so the rule is applied rather than discovered by
|
|
759
|
+
// building a RegExp per pair of characters and seeing which ones match, which
|
|
760
|
+
// would be the screen reaching for the construct it exists to screen.
|
|
760
761
|
//
|
|
761
762
|
// Only characters PRESENT in the pattern can create an overlap between two of
|
|
762
763
|
// its sets, so the comparison is made over that alphabet alone. Pairs whose
|
|
@@ -796,9 +797,9 @@ function _foldGroups(src, flags) {
|
|
|
796
797
|
var x = alphabet[a], y = alphabet[b];
|
|
797
798
|
if (_linkedByCase(x, y)) continue; // already found by folding
|
|
798
799
|
// Which characters an engine treats as the same under `i` is a rule, not
|
|
799
|
-
// something to be discovered by asking.
|
|
800
|
-
//
|
|
801
|
-
//
|
|
800
|
+
// something to be discovered by asking. Building a RegExp per pair and
|
|
801
|
+
// seeing whether one matches the other is the screen reaching for the very
|
|
802
|
+
// construct it screens, and a pattern's worth of them per call.
|
|
802
803
|
// The rule itself is exact and costs a comparison.
|
|
803
804
|
if (_canonical(x, unicodeMode) !== _canonical(y, unicodeMode)) continue;
|
|
804
805
|
_linkFold(groups, x, y);
|
|
@@ -2586,7 +2587,7 @@ function _detectNestedExtglob(input, opts, issues) {
|
|
|
2586
2587
|
* bidiPolicy: "reject"|"audit"|"allow",
|
|
2587
2588
|
* controlPolicy: "reject"|"audit"|"allow",
|
|
2588
2589
|
* nullBytePolicy: "reject"|"audit"|"allow",
|
|
2589
|
-
* zeroWidthPolicy: "reject"|"
|
|
2590
|
+
* zeroWidthPolicy: "reject"|"audit"|"allow",
|
|
2590
2591
|
* nestedQuantPolicy: "reject"|"audit"|"allow",
|
|
2591
2592
|
* alternationQuantPolicy: "reject"|"audit"|"allow",
|
|
2592
2593
|
* boundedRepeatPolicy: "reject"|"audit"|"allow",
|
|
@@ -2840,7 +2841,22 @@ function assertSafe(input, label, ErrorClass, code, opts) {
|
|
|
2840
2841
|
// compliancePosture / loadRulePack wiring, plus the per-guard inspection
|
|
2841
2842
|
// surface (validate / sanitize / gate). The bespoke `gate` carries
|
|
2842
2843
|
// guardRegex's ctx.identifier || ctx.pattern dispatch unchanged.
|
|
2844
|
+
// Each policy's vocabulary, so a misspelling is a boot error rather than a
|
|
2845
|
+
// runtime surprise. Read leniently, a typo takes whichever branch is not the
|
|
2846
|
+
// strict one: `nestedQuantPolicy: "rejct"` is not "allow", so the check runs,
|
|
2847
|
+
// and it is not "reject" either, so a catastrophic-backtracking pattern drops
|
|
2848
|
+
// from high to warn.
|
|
2849
|
+
//
|
|
2850
|
+
// The character policies are absent on purpose — they are derived for the
|
|
2851
|
+
// whole guard family, and an entry here would shadow that derivation.
|
|
2852
|
+
var POLICY_ENUM = gateContract.policyVocabulary([
|
|
2853
|
+
"nestedQuantPolicy", "alternationQuantPolicy", "boundedRepeatPolicy",
|
|
2854
|
+
"lookaroundQuantPolicy", "consecutiveStarPolicy", "nestedExtglobPolicy",
|
|
2855
|
+
"unanchoredScanPolicy",
|
|
2856
|
+
], gateContract.POLICY_VALUES.rejectAuditAllow);
|
|
2857
|
+
|
|
2843
2858
|
var _guard = module.exports = gateContract.defineGuard({
|
|
2859
|
+
enumOpts: POLICY_ENUM,
|
|
2844
2860
|
name: "regex",
|
|
2845
2861
|
kind: "identifier",
|
|
2846
2862
|
errorClass: GuardRegexError,
|
package/lib/guard-shell.js
CHANGED
|
@@ -241,7 +241,7 @@ function _detectIssues(input, opts) {
|
|
|
241
241
|
* bidiPolicy: "reject"|"audit"|"allow",
|
|
242
242
|
* controlPolicy: "reject"|"audit"|"allow",
|
|
243
243
|
* nullBytePolicy: "reject"|"audit"|"allow",
|
|
244
|
-
* zeroWidthPolicy: "reject"|"
|
|
244
|
+
* zeroWidthPolicy: "reject"|"audit"|"allow",
|
|
245
245
|
* posixMetaPolicy: "reject"|"audit"|"allow",
|
|
246
246
|
* cmdMetaPolicy: "reject"|"audit"|"allow",
|
|
247
247
|
* dollarSubstPolicy: "reject"|"audit"|"allow",
|
|
@@ -338,7 +338,20 @@ var INTEGRATION_FIXTURES = gateContract.identifierFixtures("safe-arg-value", "sa
|
|
|
338
338
|
// compliancePosture / loadRulePack wiring, plus the per-guard inspection
|
|
339
339
|
// surface (validate / sanitize). The gate is the factory default chain,
|
|
340
340
|
// dispatched to `ctx.identifier` / `ctx.arg` via ctxFields.
|
|
341
|
+
// Each policy's vocabulary, so a misspelling is a boot error rather than a
|
|
342
|
+
// runtime surprise. Read leniently, a typo takes whichever branch is not the
|
|
343
|
+
// strict one: `backtickPolicy: "rejct"` is not "allow", so the check runs, and
|
|
344
|
+
// it is not "reject" either, so command substitution drops to a warning.
|
|
345
|
+
//
|
|
346
|
+
// The character policies are absent on purpose — they are derived for the
|
|
347
|
+
// whole guard family, and an entry here would shadow that derivation.
|
|
348
|
+
var POLICY_ENUM = gateContract.policyVocabulary([
|
|
349
|
+
"posixMetaPolicy", "cmdMetaPolicy", "dollarSubstPolicy", "processSubstPolicy",
|
|
350
|
+
"backtickPolicy", "newlinePolicy", "argHyphenPolicy",
|
|
351
|
+
], gateContract.POLICY_VALUES.rejectAuditAllow);
|
|
352
|
+
|
|
341
353
|
module.exports = gateContract.defineGuard({
|
|
354
|
+
enumOpts: POLICY_ENUM,
|
|
342
355
|
name: "shell",
|
|
343
356
|
kind: "identifier",
|
|
344
357
|
errorClass: GuardShellError,
|
package/lib/guard-svg.js
CHANGED
|
@@ -1005,7 +1005,24 @@ var INTEGRATION_FIXTURES = Object.freeze({
|
|
|
1005
1005
|
// unchanged; neither reduces to the dynamic
|
|
1006
1006
|
// detect→throwOnRefusalSeverity→transform path (same-severity findings
|
|
1007
1007
|
// split throw-vs-strip), so both stay bespoke.
|
|
1008
|
+
// Each policy's vocabulary, so a misspelling is a boot error rather than a
|
|
1009
|
+
// runtime surprise. Read leniently, a typo takes whichever branch is not the
|
|
1010
|
+
// strict one: `cdataPolicy: "rejct"` is not "allow", so the check runs, and it
|
|
1011
|
+
// is not "reject" either, so the finding drops from critical to warn.
|
|
1012
|
+
//
|
|
1013
|
+
// `svgzPolicy` takes one value: a gzip signature is refused before any parse
|
|
1014
|
+
// and sanitize throws on it, so no other value has ever meant anything.
|
|
1015
|
+
//
|
|
1016
|
+
// The character policies are absent on purpose — they are derived for the whole
|
|
1017
|
+
// guard family, and an entry here would shadow that derivation.
|
|
1018
|
+
var POLICY_ENUM = gateContract.policyVocabulary([
|
|
1019
|
+
"cssPolicy", "doctypePolicy", "cdataPolicy", "processingInstrPolicy",
|
|
1020
|
+
], gateContract.POLICY_VALUES.rejectStripAuditAllow, {
|
|
1021
|
+
svgzPolicy: ["reject"],
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1008
1024
|
var _guard = module.exports = gateContract.defineGuard({
|
|
1025
|
+
enumOpts: POLICY_ENUM,
|
|
1009
1026
|
name: "svg",
|
|
1010
1027
|
kind: "content",
|
|
1011
1028
|
charRepair: true,
|
package/lib/guard-template.js
CHANGED
|
@@ -228,7 +228,7 @@ function _detectIssues(input, opts) {
|
|
|
228
228
|
* bidiPolicy: "reject"|"audit"|"allow",
|
|
229
229
|
* controlPolicy: "reject"|"audit"|"allow",
|
|
230
230
|
* nullBytePolicy: "reject"|"audit"|"allow",
|
|
231
|
-
* zeroWidthPolicy: "reject"|"
|
|
231
|
+
* zeroWidthPolicy: "reject"|"audit"|"allow",
|
|
232
232
|
* jinjaPolicy: "reject"|"audit"|"allow",
|
|
233
233
|
* erbPolicy: "reject"|"audit"|"allow",
|
|
234
234
|
* pugPolicy: "reject"|"audit"|"allow",
|
|
@@ -316,7 +316,20 @@ var INTEGRATION_FIXTURES = gateContract.identifierFixtures("Hello world", "Hello
|
|
|
316
316
|
// factory default serve -> audit-only -> refuse chain; ctxFields names the
|
|
317
317
|
// ctx fields it reads (ctx.identifier, then ctx.text) so untrusted strings on
|
|
318
318
|
// either field reach the SSTI validator before any engine renders them.
|
|
319
|
+
// Each policy's vocabulary, so a misspelling is a boot error rather than a
|
|
320
|
+
// runtime surprise. Read leniently, a typo takes whichever branch is not the
|
|
321
|
+
// strict one: `jinjaPolicy: "rejct"` is not "allow", so the check runs, and it
|
|
322
|
+
// is not "reject" either, so the finding drops to a warning.
|
|
323
|
+
//
|
|
324
|
+
// The character policies are absent on purpose — they are derived for the
|
|
325
|
+
// whole guard family, and an entry here would shadow that derivation.
|
|
326
|
+
var POLICY_ENUM = gateContract.policyVocabulary([
|
|
327
|
+
"jinjaPolicy", "erbPolicy", "pugPolicy", "dollarBracePolicy",
|
|
328
|
+
"velocityDirectivePolicy",
|
|
329
|
+
], gateContract.POLICY_VALUES.rejectAuditAllow);
|
|
330
|
+
|
|
319
331
|
module.exports = gateContract.defineGuard({
|
|
332
|
+
enumOpts: POLICY_ENUM,
|
|
320
333
|
name: "template",
|
|
321
334
|
kind: "identifier",
|
|
322
335
|
errorClass: GuardTemplateError,
|