@ethisyscore/plugin-ui 1.110.0 → 1.112.0
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/components/ui/index.cjs +11 -0
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.cts +24 -1
- package/dist/components/ui/index.d.ts +24 -1
- package/dist/components/ui/index.js +10 -1
- package/dist/components/ui/index.js.map +1 -1
- package/dist/markdown/index.d.ts +236 -0
- package/dist/markdown/index.js +524 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/platform-react/index.cjs +43 -7
- package/dist/platform-react/index.cjs.map +1 -1
- package/dist/platform-react/index.d.cts +34 -6
- package/dist/platform-react/index.d.ts +34 -6
- package/dist/platform-react/index.js +44 -9
- package/dist/platform-react/index.js.map +1 -1
- package/package.json +13 -2
- package/styles/markdown.css +284 -0
- package/styles/plugin-base.css +21 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/markdown/guards/attributeNames.ts","../../src/markdown/guards/schemaMaps.ts","../../src/markdown/guards/attributes.ts","../../src/markdown/guards/attributeAliases.ts","../../src/markdown/guards/protocols.ts","../../src/markdown/guards/mutableKeyShapes.ts","../../src/markdown/guards/mutableKeys.ts","../../src/markdown/guards/returnedSchema.ts","../../src/markdown/guards/schemaKeys.ts","../../src/markdown/guards/schemaValues.ts","../../src/markdown/guards/tagNames.ts","../../src/markdown/rehypeBlockRemoteImages.ts","../../src/markdown/schemaClone.ts","../../src/markdown/schema.ts","../../src/markdown/trust.ts","../../src/markdown/blockedImage.tsx","../../src/markdown/pipeline.ts","../../src/markdown/MarkdownView.tsx"],"names":["IMAGE_TAG_SET","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { Schema } from \"hast-util-sanitize\";\n\ntype Attributes = NonNullable<Schema[\"attributes\"]>;\n\n/** One entry of a schema's attribute list: `\"href\"` or `[\"className\", /^hljs-/]`. */\nexport type AttributeEntry = Attributes[string][number];\n\n/** `[\"className\", \"foo\"]` — a tuple whose head is the attribute name. */\nexport function attributeNameOf(entry: AttributeEntry): string {\n return Array.isArray(entry) ? String(entry[0]) : String(entry);\n}\n\n/**\n * The XLink namespace prefix, stripped before anything else compares a name.\n *\n * `xlink:href` is not a different attribute from `href`; it is SVG 1.1's spelling of\n * the same one, and SVG 2 deprecated it precisely because browsers already treat the\n * two as one. The prefix survives every other normalisation here — `xlinkHref` and\n * `xlink:href` both collapse to `xlinkhref`, which matches no policed name — so\n * without this line the namespaced spelling is a name the sanitizer attaches no\n * policy to, and `<svg><a xlink:href=\"javascript:alert(1)\">` renders live while the\n * canonical `href` spelling comes back blanked.\n */\nconst XLINK_PREFIX = /^xlink/;\n\n/**\n * The form the attribute rules compare names in.\n *\n * One attribute reaches the DOM under several spellings: hast property names are\n * camelCase (`srcSet`, `xLinkHref`), the HTML/SVG attribute names are not\n * (`srcset`, `xlink:href`), and a capability author may write either. Comparing the\n * letters alone means a denylist entry cannot be stepped around with punctuation or\n * case. A spelling this collapses that the sanitizer would not have matched anyway is\n * denied rather than admitted — the direction to be wrong in.\n *\n * Shared by `./attributes` (which denies names) and `./attributeAliases` (which denies\n * a SECOND spelling of a name that already carries policy), because the two rules must\n * collapse spellings identically or one of them has a gap the other closes on paper.\n */\nexport function normalisedAttributeName(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]/g, \"\")\n .replace(XLINK_PREFIX, \"\");\n}\n","import type { Schema } from \"hast-util-sanitize\";\n\n/**\n * HOW THE SANITIZER RESOLVES A SCHEMA READ — AND THEREFORE HOW A GUARD MUST READ ONE.\n *\n * `hast-util-sanitize` resolves both levels of a schema own-only, by two different\n * mechanisms:\n *\n * - a per-tag / per-property MAP (`attributes`, `protocols`, `ancestors`, `required`)\n * with `own.call(map, key) ? map[key] : undefined` (lib/index.js);\n * - the TOP level with `{ ...defaultSchema, ...options }`, and a spread copies own\n * enumerable properties only.\n *\n * A guard that writes `map[key]` or `schema.protocols` instead reads the prototype\n * chain, and the two then disagree about what the schema says: the guard judges a rule\n * the sanitizer will never enforce, or — the direction that bites — reads an inherited\n * entry as an allowance the capability merely INHERITED and so waves it through.\n *\n * EVERY SCHEMA READ IN THIS FOLDER GOES THROUGH ONE OF THE TWO FUNCTIONS BELOW. Not\n * because each read is exploitable — the schemas reaching most of them are clones with\n * a null prototype, or literals from `effectiveSchema`'s spread — but because \"read it\n * the way the sanitizer does\" is only a rule if it has no exceptions. The one read that\n * WAS exploitable looked exactly like the others until it was not.\n */\n\n/**\n * Map entry as the sanitizer resolves it: own enumerable only, never inherited.\n *\n * For a per-tag / per-property map — `attributes`, `protocols`, `ancestors`, `required`.\n */\nexport function ownMapEntry<T>(\n map: Readonly<Record<string, T>> | undefined,\n key: string,\n): T | undefined {\n if (map === undefined || map === null) return undefined;\n return Object.prototype.hasOwnProperty.call(map, key) ? map[key] : undefined;\n}\n\n/**\n * Top-level schema key as the sanitizer's merge resolves it: own only, typed.\n *\n * Same read as {@link ownMapEntry}; a separate signature only so a guard reading\n * `protocols` or `attributes` off a `Schema` keeps the library's type for that key\n * instead of casting at every call site.\n */\nexport function ownSchemaKey<K extends keyof Schema>(\n schema: Schema | undefined,\n key: K,\n): Schema[K] | undefined {\n return ownMapEntry(schema as Readonly<Record<string, Schema[K]>> | undefined, key as string);\n}\n","import type { Schema } from \"hast-util-sanitize\";\n\nimport type { MarkdownTrust } from \"../trust\";\nimport { attributeNameOf, normalisedAttributeName } from \"./attributeNames\";\nimport { ownMapEntry, ownSchemaKey } from \"./schemaMaps\";\nimport type { SchemaPair } from \"./types\";\n\ntype Attributes = NonNullable<Schema[\"attributes\"]>;\n\n/**\n * Attributes a capability may never add, beyond the `on*` event handlers.\n *\n * Each either executes CSS that can fetch (`style`, `background`), names a URL the\n * browser requests with no user action (`srcset`, `poster`, `ping`), or retargets a\n * submission (`formaction`). None appears in `defaultSchema`, so denying them costs\n * nothing today and closes the request-forging half of a hole the `on*` check only\n * half covered. Compared case-insensitively because hast property names are\n * camelCase (`srcSet`, `formAction`) while the DOM attribute names are not.\n */\nexport const FORBIDDEN_ATTRIBUTE_NAMES: ReadonlySet<string> = new Set([\n \"style\",\n \"srcset\",\n \"poster\",\n \"formaction\",\n \"ping\",\n \"background\",\n \"lowsrc\",\n \"dynsrc\",\n]);\n\n/**\n * Attributes that name a resource the browser fetches on view, denied under\n * `trust=\"untrusted\"` on any element that was not already allowed to carry the name.\n *\n * The untrusted profile promises one thing — no outbound request fires on view — and\n * the machinery behind it was written around IMAGES: the pre-sanitize transform\n * rewrites `<img>`, and `untrustedSchema` drops the image tag set. A capability adding\n * `video`/`audio`/`track` with a `src` walks past both, and a remote `src` in a ticket\n * body is the same exfiltration channel whether the browser fetches it as a picture or\n * a film. Denying the ATTRIBUTE rather than enumerating the elements is what keeps the\n * next media element from reopening it.\n *\n * WHY `href` IS IN A FETCHING DENYLIST. It is two different things wearing one name.\n * On `<a>` it is navigation and fetches nothing on view; on SVG's resource elements —\n * `<image>`, `<use>`, `<feImage>` — it IS the fetch, the direct analogue of `<img src>`,\n * and it fires the moment the element paints. Enumerating the elements is what this\n * package has now got wrong twice (images, then media), so the NAME is denied and the\n * one exception is expressed structurally instead: the caller skips any name the schema\n * being widened already granted on that tag, and `defaultSchema` grants `href` on `<a>`.\n * So `<a href>` passes and `href` on a NEW element does not. A capability that wants a\n * link writes `<a>`.\n *\n * Deliberately NOT applied under `trust=\"internal\"`: that profile is first-party copy\n * and keeps `defaultSchema`'s image allowance, so the invariant this enforces does not\n * exist there and the denial would be a restriction with nothing behind it.\n *\n * NO SEPARATE `xlinkhref` ENTRY. `normalisedAttributeName` strips the XLink prefix, so\n * `xlinkHref` and `xlink:href` both arrive here as `href` and the one entry covers all\n * three spellings. A fourth line spelling out `xlinkhref` could never match, and a\n * denylist entry that cannot fire reads as cover it does not give.\n *\n * `srcSet`, `poster` and `background` are in {@link FORBIDDEN_ATTRIBUTE_NAMES} already,\n * denied under every profile; this set is only what a trusted profile may still allow.\n */\nexport const REMOTE_SOURCE_ATTRIBUTE_NAMES: ReadonlySet<string> = new Set([\n \"src\",\n \"data\",\n \"href\",\n]);\n\n/**\n * SVG attributes whose VALUE may be a `url(…)` reference, denied under\n * `trust=\"untrusted\"` on any element that was not already allowed to carry the name.\n *\n * A DIFFERENT SHAPE OF THE SAME PROMISE. {@link REMOTE_SOURCE_ATTRIBUTE_NAMES} covers\n * names that ARE a URL. These are names whose value is a paint or a reference and may\n * be `url(https://tracker.example/f.svg#x)` — SVG's FuncIRI syntax. The browser\n * dereferences it on paint exactly as it would an `<img src>`, so `<rect\n * fill=\"url(https://…)\">` is the same outbound request on view, and the untrusted\n * profile promises there is none. Reproduced for `filter`, `fill`, `stroke`,\n * `clipPath`, `mask`, `markerEnd` and `cursor`; the rest of the FuncIRI set is here\n * with them because the list is SVG's, not a record of which ones were tried.\n *\n * WHY A SEPARATE SET RATHER THAN MORE ENTRIES ABOVE. The rule that governs them is the\n * same, but the reason they are dangerous is not, and the two will diverge: the set\n * above will grow when a new attribute NAMES a resource, this one when a new attribute\n * accepts a FuncIRI VALUE. Merging them would leave a reader unable to tell which\n * question a given name is an answer to.\n *\n * NOT DENIED UNDER `trust=\"internal\"`, for the same reason the set above is not: that\n * profile keeps `defaultSchema`'s image allowance, so a remote fetch is already within\n * what it permits.\n *\n * THE COST, STATED. An inline-diagram capability under `trust=\"untrusted\"` cannot have\n * `fill` at all — not even `fill=\"#333\"` — because a schema grants a NAME and the\n * dangerous part is the value. The narrower rule (allow the name, constrain the value\n * with `hast-util-sanitize`'s `[name, ...allowed]` tuple form) cannot be enforced from\n * here: this guard sees what a capability declared, not what it will be handed. A\n * capability that genuinely needs painted SVG in untrusted content is a change to this\n * package with a reviewer attached, which is the same bargain {@link\n * FORBIDDEN_TAG_NAMES} in `./tagNames` strikes.\n */\nexport const FUNCIRI_ATTRIBUTE_NAMES: ReadonlySet<string> = new Set([\n \"fill\",\n \"stroke\",\n \"filter\",\n \"clippath\",\n \"mask\",\n \"marker\",\n \"markerstart\",\n \"markermid\",\n \"markerend\",\n \"cursor\",\n \"colorprofile\",\n]);\n\n/**\n * The grant list for one tag, or a stated rejection.\n *\n * Every rule below, and `./attributeAliases` after them, reads an attributes entry as a\n * LIST. A schema that puts something else there (`{ p: \"className\" }`, or an own\n * `__proto__` copied through as data) used to reach `for…of` and come out as a bare\n * `TypeError: (entries ?? []) is not iterable` from inside a guard — fail-closed, but\n * unreadable, and it names neither the capability nor the key. This is the only place\n * the shape is read first, so it is the only place that has to say so. It stays here\n * rather than in its own module for the same reason: `./attributeAliases` runs after\n * this rule, over the same `next.attributes`, so by then the shape is settled.\n */\nfunction grantedNames(\n capabilityId: string,\n tag: string,\n entries: Attributes[string] | undefined,\n): readonly Attributes[string][number][] {\n if (entries === null || entries === undefined) return [];\n if (Array.isArray(entries)) return entries;\n throw new Error(\n `Markdown capability \"${capabilityId}\" returned a sanitize schema whose attributes entry ` +\n `for <${tag}> is not a list. hast-util-sanitize reads each entry as an array of property ` +\n `definitions — \"href\" or [\"className\", /^hljs-/] — and every rule here reads it the same ` +\n `way. Write attributes: { ${tag}: [\"…\"] }.`,\n );\n}\n\n/**\n * Rejects a widened schema that allows an event handler or a fetching attribute.\n * Takes the EFFECTIVE schemas.\n */\nexport function assertAttributesNotWidened(\n capabilityId: string,\n schemas: SchemaPair,\n trust: MarkdownTrust,\n): void {\n const previousAttributes = (ownSchemaKey(schemas.previous, \"attributes\") ?? {}) as Attributes;\n const nextAttributes = (ownSchemaKey(schemas.next, \"attributes\") ?? {}) as Attributes;\n\n for (const [tag, entries] of Object.entries(nextAttributes)) {\n // Names the base already allowed on this tag. `defaultSchema` allows `srcSet` on\n // `<source>`, so the URL-fetching denylist is applied to what the capability\n // ADDS, not to what it inherited — otherwise every capability would trip over a\n // pre-existing allowance it never touched. The `on*` check stays absolute: no\n // schema in this package has ever allowed one, so any match is an addition.\n // `ownMapEntry`, not `previousAttributes[tag]`: an INHERITED entry here reads as an\n // allowance the capability did not add, and so skips every denial below, while the\n // sanitizer resolves the same map with `own.call` and never sees it. See `./schemaMaps`.\n const inherited = new Set((ownMapEntry(previousAttributes, tag) ?? []).map(attributeNameOf));\n\n for (const entry of grantedNames(capabilityId, tag, entries)) {\n const name = attributeNameOf(entry);\n\n if (/^on/i.test(name)) {\n throw new Error(\n `Markdown capability \"${capabilityId}\" widened the sanitize schema to allow the event-handler attribute \"${name}\" on <${tag}>, which is never permitted.`,\n );\n }\n\n if (inherited.has(name)) continue;\n const normalised = normalisedAttributeName(name);\n\n if (FORBIDDEN_ATTRIBUTE_NAMES.has(normalised)) {\n throw new Error(\n `Markdown capability \"${capabilityId}\" widened the sanitize schema to allow the attribute \"${name}\" on <${tag}>, which fetches or retargets with no user action and is never permitted.`,\n );\n }\n\n if (trust === \"untrusted\" && REMOTE_SOURCE_ATTRIBUTE_NAMES.has(normalised)) {\n throw new Error(\n `Markdown capability \"${capabilityId}\" widened the sanitize schema to allow the attribute \"${name}\" on <${tag}> under trust=\"untrusted\", which is never permitted: untrusted content must fire no outbound request on view, and the image-blocking transform only covers <img>. If you meant a link, allow it on <a>, which already carries \"href\".`,\n );\n }\n\n if (trust === \"untrusted\" && FUNCIRI_ATTRIBUTE_NAMES.has(normalised)) {\n throw new Error(\n `Markdown capability \"${capabilityId}\" widened the sanitize schema to allow the attribute \"${name}\" on <${tag}> under trust=\"untrusted\", which is never permitted: its value may be a \\`url(…)\\` reference, and the browser fetches that on paint exactly as it fetches an image. A schema grants the NAME, so there is no way from here to allow the safe values and not the remote one.`,\n );\n }\n }\n }\n}\n","import type { Schema } from \"hast-util-sanitize\";\n\nimport { attributeNameOf, normalisedAttributeName } from \"./attributeNames\";\nimport { ownMapEntry, ownSchemaKey } from \"./schemaMaps\";\nimport type { SchemaPair } from \"./types\";\n\ntype Attributes = NonNullable<Schema[\"attributes\"]>;\n\nconst attributesOf = (schema: Schema): Attributes =>\n (ownSchemaKey(schema, \"attributes\") ?? {}) as Attributes;\n\n/**\n * THE SANITIZER KEYS EVERY PER-PROPERTY POLICY BY EXACT NAME. THE DOM DOES NOT.\n *\n * `hast-util-sanitize` looks its two per-property policies up by the property's own\n * spelling: the URL-scheme allowlist with `own.call(schema.protocols, key)`, and the\n * DOM-clobbering prefix with `schema.clobber.includes(key)` (lib/index.js). Neither\n * lookup normalises. `setAttribute`, on the other hand, lower-cases: a hast property\n * named `HREF` reaches the DOM as the attribute `href` and the browser follows it.\n *\n * So a SECOND SPELLING of a name that already carries policy is that policy deleted,\n * without touching the key the policy lives under — and therefore without tripping\n * `assertProtocolsNotWidened`, which watches `protocols`, or `assertOnlyMutableKeysChanged`,\n * which watches `clobber`. `attributes: { a: [..., \"HREF\"] }` is the whole exploit: it\n * widens only an allowlisted key, names nothing on any denylist, and renders\n * `<a href=\"javascript:…\">` live.\n *\n * Under `trust=\"internal\"` that capability throws nothing and renders\n * `<a href=\"javascript:alert(1)\">x</a>`, past three separate defences at once — the sanitizer's `protocols` map (keyed on `href`, so\n * `HREF` is unrestricted), the `REMOTE_SOURCE_ATTRIBUTE_NAMES` denial in `./attributes`\n * (which is deliberately untrusted-only), and react-markdown's own `defaultUrlTransform`\n * (keyed on the `a`/`href` pair, so it blanks the canonical spelling — the control case\n * rendered a bare `<a>` — and never sees this one).\n *\n * That last one is worth stating plainly: `MarkdownView` passes no `urlTransform`, so\n * what blanks an ordinary `javascript:` href today is an upstream default. It is a\n * second layer, not this package's guarantee. This rule is.\n *\n * WHY IT IS ITS OWN RULE AND NOT ANOTHER DENYLIST ENTRY. There is no list of dangerous\n * names to extend here: `HREF` is dangerous only because `href` is policed, and the\n * next such name arrives whenever `hast-util-sanitize` adds a per-property policy or a\n * capability is granted a new attribute. The definition is relational — a spelling that\n * differs from a policed one only by case or punctuation — so it closes the whole class,\n * including the `ID`/`NAME` variants that would step around clobber prefixing.\n */\n\n/**\n * The sources a policed spelling can come from, BROADEST POLICY FIRST.\n *\n * Four of them, each something the sanitizer or an earlier reviewer decided about one\n * particular spelling:\n * - keys of `protocols` — the URL-scheme allowlist (`href`, `src`, `cite`, `longDesc`);\n * - entries of `clobber` — the names prefixed so markup cannot shadow a DOM property;\n * - attributes granted on `*`;\n * - attributes granted on this tag.\n *\n * THE ORDER IS THE RULE, not an accident of how the list was written. The caller keeps\n * the FIRST spelling it meets for a normalised name, so a `protocols` or `clobber` key\n * outranks an attribute grant and a grant on `*` outranks one on a single tag: the\n * canonical spelling is the one with the widest reach, which is the one the error tells\n * the author to write.\n *\n * Each source reads ONE schema; {@link policedSpellings} runs all four across both\n * schemas of the pair.\n */\nconst SPELLING_SOURCES: readonly ((schema: Schema, tag: string) => readonly string[])[] = [\n (schema) => Object.keys(ownSchemaKey(schema, \"protocols\") ?? {}),\n (schema) => ownSchemaKey(schema, \"clobber\") ?? [],\n // Own entries only, the way the sanitizer resolves these maps — see `./schemaMaps`.\n (schema) => (ownMapEntry(attributesOf(schema), \"*\") ?? []).map(attributeNameOf),\n (schema, tag) => (ownMapEntry(attributesOf(schema), tag) ?? []).map(attributeNameOf),\n];\n\n/**\n * The policed spellings a widening is judged against, normalised form → canonical\n * spelling: the policies already in force AND the ones the widening installs.\n *\n * WHY `next` AND NOT JUST `previous`. A policy is not only something a capability\n * inherits — it can ARRIVE IN THE SAME WIDENING. A capability adding\n * `protocols: { foo: [\"https\"] }` together with `attributes: { mytag: [\"foo\", \"FOO\"] }`\n * aliases nothing if only `previous` is read: `foo` carries no policy when the map is\n * built, `assertProtocolsNotWidened` passes the perfectly safe `foo` restriction, and\n * `FOO` reaches the schema with none of it.\n *\n * WHY THE LOOP IS BY SOURCE AND NOT BY SCHEMA. The policy can also arrive one\n * capability LATER. `pipeline.ts` folds capabilities one at a time, so `previous` is\n * whatever the earlier ones widened: capability A grants `attributes: { mytag: [\"FOO\"] }`\n * while nothing polices that name yet — nothing to compare it against, so it passes —\n * and capability B then adds `protocols: { foo: [\"https\"] }`, a narrowing\n * `assertProtocolsNotWidened` is right to allow. Walking all of `previous` before all of\n * `next` lets A's tag grant claim the normalised name ahead of B's `protocols` key, so\n * `policed === name` and the policy that has just arrived restricts nothing: `sanitize()`\n * on `<mytag foo=\"javascript:…\" FOO=\"javascript:…\">` returns `{ FOO: \"javascript:…\" }`,\n * canonical spelling stripped and alias through untouched.\n *\n * WHAT RANKING BY SOURCE STOPS REJECTING. Over every pair drawn from\n * {protocols, clobber, `*`, tag} x {foo, FOO}, the pairs it newly accepts are all one\n * shape: a `clobber` entry that is a second spelling of a `protocols` key, with the\n * grant matching the top-ranked one. Nothing is aliased from the GRANT there; the schema\n * itself polices two spellings. A capability cannot build it — `clobber` is not in\n * `MUTABLE_SCHEMA_KEYS`, so touching it throws in `assertOnlyMutableKeysChanged` three\n * rules earlier — and it could only arrive from the library defaults, which a test in\n * this folder pins as unaliased at both trust levels.\n *\n * So the rank is by SOURCE across the pair, and `previous` before `next` is only the\n * tie-break WITHIN one source — still enough to name the spelling already in force, and\n * no longer able to let a grant outrank a policy.\n */\nfunction policedSpellings(schemas: SchemaPair, tag: string): ReadonlyMap<string, string> {\n const canonical = new Map<string, string>();\n for (const spellingsOf of SPELLING_SOURCES) {\n for (const name of [...spellingsOf(schemas.previous, tag), ...spellingsOf(schemas.next, tag)]) {\n const normalised = normalisedAttributeName(name);\n if (!canonical.has(normalised)) canonical.set(normalised, name);\n }\n }\n return canonical;\n}\n\n/**\n * Rejects a widened schema that allows a second spelling of a name the sanitizer\n * already attaches a policy to. Takes the EFFECTIVE schemas, because both policies\n * this protects live in keys a schema may inherit from the library defaults.\n *\n * Applies at EVERY trust level: this is not the \"no fetch on view\" promise that\n * `trust=\"untrusted\"` makes, it is the URL-scheme allowlist and the clobber prefix,\n * which hold everywhere.\n */\nexport function assertNoAliasedAttributeNames(capabilityId: string, schemas: SchemaPair): void {\n for (const [tag, entries] of Object.entries(attributesOf(schemas.next))) {\n const canonical = policedSpellings(schemas, tag);\n\n for (const entry of entries ?? []) {\n const name = attributeNameOf(entry);\n const policed = canonical.get(normalisedAttributeName(name));\n if (policed === undefined || policed === name) continue;\n\n throw new Error(\n `Markdown capability \"${capabilityId}\" widened the sanitize schema to allow the attribute ` +\n `\"${name}\" on <${tag}>, which is \"${policed}\" spelled differently. hast-util-sanitize ` +\n `keys its URL-scheme allowlist and its DOM-clobbering prefix on the exact property name, ` +\n `while the DOM lower-cases it — so the second spelling reaches the browser as the same ` +\n `attribute with none of the restrictions attached to the first. Write \"${policed}\".`,\n );\n }\n }\n}\n","import type { Schema } from \"hast-util-sanitize\";\n\nimport { ownSchemaKey } from \"./schemaMaps\";\nimport type { SchemaPair } from \"./types\";\n\ntype ProtocolMap = NonNullable<Schema[\"protocols\"]>;\n\n/**\n * The URL schemes a sanitize schema in this package may name.\n *\n * `schema.protocols` is the allowlist that decides which schemes survive on a URL\n * property (`href`, `src`, `cite`, `longDesc`). It is a SEPARATE seam from `tagNames`\n * and `attributes`: a capability that touches neither of those can still hand back\n * `protocols: { href: [\"javascript\"] }` and re-open script execution through an\n * ordinary anchor, having passed every other guard untouched.\n *\n * The set is `hast-util-sanitize`'s own default union (http, https, irc, ircs,\n * mailto, xmpp) plus `tel` and `ftp`, which name a resource the browser hands to\n * another handler and carry no script. Absent on purpose: `javascript` and\n * `vbscript` (execution), `data`, `blob` and `filesystem` (an inline document with\n * its own origin behaviour), and anything unrecognised.\n */\nexport const SAFE_PROTOCOLS: ReadonlySet<string> = new Set([\n \"http\",\n \"https\",\n \"mailto\",\n \"tel\",\n \"ftp\",\n \"irc\",\n \"ircs\",\n \"xmpp\",\n]);\n\n/** The properties a schema restricts, dropping the ones it leaves unrestricted. */\nfunction restrictionsOf(schema: Schema): Map<string, readonly string[]> {\n const restrictions = new Map<string, readonly string[]>();\n const map = (ownSchemaKey(schema, \"protocols\") ?? {}) as ProtocolMap;\n for (const [property, protocols] of Object.entries(map)) {\n // An empty list is not an empty allowlist: hast-util-sanitize reads\n // `!protocols || protocols.length === 0` as \"no restriction on this property at\n // all\" (lib/index.js), so `href: []` admits every scheme including javascript:.\n // Treating it as unrestricted here is what lets the dropped-restriction check\n // below catch it.\n if (protocols && protocols.length > 0) restrictions.set(property, protocols);\n }\n return restrictions;\n}\n\n/**\n * Rejects a widened schema that admits an unsafe URL scheme, or that stops\n * restricting a property the previous schema restricted. Takes the EFFECTIVE\n * schemas.\n *\n * Both halves are the same hole. A capability can re-open `javascript:` by NAMING it\n * (`href: [\"javascript\"]`) or by REMOVING the restriction that kept it out — setting\n * the list to `[]`, deleting the property from the map, or replacing the map with one\n * that omits it. The first is caught by the allowlist, the last three by comparing\n * against what the capability was handed.\n */\nexport function assertProtocolsNotWidened(capabilityId: string, schemas: SchemaPair): void {\n const next = restrictionsOf(schemas.next);\n\n for (const [property, protocols] of next) {\n for (const protocol of protocols) {\n if (SAFE_PROTOCOLS.has(String(protocol).toLowerCase())) continue;\n throw new Error(\n `Markdown capability \"${capabilityId}\" widened the sanitize schema to allow the \"${protocol}\" ` +\n `URL scheme on \"${property}\", which is never permitted. Allowed schemes: ` +\n `${[...SAFE_PROTOCOLS].join(\", \")}.`,\n );\n }\n }\n\n for (const property of restrictionsOf(schemas.previous).keys()) {\n if (next.has(property)) continue;\n throw new Error(\n `Markdown capability \"${capabilityId}\" removed the URL-scheme restriction on \"${property}\". ` +\n \"hast-util-sanitize treats a missing or empty protocol list as no restriction at all, so \" +\n \"this admits every scheme, javascript: included. Spread the protocols you were given: \" +\n \"(s) => ({ ...s, protocols: { ...s.protocols, ... } }).\",\n );\n }\n}\n","import type { Schema } from \"hast-util-sanitize\";\n\nimport { ownSchemaKey } from \"./schemaMaps\";\n\n/**\n * WHAT `hast-util-sanitize` DOES WITH EACH MUTABLE KEY WHEN IT IS FALSY — STATED ONCE.\n *\n * `./mutableKeys` says WHICH top-level keys a capability may change. It deliberately\n * says nothing about what it may change them TO, because the dangerous CONTENT of each\n * is a different question answered by a different module (`./tagNames`,\n * `./attributes`, `./protocols`). That left a third question nobody owned: what happens\n * when a mutable key holds no content at all.\n *\n * It is not one answer. The sanitizer reads a falsy value differently for each key, and\n * two of the three readings are the OPPOSITE of \"nothing is allowed\":\n *\n * - `tagNames` — `!schema.tagNames || schema.tagNames.includes(name)` (lib/index.js).\n * Falsy removes the element allowlist: EVERY element is allowed, `<script>` with it.\n * - `attributes` — `attributes && own.call(attributes, tagName)` (lib/index.js).\n * Falsy allows NO attribute anywhere. Fail-safe, but it silently strips `className`,\n * `href` and the DOM-clobber prefix, and the author is told nothing.\n * - `protocols` — a property is only scheme-checked if the map restricts it. Falsy\n * restricts nothing: every scheme survives, `javascript:` with it.\n *\n * THIS MODULE EXISTS BECAUSE THE ANSWER WAS REACHED THREE TIMES SEPARATELY AND ONCE\n * WRONGLY. `protocols` was covered by `assertProtocolsNotWidened`'s dropped-restriction\n * half, `attributes` by the sanitizer happening to fail safe, and `tagNames` by nothing\n * at all until a review found `(s) => ({ ...s, tagNames: undefined })` disabling the\n * element allowlist outright — an exploit needing no accessor, no prototype and no\n * aliasing, only a key the capability was already allowed to touch. Three different\n * mechanisms, one of them absent, is not a rule; it is three accidents, and the odd one\n * out is only ever found by someone asking the question for all three at once. So the\n * question is asked here, mechanically, for every mutable key — and the set of mutable\n * keys is DERIVED from this table, so a fourth one cannot exist without an answer.\n *\n * The rule itself is deliberately dumb: a key that must hold a LIST must hold an array,\n * and a key that must hold a MAP must hold an object that is not one. It judges no\n * content — that is the other modules' work, and every one of them is written assuming\n * the shape this rule establishes.\n */\n\n/** The two shapes a sanitize schema's mutable keys take. */\nexport type MutableKeyShape = \"list\" | \"map\";\n\nexport interface MutableKeyShapeRule {\n /** The shape the sanitizer requires of this key. */\n readonly shape: MutableKeyShape;\n /** What the sanitizer does when this key is falsy — the reason the shape is policed. */\n readonly whenFalsy: string;\n}\n\n/**\n * THE ONE PLACE THE MUTABLE KEYS ARE NAMED.\n *\n * `MUTABLE_SCHEMA_KEYS` in `./mutableKeys` and {@link MutableSchemaKey} below are both\n * derived from this table, so a fourth key cannot be added to one and forgotten in the\n * others — the case the shape rule exists for in the first place.\n */\nexport const MUTABLE_KEY_SHAPES = {\n tagNames: {\n shape: \"list\",\n whenFalsy:\n 'hast-util-sanitize reads a missing, null or otherwise falsy tagNames as \"every element ' +\n 'name is allowed\" rather than as an empty allowlist, so this does not narrow the schema, ' +\n \"it removes the element allowlist entirely — <script> included\",\n },\n attributes: {\n shape: \"map\",\n whenFalsy:\n \"hast-util-sanitize resolves an attributes entry with own.call(attributes, tagName) and \" +\n 'reads a missing or falsy map as \"no element carries any attribute\", so this widens ' +\n \"nothing and silently strips every attribute the pipeline allowed — className, href and \" +\n \"the DOM-clobber prefix with them\",\n },\n protocols: {\n shape: \"map\",\n whenFalsy:\n \"hast-util-sanitize scheme-checks a URL property only while the protocols map restricts \" +\n \"it, so a missing or falsy map applies no URL-scheme restriction at all and every scheme \" +\n \"survives, javascript: included\",\n },\n} as const satisfies Readonly<Record<string, MutableKeyShapeRule>>;\n\n/**\n * The keys the table above covers. A UNION rather than `string`, so\n * {@link assertMutableKeyShape} cannot be called with a key the table does not know:\n * a lookup that can miss needs a branch for the miss, and a shape guard whose miss\n * branch returns quietly is the same silence this module was written to remove.\n */\nexport type MutableSchemaKey = keyof typeof MUTABLE_KEY_SHAPES;\n\n/** How an author writes the shape, for the end of the error. */\nconst EXAMPLE: Readonly<Record<MutableKeyShape, string>> = {\n list: '(s) => ({ ...s, <key>: [...s.<key>, \"…\"] })',\n map: \"(s) => ({ ...s, <key>: { ...s.<key>, … } })\",\n};\n\nfunction hasShape(value: unknown, shape: MutableKeyShape): boolean {\n if (shape === \"list\") return Array.isArray(value);\n // A map, as the sanitizer resolves one: any non-array object will answer `own.call`.\n // `Object.create(null)` counts — `cloneSchema` produces exactly that, so requiring a\n // literal `Object.prototype` here would reject every schema the pipeline snapshots.\n //\n // It also means an EXOTIC object (a `Date`, a class instance) passes, and it should:\n // this rule reads the clone, where `cloneSchema` has already rebuilt such a value from\n // its own enumerable properties — a `Date` arrives as an empty map, indistinguishable\n // from `{}` and enforced the same fail-safe way. There is nothing left here to reject.\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction shapeError(\n capabilityId: string,\n key: MutableSchemaKey,\n rule: MutableKeyShapeRule,\n): Error {\n return new Error(\n `Markdown capability \"${capabilityId}\" widened the sanitize schema so that \"${key}\" is not ` +\n `a ${rule.shape}. ${rule.whenFalsy}. Spread the ${rule.shape} you were given: ` +\n `${EXAMPLE[rule.shape].replaceAll(\"<key>\", key)}.`,\n );\n}\n\n/**\n * Rejects one mutable key whose value is not the shape the sanitizer requires.\n *\n * Exported for the guards that depend on the shape as a precondition — `./tagNames`\n * iterates its list — so the precondition and the rule are the same call rather than\n * two spellings of one idea that can drift apart.\n */\nexport function assertMutableKeyShape(\n capabilityId: string,\n key: MutableSchemaKey,\n value: unknown,\n): void {\n const rule = MUTABLE_KEY_SHAPES[key];\n if (hasShape(value, rule.shape)) return;\n throw shapeError(capabilityId, key, rule);\n}\n\n/**\n * Rejects a widened schema whose mutable keys are not the shapes the sanitizer\n * requires. Takes the EFFECTIVE schema — the one the sanitizer will enforce, where a\n * key the capability never mentioned has already fallen back to the library default\n * (which is always well-shaped) and a key it set to `undefined` has not.\n *\n * Runs BEFORE the value rules: each of them reads one of these keys as a list or a map,\n * and a rule that cannot read its key reports on a schema that is not the one enforced.\n */\nexport function assertMutableKeysWellShaped(capabilityId: string, effective: Schema): void {\n for (const key of Object.keys(MUTABLE_KEY_SHAPES) as MutableSchemaKey[]) {\n assertMutableKeyShape(capabilityId, key, ownSchemaKey(effective, key));\n }\n}\n","import { ownMapEntry } from \"./schemaMaps\";\nimport { MUTABLE_KEY_SHAPES } from \"./mutableKeyShapes\";\nimport type { SchemaPair } from \"./types\";\n\n/**\n * The top-level schema keys a capability is permitted to change.\n *\n * DERIVED from {@link MUTABLE_KEY_SHAPES} rather than written out again. The two used\n * to be separate literals kept equal by a test; the shape table is the more primitive\n * fact — it carries the shape and what the sanitizer does with a falsy value as well as\n * the name — so a fourth key is now added in exactly one place and cannot arrive here\n * without a shape rule.\n *\n * WHY AN ALLOWLIST AND NOT ONE MORE DENYLIST. The other guards in this folder each\n * name something dangerous: a tag, an attribute, a URL scheme. That shape works while\n * the set of dangerous things is known, and it was the shape of the first four rules\n * here — which left the five top-level keys nobody had thought about wide open, and\n * would leave the next key `hast-util-sanitize` adds open on the day it ships.\n *\n * These three are the keys a RENDERING FEATURE needs: a capability's own markup needs\n * its element allowed (`tagNames`), its attributes allowed (`attributes`), and\n * occasionally a URL scheme (`protocols`). Nothing a capability legitimately does\n * requires touching the rest, and two of the rest are live security controls:\n *\n * - `clobber` / `clobberPrefix` prefix `id` and `name` values so markup cannot shadow\n * a DOM property. `defaultSchema` allows both attributes globally, so emptying\n * these is the whole of the defence against `<a id=\"body\">` shadowing\n * `document.body` for any script that reads it.\n * - `required` is written straight into the output by `hast-util-sanitize`'s\n * `properties()` with NO protocol check — `result[key] = properties[key]`. A\n * required default is therefore a route past the `protocols` allowlist, which is\n * the rule this whole folder was built around.\n *\n * So a capability that wants one of these is not writing a rendering feature; it is\n * changing the sanitizer's policy, and that is a change to this file with a reviewer\n * attached rather than something a plugin can do from its own repository.\n */\nexport const MUTABLE_SCHEMA_KEYS: ReadonlySet<string> = new Set(Object.keys(MUTABLE_KEY_SHAPES));\n\n/**\n * Structural equality over schema data.\n *\n * By VALUE, not by identity, because every capability spreads: `{ ...s }` hands back\n * a fresh object for every key it did not touch, and the pipeline snapshots the result\n * on top of that, so identity is never preserved and comparing it would fail every\n * well-behaved capability. Private to this module — it is the comparison this one rule\n * needs, not a general-purpose utility.\n */\nfunction sameValue(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n // The clone rebuilds a RegExp, so the pattern must be compared by what it matches.\n if (a instanceof RegExp || b instanceof RegExp) {\n return (\n a instanceof RegExp && b instanceof RegExp && a.source === b.source && a.flags === b.flags\n );\n }\n if (Array.isArray(a) || Array.isArray(b)) {\n return (\n Array.isArray(a) &&\n Array.isArray(b) &&\n a.length === b.length &&\n a.every((entry, index) => sameValue(entry, b[index]))\n );\n }\n if (a === null || b === null || typeof a !== \"object\" || typeof b !== \"object\") return false;\n\n const left = a as Record<string, unknown>;\n const right = b as Record<string, unknown>;\n const keys = Object.keys(left);\n return (\n keys.length === Object.keys(right).length &&\n keys.every(\n (key) =>\n Object.prototype.hasOwnProperty.call(right, key) && sameValue(left[key], right[key]),\n )\n );\n}\n\n/**\n * Rejects a widened schema that changes any key outside {@link MUTABLE_SCHEMA_KEYS}.\n * Takes the RETURNED schemas — this rule is about what the capability wrote down, and\n * the merge over the library defaults would hide a key it removed behind the default.\n *\n * Runs after {@link assertNoDroppedKeys}, which already names the common accidental\n * shape (forgetting to spread) in terms the author will recognise. What is left for\n * this rule is a key that is PRESENT and DIFFERENT, or present and new.\n */\nexport function assertOnlyMutableKeysChanged(capabilityId: string, schemas: SchemaPair): void {\n const previous = schemas.previous as Record<string, unknown>;\n const next = schemas.next as Record<string, unknown>;\n\n // A key the capability ADDED is a change whatever its value, so OWN-PRESENCE on\n // `previous` is checked before any values are compared. Comparing values alone left two\n // ways for an added key to read as unchanged, both of them `undefined` in a disguise:\n //\n // - a chained `previous[key]` answered off `Object.prototype`, and an added\n // `__proto__` matched it because `Object.keys(Object.prototype)` is empty — closed\n // by reading through `ownMapEntry`, which is kept below so the read stays own-only\n // if this presence check is ever moved;\n // - an own read answers `undefined` for an ABSENT key, so an added key whose value is\n // itself `undefined` compares equal to absent — `__proto__: undefined` and\n // `somethingNew: undefined` both reach the enforced schema unnamed.\n //\n // Both are inert — neither value carries anything, and the sanitizer's\n // `{ ...defaultSchema, ...options }` cannot tell a new key set to `undefined` from an\n // absent one — but this rule's claim is that every key it does not name is what the\n // capability was handed. No false positive follows: a capability spreads, so every key\n // it hands back that it did not invent is own on `previous`.\n const changed = Object.keys(next).filter(\n (key) =>\n !MUTABLE_SCHEMA_KEYS.has(key) &&\n (!Object.prototype.hasOwnProperty.call(previous, key) ||\n !sameValue(next[key], ownMapEntry(previous, key))),\n );\n if (changed.length === 0) return;\n\n throw new Error(\n `Markdown capability \"${capabilityId}\" changed the sanitize schema key ${changed\n .map((key) => `\"${key}\"`)\n .join(\", \")}, which a capability may not change. ` +\n `A capability may widen only ${[...MUTABLE_SCHEMA_KEYS].map((key) => `\"${key}\"`).join(\", \")} ` +\n \"— the keys a rendering feature needs. The rest are the sanitizer's own policy \" +\n \"(clobber/clobberPrefix stop DOM clobbering; a `required` default is written out \" +\n \"with no protocol check), so changing one is a change to the markdown package with \" +\n \"a reviewer attached, not something a capability does. Spread what you were given \" +\n \"and touch only the keys your own markup needs.\",\n );\n}\n","import type { Schema } from \"hast-util-sanitize\";\n\n/**\n * Rejects an `extendSchema` that did not hand back a schema at all.\n *\n * WHY THIS IS A RULE AND NOT A CAST. `(s) => { if (!needed) return; ... }` is an easy\n * shape to write by accident, and `MarkdownCapability[\"extendSchema\"]` is a TypeScript\n * signature — erased at runtime, and unenforced for a JS plugin, a cast, or a function\n * whose early return TypeScript infers as `void` on a branch the author did not look\n * at. The value still failed closed without this rule, but it failed as a bare\n * `TypeError: Cannot convert undefined or null to object` thrown from inside the\n * snapshot, naming no capability — the one violation in this folder that told its\n * author nothing. Every other rule here names the capability and explains itself;\n * so does this one.\n *\n * An ARRAY is rejected with the rest. It has own keys, so the later rules would read\n * it without complaint and report, of an `extendSchema` that returned `[\"p\"]`, that it\n * dropped eight schema keys.\n *\n * Emptiness is not this rule's business: `{}` is a well-formed object and\n * {@link assertNoDroppedKeys} is the rule that has something to say about it.\n */\nexport function assertSchemaReturned(\n capabilityId: string,\n value: unknown,\n): asserts value is Schema {\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) return;\n\n throw new Error(\n `Markdown capability \"${capabilityId}\" has an extendSchema that returned ` +\n `${Array.isArray(value) ? \"an array\" : String(value)} rather than a schema. ` +\n \"extendSchema must return the schema it was given, widened: (s) => ({ ...s, ... }). \" +\n \"Returning nothing on some branch is the usual cause.\",\n );\n}\n","import type { SchemaPair } from \"./types\";\n\n/**\n * Rejects a widened schema that drops a top-level key. Takes the RETURNED schemas —\n * the merge this guard is about has not happened yet.\n *\n * WHY A DROPPED KEY IS DANGEROUS. `hast-util-sanitize` merges its options as\n * `{ ...defaultSchema, ...options }` — a SHALLOW merge, so any top-level key a schema\n * omits silently falls back to the LIBRARY DEFAULT rather than to the value this\n * package computed. A capability written as\n * `(s) => ({ attributes: { ...s.attributes, mytag: [\"dataX\"] } })` — the natural shape\n * of the mistake, widening attributes and forgetting to carry `tagNames` through —\n * would therefore restore `img` under `trust=\"untrusted\"`. Rejecting the drop is\n * clearer than silently repairing it: the capability is wrong, and its author should\n * be told so.\n *\n * WHY `hasOwnProperty` AND NOT `in`. `in` walks the prototype chain; the merge above\n * copies own enumerable properties only. A schema built as\n * `Object.assign(Object.create(previous), { attributes: … })` therefore has every key\n * of `previous` visible to `in` and none of them visible to the merge — the exact\n * widening this guard exists to catch, wearing the guard's own answer as a disguise.\n * The check has to inspect precisely what the merge will copy.\n */\nexport function assertNoDroppedKeys(capabilityId: string, schemas: SchemaPair): void {\n const dropped = Object.keys(schemas.previous).filter(\n (key) => !Object.prototype.hasOwnProperty.call(schemas.next, key),\n );\n if (dropped.length === 0) return;\n\n throw new Error(\n `Markdown capability \"${capabilityId}\" returned a schema without ${dropped\n .map((key) => `\"${key}\"`)\n .join(\", \")} as an own property. ` +\n \"hast-util-sanitize merges a schema over its own defaults one level deep and \" +\n \"copies own properties only, so a dropped (or merely inherited) key falls back \" +\n \"to the library default rather than to the pipeline's schema. Spread the schema \" +\n \"you were given: (s) => ({ ...s, ... }).\",\n );\n}\n","import type { Schema } from \"hast-util-sanitize\";\n\n/**\n * A SANITIZE SCHEMA IS DATA. A VALUE THAT CAN RUN IS NOT DATA.\n *\n * Every other rule in this folder reads the schema and decides whether what it says is\n * safe. That is only meaningful if what it says is fixed — if the value the guard read\n * is the value `rehype-sanitize` will read at render time, minutes later. `cloneSchema`\n * makes that true for data by rebuilding each property by value, and it makes it true\n * for arrays and plain objects by rebuilding them plainly. It cannot make it true for a\n * FUNCTION: `typeof fn !== \"object\"`, so the clone returns it by reference and\n * `Object.freeze` skips it.\n *\n * That is enough for a complete bypass, because the sanitizer never iterates the values\n * it checks — it ASKS them. `schema.tagNames.includes(name)` and\n * `allowed.includes(prop)` are method calls (hast-util-sanitize lib/index.js), so a\n * callable standing in for an array can answer one way while the guards walk it with\n * `for…of`, and another way once they are done: a `tagNames` that is a function with a\n * generator `[Symbol.iterator]` and a custom `includes` passes every guard, survives the\n * clone still callable and unfrozen, and answers `includes(\"script\") === true`.\n *\n * WHY A DENIAL RATHER THAN A CONVERSION. `cloneSchema` could have materialised a\n * callable by spreading it — but a capability that supplies one has not made a mistake\n * this package should quietly correct, and a silent conversion would leave the author\n * believing their value is in use. There is no legitimate schema that contains one:\n * `Schema`'s every field is a string, a boolean, an array of those, or a record of\n * them, plus the `RegExp` in an attribute tuple. Anything else is either an attack or a\n * bug, and both want the same loud answer.\n *\n * WHY IT RUNS FIRST. The rules after it assume `tagNames` is a list they can read and\n * `attributes` a record they can enumerate. Reaching them with a callable in hand is\n * how a guard comes to report on a value that is not the one enforced.\n */\n\n/** Values a schema may contain, besides arrays, plain objects and `RegExp`. */\nconst SCHEMA_VALUE_TYPES: ReadonlySet<string> = new Set([\n \"string\",\n \"number\",\n \"boolean\",\n \"undefined\",\n]);\n\nfunction describe(value: unknown): string {\n if (typeof value === \"function\") return \"a function\";\n return `a ${typeof value}`;\n}\n\n/**\n * Rejects a schema holding a value that is not plain data, anywhere in it.\n *\n * Walks the CLONED schema, so arrays and objects here are already plain and the only\n * thing that can still be exotic is one the clone had to pass through by reference.\n * The path is reported (`tagNames`, `attributes.a[2]`) because a capability's\n * `extendSchema` is usually one spread and the author needs to know which key of it.\n */\nexport function assertSchemaIsData(capabilityId: string, schema: Schema): void {\n walk(capabilityId, schema as unknown, \"\");\n}\n\nfunction walk(capabilityId: string, value: unknown, path: string): void {\n if (value === null || value instanceof RegExp) return;\n if (SCHEMA_VALUE_TYPES.has(typeof value)) return;\n\n if (Array.isArray(value)) {\n value.forEach((entry, index) => walk(capabilityId, entry, `${path}[${index}]`));\n return;\n }\n\n if (typeof value === \"object\") {\n for (const [key, entry] of Object.entries(value)) {\n walk(capabilityId, entry, path ? `${path}.${key}` : key);\n }\n return;\n }\n\n throw new Error(\n `Markdown capability \"${capabilityId}\" returned a sanitize schema whose ` +\n `${path || \"root\"} is ${describe(value)}, not data. A schema is read by the guards ` +\n `here and then again by hast-util-sanitize at render time, and the sanitizer ASKS ` +\n `its values rather than iterating them — \\`tagNames.includes(name)\\` — so a value ` +\n `that can run can answer the two differently. Only strings, numbers, booleans, ` +\n `arrays, plain objects and RegExp belong in a schema.`,\n );\n}\n","import type { Schema } from \"hast-util-sanitize\";\n\nimport type { MarkdownTrust } from \"../trust\";\nimport { assertMutableKeyShape } from \"./mutableKeyShapes\";\nimport { ownSchemaKey } from \"./schemaMaps\";\n\n/**\n * Elements a capability may never add to the allowlist. Widening to any of these is\n * a script-execution or request-forging primitive, not a rendering feature.\n *\n * WHY THIS ONE STAYS A DENYLIST. `./mutableKeys` argues the opposite case for the\n * top-level keys, and the argument is sound there because the set a capability\n * legitimately needs is CLOSED — three keys, and nothing a rendering feature does\n * requires a fourth. Tags are the opposite: the whole point of a capability is its own\n * markup, so the set of tags it may legitimately add is open-ended by design\n * (`attachmentchip`, a diagram element, an SVG subset) and an allowlist would have to be\n * edited in this file for every capability anyone ever writes. That is a decision, not\n * an omission — but it is the decision that has been wrong three times (images, then\n * media, then SVG resource elements), and each time the durable fix has been to deny\n * the dangerous ATTRIBUTE in `./attributes` instead. Reach for that first. A tag\n * belongs here only when the ELEMENT ITSELF is the primitive, with no attribute to\n * point at — which is exactly the case for {@link ANIMATION_TAG_NAMES} below.\n */\nexport const FORBIDDEN_TAG_NAMES: readonly string[] = [\n \"script\",\n \"style\",\n \"iframe\",\n \"object\",\n \"embed\",\n \"base\",\n \"meta\",\n \"form\",\n // The third spelling of `style`. `<style>` is the inline one and `style=` the\n // attribute (denied in `./attributes`); `<link rel=\"stylesheet\" href>` is the same\n // primitive fetched from off-origin, and it was the one missing. Under\n // `trust=\"untrusted\"` the `href` denial in `./attributes` happens to stop it, but\n // under `internal` it rendered live: `<link rel=\"stylesheet\" href=\"https://…\">`.\n // Attacker CSS is not cosmetic — it reads the page through selectors and exfiltrates\n // through background URLs.\n \"link\",\n];\n\n/**\n * SMIL animation elements: they do not render anything, they REWRITE another element\n * at render time.\n *\n * `<a href=\"#\"><animate attributeName=\"href\" to=\"javascript:…\" begin=\"0s\" fill=\"freeze\"/></a>`\n * retargets an anchor the sanitizer has already approved. Every value rule in this\n * folder — the URL-scheme allowlist above all — reads the attribute a schema NAMES;\n * here the dangerous URL is a string VALUE of `to`, on an element whose own attributes\n * are unremarkable, so those rules have nothing to look at. The attribute-denial\n * approach `./attributes` took (deny the NAME, not the element) cannot see it either,\n * for the same reason. Denying the element is the only reading that works, which is\n * what earns these a place in a list the note above says to reach for last.\n *\n * Absolute, on both trust profiles: this is script execution, not an image policy. And\n * reachable by a plausible capability rather than a contrived one — an inline-SVG or\n * diagram capability widens `svg` plus a batch of SVG child tags, and `animate` is an\n * ordinary member of that batch.\n *\n * THE LIST IS SMIL'S, NOT ONE WE OBSERVED. `animate`, `animateMotion`,\n * `animateTransform` and `set` were each reproduced surviving the guard into the\n * rendered tree; `animateColor` was not — it is deprecated and current engines no\n * longer honour it — and it is here because the definition above is \"an SMIL element\n * that rewrites another element\", which it is by specification. An element enumerated\n * because it belongs to a closed spec set is not the same claim as one enumerated\n * because an exploit was watched to work, and only `animateColor` is the former.\n * `discard`, `mpath` and `handler` were tried and are NOT here: `discard` removes an\n * element rather than retargeting one, and the other two are inert without a parent\n * this list already denies.\n */\nexport const ANIMATION_TAG_NAMES: readonly string[] = [\n \"animate\",\n \"animatecolor\",\n \"animatemotion\",\n \"animatetransform\",\n \"set\",\n];\n\n/**\n * The image elements. Under `trust=\"untrusted\"` these are dropped from the allowlist\n * and `rehypeBlockRemoteImages` rewrites `<img>` into a visible placeholder.\n *\n * NOT the whole of \"fires an outbound request on view\", and a capability author must\n * not read it as such: `<video src>` fetches too, and so does SVG `<image href>`, and\n * neither the transform nor this list looks at either. The rest is covered one layer\n * over by `REMOTE_SOURCE_ATTRIBUTE_NAMES` in `./attributes`, which denies the fetching\n * ATTRIBUTE (`src`, `data`, `href`) on any element that did not already carry it,\n * rather than trying to enumerate the elements — the enumeration here is what let\n * `<video src>` and then `<image href>` through in the first place. Between the two,\n * the attribute rule is the one that generalises; this list is the belt to its braces\n * for the image case the pre-sanitize transform also rewrites. Neither is a claim that\n * every fetch is closed: add the next one to `./attributes`, not here.\n */\nexport const IMAGE_TAG_NAMES: readonly string[] = [\"img\", \"picture\", \"source\"];\n\nconst FORBIDDEN_TAG_SET = new Set(FORBIDDEN_TAG_NAMES);\nconst ANIMATION_TAG_SET = new Set(ANIMATION_TAG_NAMES);\nconst IMAGE_TAG_SET = new Set(IMAGE_TAG_NAMES);\n\n/**\n * Rejects a widened schema that allows a forbidden element. Takes the EFFECTIVE\n * schema — the one the sanitizer will enforce.\n *\n * WHY EVERY NAME IS LOWER-CASED FIRST. Both denylists are written in HTML's canonical\n * lower case, but `tagNames` is matched by `hast-util-sanitize` against the hast\n * node's `tagName` BY IDENTITY, and nothing upstream normalises a tag name that a\n * capability's own rehype plugin put into the tree. So `tagNames: [..., \"SCRIPT\"]`\n * plus a plugin emitting `{ tagName: \"SCRIPT\" }` survives sanitize, and\n * react-markdown hands that name to `createElement`, which yields a real `<script>`\n * host element. A case-sensitive `has()` is not a denylist; it is a spelling test.\n *\n * The ORIGINAL spelling goes in the message, so the author sees what they wrote\n * rather than a normalised form they will not find in their own source.\n *\n * WHY THE LIST ITSELF IS CHECKED BEFORE ANY ENTRY IS. `hast-util-sanitize`'s tag\n * check is `!schema.tagNames || schema.tagNames.includes(name)` (lib/index.js) — a\n * FALSY `tagNames` does not mean \"nothing is allowed\", it means the whole check\n * short-circuits to true and every element name is allowed, `<script>` included.\n * `./mutableKeys` puts `tagNames` on the list a capability may freely widen and\n * `assertOnlyMutableKeysChanged` skips value comparison for every mutable key by\n * design, so `{ ...s, tagNames: undefined }` names no tag for the loop below to reject\n * while disabling the element allowlist at sanitize time — `<script>pwned</script>`\n * survives `sanitize()` untouched.\n *\n * THE RULE IS NOT THIS MODULE'S. Every mutable key has a falsy reading and they differ,\n * so the answer for all three is stated once in\n * `./mutableKeyShapes`; `schema.ts` applies it before this function runs. The call\n * below is the same rule from the same module, re-asserted where the loop depends on\n * it, so this guard stays correct if it is ever called on its own and the two cannot\n * drift into disagreeing.\n */\nexport function assertTagNamesAllowed(\n capabilityId: string,\n effective: Schema,\n trust: MarkdownTrust,\n): void {\n const tagNames = ownSchemaKey(effective, \"tagNames\");\n assertMutableKeyShape(capabilityId, \"tagNames\", tagNames);\n\n for (const tagName of tagNames as readonly string[]) {\n const normalised = String(tagName).toLowerCase();\n\n if (FORBIDDEN_TAG_SET.has(normalised)) {\n throw new Error(\n `Markdown capability \"${capabilityId}\" widened the sanitize schema to allow <${tagName}>, which is never permitted.`,\n );\n }\n\n if (ANIMATION_TAG_SET.has(normalised)) {\n throw new Error(\n `Markdown capability \"${capabilityId}\" widened the sanitize schema to allow <${tagName}>, ` +\n \"which rewrites another element's attributes after sanitize and so steps around every \" +\n \"value rule here, the URL-scheme allowlist included. Never permitted, on any trust profile.\",\n );\n }\n\n if (trust === \"untrusted\" && IMAGE_TAG_SET.has(normalised)) {\n throw new Error(\n `Markdown capability \"${capabilityId}\" widened the sanitize schema to allow <${tagName}> under trust=\"untrusted\", which is never permitted: untrusted content must fire no outbound request on view.`,\n );\n }\n }\n}\n","import type { Root, Element, RootContent } from \"hast\";\n\n/** hast tag name the transform emits in place of an `<img>`. Allowed by `untrustedSchema`. */\nexport const BLOCKED_IMAGE_TAG_NAME = \"blockedimage\";\n/** hast property carrying the original `alt` text. `data-alt` in the DOM. */\nexport const BLOCKED_IMAGE_ALT_PROP = \"dataAlt\";\n\n/**\n * Rewrites every `<img>` into `<blockedimage data-alt=\"…\">` BEFORE the sanitizer runs.\n *\n * Under `trust=\"untrusted\"` the schema drops `img` wholesale, so without this an image\n * in a ticket body or a chat message would silently vanish. Rewriting first turns it\n * into a visible \"[Image not shown]\" placeholder instead, which is the difference\n * between a policy and a bug report.\n *\n * Every `<img>` is rewritten, not only remote ones: the URL is not inspected at all.\n * A `data:` URI fires no request, but the untrusted schema strips `img` regardless, so\n * matching on the source would only decide which images disappear silently.\n *\n * A component that renders its own image (helpdesk's `AttachmentImage`, from a `blob:`\n * URL) is unaffected — it is React output, never hast, so neither this transform nor\n * the allowlist can reach it.\n */\nexport function rehypeBlockRemoteImages() {\n return (tree: Root): void => {\n visit(tree);\n };\n}\n\nfunction visit(node: Root | Element): void {\n const children = node.children;\n if (!children) return;\n\n for (let index = 0; index < children.length; index += 1) {\n const child = children[index] as RootContent;\n if (child.type !== \"element\") continue;\n\n if (child.tagName === \"img\") {\n children[index] = toPlaceholder(child);\n continue;\n }\n\n visit(child);\n }\n}\n\nfunction toPlaceholder(image: Element): Element {\n const alt = image.properties?.alt;\n return {\n type: \"element\",\n tagName: BLOCKED_IMAGE_TAG_NAME,\n properties: typeof alt === \"string\" && alt ? { [BLOCKED_IMAGE_ALT_PROP]: alt } : {},\n children: [],\n };\n}\n","/**\n * Structural copy and freeze for a sanitize schema.\n *\n * Both halves exist for the same reason and are used by the same two callers\n * (`schema.ts` builds the module singletons; `pipeline.ts` snapshots what a capability\n * returns before judging it, and freezes the snapshot), so they live together rather\n * than one of them being module-private to whichever file needed it first.\n */\n\n/**\n * Snapshot of a sanitize schema: own enumerable properties, read once, as plain data.\n *\n * WHY COPYING MATTERS. Spreading a schema (`{ ...defaultSchema, tagNames: […] }`)\n * copies the TOP level only: `attributes` still points at the library's own arrays.\n * One in-place `schema.attributes[\"*\"].push(\"style\")` anywhere would therefore widen\n * `hast-util-sanitize`'s exported defaults for every consumer in the process,\n * permanently, and every later render in that tab would use the loosened allowlist.\n * The module schemas are built from a copy and then deep-frozen, so nothing this\n * package exports shares a mutable reference with the library or with each other.\n *\n * WHY EVERY PROPERTY IS READ BY VALUE, AT EVERY LEVEL. This is the security half, and\n * it is not the same as copying. `Object.freeze` locks a property DESCRIPTOR; it does\n * not neutralise an accessor. A schema that defines `tagNames` as an enumerable getter\n * can therefore answer safely while the guards are looking and answer differently when\n * `rehype-sanitize` reads the same property at RENDER time — frozen throughout, and\n * defeating every guard at once rather than any one of them. Rebuilding the object\n * from `Object.entries` invokes each accessor EXACTLY ONCE and stores what it returned\n * as a data property, so the value that was judged is provably the value that is\n * enforced. The prototype is dropped for the same reason: only own properties survive,\n * which is also precisely what `hast-util-sanitize`'s own merge copies.\n *\n * A `Schema` is otherwise plain data — strings, booleans, arrays and plain objects —\n * so a hand-rolled walk is enough and raises no question about `structuredClone`\n * availability in a consumer's runtime. `RegExp` below is the one exception where\n * \"plain data\" is not quite true. Any OTHER exotic object (a `Date`, a class instance)\n * is not schema content at all, and is reduced to its own data rather than shared by\n * reference: sharing would hand the sanitizer a live object the guards cannot see\n * through, whereas the reduction fails closed — `hast-util-sanitize` compares an\n * attribute value by `===` or by `.test()`, and a bare `{}` matches nothing.\n */\nexport function cloneSchema<T>(value: T): T {\n if (Array.isArray(value)) {\n // `Array.from`, NOT `.map`. `map` goes through ArraySpeciesCreate, so an `Array`\n // SUBCLASS maps to another instance of itself and keeps its own methods — and the\n // method the sanitizer calls on `tagNames` is `.includes()`. A subclass overriding\n // it answers the guards' `for…of` honestly and the sanitizer's `includes(\"script\")`\n // however it likes, which is the accessor hole one door along. `Array.from` always\n // builds a plain `Array`.\n return Array.from(value, (entry) => cloneSchema(entry)) as unknown as T;\n }\n // A RegExp is legitimate schema content: hast-util-sanitize documents\n // `span: [[\"className\", /^hljs-/]]` and matches with `allowed.test(value)`. It has\n // no own enumerable keys, so the generic object branch below would rebuild it as\n // `{}` and the rule would silently stop matching — the capability's own markup then\n // stripped by the schema it had just widened. Copied rather than shared so freezing\n // one schema cannot reach a pattern another schema still holds.\n if (value instanceof RegExp) {\n return new RegExp(value.source, value.flags) as unknown as T;\n }\n if (value !== null && typeof value === \"object\") {\n // `Object.create(null)`, NOT `{}`. A plain object inherits `Object.prototype`, and\n // with it the legacy `__proto__` ACCESSOR — so for the one key named `__proto__`\n // the line below is not an assignment at all, it is a call to `setPrototypeOf`.\n // A capability returning an own enumerable `__proto__` (`JSON.parse`, or\n // `Object.defineProperty`; an object LITERAL cannot express it) would therefore have\n // its value installed as the snapshot's PROTOTYPE rather than copied as data, which\n // is this function's one job — an entry no own-key guard and no `own.call` in the\n // sanitizer can see, which a later capability can then ride. A null-prototype\n // dictionary has no inherited setter, so `__proto__` copies as an ordinary data\n // property and `assertOnlyMutableKeysChanged` rejects it at the top level like any\n // other key a capability may not add.\n //\n // It also makes the prototype-dropping this function documents literally true, and\n // closes the same class one door along: `attributes[\"constructor\"]` on a plain\n // object answers with a function the schema never contained.\n const out: Record<string, unknown> = Object.create(null) as Record<string, unknown>;\n for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {\n out[key] = cloneSchema(entry);\n }\n return out as T;\n }\n return value;\n}\n\n/**\n * Freeze an object AND everything it holds.\n *\n * `Object.freeze` is shallow. The pipeline object and its plugin arrays are frozen\n * but the schema is the object that actually decides what survives sanitize,\n * so leaving its nested `tagNames` / `attributes` arrays mutable would leave the one\n * unenforced rule in a module whose whole thesis is that conventions are what failed.\n *\n * Freezing is the SECOND half of the guarantee, never the whole of it: it stops a\n * write, and stops nothing else. What makes the frozen value trustworthy is that\n * {@link cloneSchema} ran first, so every property is data. Freeze what you snapshot.\n */\nexport function deepFreeze<T>(value: T): T {\n if (value === null || typeof value !== \"object\") return value;\n // Deliberately NOT frozen. `.test()` writes `lastIndex` on a global or sticky\n // pattern, which throws on a frozen RegExp in strict mode — freezing one would turn\n // a syntax-highlighting rule into a render-time crash. Nothing security-relevant is\n // mutable on a RegExp anyway: `source` and `flags` are read-only by specification.\n if (value instanceof RegExp) return value;\n Object.freeze(value);\n for (const entry of Object.values(value as Record<string, unknown>)) deepFreeze(entry);\n return value;\n}\n","// `defaultSchema` is imported from `hast-util-sanitize`, not from `rehype-sanitize`\n// which merely re-exports it. That keeps `rehype-sanitize` itself importable from\n// exactly one module in this package (`pipeline.ts`) — an invariant a test asserts.\nimport { defaultSchema, type Schema } from \"hast-util-sanitize\";\n\nimport {\n assertAttributesNotWidened,\n assertMutableKeysWellShaped,\n assertNoAliasedAttributeNames,\n assertNoDroppedKeys,\n assertOnlyMutableKeysChanged,\n assertProtocolsNotWidened,\n assertSchemaIsData,\n assertTagNamesAllowed,\n IMAGE_TAG_NAMES,\n} from \"./guards\";\nimport { BLOCKED_IMAGE_ALT_PROP, BLOCKED_IMAGE_TAG_NAME } from \"./rehypeBlockRemoteImages\";\nimport { cloneSchema, deepFreeze } from \"./schemaClone\";\nimport type { MarkdownTrust } from \"./trust\";\n\ntype Attributes = NonNullable<Schema[\"attributes\"]>;\n\nconst defaults = cloneSchema(defaultSchema);\n\nconst IMAGE_TAG_SET = new Set(IMAGE_TAG_NAMES);\n\n/**\n * `defaultSchema` plus `<input type/checked/disabled/className>`.\n *\n * WHY `input` IS ALLOWED. remark-gfm renders a task list (`- [x] done`) as\n * `<input type=\"checkbox\" disabled>` inside the list item. The base schema strips\n * `<input>` entirely, so a task list would otherwise render as an empty bullet with\n * the text beside it and no checkbox at all. All five of the renderer-local schema\n * copies this module replaces carried exactly this allowance; it is reproduced here\n * verbatim rather than re-litigated, so the consolidation is not also a behaviour\n * change. (Follow-up, deliberately not done here: no consumer intercepts a checkbox\n * toggle, so the allowance either wants wiring up or dropping.)\n *\n * Deep-frozen, and built from a private copy of `defaultSchema` — see `cloneSchema`.\n * It is part of the public surface (the barrel exports it), so \"nobody would mutate\n * it\" is not an argument available here.\n */\nexport const baseMarkdownSchema: Schema = deepFreeze({\n ...defaults,\n tagNames: [...new Set([...(defaults.tagNames ?? []), \"input\"])],\n attributes: {\n ...((defaults.attributes ?? {}) as Attributes),\n input: [\"type\", \"checked\", \"disabled\", \"className\"],\n },\n});\n\n/**\n * {@link baseMarkdownSchema} with every image-bearing element removed and the\n * placeholder element emitted by `rehypeBlockRemoteImages` allowed in their place.\n *\n * Belt AND braces on purpose: the pre-sanitize transform rewrites `<img>` into\n * `<blockedimage>`, and this schema would strip any `<img>` the transform somehow\n * missed (a capability's rehype plugin adding one after it, say) rather than let it\n * reach the DOM and load.\n */\nexport const untrustedSchema: Schema = deepFreeze({\n ...baseMarkdownSchema,\n tagNames: [\n ...(baseMarkdownSchema.tagNames ?? []).filter((tag) => !IMAGE_TAG_SET.has(tag)),\n BLOCKED_IMAGE_TAG_NAME,\n ],\n attributes: {\n ...((baseMarkdownSchema.attributes ?? {}) as Attributes),\n [BLOCKED_IMAGE_TAG_NAME]: [BLOCKED_IMAGE_ALT_PROP],\n },\n});\n\nexport interface CapabilitySchemaContext {\n /** The trust profile the pipeline is being built for. Already normalised. */\n trust: MarkdownTrust;\n /** The schema the capability was handed, i.e. what it was asked to widen. */\n previous: Schema;\n}\n\n/**\n * Mirrors `hast-util-sanitize`'s own merge, so a guard inspects what the sanitizer\n * will actually enforce rather than what the capability happened to write down.\n */\nfunction effectiveSchema(schema: Schema): Schema {\n return { ...defaultSchema, ...schema };\n}\n\n/**\n * Throws if a capability's widened schema would loosen the sanitizer past what a\n * rendering feature can justify.\n *\n * Capability review is security review; this is its floor. `extendSchema` is the one\n * seam that can legitimately loosen the sanitizer, so it is the one seam that needs a\n * guard — without it, \"widen the schema\" would be a back door around the ordering the\n * rest of the design makes unrepresentable.\n *\n * This function decides nothing: each rule lives in `./guards`, with its own tests,\n * because each is a DEFINITION of what is safe rather than a step in a procedure. All\n * that is left here is the order, and the split it follows.\n *\n * The first two rules read the RETURNED schemas, and must: every rule after them reads\n * a schema merged over the library defaults, which is exactly where a change to the\n * SHAPE hides. Merged, a dropped `tagNames` looks like the library's own — and so does\n * an emptied `clobber`.\n *\n * 1. {@link assertNoDroppedKeys} — is every key still here, as an own property?\n * 2. {@link assertOnlyMutableKeysChanged} — is every key the capability had no\n * business touching still what it was handed? This is the ALLOWLIST half of the\n * design: the three value rules below name dangerous content inside the three keys\n * a capability may widen, and this one says those three are the only keys it may\n * reach at all, so the next key hast-util-sanitize grows is closed on arrival.\n * 3. {@link assertMutableKeysWellShaped} — is each of those three keys still the\n * SHAPE the sanitizer requires? Rule 2 says which keys may be touched and the value\n * rules below say what may be put in them, and between the two sat the question\n * nobody owned: a mutable key holding no content at all. Two of the three keys read\n * falsy as \"unrestricted\", not as \"empty\". It runs here, before the value rules,\n * because each of those reads one of these keys as a list or a map.\n * 4. the value rules, against what the sanitizer will actually enforce. The last of\n * them, {@link assertNoAliasedAttributeNames}, is about SPELLING rather than\n * content: the sanitizer keys `protocols` and `clobber` on the exact property\n * name while the DOM lower-cases it, so a second spelling of a policed name is\n * that policy deleted without any policed key being touched.\n */\nexport function assertCapabilitySchema(\n schema: Schema,\n capabilityId: string,\n context: CapabilitySchemaContext,\n): void {\n // FIRST: every rule after this one reads the schema as data, which is only true once\n // nothing in it can answer them differently than it answers the sanitizer.\n assertSchemaIsData(capabilityId, schema);\n\n const returned = { next: schema, previous: context.previous };\n assertNoDroppedKeys(capabilityId, returned);\n assertOnlyMutableKeysChanged(capabilityId, returned);\n\n const effective = {\n next: effectiveSchema(returned.next),\n previous: effectiveSchema(returned.previous),\n };\n assertMutableKeysWellShaped(capabilityId, effective.next);\n assertTagNamesAllowed(capabilityId, effective.next, context.trust);\n assertAttributesNotWidened(capabilityId, effective, context.trust);\n assertNoAliasedAttributeNames(capabilityId, effective);\n assertProtocolsNotWidened(capabilityId, effective);\n}\n","/**\n * The trust profiles, as a runtime value.\n *\n * A TypeScript union is erased at compile time, so `trust` as a type alone is not a\n * control: a JS caller, an `as` cast, or a value read out of JSON or a plugin\n * manifest can hand in any string, and equality checks against the ONE safe value\n * (\"untrusted\") then select the permissive branch by falling through. The list lives\n * here, once, and {@link MarkdownTrust} is derived from it so the type and the\n * runtime check cannot drift.\n */\nexport const MARKDOWN_TRUST_VALUES = [\"untrusted\", \"internal\"] as const;\n\n/**\n * Who wrote the string. Drives image policy and NOTHING else.\n * - \"untrusted\": authored by a user or an external requester (ticket bodies and\n * comments, chat messages, project descriptions/updates, a local compose\n * preview). Remote images are replaced by a placeholder before sanitize, and\n * `img` is stripped from the allowlist as belt-and-braces. No outbound\n * request fires on view.\n * - \"internal\": FIRST-PARTY copy shown to its own audience — content this\n * product wrote, such as the gogo ai-chat template demo. Keeps the current\n * `defaultSchema` image allowance (src restricted to http/https).\n *\n * MODEL OUTPUT IS NOT AUTOMATICALLY \"internal\". The question is the provenance of\n * the model's INPUT, not of its output. A summary generated FROM a ticket body,\n * a chat thread or a commit message inherits that input's trust: an external\n * requester who gets the model to emit `` has an\n * exfiltration channel that fires from an authenticated agent's browser on view.\n * Every AI surface in this estate is derived from user-authored input and is\n * therefore \"untrusted\"; reach for \"internal\" only for copy nobody outside the\n * product wrote. If a derived surface ever genuinely needs images, that is the\n * argument for a third profile, not for widening this one.\n *\n * There is no third value and no way to hand a schema in from a call site.\n */\nexport type MarkdownTrust = (typeof MARKDOWN_TRUST_VALUES)[number];\n\n/** The profile you get by forgetting, by omitting, or by getting it wrong. */\nexport const DEFAULT_MARKDOWN_TRUST: MarkdownTrust = \"untrusted\";\n\nconst TRUST_VALUE_SET: ReadonlySet<string> = new Set(MARKDOWN_TRUST_VALUES);\n\n/**\n * Maps anything that is not a known profile onto the strictest one.\n *\n * Fail closed rather than throw: `trust` arrives as a prop from call sites across\n * six repositories, and a typo there must degrade to \"show no remote images\"\n * rather than crash a ticket view. A wrong-but-safe render is recoverable; a\n * permissive one has already fired the request by the time anyone notices.\n */\nexport function normalizeTrust(value: unknown): MarkdownTrust {\n return typeof value === \"string\" && TRUST_VALUE_SET.has(value)\n ? (value as MarkdownTrust)\n : DEFAULT_MARKDOWN_TRUST;\n}\n","import type { Components } from \"react-markdown\";\n\nimport { BLOCKED_IMAGE_ALT_PROP, BLOCKED_IMAGE_TAG_NAME } from \"./rehypeBlockRemoteImages\";\nimport type { MarkdownElementProps } from \"./types\";\n\n/** Renders the placeholder `rehypeBlockRemoteImages` leaves behind. */\nfunction BlockedImage({ node }: MarkdownElementProps) {\n const alt = node?.properties?.[BLOCKED_IMAGE_ALT_PROP];\n const label = typeof alt === \"string\" && alt ? `[Image not shown: ${alt}]` : \"[Image not shown]\";\n return (\n <span className=\"ethisys-md-blocked-image\" data-testid=\"markdown-blocked-image\">\n {label}\n </span>\n );\n}\n\n/**\n * Merged LAST in `buildMarkdownPipeline`, after the call site's own `components`, so a\n * caller cannot re-point the placeholder tag at something that renders a remote `src`.\n * The tag name is not an intrinsic element, hence the cast — the same pattern\n * helpdesk's `<attachmentchip>` renderer already uses.\n */\nexport const BLOCKED_IMAGE_COMPONENTS = {\n [BLOCKED_IMAGE_TAG_NAME]: BlockedImage,\n} as Components;\n","// THE ONLY MODULE IN THIS PACKAGE — AND, BY LINT RULE, IN THE HOST OR ANY PLUGIN —\n// PERMITTED TO IMPORT `rehype-sanitize`. A test asserts there is exactly one such\n// import across `src/`. If you are adding a second one, you are about to reintroduce\n// the problem this package was written to remove.\nimport rehypeSanitize from \"rehype-sanitize\";\nimport remarkGfm from \"remark-gfm\";\nimport remarkBreaks from \"remark-breaks\";\nimport type { Pluggable, PluggableList } from \"unified\";\nimport type { Components } from \"react-markdown\";\nimport type { Schema } from \"hast-util-sanitize\";\n\nimport { assertSchemaReturned } from \"./guards\";\nimport { assertCapabilitySchema, baseMarkdownSchema, untrustedSchema } from \"./schema\";\nimport { cloneSchema, deepFreeze } from \"./schemaClone\";\nimport { normalizeTrust } from \"./trust\";\nimport { rehypeBlockRemoteImages } from \"./rehypeBlockRemoteImages\";\nimport { BLOCKED_IMAGE_COMPONENTS } from \"./blockedImage\";\nimport type { MarkdownCapability, MarkdownPipeline, MarkdownTrust } from \"./types\";\n\nexport interface BuildMarkdownPipelineOptions {\n trust?: MarkdownTrust;\n softBreaks?: boolean;\n capabilities?: readonly MarkdownCapability[];\n components?: Components;\n}\n\n/**\n * Assembles the one markdown pipeline.\n *\n * DELIBERATELY NOT EXPORTED FROM `./index`. `Object.freeze` stops `push`, not spread:\n * a caller holding the result can write `[...pipeline.rehypePlugins, rehypeRaw]` and\n * put a plugin after the sanitizer, which is the single thing this package exists to\n * make unrepresentable. The ordering tests import it relatively, from inside the\n * module, so nothing is lost by keeping it off the public surface — and a test\n * asserts the barrel's shape so re-adding it fails CI rather than review.\n *\n * No call site renders through it either; call sites use `MarkdownView`.\n */\nexport function buildMarkdownPipeline(\n options: BuildMarkdownPipelineOptions = {},\n): MarkdownPipeline {\n const { softBreaks = false, capabilities = [], components } = options;\n // Normalised once, here, and used everywhere below. The union is erased at\n // runtime, so an unrecognised value would otherwise fall through every\n // `=== \"untrusted\"` check into the permissive branch. See `./trust`.\n const trust = normalizeTrust(options.trust);\n\n const seen = new Set<string>();\n for (const cap of capabilities) {\n if (seen.has(cap.id)) throw new Error(`Duplicate markdown capability id: ${cap.id}`);\n seen.add(cap.id);\n }\n\n const schema = capabilities.reduce<Schema>(\n (acc, cap) => {\n if (!cap.extendSchema) return acc;\n // Hand the capability a PRIVATE COPY. `extendSchema` is documented as pure, but\n // a capability that pushes in place instead of spreading would otherwise widen\n // the module-singleton schema — and, through the array references a spread\n // shares, `hast-util-sanitize`'s own exported defaults — permanently, for every\n // later render in the process, including ones that pass no capability at all and\n // so never reach the guard below. Purity is now a property of the call, not a\n // request in a doc comment. The copy is unconditional: `cloneSchema` rebuilds\n // every object from its own properties, so there is no shape of `acc` for which\n // this hands back `acc` itself.\n //\n // And SNAPSHOT BEFORE JUDGING. The capability's own object is never validated,\n // frozen or enforced — only a `cloneSchema` of it is. Freezing the object\n // itself would have locked its property DESCRIPTORS while leaving any accessor\n // live, so a schema defining `tagNames` as an enumerable getter could answer\n // safely to every guard and differently to `rehype-sanitize` at RENDER time,\n // frozen throughout. The snapshot reads each property exactly once, at every\n // level, and stores it as data — which is what makes the sentence below true.\n //\n // The return is checked BEFORE the snapshot: `cloneSchema(null)` is `null`, and\n // every rule downstream would then fail on a value it cannot describe.\n const returned = cap.extendSchema(cloneSchema(acc));\n assertSchemaReturned(cap.id, returned);\n const widened = cloneSchema(returned);\n assertCapabilitySchema(widened, cap.id, { trust, previous: acc });\n // Frozen the instant it is judged safe, so THE VALIDATED SCHEMA AND THE ENFORCED\n // SCHEMA ARE THE SAME IMMUTABLE OBJECT. Without this, validation would be a\n // check on a value that is still free to change afterwards: the sanitizer reads\n // this object at RENDER time, long after this function returned, and the caller\n // holds it (via the plugin tuple) until then.\n return deepFreeze(widened);\n },\n trust === \"untrusted\" ? untrustedSchema : baseMarkdownSchema,\n );\n\n const remarkPlugins = Object.freeze([\n remarkGfm,\n ...(softBreaks ? [remarkBreaks] : []),\n ...capabilities.flatMap((c) => [...(c.remarkPlugins ?? [])]),\n ]) as PluggableList;\n\n // THE ONE EXPRESSION IN THE PACKAGE THAT BUILDS A REHYPE ARRAY.\n // Contributions first; the sanitizer is appended here, from a local import, with no\n // branch, no option and no ordering parameter; the result is frozen before it leaves\n // this function.\n const rehypePlugins = Object.freeze([\n ...(trust === \"untrusted\" ? [rehypeBlockRemoteImages] : []),\n ...capabilities.flatMap((c) => [...(c.rehypeBeforeSanitize ?? [])]),\n // The TUPLE is frozen too, not just the array around it. `Object.freeze` is one\n // level deep: a frozen array still holds a writable tuple, and\n // `rehypePlugins.at(-1)[1] = permissiveSchema` would then replace the schema this\n // function spent its whole body validating — or `[0]` the sanitizer itself —\n // without touching anything frozen. Unreachable today (nothing outside this module\n // holds the array), but `MarkdownPipeline` documents the last entry as frozen, and\n // a freeze that does not freeze what it appears to is the trap this package exists\n // to stop shipping.\n Object.freeze([rehypeSanitize, schema]) as Pluggable,\n ]) as PluggableList;\n\n const mergedComponents = Object.assign(\n {},\n ...capabilities.map((c) => c.components ?? {}),\n components ?? {},\n // LAST: the built-in blocked-image renderer cannot be overridden by a caller.\n trust === \"untrusted\" ? BLOCKED_IMAGE_COMPONENTS : {},\n ) as Components;\n\n return Object.freeze({\n remarkPlugins,\n rehypePlugins,\n components: mergedComponents,\n skipHtml: true as const,\n });\n}\n","import { useMemo } from \"react\";\nimport ReactMarkdown from \"react-markdown\";\n\nimport { buildMarkdownPipeline } from \"./pipeline\";\nimport type { MarkdownViewProps } from \"./types\";\n\n/**\n * The shared markdown renderer. Emits no wrapper element — put your own class\n * (`.ethisys-md` / `.ethisys-md--dense` from `@ethisyscore/plugin-ui/markdown.css`,\n * or your surface's own) on the element you render this inside.\n *\n * It never concatenates a plugin array: it spreads the frozen pipeline and nothing\n * else, so there is no second place where ordering could go wrong.\n */\nexport function MarkdownView({\n children,\n // No default here on purpose: `buildMarkdownPipeline` normalises `trust`, and one\n // definition of \"the profile you get by forgetting\" is the point — see `./trust`.\n trust,\n softBreaks = false,\n capabilities,\n components,\n}: MarkdownViewProps) {\n const pipeline = useMemo(\n () => buildMarkdownPipeline({ trust, softBreaks, capabilities, components }),\n [trust, softBreaks, capabilities, components],\n );\n\n return <ReactMarkdown {...pipeline}>{children}</ReactMarkdown>;\n}\n"]}
|
|
@@ -27,6 +27,7 @@ function _interopNamespace(e) {
|
|
|
27
27
|
return Object.freeze(n);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
var reactQuery__namespace = /*#__PURE__*/_interopNamespace(reactQuery);
|
|
30
31
|
var extensionRuntime__namespace = /*#__PURE__*/_interopNamespace(extensionRuntime);
|
|
31
32
|
|
|
32
33
|
// src/platform-react/definePlatformReactPluginPage.tsx
|
|
@@ -109,6 +110,39 @@ function createPluginQueryClient(config) {
|
|
|
109
110
|
}
|
|
110
111
|
});
|
|
111
112
|
}
|
|
113
|
+
|
|
114
|
+
// src/platform-react/hostQueryClient.ts
|
|
115
|
+
var hostRealmExposesContext = isReactContext(reactQuery__namespace.QueryClientContext);
|
|
116
|
+
var resolvedHostContext = hostRealmExposesContext ? reactQuery__namespace.QueryClientContext : react.createContext(void 0);
|
|
117
|
+
function isReactContext(value) {
|
|
118
|
+
return typeof value === "object" && value !== null && "Provider" in value;
|
|
119
|
+
}
|
|
120
|
+
var bundleQueryClient;
|
|
121
|
+
function pluginBundleQueryClient() {
|
|
122
|
+
bundleQueryClient ??= createPluginQueryClient();
|
|
123
|
+
return bundleQueryClient;
|
|
124
|
+
}
|
|
125
|
+
var warnedAboutMissingHostClient = false;
|
|
126
|
+
function warnOnceAboutMissingHostClient() {
|
|
127
|
+
if (warnedAboutMissingHostClient) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
warnedAboutMissingHostClient = true;
|
|
131
|
+
console.warn(
|
|
132
|
+
"[plugin-ui] The host realm provides no QueryClient" + (hostRealmExposesContext ? " (@tanstack/react-query is served, but this surface renders outside the host's QueryClientProvider)" : " (the host's @tanstack/react-query namespace has no QueryClientContext export)") + ". This surface falls back to a client of its own, so a mutation here will not invalidate another surface's cache. Deploy a host that mounts plugin surfaces inside its QueryClientProvider to share one cache."
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
function useSurfaceQueryClient(override) {
|
|
136
|
+
const hostQueryClient = react.useContext(resolvedHostContext);
|
|
137
|
+
if (override !== void 0) {
|
|
138
|
+
return override;
|
|
139
|
+
}
|
|
140
|
+
if (hostQueryClient !== void 0) {
|
|
141
|
+
return hostQueryClient;
|
|
142
|
+
}
|
|
143
|
+
warnOnceAboutMissingHostClient();
|
|
144
|
+
return pluginBundleQueryClient();
|
|
145
|
+
}
|
|
112
146
|
var TemplateContext = react.createContext(null);
|
|
113
147
|
function injectPluginStyles(id, css) {
|
|
114
148
|
if (typeof document === "undefined") return;
|
|
@@ -328,7 +362,6 @@ function useReportingCurrencySync() {
|
|
|
328
362
|
}
|
|
329
363
|
|
|
330
364
|
// src/platform-react/definePlatformReactPluginPage.tsx
|
|
331
|
-
var defaultQueryClient = createPluginQueryClient();
|
|
332
365
|
var EHX_PLUGIN_ROOT_CLASS = "ehx-plugin-root";
|
|
333
366
|
function definePlatformReactPluginPage(Page, options) {
|
|
334
367
|
const {
|
|
@@ -336,10 +369,11 @@ function definePlatformReactPluginPage(Page, options) {
|
|
|
336
369
|
rootClassName,
|
|
337
370
|
styleId,
|
|
338
371
|
css,
|
|
339
|
-
queryClient
|
|
372
|
+
queryClient: queryClientOverride,
|
|
340
373
|
wrapPage
|
|
341
374
|
} = options;
|
|
342
375
|
function PluginPage(props) {
|
|
376
|
+
const queryClient = useSurfaceQueryClient(queryClientOverride);
|
|
343
377
|
useOrgFormatSync();
|
|
344
378
|
useReportingCurrencySync();
|
|
345
379
|
const page = react.createElement(Page, props);
|
|
@@ -819,18 +853,19 @@ function useOverlayHost() {
|
|
|
819
853
|
}
|
|
820
854
|
return api;
|
|
821
855
|
}
|
|
822
|
-
var defaultQueryClient2 = createPluginQueryClient();
|
|
823
856
|
function definePlatformReactPluginOverlay(Overlay, options) {
|
|
824
|
-
const { rootClassName, styleId, css, manifest = {}, queryClient
|
|
857
|
+
const { rootClassName, styleId, css, manifest = {}, queryClient: queryClientOverride, wrapOverlay } = options;
|
|
825
858
|
return function MountedOverlay(props) {
|
|
859
|
+
const queryClient = useSurfaceQueryClient(queryClientOverride);
|
|
826
860
|
useOrgFormatSync();
|
|
827
861
|
useReportingCurrencySync();
|
|
828
862
|
const tree = react.createElement(
|
|
829
863
|
OverlayHostContext.Provider,
|
|
830
864
|
{ value: props.overlayHost },
|
|
831
|
-
// react-query provider
|
|
832
|
-
//
|
|
833
|
-
//
|
|
865
|
+
// react-query provider. Normally re-providing the host's own client, which
|
|
866
|
+
// is a no-op for the subtree and deliberate: the tree shape stays identical
|
|
867
|
+
// whichever client `useSurfaceQueryClient` returned, so the fallback path is
|
|
868
|
+
// not a second code path through the renderer.
|
|
834
869
|
react.createElement(
|
|
835
870
|
reactQuery.QueryClientProvider,
|
|
836
871
|
{ client: queryClient },
|
|
@@ -1289,6 +1324,7 @@ exports.useOrgFormatSync = useOrgFormatSync;
|
|
|
1289
1324
|
exports.useOverlayHost = useOverlayHost;
|
|
1290
1325
|
exports.usePluginRealtime = usePluginRealtime;
|
|
1291
1326
|
exports.useReportingCurrencySync = useReportingCurrencySync;
|
|
1327
|
+
exports.useSurfaceQueryClient = useSurfaceQueryClient;
|
|
1292
1328
|
exports.useSurfaceUrl = useSurfaceUrl;
|
|
1293
1329
|
exports.useToolInvoker = useToolInvoker;
|
|
1294
1330
|
exports.useToolInvokerMap = useToolInvokerMap;
|