feat: serve /.well-known/supertab/status self-report endpoint - #18
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a WordPress-owned self-report endpoint at GET /.well-known/supertab/status so the Supertab Connect backend can probe a merchant site for plugin/SDK versions and effective bot-protection configuration, while returning a 404 decoy for unauthenticated/invalid challenges.
Changes:
- Introduces
Status_Handlerto interceptparse_requestearly and serve the status/decoy JSON responses. - Updates bot-protection handling to correctly serve SDK “respond” results (e.g., SDK status endpoint) and sanitizes headers before emitting them.
- Bumps
getsupertab/connect-sdk-phpto1.4.0-beta.8and adds unit + integration tests for the new endpoint and routing behavior.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/wp-stubs.php | Extends WP stubs to support new tests (actions tracking, json/ssl/sanitization helpers, WP::$request). |
| tests/StatusHandlerTest.php | Adds unit coverage for status handler decoy behavior, verification gating, and payload shape. |
| tests/integration/StatusRoutingTest.php | Adds integration test verifying rewrite/parse_request receives /.well-known/... path. |
| tests/BotProtectionTest.php | Adds tests for safe header filtering behavior in Bot_Protection. |
| src/class-status-handler.php | Implements the /.well-known/supertab/status endpoint handler and challenge verification flow. |
| src/class-plugin.php | Registers Status_Handler and makes get_enforcement_mode() accessible for status reporting. |
| src/class-bot-protection.php | Handles SDK RespondResult and centralizes/sanitizes header emission. |
| composer.json | Bumps SDK dependency to 1.4.0-beta.8. |
| composer.lock | Updates lockfile for SDK bump. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tomasstark
added a commit
to getsupertab/connect-sdk-php
that referenced
this pull request
Jul 14, 2026
This PR adds a self-describing `component: { kind: "php-sdk", version }`
to the `/.well-known/supertab/status` payload — the PHP counterpart of
getsupertab/connect-sdk-typescript#40.
## Context
The status payload reported only `sdkVersion`, which tells the backend
nothing about *which* package a deployment actually updates from. The
backend's per-component version resolver
(laterpay/supertab-connect#1094) maps `component.kind` to the right
registry and label; a bare `sdkVersion` payload gets legacy-shimmed to
`ts-sdk` and checked against npm — the wrong registry for a
Packagist-installed PHP deployment. (The WordPress plugin builds its own
status payload with `kind: "wordpress-plugin"` —
getsupertab/connect-wp#18 — so this field covers standalone PHP
deployments and the plain-permalink WP fallback where the SDK answers
the probe itself.)
## Key Changes
- `handleStatusRequest()` now emits `component: { kind: "php-sdk",
version: HttpClient::resolveVersion() }` alongside the unchanged
`sdkVersion`.
- Test asserting the component identity and that its version matches
`sdkVersion`; README payload example updated.
Additive and backward-compatible — older backends ignore the new field,
and the backend degrades unknown kinds to "show version, no nudge" until
it wires a `php-sdk` resolver. No version bump or release here; that
stays a separate step.
github-actions Bot
pushed a commit
that referenced
this pull request
Jul 14, 2026
# [1.3.0-beta.8](v1.3.0-beta.7...v1.3.0-beta.8) (2026-07-14) ### Features * serve /.well-known/supertab/status self-report endpoint ([#18](#18)) ([d44a344](d44a344)), closes [getsupertab/connect-sdk-typescript#40](getsupertab/connect-sdk-typescript#40)
|
🎉 This PR is included in version 1.3.0-beta.8 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
tomasstark
added a commit
that referenced
this pull request
Jul 16, 2026
#20) This PR adds real-database integration coverage for `Analytics_Queue_Table` — the SQL semantics the unit suite's in-memory `wpdb` spy records but cannot execute. **Stacked on #17** (base: `feat/analytics-batch-buffer`): review only the test diff here; the stack lands on `beta` as one work package once #17 merges. ## Context #17 gained two SQL-level behaviors late in review — the `FOR UPDATE` claim transaction and the O(1) id-span capacity probe — that nothing executes against a real database. The existing integration suite (WP tests lib + MySQL 8.0 in CI, WP 6.4/7.0 × PHP 8.1/8.4) is the right home for that proof. ## Key Changes `tests/integration/AnalyticsQueueTableTest.php` (new, 6 tests): - **Schema** — `install()` materializes the exact dbDelta schema (`DESCRIBE`-verified) and records the version option; a version-current re-run provably leaves existing rows untouched. - **Round-trip** — payloads survive byte-for-byte (multibyte UTF-8 included); `created_at` is current UTC. - **Drain** — `claim_batch()` returns oldest-first, deletes what it returns, and reports a drained buffer as an empty claim. - **Capacity probe** — `is_full()` boundary at the cap, plus the documented id-span early-trip: a carved id gap trips the cap with only 2 rows present (the fail-open direction, pinned as intended). - **`FOR UPDATE` claim** — a second mysqli session holds locks on the oldest rows while the main connection claims: the contending claim must return nothing (never the locked rows, `innodb_lock_wait_timeout = 1` to fail fast); after the holder deletes-and-commits, a follow-up claim returns exactly the survivors. Asserts claims are disjoint and exhaustive — no double delivery, no loss. Test-framework notes (documented in the file header): the suite's `CREATE TEMPORARY TABLE` rewrite is disabled (temporary tables are invisible to the second connection) and cleanup is explicit, since `claim_batch()`'s own `COMMIT` ends the per-test rollback wrapper. ## Verification - `composer lint`, `phpcs` (WordPress-VIP-Go), `phpstan` (level 5) clean. - The WP tests lib + MySQL harness is CI-only (same as #18's `StatusRoutingTest`) — the Integration matrix on this PR is the gate.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR exposes
GET /.well-known/supertab/statusso the Supertab Connect API can probe a merchant site and learn what's running there — plugin version, bundled PHP SDK version, effective enforcement mode, and whether event reporting is active. It's the WordPress counterpart of getsupertab/connect-sdk-typescript#40 and pairs with the backend's per-component version resolver, which mapscomponent.kind: "wordpress-plugin"to the wordpress.org update registry.Context
The backend's live-health check probes sites with a backend-minted ES256 challenge JWT (
Authorization: Bearer, purposestatus-probe,aud= the site origin, verifiable against the platform JWKS). A valid challenge gets the live config; anything else gets a minimal404 {"supertab": true}decoy, so the endpoint discloses nothing to anyone without a challenge. Without acomponentidentity, the backend's legacy shim would resolve a WordPress site's version against npm — wrong registry, wrong label.The PHP SDK's own
handleRequest()also serves this path, but in this plugin it only runs when bot protection is enabled and the path matches active patterns — and it reports the SDK's identity, not the plugin's. The plugin needs to own its component identity and answer probes regardless of bot-protection state.Key Changes
Status_Handler(new, mirrorsRSL_License_Handler): hooksparse_requestat priority 5 — beforeBot_Protection's 9, so probes never reach bot detection or analytics — exact-matches.well-known/supertab/status, verifies the challenge via the SDK'sStatusChallengeVerifier(JWKS cached in WP transients, refresh-and-retry on key rotation), and responds:404{"supertab": true}(fail-closed: every verification failure, including JWKS fetch errors, resolves to the decoy)200with{runtime: null, sdkVersion, component: {kind: "wordpress-plugin", version}, enforcement, eventReporting}Content-Type: application/json+Cache-Control: no-storePlugin::init()(like the RSL handler), so the backend can learn the plugin version even when bot protection is off;enforcement/eventReportinghonestly reportdisabled/falsein that case.1.4.0-beta.6→1.4.0-beta.8forStatusChallengeVerifier/JwksProvider.Bot_Protectionnow serves the SDK'sRespondResult(new in beta.8) instead of leaking its headers onto the next rendered page — relevant on plain-permalink sites, where the SDK's URL-based status check fires instead of the plugin's handler.parse_request. Verified end-to-end in wp-env: unauthenticated and garbage-bearer probes return the exact decoy with correct headers.Reachability was verified live against a WP Engine production site (theleek.demo.supertab.co): the path passes through Cloudflare and WP Engine's proxy into WordPress's front controller.
Notes
Both degradation modes fail closed and look like "plugin not installed" to the backend (which degrades to "show version, no nudge"):
$wp->requestisn't populated without rewrite rules, so the plugin handler can't match (same constraint aslicense.xml); with bot protection on, the SDK's own status response answers instead (withoutcomponent).