/**
* Strip HTML tags from notification content, hardened against
* multi-character bypasses.
*
* Every plain-text sender (APNs, Web Push, webhook, ntfy, email) used to
* carry its own single-pass `replace(/<[^>]*>/g, "")` — the shape CodeQL
* flags as incomplete multi-character sanitization, because a removal can
* re-form the very pattern it removed (`<script>` loses `` and the
* remaining characters close ranks). Whether a given regex happens to be
* idempotent is an argument about that regex; five copies of the argument
* across five senders is how the next edit quietly breaks one of them.
*
* This helper makes the guarantee structural instead: re-apply the strip
* until the string stops changing, so a tag re-formed by an earlier
* removal is removed on the next pass no matter how deep the nesting. The
* iteration cap bounds CPU on adversarial input; a string still changing
* at the cap is pathological by construction, and for that remnant the
* angle brackets themselves are dropped so no `<...>` sequence can ever
* survive. Plain input without tags returns unchanged, same as the old
* single pass.
*/
const TAG_RE = /<[^>]*>/g;
const MAX_STRIP_PASSES = 10;
/** Remove HTML tags, re-applying until stable so stripped tags cannot re-form. */
export function stripHtml(text: string): string {
let out = text;
for (let i = 0; i < MAX_STRIP_PASSES; i++) {
const next = out.replace(TAG_RE, "");
if (next === out) return next;
out = next;
}
// Still changing after the cap — nesting this deep is adversarial.
// Drop the brackets outright so nothing tag-shaped remains.
return out.replace(/[<>]/g, "");
}