How CVE-2026-93303 Works
HT Contact Form's Save & Resume feature exposes an unauthenticated REST endpoint, POST /wp-json/ht-form/v1/draft/save, registered with __return_true as its permission callback. The wp_rest nonce it requires is localized onto every page that renders a form, so it is an anti-CSRF check, not authentication. In 2.10.1, the save_draft() and update_draft() handlers persist the form_data parameter verbatim — including Rich Text field HTML — while the final-submission path sanitizes it. The save response returns draft_key and access_token, so the attacker can build a resume link. When any user opens that link, assets/js/form.js loads the draft and assigns the stored value straight into the Quill editor DOM via container._quill.root.innerHTML = value, executing attacker script in the site origin inside the victim's session.
BitFire WAF: Blocked Before WordPress Runs
BitFire FREE's Web Application Firewall inspects form fields and POST bodies before WordPress or any plugin processes them, and its cross-site scripting rule class matches the documented payload for CVE-2026-93303 exactly: script-capable HTML submitted in a form_data Rich Text field. The exploit cannot survive that inspection. BitFire detects the malicious markup and blocks the request at the perimeter — the payload never reaches save_draft(), never lands in the draft store, and never loads into a victim's browser. This is behavior-based protection, not a CVE-specific virtual patch, so it also covers unreported variants of the same injection class. The WAF ships in BitFire FREE for eligible non-commercial sites; commercial sites run it under the appropriate commercial license.
The 2.10.2 Patch: Sanitization at Both Ends
Version 2.10.2 interrupts this chain at both ends. Server-side, the plugin promotes its existing Rich Text sanitizer into a shared Submission::sanitize_richtext_field() — a wp_kses element and attribute allow-list with protocol and style/class filtering plus a length cap — and routes every draft write through sanitize_draft_form_data() in save_draft() (including the update-in-place branch) and update_draft(); unconfigured string values fall back to sanitize_text_field(). Client-side, form.js replaces the raw innerHTML assignment with container._quill.clipboard.dangerouslyPasteHTML(value), filtering stored HTML through Quill's format-restricted clipboard parser as defense in depth. Update to 2.10.2 or later immediately — and remember that patching removes the flaw, not a compromise that already happened.
If Your Site Was Affected, Investigate for Persistence
A stored XSS in a form plugin is a foothold, not merely a bug. If your site ever ran 2.10.1 or earlier, treat it as potentially compromised: anyone persuaded to open a crafted resume link handed attacker script a session in your site origin. Patch first, then investigate. BitFire Threat Hunter performs a thorough post-compromise sweep for backdoor WordPress administrator accounts, hidden database triggers, long-running PHP processes, and droppers that can restore malware or reinfect the site. Remove every persistence mechanism it finds and rotate relevant credentials, including administrator passwords. An absence of obvious malicious files is not evidence your site is clean.
Protect Your Site With BitFire
Unauthenticated REST endpoints and unsanitized persistence should never meet, and CVE-2026-93303 shows what happens when they do. BitFire's WAF blocks the malicious form payload before WordPress runs, closing this attack class at the request layer without waiting for a plugin fix. Turn on BitFire today, update HT Contact Form to 2.10.2 or later, and run Threat Hunter if the vulnerable version was ever live. One blocked request is the difference between a normal day and an incident response.