Skip to content

feat: serve /.well-known/supertab/status self-report endpoint - #18

Merged
tomasstark merged 6 commits into
betafrom
feat/status-endpoint
Jul 14, 2026
Merged

feat: serve /.well-known/supertab/status self-report endpoint#18
tomasstark merged 6 commits into
betafrom
feat/status-endpoint

Conversation

@tomasstark

@tomasstark tomasstark commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

This PR exposes GET /.well-known/supertab/status so 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 maps component.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, purpose status-probe, aud = the site origin, verifiable against the platform JWKS). A valid challenge gets the live config; anything else gets a minimal 404 {"supertab": true} decoy, so the endpoint discloses nothing to anyone without a challenge. Without a component identity, 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, mirrors RSL_License_Handler): hooks parse_request at priority 5 — before Bot_Protection's 9, so probes never reach bot detection or analytics — exact-matches .well-known/supertab/status, verifies the challenge via the SDK's StatusChallengeVerifier (JWKS cached in WP transients, refresh-and-retry on key rotation), and responds:
    • invalid/missing challenge → 404 {"supertab": true} (fail-closed: every verification failure, including JWKS fetch errors, resolves to the decoy)
    • valid challenge → 200 with {runtime: null, sdkVersion, component: {kind: "wordpress-plugin", version}, enforcement, eventReporting}
    • both with Content-Type: application/json + Cache-Control: no-store
  • Registered unconditionally in Plugin::init() (like the RSL handler), so the backend can learn the plugin version even when bot protection is off; enforcement/eventReporting honestly report disabled/false in that case.
  • SDK bump 1.4.0-beta.61.4.0-beta.8 for StatusChallengeVerifier/JwksProvider.
  • Bot_Protection now serves the SDK's RespondResult (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.
  • Tests: 14 new unit tests (decoy paths, fail-closed behavior incl. throwing verifier, payload shape, hook priority, header filtering) plus an integration test proving WP's rewrite pipeline delivers the dot-prefixed path to 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"):

  • Plain permalinks: $wp->request isn't populated without rewrite rules, so the plugin handler can't match (same constraint as license.xml); with bot protection on, the SDK's own status response answers instead (without component).

@tomasstark
tomasstark requested a review from Copilot July 14, 2026 03:56
@tomasstark tomasstark changed the title Serve /.well-known/supertab/status self-report endpoint feat: serve /.well-known/supertab/status self-report endpoint Jul 14, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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_Handler to intercept parse_request early 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-php to 1.4.0-beta.8 and 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.

Comment thread src/class-status-handler.php
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.
@tomasstark
tomasstark merged commit d44a344 into beta Jul 14, 2026
5 checks passed
@tomasstark
tomasstark deleted the feat/status-endpoint branch July 14, 2026 09:24
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)
@github-actions

Copy link
Copy Markdown

🎉 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants