Introduction
A stored cross-site scripting flaw in Gravity Forms lets anonymous form submitters plant markup that later executes in the browser of a user who views the injected content, via the plugin's own administrative JavaScript. The payload travels as entity-encoded text inside a data-dialog-confirm attribute, and that text passes the save-time sanitizer most WordPress developers would expect to stop it.
Gravity Forms is a commercial form management plugin for WordPress developed by Rocketgenius Inc. The vendor states that it powers more than 5 million websites worldwide, which is a vendor footprint claim rather than an independently measured count of vulnerable installations. Forms are frequently the only part of a WordPress site that accepts input from anonymous visitors, so the way a forms plugin stores and renders submissions sits directly on the trust boundary between the public internet and site administrators.
Technical information
Root cause
The CVE record classifies the issue as CWE-79, improper neutralization of input during web page generation, caused by insufficient input sanitization and output escaping. No WordPress account is required to submit the payload.
Three field types are involved, and all three return true from allow_html(), meaning they accept raw HTML submissions:
- Post Excerpt
- Post Custom Field
- Paragraph Text, but only when its rich text editor is enabled. Plain Paragraph Text configurations are not named in the advisory.
The failure spans two separate trust boundaries:
- Storage. Values from these fields pass through
wp_kses_post()when saved. WordPress documents this function as allowing the permitted post HTML tags and attributes, and thepostallowlist permitsdata-*attributes. An entity-encoded payload placed insidedata-dialog-confirmtherefore survives sanitization as harmless-looking attribute text. - Rendering. When the browser reads the attribute, it decodes the entities. The plugin's admin JavaScript then passes that decoded string to
insertAdjacentHTML(). MDN documentsinsertAdjacentHTML()as an injection sink that parses its input into the DOM and does not sanitize unsafe elements or event handler attributes.
On the PHP side, every rendering path in 3.1.1 trusted the value that had been saved to the database. The pattern repeated across output paths looked like this:
// The value contains HTML but the value was sanitized before saving. $return = $value;
That comment records the design assumption that broke. A sanitizer run once at save time was treated as a guarantee about every later use of the value. The stored payload never needed a <script> tag or an on* event handler. It only becomes dangerous when admin JavaScript reads the attribute and feeds it to an HTML-parsing sink.
Attack flow
Based on the advisory, the documented sequence is:
- An unauthenticated attacker submits an entity-encoded payload through a Post Excerpt, Post Custom Field, or rich-text Paragraph Text field on a public form.
- The payload is stored inside a
data-dialog-confirmattribute and passeswp_kses_post()at save time. - A user later accesses a page where the stored value is rendered. The browser decodes the attribute content.
- The plugin's administrative JavaScript passes the decoded value to
insertAdjacentHTML(), and the content becomes executable markup in that user's browser session.
The available records do not identify a specific administrative screen, a complete exploit request, or a confirmed server compromise resulting from this CVE.
Scoring and exploitation status
The CNA vector is CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N for a score of 7.2. NVD displays this CNA assessment but has not supplied an independent NIST score. Patchstack scores the issue 7.1 and states that a privileged user must perform an action for successful exploitation. The CVE narrative itself describes execution when a user accesses the injected page, so we think it is reasonable to model this as unauthenticated submission followed by execution when a victim views the content, while keeping both published scores in view. The reviewed sources do not explain the disagreement.
As of October 30, 2026, Wiz reported no public proof of concept, no evidence of exploitation in the wild, and no actor attribution. TheHackerWire reports no CISA KEV listing. Patchstack expects the issue to become exploited and says it may appear in mass exploitation campaigns, which is a prediction rather than an observation. Wordfence credits the researcher daroo with the discovery.
Patch information
Gravity Forms 3.1.2 fixes CVE-2026-90437. Wordfence marks the issue as patched and lists the remediation as "Update to version 3.1.2, or a newer patched version." The vendor's 3.1.2 release notes do not name the CVE. They only say "Added security enhancements."
To see what actually changed, we compared 3.1.2 against 3.1.1 using commit 87f9f72 ("Updates to 3.1.2") in the pronamic/gravityforms GitHub repository, which is a third-party mirror of vendor releases.
Fix 1: a narrower allowlist with no data-* attributes
3.1.2 adds a new method, GF_Field::get_entry_allowed_html(), to the base field class. It starts from the standard post allowlist and removes the data-* wildcard from every tag:
public function get_entry_allowed_html( $allowable_tags = true ) { $allowed = wp_kses_allowed_html( 'post' ); foreach ( $allowed as $tag => $attributes ) { // wp_kses normally allows data-* attributes, so we explicitly remove them preventing stored entry values from activating admin behaviors such as data-dialog-confirm. unset( $attributes['data-*'] ); $allowed[ $tag ] = $attributes; } if ( $allowable_tags === true ) { return $allowed; }
The method also handles a form's custom allowed tags setting. It reads tag names from that setting (either a string or an array) and rejects any name that does not start with a lowercase letter followed only by lowercase letters, digits, or hyphens. Tags that also appear in the post allowlist keep their normal attributes, still without data-*. Tags outside that list are allowed with no attributes at all.
The main design decision is to strip the entire data-* family. The payload lived in a data attribute that only becomes dangerous when admin JavaScript consumes it, so removing the attribute family means no stored entry value can trigger admin behaviors such as data-dialog-confirm.
Fix 2: sanitizing again at output
The patch removes the "sanitized before saving" comment everywhere and runs wp_kses() with the new allowlist each time a value is rendered. In the base GF_Field class this covers get_value_merge_tag(), get_value_entry_list() and get_value_entry_detail().
Before (3.1.1):
// The value contains HTML but the value was sanitized before saving. $return = $value;
After (3.1.2):
$return = wp_kses( $value, $this->get_entry_allowed_html( $allowable_tags ) );
The same change appears in field classes that override these methods:
- Post Excerpt: the merge tag output receives the identical replacement shown above.
- Paragraph Text / rich text: values are now filtered before
wpautop()in both entry detail and merge tag output.
Before (3.1.1):
$return = wpautop( $value );
After (3.1.2):
$return = wpautop( wp_kses( $value, $this->get_entry_allowed_html() ) );
- Single Line Text: the same fix in merge tag, entry list and entry detail output.
- Repeater: subfield values were already filtered with
wp_kses_allowed_html( 'post' ), which still alloweddata-*. They now use$this->get_entry_allowed_html().
Why output-time filtering matters
If the patch had only tightened sanitization at save time, malicious entries already sitting in the database would still fire after the upgrade. Because 3.1.2 filters values when they are rendered, a stored data-dialog-confirm payload displayed through these paths loses the attribute once the site is upgraded. Input is sanitized when saved, and output is still treated as untrusted.
Other changes in the same release
The 3.1.2 commit also rebuilt the minified admin JavaScript bundles, including a renamed form editor chunk. Because the files are minified, the diff does not show whether the insertAdjacentHTML() call itself changed. The fix we can confirm is the server-side data-* removal and output filtering.
The release also contains unrelated hardening, which suggests the "security enhancements" bundle addressed several issues:
- An HMAC (
hash_hmac( 'sha256', ..., wp_salt( 'auth' ) )) to verify temporary upload files. - Size limits on chunked uploads.
esc_html()for Phone field entry list output.esc_url()for Website field entry list output.
After upgrading
Wiz recommends auditing existing entries for injected JavaScript and monitoring suspicious submissions. Vendor guidance also supports keeping an active, authenticated license key in plugin settings, updating Gravity Forms before its add-ons, and making sure Rocketgenius security notification emails reach administrators. Least-privilege capabilities, forms restricted to logged-in users where appropriate, and Content Security Policy are documented hardening measures, but none of them is a verified substitute for installing 3.1.2.
Affected systems and versions
- Product: Gravity Forms plugin for WordPress (Rocketgenius Inc.)
- Affected: all versions up to and including 3.1.1
- Fixed: 3.1.2 or a newer patched version
- Vulnerable configurations: forms containing a Post Excerpt field, a Post Custom Field, or a Paragraph Text field with the rich text editor enabled
The vendor's 3.1.1 announcement says it added security enhancements, yet Wordfence explicitly includes 3.1.1 in the affected range. The boundary comes from the advisory rather than from release note language.
Wordfence dates its advisory to October 29, 2026, while CVE.org and NVD record publication on October 30, 2026. These are separate publication events for the same vulnerability.
Vendor security history
CVE-2023-28782. Gravity Forms through 2.7.3 was affected by an unauthenticated PHP object injection that Patchstack says could be triggered in a default configuration when a form contained a list field. Version 2.7.4 fixed it, and NVD independently identifies the affected range. Patchstack noted it did not find a significant property-oriented programming chain in the plugin at publication, which limited the demonstrated impact.
July 2025 package tampering. Rocketgenius disclosed that an external agent maliciously modified a limited number of manually downloadable 2.9.11.1 and 2.9.12 packages on July 9 and 10, including certain Composer installations of 2.9.11.1. The vendor says the modified code blocked update attempts and contacted an external server for an additional payload that could attempt to create an administrator account. The vendor released a clean 2.9.13, scanned packages, changed credentials, and audited administrative accounts. SecurityWeek independently reported the affected versions. This was a distribution integrity failure and is unrelated to CVE-2026-90437.
Disclosure process. The vendor documents a responsible disclosure process in which reports are acknowledged, verified and assessed for severity, with fixes delivered in an upcoming release or an immediate security release depending on severity.
References
- NVD: CVE-2026-90437
- CVE Record: CVE-2026-90437
- Wordfence: Gravity Forms 3.1.1 and earlier, Unauthenticated Stored Cross Site Scripting
- Wordfence Threat Intel entry (CVE source link)
- Gravity Forms 3.1.2 release announcement
- pronamic/gravityforms commit 87f9f72 (Updates to 3.1.2)
- Patchstack: Cross Site Scripting in WordPress Gravity Forms Plugin
- Wiz: CVE-2026-90437 Impact, Exploitability, and Mitigation Steps
- TheHackerWire: CVE-2026-90437 Analysis
- BaseFortify: CVE-2026-90437 Stored XSS in Gravity Forms
- Gravity Forms Documentation
- Gravity Forms Security Best Practices
- Gravity Forms Security Whitepaper
- About Gravity Forms
- Gravity Forms 3.1.1 release announcement
- Gravity Forms Changelog
- The Importance of Updates
- Staying Informed About Gravity Security Updates
- Gravity Forms Security Incident Notice, July 2025
- SecurityWeek: Hackers Inject Malware Into Gravity Forms WordPress Plugin
- Patchstack: Unauthenticated PHP Object Injection in Gravity Forms
- NVD: CVE-2023-28782
- MDN: Element insertAdjacentHTML() method
- WordPress Developer Resources: Escaping Data
- WordPress Developer Resources: wp_kses_post()
- CISA Known Exploited Vulnerabilities Catalog



