@suveren/gateway 0.28.1 → 0.29.1

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.
@@ -14,8 +14,8 @@
14
14
  <meta name="apple-mobile-web-app-title" content="Suveren" />
15
15
  <meta name="theme-color" content="#111111" />
16
16
  <title>Suveren</title>
17
- <script type="module" crossorigin src="/assets/index-I9WCdLMY.js"></script>
18
- <link rel="stylesheet" crossorigin href="/assets/index-RX5juZkm.css">
17
+ <script type="module" crossorigin src="/assets/index-wtX8LuiU.js"></script>
18
+ <link rel="stylesheet" crossorigin href="/assets/index-DUIlMVlP.css">
19
19
  </head>
20
20
  <body>
21
21
  <div id="root"></div>
@@ -1,7 +1,6 @@
1
1
  const htmlparser = require('htmlparser2');
2
2
  const escapeStringRegexp = require('escape-string-regexp');
3
3
  const { isPlainObject } = require('is-plain-object');
4
- const deepmerge = require('deepmerge');
5
4
  const parseSrcset = require('parse-srcset');
6
5
  const { parse: postcssParse } = require('postcss');
7
6
  const { naughtyHref: launderNaughtyHref } = require('launder');
@@ -53,6 +52,29 @@ function has(obj, key) {
53
52
  return ({}).hasOwnProperty.call(obj, key);
54
53
  }
55
54
 
55
+ // Combine the tag-specific and wildcard lists of allowed classes
56
+ function mergeClasses(specific, wildcard) {
57
+ if (!Array.isArray(specific) || !Array.isArray(wildcard)) {
58
+ return wildcard;
59
+ }
60
+ return specific.concat(wildcard);
61
+ }
62
+
63
+ // Combine the tag-specific and wildcard allowed styles, concatenating the
64
+ // lists of regular expressions for properties that appear in both. The result
65
+ // has no prototype, so a property named `__proto__` is just another property
66
+ function mergeStyles(specific, wildcard) {
67
+ const merged = Object.create(null);
68
+ for (const rules of [ specific, wildcard ]) {
69
+ for (const prop of Object.keys(rules)) {
70
+ merged[prop] = has(merged, prop)
71
+ ? merged[prop].concat(rules[prop])
72
+ : rules[prop];
73
+ }
74
+ }
75
+ return merged;
76
+ }
77
+
56
78
  // Returns those elements of `a` for which `cb(a)` returns truthy
