/** * notice-text.ts, making attacker-chosen text inert before it reaches a * notification the owner reads and acts on. * * ── The defect class this exists for ────────────────────────────────────── * * The inbound-mail notice sanitized its delivered-to address for control * characters only, on the premise that a sender cannot forge which mailbox * received the mail. That premise is true about the MAILBOX and false about the * local part in front of the `@`, which under catch-all or plus-addressing is * whatever the sender chose to type. So * `[Approved](https://evil.example)@ourdomain.com` rendered as a clickable link, * and `@everyone@ourdomain.com` formed a real mention, on the one field the * notice existed to make trustworthy. * * The reasoning that produced it is the part worth remembering: **a field is not * safe because part of its provenance is verified.** "The sender cannot forge * which mailbox this arrived at" is a different claim from "the sender cannot * influence this string", and only the second one licenses skipping * neutralisation. * * Payment notices are a strictly worse version of the same surface: a merchant * page controls the item title, the seller name and the shipping labels, the * owner reads them on their phone under a ten-minute clock, and their reply * authorises a charge. * * ── Where per-channel escaping lives, and why this is not it ────────────── * * This function does NOT know which channel a notice will fan out to. Telegram * MarkdownV2, Slack mrkdwn, Discord markdown, ntfy and a bare terminal each * escape differently and have different mention forms, and a trigger set tuned * for one lets something through on another. * * So the split is deliberate: * * - **Here (source):** neutralise the UNION of trigger characters, so the value * is inert whatever the route turns out to be. This is the defence that does * not depend on guessing the destination, and it is the one that has to hold. * - **Channel adapter (delivery):** whatever escaping that specific channel's * formatter requires. That is a rendering concern belonging to the code that * knows the wire format. * * Neutralising the union at the source is not a substitute for correct per- * channel escaping; it is what keeps a missing escape from becoming a clickable * link in a message about money. * * ── Convergence note ────────────────────────────────────────────────────── * * `platform/email/inbound-notice.ts` (branch `inbound-email-config`, commit * `140cbcb4`) currently carries its own copy of this logic, written first. This * module is deliberately behaviourally identical, same trigger sets, same * mention-breaking, same ordering, so the two can be collapsed onto this one * when that branch merges. Two copies of a security escaper is the drift class * that lets one of them quietly fall behind. */ /** * Break `@everyone`, `@here`, `@channel` and `@role` mention forms. * * Discord and Slack turn a literal contiguous `@word` into a real mention when * the text is un-escaped. A zero-width space after every `@` that precedes a * word character breaks the contiguous match while staying invisible to a human * reader, `user@example.com` still reads as `user@example.com`. */ export declare function breakMentionForms(text: string): string; /** * The sanitize every ATTACKER-CHOSEN free-text string passes through before it * can appear in a notice: a merchant's item title, seller or store name, a * shipping-option label, promotional text, a currency string, an email subject. * * Assume attacker-chosen unless the string demonstrably originated with the * owner. Provenance being partly verified is not an exemption, see the header. */ export declare function sanitizeNoticeField(raw: string, maxLength?: number): string; /** * The sanitize for a field the OWNER authored, where underscore is worth * keeping for legibility. * * Still sanitized rather than trusted: threading provenance correctly is a thing * code gets wrong, and neutralising the owner's own text costs nothing but the * underscore exemption. A guarantee that holds only while every call site stays * correct is not a guarantee. */ export declare function sanitizeOwnerNoticeField(raw: string, maxLength?: number): string; /** * True when `host` is a bare hostname carrying no markup potential at all. * * Used as an assertion on a merchant identity that was supposedly computed by * `registrableDomain()` rather than read off a page. A computed value should * always pass; if one ever does not, the identity did not come from where the * caller believed, and rendering it would be exactly the defect this module is * about. */ export declare function isPlainHostname(host: string): boolean; //# sourceMappingURL=notice-text.d.ts.map