Conversation
The prior fix (#1667) restricted post-submission smart tag re-resolution to Hidden fields, but a Hidden field's POST value is just as attacker-controllable as any other field, so it didn't close the hole. Resolve smart tags strictly from the field's own admin-configured default_value (the trusted form definition) instead of the submitted value, in both do_task() and entry_save(). Also fixes a related bypass: {field_id="X"} merge tags (used to embed a submitted field's value in notification/confirmation templates) were substituted in before the generic smart tag scan ran, so typing a tag like {post_meta key=...} into any ordinary visible field referenced by such a template would get it re-resolved. Generic tags now resolve first, against the original trusted template, with {field_id} substitution done last and never re-scanned. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
QA suite — BROKEN, not passedThe runner executed 0 tests. That is a broken harness, not a pass — Likely causes: the grep matched nothing, Automated check — no AI involved. It runs the tests in this branch. |
|
Build for ⬇️ Download everest-forms.zip (11M) Installs directly via Plugins → Add New → Upload Plugin. A later push only rebuilds this if its commit message includes |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Regression tests do not execute the production task/save paths, leaving the security fix insufficiently covered.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Hardens smart-tag resolution against attacker-controlled submitted values and adds security regression coverage.
Changes:
- Resolves tags from trusted configured defaults.
- Prevents rescanning
{field_id}substitutions. - Retains protected-meta checks.
- Adds injection regression tests.
| File | Summary |
|---|---|
tests/phpunit/includes/class-evf-smart-tags-security-test.php |
Adds smart-tag security regression tests. |
includes/class-evf-smart-tags.php |
Reorders resolution to prevent rescanning submitted values. |
includes/class-evf-form-task.php |
Uses trusted defaults during submission and entry saving. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Guards the {field_id} bypass (the second, previously-unreported issue
fixed alongside #1667's gap): a visitor's own field value embedded into
an admin-authored redirect query string via {field_id="X"} must stay
literal text, never re-resolved as a smart tag.
Proven against both versions: 3/3 pass on the fix, fails on the
pre-fix code with the real admin email leaking through the redirect
query string (`Expected: "{admin_email}" Received: "qa@example.test"`).
Does not cover the Hidden-field default_value path (#1667's original
gap) — that's guarded at the unit level in
tests/phpunit/includes/class-evf-smart-tags-security-test.php, since it
needs a Hidden field with a specific default_value and this suite has
no builder-driven fixture for adding a custom field yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Added the requested regression spec: `tests/e2e/specs/submission/field-value-smart-tag-not-reresolved.spec.ts`. It guards the `{field_id}` bypass (the second, previously-unreported issue fixed in this PR alongside #1667's gap): a visitor-typed smart tag embedded into an admin-authored redirect query string via `{field_id="X"}` must stay literal text. Proven against both versions locally (fresh Playground boot):
Also added a PHPUnit test (`tests/phpunit/includes/class-evf-smart-tags-security-test.php`) covering both fixed paths at the unit level, including the Hidden-field `default_value` path from #1667's original gap, which the e2e suite has no builder fixture to drive yet. |
|
@claudegrill suite |
|
The `@claudegrill suite` run just now (run 36556142064) failed with `exit 2` — a harness failure, not a test failure: `ran_nothing: true`, `reason: "the runner executed 0 tests"`. Root cause is a shell-quoting bug in the shared `ThemeGrill/claudegrill` reusable workflow, not in this PR: since the latest commit here only touched a new spec file, the scope computation set ``` with the single quotes baked into the string, then expanded it unquoted (`$SCOPE_ARGS`) in the `run-suite.mjs` call. Bash word-splits it and the literal quote characters end up glued onto the path, so Playwright matches no file and runs nothing. This would hit any PR whose latest commit is spec-only — it's a bug in that shared workflow's scope script, not something fixable from this repo. Locally (fresh Playground boot, documented above): fixed code passes 3/3, and the spec fails against the pre-fix code with the real admin email leaking through the redirect (`Expected: "{admin_email}" Received: "qa@example.test"`). The fix and its regression coverage are sound; this is a CI tooling gap in `ThemeGrill/claudegrill` that needs a fix on that side. |
|
Root-caused and fixed the shared workflow bug: themegrill/claudegrill#8. Once merged there, |
…ests Copilot flagged (correctly) that the two hidden-field regression tests called a test-local copy of the resolution loop instead of the actual EVF_Form_Task code, so reverting either production fix would still leave them green. Rewritten to drive the real entry point: a genuine `everest_form` post is created, submitted through evf()->task->do_task() with a real nonce, and assertions read both the in-memory $task->form_fields and the persisted evf_entrymeta row back from the database -- exercising do_task() and entry_save() exactly as a live request does. Doing this surfaced two small pre-existing bugs in evf_get_browser() (unrelated to the smart-tag fix, just never exercised by any prior test that reaches entry_save()): an uninitialized $ub used before assignment when the user agent matches no known browser, and an undefined array offset when zero regex matches are found. Both are now guarded. Verified locally: all 5 tests pass (temporarily patched the repo's independently-broken PHPUnit bootstrap -- missing WP_Mock/phpunit- polyfills dev dependencies, unrelated to this change -- to actually run them; that patch is not part of this commit). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Audited the e2e spec ( It's already a genuine black-box test with no duplicated logic:
Already proven (documented above): fails against the pre-fix code with the real admin email leaking, passes 3/3 on the fix. No change needed here. |

Background
Wordfence reported (Everest Forms <= 3.6.1): unauthenticated visitors could read arbitrary non-protected post/user meta by injecting a smart tag like
{post_meta key=...}into a form field. #1667 attempted a fix but the patch was rejected — Wordfence reproduced a bypass against it.What was missed in #1667
#1667 made two changes:
type === 'hidden'fields.is_protected_meta()check before resolvingpost_meta/posts_meta_current_page_id/user_metatags.The bypass: a Hidden field's submitted value is still just a normal POST parameter — nothing stops a request from setting it directly, regardless of field type. Restricting by type doesn't stop the value from being attacker-controlled, so the same payload placed in a form's hidden field (e.g. a UTM-tracking field) still got resolved and echoed back via the entry preview.
Fix
do_task()/entry_save()now resolve smart tags only from the field's own admin-configureddefault_value(the trusted form definition), never from the submitted value. A crafted request body can no longer influence what gets resolved.{field_id="X"}merge tags (used to embed a submitted field's value into notification/confirmation templates) were substituted in before the generic tag scan ran. That meant typing{post_meta key=...}into an ordinary visible field — not just hidden ones — could get re-resolved if any template referenced that field. Generic tags now resolve first against the original trusted template, and{field_id}substitution happens last with no further re-scanning.is_protected_meta()check from Fix: tighten smart tag resolution scope and meta-key access #1667 as an additional layer.Test plan
{field_id}bypass no longer resolves, protected (_-prefixed) meta stays blocked.{post_meta}tags still resolve, and a Hidden field'sdefault_valuecontaining{field_id="X"}(the legitimate use case) still resolves correctly.tests/phpunit/includes/class-evf-smart-tags-security-test.phpcovering all of the above.Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com