projen-pipelines 0.3.21 → 0.3.23

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.
Files changed (123) hide show
  1. package/.jsii +7 -4
  2. package/API.md +420 -0
  3. package/lib/assign-approver/base.js +3 -4
  4. package/lib/assign-approver/github.js +4 -4
  5. package/lib/awscdk/base.js +48 -6
  6. package/lib/awscdk/bash.js +2 -4
  7. package/lib/awscdk/github.js +13 -6
  8. package/lib/awscdk/gitlab.js +11 -6
  9. package/lib/drift/base.js +8 -4
  10. package/lib/drift/bash.js +3 -4
  11. package/lib/drift/detect-drift.js +3 -2
  12. package/lib/drift/github.js +5 -4
  13. package/lib/drift/gitlab.js +5 -4
  14. package/lib/drift/step.js +2 -4
  15. package/lib/release.js +5 -2
  16. package/lib/steps/amplify-deploy.step.js +5 -4
  17. package/lib/steps/artifact-steps.js +5 -6
  18. package/lib/steps/aws-assume-role.step.js +3 -4
  19. package/lib/steps/github-summary.step.js +3 -4
  20. package/lib/steps/package-manager-setup.step.js +4 -6
  21. package/lib/steps/registries.js +7 -8
  22. package/lib/steps/set-env.step.js +2 -1
  23. package/lib/steps/step.js +9 -10
  24. package/lib/versioning/computation.js +6 -8
  25. package/lib/versioning/config.js +3 -6
  26. package/lib/versioning/outputs.js +13 -16
  27. package/lib/versioning/setup.js +9 -9
  28. package/lib/versioning/strategy.js +4 -4
  29. package/lib/versioning/version-info.js +18 -9
  30. package/node_modules/@babel/code-frame/package.json +2 -2
  31. package/node_modules/@babel/helper-validator-identifier/package.json +1 -1
  32. package/node_modules/@nodable/entities/README.md +52 -1
  33. package/node_modules/@nodable/entities/package.json +4 -1
  34. package/node_modules/@nodable/entities/src/EntityDecoder.js +105 -4
  35. package/node_modules/@nodable/entities/src/EntityEncoder.js +79 -34
  36. package/node_modules/@nodable/entities/src/all-entities.js +53 -0
  37. package/node_modules/@nodable/entities/src/entities.js +0 -38
  38. package/node_modules/@nodable/entities/src/entityTries.js +48 -37
  39. package/node_modules/@nodable/entities/src/index.d.ts +93 -2
  40. package/node_modules/@nodable/entities/src/index.js +2 -2
  41. package/node_modules/anynum/LICENSE +21 -0
  42. package/node_modules/anynum/README.md +142 -0
  43. package/node_modules/anynum/anynum.js +135 -0
  44. package/node_modules/anynum/digitTable.js +116 -0
  45. package/node_modules/anynum/package.json +42 -0
  46. package/node_modules/dotgitignore/node_modules/brace-expansion/README.md +23 -1
  47. package/node_modules/dotgitignore/node_modules/brace-expansion/index.js +265 -86
  48. package/node_modules/dotgitignore/node_modules/brace-expansion/package.json +1 -1
  49. package/node_modules/fast-xml-builder/CHANGELOG.md +12 -0
  50. package/node_modules/fast-xml-builder/README.md +4 -1
  51. package/node_modules/fast-xml-builder/lib/fxb.cjs +1 -1
  52. package/node_modules/fast-xml-builder/lib/fxb.min.js +1 -1
  53. package/node_modules/fast-xml-builder/lib/fxb.min.js.map +1 -1
  54. package/node_modules/fast-xml-builder/package.json +6 -10
  55. package/node_modules/fast-xml-builder/src/fxb.js +34 -24
  56. package/node_modules/fast-xml-builder/src/orderedJs2Xml.js +18 -17
  57. package/node_modules/fast-xml-builder/src/util.js +10 -3
  58. package/node_modules/fast-xml-parser/CHANGELOG.md +34 -0
  59. package/node_modules/fast-xml-parser/README.md +10 -6
  60. package/node_modules/fast-xml-parser/lib/fxbuilder.min.js +1 -1
  61. package/node_modules/fast-xml-parser/lib/fxbuilder.min.js.map +1 -1
  62. package/node_modules/fast-xml-parser/lib/fxp.cjs +1 -1
  63. package/node_modules/fast-xml-parser/lib/fxp.d.cts +3 -1
  64. package/node_modules/fast-xml-parser/lib/fxp.min.js +1 -1
  65. package/node_modules/fast-xml-parser/lib/fxp.min.js.map +1 -1
  66. package/node_modules/fast-xml-parser/lib/fxparser.min.js +1 -1
  67. package/node_modules/fast-xml-parser/lib/fxparser.min.js.map +1 -1
  68. package/node_modules/fast-xml-parser/package.json +8 -10
  69. package/node_modules/fast-xml-parser/src/fxp.d.ts +5 -1
  70. package/node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js +16 -1
  71. package/node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js +2 -1
  72. package/node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js +36 -3
  73. package/node_modules/fast-xml-parser/src/xmlparser/xmlNode.js +14 -0
  74. package/node_modules/get-pkg-repo/node_modules/yargs/package.json +27 -26
  75. package/node_modules/get-pkg-repo/node_modules/yargs/yargs +5 -9
  76. package/node_modules/get-pkg-repo/node_modules/yargs/yargs.cjs +15 -0
  77. package/node_modules/hasown/CHANGELOG.md +7 -0
  78. package/node_modules/hasown/index.d.ts +0 -1
  79. package/node_modules/hasown/package.json +4 -5
  80. package/node_modules/is-unsafe/LICENSE +21 -0
  81. package/node_modules/is-unsafe/README.md +599 -0
  82. package/node_modules/is-unsafe/package.json +56 -0
  83. package/node_modules/is-unsafe/src/contexts/html.js +102 -0
  84. package/node_modules/is-unsafe/src/contexts/log.js +98 -0
  85. package/node_modules/is-unsafe/src/contexts/nosql.js +114 -0
  86. package/node_modules/is-unsafe/src/contexts/redos.js +60 -0
  87. package/node_modules/is-unsafe/src/contexts/shell.js +105 -0
  88. package/node_modules/is-unsafe/src/contexts/sql-strict.js +47 -0
  89. package/node_modules/is-unsafe/src/contexts/sql.js +95 -0
  90. package/node_modules/is-unsafe/src/contexts/svg.js +81 -0
  91. package/node_modules/is-unsafe/src/contexts/xml.js +79 -0
  92. package/node_modules/is-unsafe/src/index.cjs +27 -0
  93. package/node_modules/is-unsafe/src/index.d.ts +95 -0
  94. package/node_modules/is-unsafe/src/index.js +265 -0
  95. package/node_modules/path-expression-matcher/README.md +82 -7
  96. package/node_modules/path-expression-matcher/lib/pem.cjs +1 -1
  97. package/node_modules/path-expression-matcher/lib/pem.d.cts +94 -3
  98. package/node_modules/path-expression-matcher/lib/pem.min.js +1 -1
  99. package/node_modules/path-expression-matcher/lib/pem.min.js.map +1 -1
  100. package/node_modules/path-expression-matcher/package.json +2 -1
  101. package/node_modules/path-expression-matcher/src/ExpressionSet.js +18 -2
  102. package/node_modules/path-expression-matcher/src/Matcher.js +109 -16
  103. package/node_modules/path-expression-matcher/src/index.d.ts +67 -1
  104. package/node_modules/path-expression-matcher/src/index.js +3 -2
  105. package/node_modules/semver/classes/range.js +24 -4
  106. package/node_modules/semver/classes/semver.js +19 -2
  107. package/node_modules/semver/package.json +2 -2
  108. package/node_modules/semver/ranges/subset.js +2 -2
  109. package/node_modules/strnum/CHANGELOG.md +14 -0
  110. package/node_modules/strnum/README.md +9 -0
  111. package/node_modules/strnum/package.json +7 -3
  112. package/node_modules/strnum/strnum.js +14 -2
  113. package/node_modules/xml-naming/LICENSE +21 -0
  114. package/node_modules/xml-naming/README.md +99 -1
  115. package/node_modules/xml-naming/package.json +4 -2
  116. package/node_modules/xml-naming/src/index.d.ts +34 -0
  117. package/node_modules/xml-naming/src/index.js +134 -22
  118. package/node_modules/yargs/build/index.cjs +1 -1
  119. package/node_modules/yargs/package.json +6 -4
  120. package/node_modules/yargs/yargs +5 -9
  121. package/node_modules/yargs/yargs.cjs +15 -0
  122. package/package.json +8 -9
  123. package/node_modules/yargs/browser.d.ts +0 -5
