FIX: Secure SSR callbacks with admin-managed registry - #710
Conversation
🤖 AI PR ReviewRisk level: ReviewSummary This PR replaces the per-form SSR allowlist with a single admin-managed registry and adds a bounded migration that imports candidate callback names as "pending" for admin review. It also adds UI for settings, an admin notice for migration results, and several migration/transaction fixes to preserve resume cursors. Overall the changes address the security issue (self-service allowlisting) and are thoughtfully implemented (time-boxed migrations, pending queue, denylist enforcement, masking callback names in public JSON). What I checked
Issues, risks and suggestions (actionable)
Minor nits
Conclusion
Suggested changelog entry
|
There was a problem hiding this comment.
🟡 Changes recommended
Migration reliability, callback compatibility, and incomplete CLI reporting contain unresolved defects.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Introduces an administrator-managed registry to prevent untrusted SSR validation callbacks from executing.
Changes:
- Replaces automatic per-form allowlisting with an explicit global registry.
- Adds resumable migrations, review notices, and blocked-callback reporting.
- Adds settings UI, editor guidance, and regression tests.
File summaries
| File | Description |
|---|---|
.gitignore |
Tracks the new tests. |
jet-form-builder.php |
Bumps version to 3.6.5.3. |
includes/admin/tabs-handlers/ssr-callbacks-handler.php |
Handles registry administration. |
includes/migrations/auto-migrator.php |
Registers the new migration. |
includes/migrations/migration-incomplete-exception.php |
Defines resumable-migration signaling. |
includes/migrations/migrator.php |
Registers migration 3.6.5.3. |
includes/migrations/versions/version-3-6-5-2.php |
Documents time-boxed legacy migration behavior. |
includes/migrations/versions/version-3-6-5-3.php |
Migrates existing callbacks for review. |
modules/cli/commands/upgrade-database.php |
Handles incomplete CLI migrations. |
modules/rest-api/endpoints/install-migrations-endpoint.php |
Reports incomplete REST migrations. |
modules/validation/advanced-rules/server-side-rule.php |
Enforces the registry and safe callbacks. |
modules/validation/advanced-rules/ssr-callback-allowlist.php |
Retains bounded legacy migration support. |
modules/validation/advanced-rules/ssr-callback-registry.php |
Implements trusted and pending registries. |
modules/validation/module.php |
Integrates registry UI, notices, and usage tracking. |
modules/validation/ssr/ssr-blocked-callback-usages.php |
Records forms using denied callbacks. |
modules/validation/ssr/ssr-registry-migration-notice.php |
Prompts administrators to review imports. |
assets/src/package/validation/components/AdvancedRuleModalItem.js |
Links editors to callback settings. |
assets/src/admin/pages/jfb-settings/SettingsPage.vue |
Registers the settings tab. |
assets/src/admin/pages/jfb-settings/tabs/ssr-callbacks/index.js |
Exports the new tab. |
assets/src/admin/pages/jfb-settings/tabs/ssr-callbacks/source.js |
Defines tab labels and guidance. |
assets/src/admin/pages/jfb-settings/tabs/ssr-callbacks/SsrCallbacksTab.vue |
Implements registry review UI. |
assets/build/editor/package.asset.php |
Updates editor build metadata. |
assets/build/editor/form.builder.asset.php |
Updates form-builder build metadata. |
assets/build/admin/pages/jfb-settings.js |
Includes the compiled settings UI. |
assets/build/admin/pages/jfb-settings.asset.php |
Updates settings build metadata. |
tests/wpunit/SsrCallbackAllowlistTest.php |
Removes obsolete allowlist tests. |
tests/wpunit/SsrCallbackRegistryTest.php |
Tests registry enforcement and migration. |
tests/wpunit/SsrCallbackMigrationBatchingTest.php |
Tests resumable scanning. |
tests/wpunit/AutoMigratorTransactionTest.php |
Tests transaction-boundary behavior. |
tests/wpunit/AutoMigratorTest.php |
Expects the new automatic migration. |
Review details
Suppressed comments (2)
includes/migrations/versions/version-3-6-5-3.php:90
- A failed
SELECTis treated as an empty final page, so this migration can delete its progress, write an empty result, and be marked installed. The later successful option queries can also clear$wpdb->last_errorbeforeBase_Migration::run_up()checks it. Check the query error before handlingempty( $form_ids )so a transient DB failure is retried instead of silently skipping every remaining form.
if ( empty( $form_ids ) ) {
break;
}
modules/validation/advanced-rules/ssr-callback-allowlist.php:136
- This has the same silent-completion failure mode as the registry scan:
$wpdb->get_col()returns an empty value on an SQL error, and the subsequent option writes can clear$wpdb->last_error. Check the error here before treating the page as complete, otherwise the historical migration may be permanently stamped after skipping the remaining forms.
if ( empty( $form_ids ) ) {
break;
}
- Files reviewed: 28/32 changed files
- Comments generated: 5
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if ( ! function_exists( $name ) ) { | ||
| $error = __( 'No such PHP function exists.', 'jet-form-builder' ); | ||
|
|
||
| return ''; |
🤖 AI PR ReviewRisk level: ReviewSummary This PR introduces a secure, admin-managed registry for "Server-Side callback" validation (Ssr_Callback_Registry), retires the old per-form allowlist, adds a bounded, resumable migration (Version_3_6_5_3) to restore previously-used callbacks into the new registry, hardens the denylist in Server_Side_Rule, and adds admin UI (settings tab + migration notice) and tests around the migration transaction behavior. Overall the changes address the security gap described in issues-tracker #20361 and add sensible batching/time-budgeting to long-running scans. The migration/transaction handling and the new Migration_Incomplete_Exception handling in CLI/REST are good improvements. What I checked and notable positive points
Main concerns / required follow-ups
Summary recommendation This is a valuable security hardening and the migration approach appears careful and well thought out. Before merging, please:
If these are addressed, I think this is ready to merge. Suggested changelog entry
|
There was a problem hiding this comment.
🟡 Changes recommended
The migration bypasses administrator approval, while blocked-usage storage has stale-state, concurrency, and scalability defects.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
includes/migrations/versions/version-3-6-5-3.php:224
- This is not actually a batched write: for up to 200 forms,
replace_for_form()rereads, filters, sanitizes, and rewrites the entire growing option once per form. The work also occurs after the time-budget check, so a large blocked-usage list can make the supposedly bounded migration overrun badly. Merge the batch into one in-memory snapshot and persist once, or store usages per form.
foreach ( $batch_blocked as $form_id => $form_blocked ) {
// Every form in the batch is visited exactly once, so this form's current set
// of blocked usages (possibly empty) fully replaces whatever was recorded for
// it before — including nothing, if a prior migration run or a later manual fix
// already cleared it. This is the same call `Module::
- Files reviewed: 31/35 changed files
- Comments generated: 3
- Review effort level: Balanced
| array &$blocked_form_ids_total | ||
| ) { | ||
| if ( ! empty( $batch_callbacks ) ) { | ||
| $result = Ssr_Callback_Registry::import_trusted_callbacks( array_values( array_keys( $batch_callbacks ) ) ); |
| public static function replace_for_form( int $form_id, array $usages ) { | ||
| $remaining = array_values( | ||
| array_filter( | ||
| self::get_usages(), | ||
| static function ( $usage ) use ( $form_id ) { |
| add_action( | ||
| 'save_post_jet-form-builder', | ||
| array( $this, 'refresh_blocked_callback_usages' ) | ||
| ); |
🤖 AI PR ReviewRisk level: ReviewOverall: This PR significantly improves security around the Server-Side callback (SSR) validation rule by replacing the per-form self-service allowlist with a single admin-managed registry, expanding the denylist, and adding a robust, time-boxed migration to import previously-used callbacks. The changes are well-structured, include transaction/exception handling, CLI/REST handling for the time-boxed migration, and unit tests that exercise the important transaction/resume scenarios. Good attention to sanitization and the migration edge-cases. What I inspected closely (high-level):
Positive notes:
Potential issues, recommendations and questions (please address or confirm):
Summary of actionable items I recommend before merge:
Given the sensitivity of this surface (it controls code execution that can run server-side) the approach taken is sound and carefully implemented. The remaining items are follow-ups (mainly around explicit capability checks, additional tests, and large-site performance for the settings UI). Suggested changelog entry
|
🤖 AI PR ReviewRisk level: ReviewSummary What I like
Files / locations reviewed (high level)
Security review / comments
Performance & scalability
Backward compatibility
Multisite
Testing gaps / suggestions
Small code suggestions
Overall recommendation Specific lines/files to review by author before merge
Suggested tests to add prior to merge
Good job overall — careful work that balances security, migration UX, and data integrity. Suggested changelog entry
|
Issue - https://github.com/Crocoblock/issues-tracker/issues/20361