57
79
  function filter(a, cb) {
58
80
  const n = [];
@@ -103,14 +125,23 @@ module.exports = sanitizeHtml;
103
125
  // attribute name state with an empty attribute name buffer.
104
126
  const VALID_HTML_ATTRIBUTE_NAME = /^[^\0\t\n\f\r /<=>]+$/;
105
127
 
106
- // Ignore the _recursing flag; it's there for recursive
107
- // invocation as a guard against this exploit:
108
- // https://github.com/fb55/htmlparser2/issues/105
128
+ // How many discarded <iframe> elements can be nested while their fallback
129
+ // markup is still re-sanitized as HTML (see the `ontext` handler). Deeper
130
+ // fallback is escaped as text, so that crafted input cannot recurse without
131
+ // bound or re-parse the remainder of the input once per iframe
132
+ // (GHSA-c9rx-x947-fp3h).
133
+ const MAX_IFRAME_FALLBACK_DEPTH = 8;
109
134
 
110
- function sanitizeHtml(html, options, _recursing) {
135
+ // `_fallback` is internal: it is set when re-sanitizing the fallback markup
136
+ // of a discarded <iframe>, to carry the current nesting depth into the
137
+ // recursive call.
138
+
139
+ function sanitizeHtml(html, options, _fallback) {
111
140
  if (html == null) {
112
141
  return '';
113
142
  }
143
+ const baseDepth = (_fallback && _fallback.depth) || 0;
144
+ const iframeFallbackDepth = (_fallback && _fallback.iframeFallbackDepth) || 0;
114
145
  if (typeof html === 'number') {
115
146
  html = html.toString();
116
147
  }
@@ -542,7 +573,7 @@ function sanitizeHtml(html, options, _recursing) {
542
573
  if (allowedSpecificClasses && allowedWildcardClasses) {
543
574
  value = filterClasses(
544
575
  value,
545
- deepmerge(allowedSpecificClasses, allowedWildcardClasses),
576
+ mergeClasses(allowedSpecificClasses, allowedWildcardClasses),
546
577
  allowedClassesGlobs
547
578
  );
548
579
  } else {
@@ -682,11 +713,17 @@ function sanitizeHtml(html, options, _recursing) {
682
713
  // (including after an unclosed <iframe>) arrives as a single text node.
683
714
  // When the iframe is discarded, re-sanitize that fallback markup as HTML
684
715
  // instead of escaping it as plain text (issue #5550).
716
+ // The nested call continues from the current depth so that
717
+ // `nestingLimit` applies to the whole output.
685
718
  tag === 'iframe' &&
686
719
  !tagAllowed(tag) &&
687
- options.disallowedTagsMode === 'discard'
720
+ options.disallowedTagsMode === 'discard' &&
721
+ iframeFallbackDepth < MAX_IFRAME_FALLBACK_DEPTH
688
722
  ) {
689
- result += sanitizeHtml(text, options);
723
+ result += sanitizeHtml(text, options, {
724
+ depth,
725
+ iframeFallbackDepth: iframeFallbackDepth + 1
726
+ });
690
727
  } else if (!addedText) {
691
728
  const escaped = escapeHtml(text, false);
692
729
  if (options.textFilter) {
@@ -819,7 +856,7 @@ function sanitizeHtml(html, options, _recursing) {
819
856
 
820
857
  function initializeState() {
821
858
  result = '';
822
- depth = 0;
859
+ depth = baseDepth;
823
860
  stack = [];
824
861
  skipMap = {};
825
862
  transformMap = {};
@@ -1053,7 +1090,7 @@ function sanitizeHtml(html, options, _recursing) {
1053
1090
 
1054
1091
  // Merge global and tag-specific styles into new AST.
1055
1092
  if (allowedStyles[astRules.selector] && allowedStyles['*']) {
1056
- selectedRule = deepmerge(
1093
+ selectedRule = mergeStyles(
1057
1094
  allowedStyles[astRules.selector],
1058
1095
  allowedStyles['*']
1059
1096
  );
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanitize-html",
3
- "version": "2.18.0",
3
+ "version": "2.18.1",
4
4
  "description": "Clean up user-submitted HTML, preserving allowlisted elements and allowlisted attributes on a per-element basis",
5
5
  "sideEffects": false,
6
6
  "main": "index.js",
@@ -29,7 +29,6 @@
29
29
  "node": ">=22.12.0"
30
30
  },
31
31
  "dependencies": {
32
- "deepmerge": "^4.3.1",
33
32
  "escape-string-regexp": "^4.0.0",
34
33
  "htmlparser2": "^12.0.0",
35
34
  "is-plain-object": "^5.1.0",
@@ -40,7 +39,7 @@
40
39
  "devDependencies": {
41
40
  "eslint": "^9.39.5",
42
41
  "eslint-config-apostrophe": "^6.1.0",
43
- "mocha": "^11.8.0",
42
+ "mocha": "^12.0.3",
44
43
  "sinon": "^9.2.4"
45
44
  },
46
45
  "apostropheTestConfig": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suveren/gateway",
3
- "version": "0.28.1",
3
+ "version": "0.29.1",
4
4
  "description": "Suveren gateway — local agent gateway built in compliance with the Human Agency Protocol (HAP). Runs the UI, control plane, and MCP server in one Node process.",
5
5
  "type": "module",
6
6
  "main": "server.js",