Files
WRNexusJS/packages/security/src/trusted-html.ts
T
2026-08-01 10:04:42 +05:30

53 lines
1.6 KiB
TypeScript

import { SecurityError } from "./errors.ts";
const TRUSTED_HTML = Symbol("wrnexus.trusted-html");
export interface TrustedHtmlPolicy {
/** Stable policy name used in diagnostics and CSP/Trusted Types integration. */
name: string;
/** Application-supplied, reviewed sanitizer. It must return sanitized HTML. */
sanitize(input: string): string;
}
export interface TrustedHtmlValue {
readonly policy: string;
readonly value: string;
readonly [TRUSTED_HTML]: true;
}
export function createTrustedHtml(input: string, policy: TrustedHtmlPolicy): TrustedHtmlValue {
if (!policy?.name?.trim() || typeof policy.sanitize !== "function") {
throw new SecurityError(
"WRN-SEC-TRUSTED-HTML-POLICY",
"Trusted HTML requires a named sanitizer policy.",
);
}
const value = policy.sanitize(String(input));
if (typeof value !== "string") {
throw new SecurityError(
"WRN-SEC-TRUSTED-HTML-RESULT",
`Trusted HTML policy '${policy.name}' must return a string.`,
);
}
return Object.freeze({ policy: policy.name.trim(), value, [TRUSTED_HTML]: true as const });
}
export function isTrustedHtml(value: unknown): value is TrustedHtmlValue {
return Boolean(
value &&
typeof value === "object" &&
(value as Partial<TrustedHtmlValue>)[TRUSTED_HTML] === true &&
typeof (value as Partial<TrustedHtmlValue>).value === "string",
);
}
export function unwrapTrustedHtml(value: TrustedHtmlValue): string {
if (!isTrustedHtml(value)) {
throw new SecurityError(
"WRN-SEC-TRUSTED-HTML-REQUIRED",
"Raw HTML must be produced by createTrustedHtml().",
);
}
return value.value;
}