Skip to content

Restrict Gravity Forms REST routes to authenticated users - #2049

Merged
subodhr258 merged 2 commits into
developfrom
hotfix/1289-gforms-authz
Jul 29, 2026
Merged

Restrict Gravity Forms REST routes to authenticated users#2049
subodhr258 merged 2 commits into
developfrom
hotfix/1289-gforms-authz

Conversation

@elifvish

Copy link
Copy Markdown
Collaborator

This pull request addresses a security issue in the Gravity Forms REST API integration by restricting which form fields can be exposed, tightening permissions, and adding comprehensive unit tests to prevent regression. It also updates the frontend to send authentication headers and improves test bootstrapping for isolated environments.

Security and Permissions:

  • Only the id, title, and description fields are now allowed in Gravity Forms REST API responses, preventing accidental disclosure of sensitive form data such as notifications, confirmations, or add-on settings. (inc/classes/rest-api/class-gf.php) [1] [2]
  • REST API endpoints for Gravity Forms now require the upload_files capability, matching the Video Editor page's permission requirements. (inc/classes/rest-api/class-gf.php) [1] [2]

Backend Logic and Tests:

  • Introduced the resolve_requested_fields method to strictly enforce the allowlist, regardless of the fields parameter in the request. (inc/classes/rest-api/class-gf.php)
  • Added unit tests to ensure only allowed fields are ever returned, covering various edge cases and regression scenarios. (tests/php/GravityFormsFieldAllowlistTest.php)
  • Added a stub for WP_REST_Controller to allow unit tests to run without a full WordPress environment. (tests/stubs/class-wp-rest-controller.php, tests/bootstrap.php) [1] [2]

Frontend Integration:

  • The frontend now always sends the correct REST API nonce for authentication with Gravity Forms endpoints, improving security for API requests. (pages/video-editor/redux/api/gravity-forms.js)…routes

GET /godam/v1/gforms was registered with __return_true and returned GFAPI::get_forms() verbatim. The response was narrowed only when the caller passed a fields parameter, so an unauthenticated request that omitted it received the complete form configuration: notification recipients and routing, confirmation URLs, the full field structure, and any settings an add-on had persisted into form meta — including credentials.

  • Gate /gforms and /gform on current_user_can( 'upload_files' ), matching the capability the Video Editor page itself registers with.
  • Narrow the collection to id, title and description on the server, unconditionally, so an omitted or unrecognised fields value can no longer widen the response.
  • Send X-WP-Nonce from the Video Editor's Gravity Forms slice. Without it a cookie-authenticated request is evaluated as anonymous and the form picker breaks under the new capability check.

Adds unit cover for the allowlist. Authorisation itself needs the wp-phpunit harness the bootstrap notes as a follow-up.

Refs rtCamp/godam-core#1289

Copilot AI review requested due to automatic review settings July 29, 2026 09:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Hardens the Gravity Forms REST API integration by enforcing authenticated access and preventing sensitive form configuration leakage via REST responses.

Changes:

  • Restricts /godam/v1/gforms responses to an explicit allowlist (id, title, description) via resolve_requested_fields.
  • Requires upload_files capability for Gravity Forms REST endpoints (matching Video Editor permissions).
  • Adds unit tests + minimal WordPress REST controller stub and updates Video Editor API client to send X-WP-Nonce.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
inc/classes/rest-api/class-gf.php Adds allowlist enforcement and permission checks for Gravity Forms REST routes.
pages/video-editor/redux/api/gravity-forms.js Sends REST nonce header so authenticated requests pass the new capability gate.
tests/php/GravityFormsFieldAllowlistTest.php Adds unit coverage for the allowlist field resolution logic.
tests/stubs/class-wp-rest-controller.php Provides a minimal WP REST controller stub for pure unit tests.
tests/bootstrap.php Bootstraps the new stub and loads REST base/GF classes for unit tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread inc/classes/rest-api/class-gf.php
Comment thread inc/classes/rest-api/class-gf.php
Comment thread inc/classes/rest-api/class-gf.php Outdated
Comment thread pages/video-editor/redux/api/gravity-forms.js
elifvish added 2 commits July 29, 2026 15:56
…routes

GET /godam/v1/gforms was registered with __return_true and returned
GFAPI::get_forms() verbatim. The response was narrowed only when the caller
passed a `fields` parameter, so an unauthenticated request that omitted it
received the complete form configuration: notification recipients and routing,
confirmation URLs, the full field structure, and any settings an add-on had
persisted into form meta — including credentials.

- Gate /gforms and /gform on current_user_can( 'upload_files' ), matching the
  capability the Video Editor page itself registers with.
- Narrow the collection to id, title and description on the server,
  unconditionally, so an omitted or unrecognised `fields` value can no longer
  widen the response.
- Send X-WP-Nonce from the Video Editor's Gravity Forms slice. Without it a
  cookie-authenticated request is evaluated as anonymous and the form picker
  breaks under the new capability check.

Adds unit cover for the allowlist. Authorisation itself needs the wp-phpunit
harness the bootstrap notes as a follow-up.

