/** * Shared allowlist HTML sanitizer — pure string functions, Node-safe (no DOM). * * One implementation for every sink that turns translation data into HTML: * core's HTMLSerializer/auto-translator, the editor text sinks, the snapshot * viewer, react ``, and WordPress TS. The WordPress PHP port must * mirror `SAFE_TAGS`/`SAFE_ATTRS` exactly (see improvement-plan §4 item 2). * * Threat model: translation values (and their `raw:` styles) come from the * server/KV and may be attacker-controlled — they are NOT guaranteed to be in * canonical form. Everything here is allowlist-based: unknown tags are * rejected, unknown attributes dropped, URL schemes checked after entity * decoding. * * @module content/sanitizer/HTMLSanitizer */ import type { ContentUnit } from '../types.js'; /** * Inline formatting elements allowed inside translated content. Block * structure lives outside ContentUnit (block-level pipeline), so anything * that isn't inline text formatting is rejected outright. */ export declare const SAFE_TAGS: Set; /** * Flat attribute allowlist (plus `data-*`/`aria-*` prefixes, handled in * `sanitizeOpeningTag`). Kept flat — not per-tag — so the PHP mirror stays * trivial; an allowed attribute on the wrong tag is inert. */ export declare const SAFE_ATTRS: Set; /** * CSS properties allowed through an inline `style` attribute. * * RISK NOTE — why `style` never passes through verbatim: with zero * JavaScript, an attacker who controls a style value can render a * full-viewport overlay (`position:fixed;inset:0` → pixel-perfect fake * login UI on the customer's real domain), fire tracking/exfiltration * beacons via `url(...)`, or hide and deface content. Translation data is * attacker-writable in our threat model, so `sanitizeStyle` re-parses the * declarations and keeps only the properties below: cosmetic text/font * properties whose value grammar cannot fetch a URL and cannot reposition * or restack the element (no `position`/`inset`/`display`/`z-index`/ * `opacity`/`transform`, no `background` shorthand). A smuggled `url(...)` * inside an allowed property is doubly inert: the value is rejected here, * and these properties ignore URL values anyway. * * CONTINUOUS DEVELOPMENT: this allowlist is intentionally minimal and is * expected to grow deliberately as real sites surface legitimate inline * styling the strict set drops (tracked as a WATCH item in * IMPLEMENTATION.md). When extending it: (1) never add a property that * accepts `url()`/`image()`/`element()` values or that affects layout, * positioning, or stacking; (2) add both hostile and benign cases to * html-sanitizer.test.ts; (3) mirror the change in the WordPress PHP port. */ export declare const SAFE_STYLE_PROPS: Set; /** HTML-escape text for safe use in element content or attribute values. */ export declare function escapeHtml(text: string): string; /** * Validate a URL attribute value. Returns the value unchanged when safe, * or `null` when it must be dropped. * * Entity-decodes first, then strips the control/whitespace characters * browsers ignore inside a scheme (`jav\tascript:`), then requires any * explicit scheme to be on the allowlist. Scheme-less (relative) URLs pass. */ export declare function sanitizeUrl(value: string): string | null; /** * Sanitize an inline `style` attribute value by re-parsing its declarations * and keeping only `SAFE_STYLE_PROPS` with clean values (see the risk note * on `SAFE_STYLE_PROPS`). Returns the rebuilt declaration list in canonical * `prop: value; prop: value` form, or `null` when nothing survives — the * caller then drops the attribute entirely. */ export declare function sanitizeStyle(value: string): string | null; /** * Relaxed CSS profile for the snapshot viewer: keeps ANY standard property * (a snapshot is a full captured page — `position:fixed` navbars and layout * styles are legitimate and must render) but still rejects value-level * dangers: `url(...)` beacons, `expression()`, CSS escape smuggling. The * residual risk (page-authored overlay styles) is accepted for snapshots: * the PM is deliberately viewing arbitrary captured pages, and the editor * UI itself lives in a Shadow DOM host stacked above the snapshot root. */ export declare function sanitizeCssDeclarations(value: string): string | null; /** * Sanitize a raw opening tag (`raw:` style payload). Returns the canonical * rebuilt tag with only allowlisted attributes, or `null` when the element * itself is not allowed — callers must then drop the tag entirely (emit the * indexed marker verbatim / skip the element). */ export declare function sanitizeOpeningTag(rawOpen: string): string | null; /** * Sanitize a ContentUnit at an ingest boundary: run every `raw:` style * through {@link sanitizeOpeningTag} so no dangerous element/attribute/scheme * enters the translation store. Render-time sinks (HTMLSerializer etc.) * sanitize again — this is the store-side half, protecting every other * consumer of stored units (server, MT/TM, exports, the WP PHP port). * * `text` is never modified, so `generateAutoKey(unit.text)` is unaffected — * key parity across surfaces holds even for hostile input. A rejected * element's `raw:` style is dropped entirely; the indexed tag left in the * text is emitted by serializers as an inert marker. Returns the SAME object * when nothing changed, so benign units round-trip identically. */ export declare function sanitizeContentUnit(unit: ContentUnit): ContentUnit; //# sourceMappingURL=HTMLSanitizer.d.ts.map