@@ -0,0 +1,81 @@
1
+ /**
2
+ * SVG context patterns.
3
+ *
4
+ * SVG is XML-based but renders in browsers, giving it a unique attack surface
5
+ * that combines XML parser behaviour with browser rendering and JavaScript execution.
6
+ *
7
+ * Many of these vectors bypass HTML sanitizers that don't understand SVG semantics
8
+ * (DOMPurify has documented bypass vulnerabilities specifically in SVG/XML context).
9
+ */
10
+
11
+ const SVG_PATTERNS = [
12
+ {
13
+ id: 'svg-script-element',
14
+ description: '<script element inside SVG executes JavaScript',
15
+ pattern: /<script[\s>/]/i,
16
+ },
17
+ {
18
+ id: 'svg-xlink-href-javascript',
19
+ description: 'xlink:href with javascript: — classic SVG XSS via <a> or <use>',
20
+ pattern: /xlink\s*:\s*href\s*=\s*["']?\s*javascript\s*:/i,
21
+ },
22
+ {
23
+ id: 'svg-href-javascript',
24
+ description: 'href= with javascript: in SVG context (<a>, <animate>, etc.)',
25
+ pattern: /href\s*=\s*["']?\s*javascript\s*:/i,
26
+ },
27
+ {
28
+ id: 'svg-foreignobject',
29
+ description: '<foreignObject embeds HTML inside SVG — can execute scripts',
30
+ pattern: /<foreignObject[\s>/]/i,
31
+ },
32
+ {
33
+ id: 'svg-use-external',
34
+ description: '<use xlink:href or href pointing to external resource (non-fragment URL)',
35
+ // Match <use with href= where the value starts with a non-# character (external URL)
36
+ // [\"'][^#] catches quoted values not starting with #; [^\"'#\s>] catches unquoted
37
+ pattern: /<use[\s\S]{0,60}(?:xlink\s*:\s*)?href\s*=\s*(?:["'][^#]|[^"'#\s>])/i,
38
+ },
39
+ {
40
+ id: 'svg-animate-href',
41
+ description: '<animate attributeName="href" — can dynamically change href to javascript:',
42
+ pattern: /<animate[\s\S]{0,80}attributeName\s*=\s*["'][\s]*href["']/i,
43
+ },
44
+ {
45
+ id: 'svg-animate-xlinkhref',
46
+ description: '<animate attributeName="xlink:href"',
47
+ pattern: /<animate[\s\S]{0,80}attributeName\s*=\s*["'][\s]*xlink\s*:\s*href["']/i,
48
+ },
49
+ {
50
+ id: 'svg-set-javascript',
51
+ description: '<set to="javascript:..." — sets an attribute to a javascript: URI',
52
+ pattern: /<set[\s\S]{0,80}to\s*=\s*["']?\s*javascript\s*:/i,
53
+ },
54
+ {
55
+ id: 'svg-event-handler',
56
+ description: 'SVG-specific event handler attributes: onload=, onerror=, onactivate=, etc.',
57
+ pattern: /\bon(?:load|error|activate|begin|end|repeat|focus|blur|click|mouse\w{1,20}|key\w{1,20})\s*=/i,
58
+ },
59
+ {
60
+ id: 'svg-handler-generic',
61
+ description: 'Generic on* handler catch-all for SVG attributes',
62
+ pattern: /\bon\w{1,30}\s*=/i,
63
+ },
64
+ {
65
+ id: 'svg-filter-feimage',
66
+ description: '<feImage href= — filter primitive that can load external resources',
67
+ pattern: /<feImage[\s\S]{0,80}(?:xlink\s*:\s*)?href\s*=/i,
68
+ },
69
+ {
70
+ id: 'svg-image-external',
71
+ description: '<image xlink:href with http/https or javascript protocol',
72
+ pattern: /<image[\s\S]{0,80}(?:xlink\s*:\s*)?href\s*=\s*["']?\s*(?:https?|javascript)\s*:/i,
73
+ },
74
+ {
75
+ id: 'svg-style-javascript',
76
+ description: 'style= attribute containing javascript: (e.g. background:url(javascript:...))',
77
+ pattern: /style\s*=[\s\S]{0,60}javascript\s*:/i,
78
+ },
79
+ ];
80
+
81
+ export default SVG_PATTERNS;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * XML context patterns.
3
+ *
4
+ * Detects injection vectors that are specifically dangerous when a string
5
+ * is inserted into an XML document (not HTML rendering context).
6
+ *
7
+ * Key distinction from HTML: these patterns target parser-level attacks —
8
+ * things that can confuse or subvert an XML parser, trigger external entity
9
+ * resolution, or inject DTD content. HTML rendering concerns (XSS) belong
10
+ * in the HTML context.
11
+ */
12
+
13
+ const XML_PATTERNS = [
14
+ {
15
+ id: 'xml-cdata-injection',
16
+ description: 'CDATA section injection: <![CDATA[ breaks out of text node context',
17
+ pattern: /<!\[CDATA\[/i,
18
+ },
19
+ {
20
+ id: 'xml-cdata-close',
21
+ description: 'CDATA close sequence: ]]> can terminate an enclosing CDATA section',
22
+ pattern: /\]\]>/,
23
+ },
24
+ {
25
+ id: 'xml-processing-instruction',
26
+ description: 'XML processing instruction: <?xml-stylesheet or <?php etc.',
27
+ pattern: /<\?(?:xml[\- ]|php|asp)/i,
28
+ },
29
+ {
30
+ id: 'xml-doctype-injection',
31
+ description: 'DOCTYPE declaration embedded in content — can define entities',
32
+ // Match <!DOCTYPE followed by end-of-string, whitespace, or [ (internal subset)
33
+ pattern: /<!DOCTYPE(?:[\s[]|$)/i,
34
+ },
35
+ {
36
+ id: 'xml-entity-system',
37
+ description: 'SYSTEM keyword — used in external entity declarations (XXE)',
38
+ pattern: /\bSYSTEM\s+["']/i,
39
+ },
40
+ {
41
+ id: 'xml-entity-public',
42
+ description: 'PUBLIC keyword — used in external entity declarations (XXE)',
43
+ pattern: /\bPUBLIC\s+["']/i,
44
+ },
45
+ {
46
+ id: 'xml-entity-declaration',
47
+ description: '<!ENTITY declaration — defines entities, potential XXE or entity expansion',
48
+ pattern: /<!ENTITY[\s%]/i,
49
+ },
50
+ {
51
+ id: 'xml-billion-laughs',
52
+ description: 'Entity reference chaining / billion laughs: repeated &eX; style references',
53
+ // Heuristic: 3+ consecutive entity refs suggests expansion attack
54
+ pattern: /(?:&\w{1,20};){3,}/,
55
+ },
56
+ {
57
+ id: 'xml-namespace-confusion',
58
+ description: 'xmlns: attribute injection — can redefine namespaces to confuse parsers',
59
+ // pattern: /\bxmlns\s*(?::\w{1,40})?\s*=/i,
60
+ pattern: /\bxmlns(?::\w{1,40})?\s*=/i,
61
+ },
62
+ {
63
+ id: 'xml-comment-injection',
64
+ description: '<!-- comment injection — can hide content from some parsers',
65
+ pattern: /<!--/,
66
+ },
67
+ {
68
+ id: 'xml-comment-close',
69
+ description: '--> closes an enclosing XML comment',
70
+ pattern: /-->/,
71
+ },
72
+ {
73
+ id: 'xml-pi-close',
74
+ description: '?> closes an enclosing processing instruction',
75
+ pattern: /\?>/,
76
+ },
77
+ ];
78
+
79
+ export default XML_PATTERNS;
@@ -0,0 +1,27 @@
1
+ // CommonJS shim — generated from src/index.js
2
+ // For bundlers and Node.js environments that require CJS interop.
3
+ // This file re-exports via dynamic import to bridge ESM → CJS.
4
+
5
+ let _mod;
6
+ async function _load() {
7
+ if (!_mod) _mod = await import('./index.js');
8
+ return _mod;
9
+ }
10
+
11
+ // Synchronous-style exports via module.exports proxy
12
+ // (Works for environments that await the module, e.g. Jest with transformIgnorePatterns)
13
+ module.exports = new Proxy(
14
+ {},
15
+ {
16
+ get(_, key) {
17
+ throw new Error(
18
+ `is-unsafe: CommonJS require() is not fully supported. ` +
19
+ `Use dynamic import(): const { isUnsafe } = await import('is-unsafe'). ` +
20
+ `Or set "type": "module" in your package.json.`
21
+ );
22
+ },
23
+ }
24
+ );
25
+
26
+ // Export the loader for async CJS callers
27
+ module.exports.load = _load;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * is-unsafe v2 — TypeScript declarations
3
+ *
4
+ * Each context is a tree-shakeable named export. Import only what you need.
5
+ */
6
+
7
+ /** A single detection rule: a pattern plus metadata. */
8
+ export interface Rule {
9
+ id: string;
10
+ description: string;
11
+ pattern: RegExp;
12
+ }
13
+
14
+ /**
15
+ * An ordered list of Rules for a given injection context.
16
+ *
17
+ * Built-in contexts (HTML, XML, etc.) have `label` pre-set by the library.
18
+ * Custom lists default to `'CUSTOM'` in MatchResult unless you set `label`:
19
+ *
20
+ * ```js
21
+ * const MY_RULES = [{ id: 'my-rule', description: '...', pattern: /danger/ }];
22
+ * MY_RULES.label = 'MY_CONTEXT';
23
+ * whyUnsafe('danger', MY_RULES); // → { context: 'MY_CONTEXT', ... }
24
+ * ```
25
+ */
26
+ export type PatternList = Rule[] & { label?: string };
27
+
28
+ /**
29
+ * Valid context argument:
30
+ * - A single PatternList (e.g. `HTML`)
31
+ * - An array of PatternLists (e.g. `[HTML, XML]`) — unsafe if matches ANY
32
+ * - A custom RegExp
33
+ */
34
+ export type ContextArg = PatternList | PatternList[] | RegExp;
35
+
36
+ /** Describes which rule matched and why. */
37
+ export interface MatchResult {
38
+ /** Context label ('HTML', 'XML', 'CUSTOM', etc.) */
39
+ context: string;
40
+ id: string;
41
+ description: string;
42
+ pattern: RegExp;
43
+ }
44
+
45
+ // ─── Named context exports (tree-shakeable) ────────────────────────────────
46
+
47
+ export const HTML: PatternList;
48
+ export const XML: PatternList;
49
+ export const SVG: PatternList;
50
+ export const SQL: PatternList;
51
+ export const SQL_STRICT: PatternList;
52
+ export const SHELL: PatternList;
53
+ export const REDOS: PatternList;
54
+ export const NOSQL: PatternList;
55
+ export const LOG: PatternList;
56
+
57
+ // ─── VALID_CONTEXTS (convenience — pulls in all contexts) ──────────────────
58
+
59
+ export const VALID_CONTEXTS: {
60
+ readonly HTML: PatternList;
61
+ readonly XML: PatternList;
62
+ readonly SVG: PatternList;
63
+ readonly SQL: PatternList;
64
+ readonly 'SQL-STRICT': PatternList;
65
+ readonly SHELL: PatternList;
66
+ readonly REDOS: PatternList;
67
+ readonly NOSQL: PatternList;
68
+ readonly LOG: PatternList;
69
+ };
70
+
71
+ // ─── Core functions ────────────────────────────────────────────────────────
72
+
73
+ /**
74
+ * Returns `true` if `value` is unsafe in the given context(s).
75
+ *
76
+ * @example
77
+ * import { isUnsafe, HTML, XML } from 'is-unsafe';
78
+ * isUnsafe('<script>alert(1)</script>', HTML) // true
79
+ * isUnsafe('hello', [HTML, XML]) // false
80
+ * isUnsafe('hello', /danger/) // false
81
+ */
82
+ export function isUnsafe(value: string, context: ContextArg): boolean;
83
+
84
+ /**
85
+ * Returns the first `MatchResult` explaining why `value` is unsafe, or `null` if safe.
86
+ */
87
+ export function whyUnsafe(value: string, context: ContextArg): MatchResult | null;
88
+
89
+ /**
90
+ * Returns all matching rules across the given context(s), or `[]` if safe.
91
+ */
92
+ export function allUnsafe(value: string, context: ContextArg): MatchResult[];
93
+
94
+ declare const isUnsafeDefault: typeof isUnsafe;
95
+ export default isUnsafeDefault;
@@ -0,0 +1,265 @@
1
+ /**
2
+ * is-unsafe v2
3
+ *
4
+ * Zero-dependency, DOM-free, pure predicate for detecting unsafe strings
5
+ * across HTML, XML, SVG, SQL, SQL-STRICT, SHELL, REDOS, NOSQL, and LOG contexts.
6
+ *
7
+ * v2 change: contexts are imported as named pattern arrays rather than resolved
8
+ * via a string-keyed registry. This makes each context independently
9
+ * tree-shakeable — bundlers can drop any context you never import.
10
+ *
11
+ * @module is-unsafe
12
+ */
13
+
14
+ // ─── Context pattern arrays (named exports) ────────────────────────────────
15
+ // Import only the ones you need. Each is independently tree-shakeable.
16
+
17
+ export { default as HTML } from './contexts/html.js';
18
+ export { default as XML } from './contexts/xml.js';
19
+ export { default as SVG } from './contexts/svg.js';
20
+ export { default as SQL } from './contexts/sql.js';
21
+ export { default as SHELL } from './contexts/shell.js';
22
+ export { default as REDOS } from './contexts/redos.js';
23
+ export { default as NOSQL } from './contexts/nosql.js';
24
+ export { default as LOG } from './contexts/log.js';
25
+
26
+ // SQL-STRICT needs a quoted identifier because of the hyphen
27
+ import SQL_STRICT from './contexts/sql-strict.js';
28
+ export { SQL_STRICT };
29
+
30
+ // ─── VALID_CONTEXTS convenience re-export ─────────────────────────────────
31
+ // Importing this pulls in ALL contexts. Use it only when you need all of them
32
+ // (e.g. for validation UI, tooling, or exhaustive audits).
33
+ // If you only need a subset, import the named contexts directly instead.
34
+
35
+ import HTML from './contexts/html.js';
36
+ import XML from './contexts/xml.js';
37
+ import SVG from './contexts/svg.js';
38
+ import SQL from './contexts/sql.js';
39
+ import SHELL from './contexts/shell.js';
40
+ import REDOS from './contexts/redos.js';
41
+ import NOSQL from './contexts/nosql.js';
42
+ import LOG from './contexts/log.js';
43
+
44
+ // ─── Attach labels to named contexts ──────────────────────────────────────
45
+ // Each built-in PatternList carries its canonical name so matchList can read
46
+ // list.label directly — no registry lookup needed at match time.
47
+ // Custom PatternLists default to 'CUSTOM' unless the caller sets list.label.
48
+
49
+ HTML.label = 'HTML';
50
+ XML.label = 'XML';
51
+ SVG.label = 'SVG';
52
+ SQL.label = 'SQL';
53
+ SQL_STRICT.label = 'SQL-STRICT';
54
+ SHELL.label = 'SHELL';
55
+ REDOS.label = 'REDOS';
56
+ NOSQL.label = 'NOSQL';
57
+ LOG.label = 'LOG';
58
+
59
+ export const VALID_CONTEXTS = Object.freeze({
60
+ HTML,
61
+ XML,
62
+ SVG,
63
+ SQL,
64
+ 'SQL-STRICT': SQL_STRICT,
65
+ SHELL,
66
+ REDOS,
67
+ NOSQL,
68
+ LOG,
69
+ });
70
+
71
+ // ─── Types ────────────────────────────────────────────────────────────────
72
+
73
+ /**
74
+ * @typedef {{ id: string, description: string, pattern: RegExp }} Rule
75
+ */
76
+
77
+ /**
78
+ * @typedef {Rule[]} PatternList
79
+ */
80
+
81
+ /**
82
+ * @typedef {Object} MatchResult
83
+ * @property {string} context - Label identifying which context matched ('HTML', 'CUSTOM', etc.)
84
+ * @property {string} id - Rule identifier
85
+ * @property {string} description - Human-readable description of what was matched
86
+ * @property {RegExp} pattern - The pattern that matched
87
+ */
88
+
89
+ // ─── Internal helpers ──────────────────────────────────────────────────────
90
+
91
+ /**
92
+ * @param {unknown} value
93
+ */
94
+ function assertString(value) {
95
+ if (typeof value !== 'string') {
96
+ throw new TypeError(
97
+ `is-unsafe: first argument must be a string, got ${typeof value}`
98
+ );
99
+ }
100
+ }
101
+
102
+ /**
103
+ * @param {unknown} context
104
+ */
105
+ function assertContext(context) {
106
+ if (context instanceof RegExp) return;
107
+
108
+ if (Array.isArray(context)) {
109
+ if (context.length === 0) {
110
+ throw new TypeError('is-unsafe: context must not be an empty array');
111
+ }
112
+ // Detect array-of-arrays vs flat pattern list
113
+ if (Array.isArray(context[0])) {
114
+ // Array of PatternLists
115
+ for (const list of context) {
116
+ if (!Array.isArray(list) || list.length === 0) {
117
+ throw new TypeError(
118
+ 'is-unsafe: each context in the array must be a non-empty pattern array (PatternList)'
119
+ );
120
+ }
121
+ }
122
+ }
123
+ // else: flat PatternList — trust it, no deep validation needed
124
+ return;
125
+ }
126
+
127
+ throw new TypeError(
128
+ `is-unsafe: second argument must be a PatternList (e.g. HTML), ` +
129
+ `an array of PatternLists (e.g. [HTML, XML]), or a RegExp. Got: ${typeof context}`
130
+ );
131
+ }
132
+
133
+ /**
134
+ * Normalise any valid context arg into an array of PatternLists.
135
+ *
136
+ * @param {Rule[]|Rule[][]|RegExp} context
137
+ * @returns {{ lists: Rule[][]|null, regex: RegExp|null }}
138
+ */
139
+ function normalise(context) {
140
+ if (context instanceof RegExp) return { lists: null, regex: context };
141
+ // Distinguish PatternList (array of rule objects) from array of PatternLists
142
+ if (Array.isArray(context[0])) return { lists: context, regex: null };
143
+ return { lists: [context], regex: null };
144
+ }
145
+
146
+ /**
147
+ * Test value against a single PatternList. Returns the first MatchResult or null.
148
+ *
149
+ * @param {string} value
150
+ * @param {Rule[]} list
151
+ * @returns {MatchResult|null}
152
+ */
153
+ function matchList(value, list) {
154
+ const label = list.label ?? 'CUSTOM';
155
+ for (const rule of list) {
156
+ if (rule.pattern.test(value)) {
157
+ return { context: label, id: rule.id, description: rule.description, pattern: rule.pattern };
158
+ }
159
+ }
160
+ return null;
161
+ }
162
+
163
+ // ─── Public API ───────────────────────────────────────────────────────────
164
+
165
+ /**
166
+ * Returns `true` if `value` is unsafe in the given context(s), `false` otherwise.
167
+ *
168
+ * @param {string} value - The string to test
169
+ * @param {PatternList | PatternList[] | RegExp} context
170
+ * - A PatternList imported from is-unsafe (e.g. `HTML`, `XML`)
171
+ * - An array of PatternLists — returns true if unsafe in **any** of them
172
+ * - A custom RegExp — returns true if the pattern matches
173
+ * @returns {boolean}
174
+ *
175
+ * @example
176
+ * import { isUnsafe, HTML, SQL } from 'is-unsafe';
177
+ *
178
+ * isUnsafe('<script>alert(1)</script>', HTML) // true
179
+ * isUnsafe('hello world', HTML) // false
180
+ * isUnsafe('value', [HTML, SQL]) // false
181
+ * isUnsafe('value', /my-pattern/i) // false
182
+ */
183
+ function isUnsafe(value, context) {
184
+ assertString(value);
185
+ assertContext(context);
186
+
187
+ const { lists, regex } = normalise(context);
188
+
189
+ if (regex) return regex.test(value);
190
+
191
+ for (const list of lists) {
192
+ if (matchList(value, list) !== null) return true;
193
+ }
194
+ return false;
195
+ }
196
+
197
+ /**
198
+ * Like `isUnsafe`, but returns the first `MatchResult` describing **why**
199
+ * the value was flagged, or `null` if it is safe.
200
+ *
201
+ * @param {string} value
202
+ * @param {PatternList | PatternList[] | RegExp} context
203
+ * @returns {MatchResult|null}
204
+ *
205
+ * @example
206
+ * import { whyUnsafe, HTML } from 'is-unsafe';
207
+ *
208
+ * whyUnsafe('<script>alert(1)</script>', HTML)
209
+ * // { context: 'HTML', id: 'html-script-open', description: '...', pattern: /.../ }
210
+ */
211
+ function whyUnsafe(value, context) {
212
+ assertString(value);
213
+ assertContext(context);
214
+
215
+ const { lists, regex } = normalise(context);
216
+
217
+ if (regex) {
218
+ return regex.test(value)
219
+ ? { context: 'CUSTOM', id: 'custom-regex', description: 'Matched caller-supplied pattern', pattern: regex }
220
+ : null;
221
+ }
222
+
223
+ for (const list of lists) {
224
+ const result = matchList(value, list);
225
+ if (result !== null) return result;
226
+ }
227
+ return null;
228
+ }
229
+
230
+ /**
231
+ * Returns **all** matching rules across the given context(s), or an empty
232
+ * array if the value is safe. Useful for comprehensive auditing.
233
+ *
234
+ * @param {string} value
235
+ * @param {PatternList | PatternList[] | RegExp} context
236
+ * @returns {MatchResult[]}
237
+ */
238
+ function allUnsafe(value, context) {
239
+ assertString(value);
240
+ assertContext(context);
241
+
242
+ const { lists, regex } = normalise(context);
243
+ const results = [];
244
+
245
+ if (regex) {
246
+ if (regex.test(value)) {
247
+ results.push({ context: 'CUSTOM', id: 'custom-regex', description: 'Matched caller-supplied pattern', pattern: regex });
248
+ }
249
+ return results;
250
+ }
251
+
252
+ for (const list of lists) {
253
+ const label = list.label ?? 'CUSTOM';
254
+ for (const rule of list) {
255
+ if (rule.pattern.test(value)) {
256
+ results.push({ context: label, id: rule.id, description: rule.description, pattern: rule.pattern });
257
+ }
258
+ }
259
+ }
260
+
261
+ return results;
262
+ }
263
+
264
+ export { isUnsafe, whyUnsafe, allUnsafe };
265
+ export default isUnsafe;