Refs rtCamp/godam-core#1289
- Fall back to the allowlist when a `fields` selection names nothing
  allowed. It previously resolved to an empty set, so the endpoint
  returned a list of empty objects. Falling back narrows to the same
  three fields, so it cannot widen the response.
- Collapse duplicates in the requested list, so `fields=id,id,title`
  no longer yields a repeated entry.
- Only send X-WP-Nonce when a nonce is actually available, rather than
  setting an empty header.

Refs rtCamp/godam-core#1289
@elifvish
elifvish force-pushed the hotfix/1289-gforms-authz branch from 8dcd608 to 19d111f Compare July 29, 2026 10:29
@subodhr258

subodhr258 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Tested locally against godam-dev.local (Local by Flywheel, Gravity Forms active, form id=1 present), on the latest commit 19d111f4 (after the Copilot round). The fix does what it claims — closes the unauthenticated disclosure, blocks the fields widening bypass server-side, and doesn't break the admin form picker/preview.

Summary

Case Before (develop) After (patched)
Anon GET /gforms 200 — 21 keys/form incl. notifications, confirmations, fields 401 rest_forbidden
Anon GET /gform?id=1 200 (renders form) 401 rest_forbidden
Admin GET /gforms 200, only [id, title, description]
Admin ?fields=notifications,confirmations,fields (bypass attempt) 200, still only [id, title, description]
Admin GET /gform?id=1 200, renders form HTML (no preview regression)
  • Capability choice is right: upload_files matches the Video Editor page's own registration (class-pages.php — "Video Editor is accessible to authors and above").
  • The frontend nonce resolves from godamRestRoute.nonce, so the logged-in editor authenticates correctly under the new gate.
  • The follow-up change to resolve_requested_fields (fall back to the allowlist when a caller names only disallowed fields) is safe — it's still an array_intersect against ALLOWED_FORM_FIELDS, so privileged fields can never pass; it only makes the response usable instead of a list of empty objects.

Testing logs

Unit tests — vendor/bin/phpunit --testdox (36/36 pass)

Gravity Forms Field Allowlist (RTGODAM\Tests\GravityFormsFieldAllowlist)
 ✔ Missing fields returns allowlist
 ✔ Empty fields returns allowlist
 ✔ Video editor request is honoured
 ✔ Caller may narrow selection
 ✔ Privileged fields are rejected
 ✔ Addon credential key is rejected
 ✔ Duplicates are collapsed
 ✔ Mixed selection keeps only allowed
 ✔ Whitespace is trimmed
 ✔ Matching is case sensitive
 ✔ Non string input returns allowlist
 ✔ Result is a list
...
Time: 00:00.025, Memory: 6.00 MB
OK (36 tests, 69 assertions)

Baseline — unauthenticated, on develop (pre-fix)

$ curl -s -o /dev/null -w "HTTP %{http_code}\n" http://godam-dev.local/wp-json/godam/v1/gforms
HTTP 200

# keys leaked per form (form[0]):
forms returned: 1
key count on form[0]: 21
keys: ['fields', 'button', 'title', 'description', 'version', 'id', 'markupVersion',
       'nextFieldId', 'useCurrentUserAsAuthor', 'postContentTemplateEnabled',
       'postTitleTemplateEnabled', 'postTitleTemplate', 'postContentTemplate',
       'lastPageButton', 'pagination', 'firstPageCssClass', 'notifications',
       'confirmations', 'is_active', 'date_created', 'is_trash']
SENSITIVE keys present: ['fields', 'button', 'notifications', 'confirmations', 'is_active']

After patch — unauthenticated (both endpoints 401)

$ curl -s -w "\nHTTP %{http_code}\n" http://godam-dev.local/wp-json/godam/v1/gforms
{"code":"rest_forbidden","message":"Sorry, you are not allowed to do that.","data":{"status":401}}
HTTP 401

$ curl -s -w "\nHTTP %{http_code}\n" "http://godam-dev.local/wp-json/godam/v1/gform?id=1"
{"code":"rest_forbidden","message":"Sorry, you are not allowed to do that.","data":{"status":401}}
HTTP 401

After patch — authenticated admin (cookie + X-WP-Nonce, replicating the Video Editor's request)

// GET /gforms  and  GET /gforms?fields=notifications,confirmations,fields
{
  "loggedInAs": "logged-in",
  "nonceSource": "godamRestRoute",
  "authList":      { "status": 200, "count": 1, "keys": ["title","description","id"] },
  "bypassAttempt": { "status": 200,             "keys": ["title","description","id"] }
}

// GET /gform?id=1  (single-form preview, no regression)
{ "status": 200, "isString": true, "length": 9101, "looksLikeForm": true,
  "snippet": "<script type=\"text/javascript\">..." }

@subodhr258
subodhr258 merged commit 753f5d8 into develop Jul 29, 2026
2 of 4 checks passed
@subodhr258
subodhr258 deleted the hotfix/1289-gforms-authz branch July 29, 2026 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants