@readme/markdown 15.0.1 → 15.0.2

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/dist/main.js CHANGED
@@ -54765,12 +54765,104 @@ const mdast = (text, opts = {}) => {
54765
54765
  */
54766
54766
  const jsxAcornParser = external_acorn_.Parser.extend(acorn_jsx_default()());
54767
54767
 
54768
+ ;// ./lib/utils/literal-expression.ts
54769
+
54770
+ const unsupported = (description) => new Error(`not a literal expression: ${description}`);
54771
+ /** Narrows a resolved value for arithmetic, so operands never coerce to `NaN` or `"[object Object]"`. */
54772
+ const asNumber = (value) => {
54773
+ if (typeof value !== 'number')
54774
+ throw unsupported('non-numeric operand');
54775
+ return value;
54776
+ };
54777
+ const asOperand = (value) => {
54778
+ if (typeof value !== 'number' && typeof value !== 'string')
54779
+ throw unsupported('non-primitive operand');
54780
+ return value;
54781
+ };
54782
+ const ARITHMETIC_OPERATORS = {
54783
+ // `+` doubles as string concatenation, so it accepts a string on either side.
54784
+ '+': (left, right) => (typeof left === 'number' && typeof right === 'number' ? left + right : `${left}${right}`),
54785
+ '-': (left, right) => asNumber(left) - asNumber(right),
54786
+ '*': (left, right) => asNumber(left) * asNumber(right),
54787
+ '/': (left, right) => asNumber(left) / asNumber(right),
54788
+ };
54789
+ /** Resolve one estree node, recursing into arrays and objects. Throws for anything not on the allowlist. */
54790
+ const resolveNode = (node) => {
54791
+ switch (node.type) {
54792
+ case 'Literal':
54793
+ // Regex and bigint `Literal`s hold values a tree consumer can't serialise;
54794
+ // a BigInt throws outright in `JSON.stringify`.
54795
+ if ('regex' in node || 'bigint' in node)
54796
+ throw unsupported('regex or bigint literal');
54797
+ return node.value;
54798
+ case 'Identifier':
54799
+ if (node.name !== 'undefined')
54800
+ throw unsupported(`identifier \`${node.name}\``);
54801
+ return undefined;
54802
+ case 'TemplateLiteral':
54803
+ if (node.expressions.length)
54804
+ throw unsupported('template substitution');
54805
+ return node.quasis.map(quasi => quasi.value.cooked).join('');
54806
+ case 'UnaryExpression':
54807
+ if (node.operator !== '-')
54808
+ throw unsupported(`operator \`${node.operator}\``);
54809
+ return -asNumber(resolveNode(node.argument));
54810
+ case 'BinaryExpression': {
54811
+ const applyOperator = ARITHMETIC_OPERATORS[node.operator];
54812
+ if (!applyOperator)
54813
+ throw unsupported(`operator \`${node.operator}\``);
54814
+ return applyOperator(asOperand(resolveNode(node.left)), asOperand(resolveNode(node.right)));
54815
+ }
54816
+ case 'ArrayExpression':
54817
+ return node.elements.map(element => (element === null ? null : resolveNode(element)));
54818
+ case 'ObjectExpression':
54819
+ return node.properties.reduce((memo, property) => {
54820
+ if (property.type !== 'Property' || property.computed)
54821
+ throw unsupported('computed or spread property');
54822
+ const { key } = property;
54823
+ if (key.type !== 'Identifier' && key.type !== 'Literal')
54824
+ throw unsupported('property key');
54825
+ const name = key.type === 'Identifier' ? key.name : String(key.value);
54826
+ // `__proto__` would replace the object's prototype rather than add a property.
54827
+ if (name === '__proto__')
54828
+ throw unsupported('`__proto__` key');
54829
+ memo[name] = resolveNode(property.value);
54830
+ return memo;
54831
+ }, {});
54832
+ default:
54833
+ throw unsupported(node.type);
54834
+ }
54835
+ };
54836
+ /**
54837
+ * Resolve a JSX attribute expression's source to its value without executing it.
54838
+ *
54839
+ * Supports the literal syntax attributes actually use — `true`, `"a"`, `` `a` ``,
54840
+ * `undefined`, `{ textAlign: "left" }`, `["left"]`, `1 + 1`, `'https://' + 'x.com'` —
54841
+ * and refuses everything else, so no identifier, member access, call, assignment or
54842
+ * function body can ever run. The trade-off is that expressions needing a scope or a
54843
+ * method call (`{item.url}`, `{"a".toUpperCase()}`) throw, and callers keep the raw
54844
+ * source instead — the same fallback they already used for an expression that threw.
54845
+ * mdxish rendering is unaffected, since it resolves those later with its scoped
54846
+ * evaluator; only consumers reading attributes straight off the tree see the source.
54847
+ *
54848
+ * @param source expression body, without the surrounding braces
54849
+ * @throws if `source` is unparseable or isn't a supported literal expression
54850
+ */
54851
+ const evaluateLiteralExpression = (source) => {
54852
+ const expression = jsxAcornParser.parseExpressionAt(source, 0, { ecmaVersion: 'latest' });
54853
+ // acorn stops at the first complete expression, so anything trailing means this isn't one.
54854
+ if (expression.end !== source.trimEnd().length)
54855
+ throw unsupported('trailing content');
54856
+ return resolveNode(expression);
54857
+ };
54858
+
54768
54859
  ;// ./processor/utils.ts
54769
54860
 
54770
54861
 
54771
54862
 
54772
54863
 
54773
54864
 
54865
+
54774
54866
  /**
54775
54867
  * Evaluate a JavaScript expression source and return its value.
54776
54868
  *
@@ -54868,8 +54960,10 @@ const getAttrs = (jsx) => jsx.attributes.reduce((memo, attr) => {
54868
54960
  memo[attr.name] = decode_decodeHTMLStrict(attr.value);
54869
54961
  }
54870
54962
  else if (attr.value?.value !== undefined) {
54963
+ // Expression values only survive to here when safeMode is off; `flattenAttributeExpressions`
54964
+ // rewrites them to plain strings at the head of the pipeline otherwise.
54871
54965
  try {
54872
- memo[attr.name] = evaluate(attr.value.value);
54966
+ memo[attr.name] = evaluateLiteralExpression(attr.value.value);
54873
54967
  }
54874
54968
  catch {
54875
54969
  memo[attr.name] = attr.value.value;
@@ -55650,6 +55744,31 @@ const embedTransformer = () => {
55650
55744
  };
55651
55745
  /* harmony default export */ const transform_embeds = (embedTransformer);
55652
55746
 
55747
+ ;// ./processor/transform/flatten-attribute-expressions.ts
55748
+
55749
+
55750
+ /**
55751
+ * Rewrites JSX attribute expressions (`icon={String(1 + 3)}`) into plain string attributes
55752
+ * holding their literal source, so nothing downstream can evaluate them.
55753
+ *
55754
+ * This is the RMDX counterpart to mdxish's `preserveExpressionsAsText` parse option: safeMode's
55755
+ * contract is enforced once, at the head of the pipeline, rather than at every `getAttrs()` call
55756
+ * site. Only registered when safeMode is on.
55757
+ */
55758
+ const flattenAttributeExpressions = () => tree => {
55759
+ visit(tree, isMDXElement, (node) => {
55760
+ node.attributes.forEach(attr => {
55761
+ if (!('name' in attr))
55762
+ return;
55763
+ if (attr.value === null || typeof attr.value === 'string')
55764
+ return;
55765
+ attr.value = attr.value.value;
55766
+ });
55767
+ });
55768
+ return tree;
55769
+ };
55770
+ /* harmony default export */ const flatten_attribute_expressions = (flattenAttributeExpressions);
55771
+
55653
55772
  ;// ./node_modules/gemoji/index.js
55654
55773
  /**
55655
55774
  * @typedef Gemoji
@@ -81419,6 +81538,7 @@ const variables = ({ asMdx } = { asMdx: true }) => tree => {
81419
81538
 
81420
81539
 
81421
81540
 
81541
+
81422
81542
  const defaultTransforms = {
81423
81543
  calloutTransformer: callouts,
81424
81544
  codeTabsTransformer: code_tabs,
@@ -81446,6 +81566,9 @@ const astProcessor = (opts = {}) => {
81446
81566
  const components = opts.components || {};
81447
81567
  let processor = remark()
81448
81568
  .use(remarkMdx)
81569
+ // Must precede every other transformer: it strips evaluable attribute expressions so no
81570
+ // downstream `getAttrs()` call can reach `evaluate()`.
81571
+ .use(opts.safeMode ? flatten_attribute_expressions : undefined)
81449
81572
  .use(remarkPlugins)
81450
81573
  .use(opts.remarkPlugins)
81451
81574
  .use(transform_variables, { asMdx: false })
@@ -96025,12 +96148,59 @@ const hastscript_lib_h = create_h_createH(node_modules_property_information_html
96025
96148
  /** @type {ReturnType<createH>} */
96026
96149
  const lib_s = create_h_createH(node_modules_property_information_svg, 'g', svg_case_sensitive_tag_names_svgCaseSensitiveTagNames)
96027
96150
 
96151
+ ;// ./utils/user.ts
96152
+ /**
96153
+ * Coerce a user variable value to a string for substitution into markdown text.
96154
+ * Non-string values (arrays, objects, numbers) are stringified via JSON or `String()`
96155
+ * so that `<<var>>` syntax doesn't produce `[object Object]` for structured data like
96156
+ * JWT `keys`.
96157
+ */
96158
+ const stringifyVariableValue = (value) => {
96159
+ if (typeof value === 'string')
96160
+ return value;
96161
+ if (value == null)
96162
+ return '';
96163
+ if (typeof value === 'object') {
96164
+ try {
96165
+ return JSON.stringify(value) ?? '';
96166
+ }
96167
+ catch {
96168
+ console.warn('[WARNING] Could not stringify a structured user variable.');
96169
+ return '';
96170
+ }
96171
+ }
96172
+ return String(value);
96173
+ };
96174
+ /**
96175
+ * Flatten `variables.user` into a string-keyed string-valued record by coercing
96176
+ * each value. Used by markdown substitution paths that need a plain
96177
+ * `Record<string, string>` lookup.
96178
+ */
96179
+ const flattenUserVariables = (user) => Object.fromEntries(Object.entries(user).map(([name, value]) => [name, stringifyVariableValue(value)]));
96180
+ const User = (variables) => {
96181
+ const { user = {}, defaults = [] } = variables || {};
96182
+ return new Proxy(user, {
96183
+ get(target, attribute) {
96184
+ if (typeof attribute === 'symbol') {
96185
+ return '';
96186
+ }
96187
+ if (attribute in target) {
96188
+ return target[attribute];
96189
+ }
96190
+ const def = defaults.find((d) => d.name === attribute);
96191
+ return def ? def.default : attribute.toUpperCase();
96192
+ },
96193
+ });
96194
+ };
96195
+ /* harmony default export */ const user = (User);
96196
+
96028
96197
  ;// ./processor/plugin/toc.ts
96029
96198
 
96030
96199
 
96031
96200
 
96032
96201
 
96033
96202
 
96203
+
96034
96204
  const HEADING_TAGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
96035
96205
  const isHeadingTag = (tag) => (tag ? HEADING_TAGS.includes(tag) : false);
96036
96206
  const isStandardHtmlElement = (node) => STANDARD_HTML_TAGS.has(node.tagName.toLowerCase());
@@ -96142,7 +96312,7 @@ const flattenVariables = (variables) => {
96142
96312
  if (!variables)
96143
96313
  return {};
96144
96314
  return {
96145
- ...variables.user,
96315
+ ...flattenUserVariables(variables.user),
96146
96316
  ...Object.fromEntries((variables.defaults || []).filter(d => !(d.name in variables.user)).map(d => [d.name, d.default])),
96147
96317
  };
96148
96318
  };
@@ -96290,7 +96460,7 @@ const exports_exports = (doc) => {
96290
96460
 
96291
96461
  const hast = (text, opts = {}) => {
96292
96462
  const components = Object.entries(opts.components || {}).reduce((memo, [name, doc]) => {
96293
- memo[name] = lib_mdast(doc);
96463
+ memo[name] = lib_mdast(doc, { safeMode: opts.safeMode });
96294
96464
  return memo;
96295
96465
  }, {});
96296
96466
  const processor = ast_processor(opts)
@@ -106926,6 +107096,7 @@ function normalizeTableSeparator(content) {
106926
107096
  ;// ./processor/transform/mdxish/variables-code.ts
106927
107097
 
106928
107098
 
107099
+
106929
107100
  // Single combined regex so that resolved values from one pattern are never re-scanned by the other.
106930
107101
  const COMBINED_VARIABLE_REGEX = new RegExp(`${variable_.VARIABLE_REGEXP}|${variable_.MDX_VARIABLE_REGEXP}`, 'giu');
106931
107102
  // Flatten variables into a single object for easy lookup
@@ -106934,7 +107105,7 @@ function variables_code_flattenVariables(variables) {
106934
107105
  return {};
106935
107106
  return {
106936
107107
  ...Object.fromEntries((variables.defaults || []).map(d => [d.name, d.default])),
106937
- ...variables.user,
107108
+ ...flattenUserVariables(variables.user),
106938
107109
  };
106939
107110
  }
106940
107111
  function resolveCodeVariables(value, resolvedVariables) {
@@ -107478,24 +107649,6 @@ const Contexts = ({ children, terms = [], variables = { user: {}, defaults: [] }
107478
107649
  };
107479
107650
  /* harmony default export */ const contexts = (Contexts);
107480
107651
 
107481
- ;// ./utils/user.ts
107482
- const User = (variables) => {
107483
- const { user = {}, defaults = [] } = variables || {};
107484
- return new Proxy(user, {
107485
- get(target, attribute) {
107486
- if (typeof attribute === 'symbol') {
107487
- return '';
107488
- }
107489
- if (attribute in target) {
107490
- return target[attribute];
107491
- }
107492
- const def = defaults.find((d) => d.name === attribute);
107493
- return def ? def.default : attribute.toUpperCase();
107494
- },
107495
- });
107496
- };
107497
- /* harmony default export */ const user = (User);
107498
-
107499
107652
  ;// ./lib/utils/makeUseMdxComponents.ts
107500
107653
 
107501
107654
 
@@ -107528,6 +107681,7 @@ const makeUseMDXComponents = (more = {}) => {
107528
107681
 
107529
107682
  ;// ./lib/utils/mdxish/mdxish-variables.ts
107530
107683
 
107684
+
107531
107685
  // The `$` guard skips template-literal interpolation: `${user.name}` embeds `{user.name}`, and
107532
107686
  // substituting it would leave a mangled `` `Hi $Name` `` behind. Those belong to an expression,
107533
107687
  // which either evaluated already or is meant to stay literal.
@@ -107551,7 +107705,7 @@ function resolveAttributeVariables(value, user) {
107551
107705
  .replace(MDX_VARIABLE_REGEX, (source, escapePrefix, name, escapeSuffix) => {
107552
107706
  if (escapePrefix || escapeSuffix)
107553
107707
  return source;
107554
- return user[name];
107708
+ return stringifyVariableValue(user[name]);
107555
107709
  });
107556
107710
  }
107557
107711
 
@@ -107827,7 +107981,8 @@ const run_run = (string, _opts = {}) => {
107827
107981
 
107828
107982
  const tags = (doc) => {
107829
107983
  const set = new Set();
107830
- visit(lib_mdast(doc), isMDXElement, (node) => {
107984
+ // Tag names never depend on evaluated attribute values, so always parse in safeMode.
107985
+ visit(lib_mdast(doc, { safeMode: true }), isMDXElement, (node) => {
107831
107986
  if (node.name?.match(/^[A-Z]/)) {
107832
107987
  set.add(node.name);
107833
107988
  }
@@ -107843,13 +107998,14 @@ const tags = (doc) => {
107843
107998
 
107844
107999
 
107845
108000
 
107846
- const { micromarkExtensions, fromMarkdownExtensions } = mdxishExtensions(FEATURES.tags);
108001
+ const { micromarkExtensions, fromMarkdownExtensions } = mdxishExtensions(FEATURES.tags, { safeMode: true });
107847
108002
  const mdxishTags_tags = (doc) => {
107848
108003
  const set = new Set();
107849
108004
  const processor = remark()
107850
108005
  .data('micromarkExtensions', micromarkExtensions)
107851
108006
  .data('fromMarkdownExtensions', fromMarkdownExtensions)
107852
- .use(mdx_blocks)
108007
+ // Tag names never depend on evaluated attribute values, so always parse in safeMode.
108008
+ .use(mdx_blocks, { safeMode: true })
107853
108009
  .use(mdxish_tables);
107854
108010
  const tree = processor.parse(doc);
107855
108011
  visit(processor.runSync(tree), isMDXElement, (node) => {
package/dist/main.node.js CHANGED
@@ -74989,12 +74989,104 @@ const mdast = (text, opts = {}) => {
74989
74989
  */
74990
74990
  const jsxAcornParser = Parser.extend(acorn_jsx_default()());
74991
74991
 
74992
+ ;// ./lib/utils/literal-expression.ts
74993
+
74994
+ const unsupported = (description) => new Error(`not a literal expression: ${description}`);
74995
+ /** Narrows a resolved value for arithmetic, so operands never coerce to `NaN` or `"[object Object]"`. */
74996
+ const asNumber = (value) => {
74997
+ if (typeof value !== 'number')
74998
+ throw unsupported('non-numeric operand');
74999
+ return value;
75000
+ };
75001
+ const asOperand = (value) => {
75002
+ if (typeof value !== 'number' && typeof value !== 'string')
75003
+ throw unsupported('non-primitive operand');
75004
+ return value;
75005
+ };
75006
+ const ARITHMETIC_OPERATORS = {
75007
+ // `+` doubles as string concatenation, so it accepts a string on either side.
75008
+ '+': (left, right) => (typeof left === 'number' && typeof right === 'number' ? left + right : `${left}${right}`),
75009
+ '-': (left, right) => asNumber(left) - asNumber(right),
75010
+ '*': (left, right) => asNumber(left) * asNumber(right),
75011
+ '/': (left, right) => asNumber(left) / asNumber(right),
75012
+ };
75013
+ /** Resolve one estree node, recursing into arrays and objects. Throws for anything not on the allowlist. */
75014
+ const resolveNode = (node) => {
75015
+ switch (node.type) {
75016
+ case 'Literal':
75017
+ // Regex and bigint `Literal`s hold values a tree consumer can't serialise;
75018
+ // a BigInt throws outright in `JSON.stringify`.
75019
+ if ('regex' in node || 'bigint' in node)
75020
+ throw unsupported('regex or bigint literal');
75021
+ return node.value;
75022
+ case 'Identifier':
75023
+ if (node.name !== 'undefined')
75024
+ throw unsupported(`identifier \`${node.name}\``);
75025
+ return undefined;
75026
+ case 'TemplateLiteral':
75027
+ if (node.expressions.length)
75028
+ throw unsupported('template substitution');
75029
+ return node.quasis.map(quasi => quasi.value.cooked).join('');
75030
+ case 'UnaryExpression':
75031
+ if (node.operator !== '-')
75032
+ throw unsupported(`operator \`${node.operator}\``);
75033
+ return -asNumber(resolveNode(node.argument));
75034
+ case 'BinaryExpression': {
75035
+ const applyOperator = ARITHMETIC_OPERATORS[node.operator];
75036
+ if (!applyOperator)
75037
+ throw unsupported(`operator \`${node.operator}\``);
75038
+ return applyOperator(asOperand(resolveNode(node.left)), asOperand(resolveNode(node.right)));
75039
+ }
75040
+ case 'ArrayExpression':
75041
+ return node.elements.map(element => (element === null ? null : resolveNode(element)));
75042
+ case 'ObjectExpression':
75043
+ return node.properties.reduce((memo, property) => {
75044
+ if (property.type !== 'Property' || property.computed)
75045
+ throw unsupported('computed or spread property');
75046
+ const { key } = property;
75047
+ if (key.type !== 'Identifier' && key.type !== 'Literal')
75048
+ throw unsupported('property key');
75049
+ const name = key.type === 'Identifier' ? key.name : String(key.value);
75050
+ // `__proto__` would replace the object's prototype rather than add a property.
75051
+ if (name === '__proto__')
75052
+ throw unsupported('`__proto__` key');
75053
+ memo[name] = resolveNode(property.value);
75054
+ return memo;
75055
+ }, {});
75056
+ default:
75057
+ throw unsupported(node.type);
75058
+ }
75059
+ };
75060
+ /**
75061
+ * Resolve a JSX attribute expression's source to its value without executing it.
75062
+ *
75063
+ * Supports the literal syntax attributes actually use — `true`, `"a"`, `` `a` ``,
75064
+ * `undefined`, `{ textAlign: "left" }`, `["left"]`, `1 + 1`, `'https://' + 'x.com'` —
75065
+ * and refuses everything else, so no identifier, member access, call, assignment or
75066
+ * function body can ever run. The trade-off is that expressions needing a scope or a
75067
+ * method call (`{item.url}`, `{"a".toUpperCase()}`) throw, and callers keep the raw
75068
+ * source instead — the same fallback they already used for an expression that threw.
75069
+ * mdxish rendering is unaffected, since it resolves those later with its scoped
75070
+ * evaluator; only consumers reading attributes straight off the tree see the source.
75071
+ *
75072
+ * @param source expression body, without the surrounding braces
75073
+ * @throws if `source` is unparseable or isn't a supported literal expression
75074
+ */
75075
+ const evaluateLiteralExpression = (source) => {
75076
+ const expression = jsxAcornParser.parseExpressionAt(source, 0, { ecmaVersion: 'latest' });
75077
+ // acorn stops at the first complete expression, so anything trailing means this isn't one.
75078
+ if (expression.end !== source.trimEnd().length)
75079
+ throw unsupported('trailing content');
75080
+ return resolveNode(expression);
75081
+ };
75082
+
74992
75083
  ;// ./processor/utils.ts
74993
75084
 
74994
75085
 
74995
75086
 
74996
75087
 
74997
75088
 
75089
+
74998
75090
  /**
74999
75091
  * Evaluate a JavaScript expression source and return its value.
75000
75092
  *
@@ -75092,8 +75184,10 @@ const getAttrs = (jsx) => jsx.attributes.reduce((memo, attr) => {
75092
75184
  memo[attr.name] = decode_decodeHTMLStrict(attr.value);
75093
75185
  }
75094
75186
  else if (attr.value?.value !== undefined) {
75187
+ // Expression values only survive to here when safeMode is off; `flattenAttributeExpressions`
75188
+ // rewrites them to plain strings at the head of the pipeline otherwise.
75095
75189
  try {
75096
- memo[attr.name] = evaluate(attr.value.value);
75190
+ memo[attr.name] = evaluateLiteralExpression(attr.value.value);
75097
75191
  }
75098
75192
  catch {
75099
75193
  memo[attr.name] = attr.value.value;
@@ -75874,6 +75968,31 @@ const embedTransformer = () => {
75874
75968
  };
75875
75969
  /* harmony default export */ const transform_embeds = (embedTransformer);
75876
75970
 
75971
+ ;// ./processor/transform/flatten-attribute-expressions.ts
75972
+
75973
+
75974
+ /**
75975
+ * Rewrites JSX attribute expressions (`icon={String(1 + 3)}`) into plain string attributes
75976
+ * holding their literal source, so nothing downstream can evaluate them.
75977
+ *
75978
+ * This is the RMDX counterpart to mdxish's `preserveExpressionsAsText` parse option: safeMode's
75979
+ * contract is enforced once, at the head of the pipeline, rather than at every `getAttrs()` call
75980
+ * site. Only registered when safeMode is on.
75981
+ */
75982
+ const flattenAttributeExpressions = () => tree => {
75983
+ visit(tree, isMDXElement, (node) => {
75984
+ node.attributes.forEach(attr => {
75985
+ if (!('name' in attr))
75986
+ return;
75987
+ if (attr.value === null || typeof attr.value === 'string')
75988
+ return;
75989
+ attr.value = attr.value.value;
75990
+ });
75991
+ });
75992
+ return tree;
75993
+ };
75994
+ /* harmony default export */ const flatten_attribute_expressions = (flattenAttributeExpressions);
75995
+
75877
75996
  ;// ./node_modules/gemoji/index.js
75878
75997
  /**
75879
75998
  * @typedef Gemoji
@@ -101643,6 +101762,7 @@ const variables = ({ asMdx } = { asMdx: true }) => tree => {
101643
101762
 
101644
101763
 
101645
101764
 
101765
+
101646
101766
  const defaultTransforms = {
101647
101767
  calloutTransformer: callouts,
101648
101768
  codeTabsTransformer: code_tabs,
@@ -101670,6 +101790,9 @@ const astProcessor = (opts = {}) => {
101670
101790
  const components = opts.components || {};
101671
101791
  let processor = remark()
101672
101792
  .use(remarkMdx)
101793
+ // Must precede every other transformer: it strips evaluable attribute expressions so no
101794
+ // downstream `getAttrs()` call can reach `evaluate()`.
101795
+ .use(opts.safeMode ? flatten_attribute_expressions : undefined)
101673
101796
  .use(remarkPlugins)
101674
101797
  .use(opts.remarkPlugins)
101675
101798
  .use(transform_variables, { asMdx: false })
@@ -116249,12 +116372,59 @@ const hastscript_lib_h = create_h_createH(node_modules_property_information_html
116249
116372
  /** @type {ReturnType<createH>} */
116250
116373
  const lib_s = create_h_createH(node_modules_property_information_svg, 'g', svg_case_sensitive_tag_names_svgCaseSensitiveTagNames)
116251
116374
 
116375
+ ;// ./utils/user.ts
116376
+ /**
116377
+ * Coerce a user variable value to a string for substitution into markdown text.
116378
+ * Non-string values (arrays, objects, numbers) are stringified via JSON or `String()`
116379
+ * so that `<<var>>` syntax doesn't produce `[object Object]` for structured data like
116380
+ * JWT `keys`.
116381
+ */
116382
+ const stringifyVariableValue = (value) => {
116383
+ if (typeof value === 'string')
116384
+ return value;
116385
+ if (value == null)
116386
+ return '';
116387
+ if (typeof value === 'object') {
116388
+ try {
116389
+ return JSON.stringify(value) ?? '';
116390
+ }
116391
+ catch {
116392
+ console.warn('[WARNING] Could not stringify a structured user variable.');
116393
+ return '';
116394
+ }
116395
+ }
116396
+ return String(value);
116397
+ };
116398
+ /**
116399
+ * Flatten `variables.user` into a string-keyed string-valued record by coercing
116400
+ * each value. Used by markdown substitution paths that need a plain
116401
+ * `Record<string, string>` lookup.
116402
+ */
116403
+ const flattenUserVariables = (user) => Object.fromEntries(Object.entries(user).map(([name, value]) => [name, stringifyVariableValue(value)]));
116404
+ const User = (variables) => {
116405
+ const { user = {}, defaults = [] } = variables || {};
116406
+ return new Proxy(user, {
116407
+ get(target, attribute) {
116408
+ if (typeof attribute === 'symbol') {
116409
+ return '';
116410
+ }
116411
+ if (attribute in target) {
116412
+ return target[attribute];
116413
+ }
116414
+ const def = defaults.find((d) => d.name === attribute);
116415
+ return def ? def.default : attribute.toUpperCase();
116416
+ },
116417
+ });
116418
+ };
116419
+ /* harmony default export */ const user = (User);
116420
+
116252
116421
  ;// ./processor/plugin/toc.ts
116253
116422
 
116254
116423
 
116255
116424
 
116256
116425
 
116257
116426
 
116427
+
116258
116428
  const HEADING_TAGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
116259
116429
  const isHeadingTag = (tag) => (tag ? HEADING_TAGS.includes(tag) : false);
116260
116430
  const isStandardHtmlElement = (node) => STANDARD_HTML_TAGS.has(node.tagName.toLowerCase());
@@ -116366,7 +116536,7 @@ const flattenVariables = (variables) => {
116366
116536
  if (!variables)
116367
116537
  return {};
116368
116538
  return {
116369
- ...variables.user,
116539
+ ...flattenUserVariables(variables.user),
116370
116540
  ...Object.fromEntries((variables.defaults || []).filter(d => !(d.name in variables.user)).map(d => [d.name, d.default])),
116371
116541
  };
116372
116542
  };
@@ -116514,7 +116684,7 @@ const exports_exports = (doc) => {
116514
116684
 
116515
116685
  const hast = (text, opts = {}) => {
116516
116686
  const components = Object.entries(opts.components || {}).reduce((memo, [name, doc]) => {
116517
- memo[name] = lib_mdast(doc);
116687
+ memo[name] = lib_mdast(doc, { safeMode: opts.safeMode });
116518
116688
  return memo;
116519
116689
  }, {});
116520
116690
  const processor = ast_processor(opts)
@@ -127150,6 +127320,7 @@ function normalizeTableSeparator(content) {
127150
127320
  ;// ./processor/transform/mdxish/variables-code.ts
127151
127321
 
127152
127322
 
127323
+
127153
127324
  // Single combined regex so that resolved values from one pattern are never re-scanned by the other.
127154
127325
  const COMBINED_VARIABLE_REGEX = new RegExp(`${variable_dist.VARIABLE_REGEXP}|${variable_dist.MDX_VARIABLE_REGEXP}`, 'giu');
127155
127326
  // Flatten variables into a single object for easy lookup
@@ -127158,7 +127329,7 @@ function variables_code_flattenVariables(variables) {
127158
127329
  return {};
127159
127330
  return {
127160
127331
  ...Object.fromEntries((variables.defaults || []).map(d => [d.name, d.default])),
127161
- ...variables.user,
127332
+ ...flattenUserVariables(variables.user),
127162
127333
  };
127163
127334
  }
127164
127335
  function resolveCodeVariables(value, resolvedVariables) {
@@ -127702,24 +127873,6 @@ const Contexts = ({ children, terms = [], variables = { user: {}, defaults: [] }
127702
127873
  };
127703
127874
  /* harmony default export */ const contexts = (Contexts);
127704
127875
 
127705
- ;// ./utils/user.ts
127706
- const User = (variables) => {
127707
- const { user = {}, defaults = [] } = variables || {};
127708
- return new Proxy(user, {
127709
- get(target, attribute) {
127710
- if (typeof attribute === 'symbol') {
127711
- return '';
127712
- }
127713
- if (attribute in target) {
127714
- return target[attribute];
127715
- }
127716
- const def = defaults.find((d) => d.name === attribute);
127717
- return def ? def.default : attribute.toUpperCase();
127718
- },
127719
- });
127720
- };
127721
- /* harmony default export */ const user = (User);
127722
-
127723
127876
  ;// ./lib/utils/makeUseMdxComponents.ts
127724
127877
 
127725
127878
 
@@ -127752,6 +127905,7 @@ const makeUseMDXComponents = (more = {}) => {
127752
127905
 
127753
127906
  ;// ./lib/utils/mdxish/mdxish-variables.ts
127754
127907
 
127908
+
127755
127909
  // The `$` guard skips template-literal interpolation: `${user.name}` embeds `{user.name}`, and
127756
127910
  // substituting it would leave a mangled `` `Hi $Name` `` behind. Those belong to an expression,
127757
127911
  // which either evaluated already or is meant to stay literal.
@@ -127775,7 +127929,7 @@ function resolveAttributeVariables(value, user) {
127775
127929
  .replace(MDX_VARIABLE_REGEX, (source, escapePrefix, name, escapeSuffix) => {
127776
127930
  if (escapePrefix || escapeSuffix)
127777
127931
  return source;
127778
- return user[name];
127932
+ return stringifyVariableValue(user[name]);
127779
127933
  });
127780
127934
  }
127781
127935
 
@@ -128051,7 +128205,8 @@ const run_run = (string, _opts = {}) => {
128051
128205
 
128052
128206
  const tags = (doc) => {
128053
128207
  const set = new Set();
128054
- visit(lib_mdast(doc), isMDXElement, (node) => {
128208
+ // Tag names never depend on evaluated attribute values, so always parse in safeMode.
128209
+ visit(lib_mdast(doc, { safeMode: true }), isMDXElement, (node) => {
128055
128210
  if (node.name?.match(/^[A-Z]/)) {
128056
128211
  set.add(node.name);
128057
128212
  }
@@ -128067,13 +128222,14 @@ const tags = (doc) => {
128067
128222
 
128068
128223
 
128069
128224
 
128070
- const { micromarkExtensions, fromMarkdownExtensions } = mdxishExtensions(FEATURES.tags);
128225
+ const { micromarkExtensions, fromMarkdownExtensions } = mdxishExtensions(FEATURES.tags, { safeMode: true });
128071
128226
  const mdxishTags_tags = (doc) => {
128072
128227
  const set = new Set();
128073
128228
  const processor = remark()
128074
128229
  .data('micromarkExtensions', micromarkExtensions)
128075
128230
  .data('fromMarkdownExtensions', fromMarkdownExtensions)
128076
- .use(mdx_blocks)
128231
+ // Tag names never depend on evaluated attribute values, so always parse in safeMode.
128232
+ .use(mdx_blocks, { safeMode: true })
128077
128233
  .use(mdxish_tables);
128078
128234
  const tree = processor.parse(doc);
128079
128235
  visit(processor.runSync(tree), isMDXElement, (node) => {