Repository navigation
Add HTTP-layer connector attribution, enforcement, and Connector Approval interop - #6
Merged
Merged
Conversation
…oval interop
Adopt the technique from the official WP AI plugin's experimental "Connector
Approval" feature: enforce/attribute at the `pre_http_request` filter by scanning
each outbound request for a configured connector credential. This gives an exact
connector identity at request time and also covers plugins that read a credential
option and call the provider API directly, bypassing wp_ai_client_prompt().
New:
- Capture\Connector_Key_Index — builds {credential => connector_id} from core
wp_get_connectors() (no dependency on the ai plugin), scans URL + headers.
- Capture\Http_Guard — hooks pre_http_request (pri 5); enriches the Gatekeeper's
pending intent to `exact` confidence + connector_id, and optionally blocks via
the shared Enforcer (translating its bool decision into a WP_Error 403). Stays
observe-only until a hard limit is configured; fails open on any error.
- Interop\Connector_Approval_Bridge — read-only view of the ai plugin's
connector-approval experiment (its blocks never reach our capture path), plus a
GET /connector-approvals REST route and a dashboard panel surfacing them.
Attribution ladder gains an `exact` tier above self-ID:
- Caller_Resolver::CONFIDENCE_EXACT const + docblock.
- Usage_Recorder allows `exact` through its confidence allowlist.
- Limit_Evaluator ranks `exact` (3) above high, so it satisfies every
min_confidence gate.
Http_Guard is wired in Plugin::register_runtime() reusing the same Caller_Resolver
and Enforcer as the Gatekeeper so both enforcement points evaluate limits
identically.
Verified live on WordPress 7.0 (real anthropic connector): a single real AI call
captured at `exact` confidence with real tokens/provider/model and surfaced on the
dashboard; plus simulated-request coverage for the 403 block on a breached hard
limit, fail-open on error, interop active/inactive paths, and REST 200/401. Passes
PHPCS (0 errors), PHPStan level 10, php-parallel-lint, and the JS build.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Filip Ilic <ilic.filip@gmail.com>
🧪 Test on WordPress PlaygroundOpen this PR in WordPress Playground →
|
…ce tier - Connector_Key_Index_Test: credential match in header / url-encoded / array header, no-match on unrelated traffic, short-credential and empty-registry guards. - Http_Guard_Test: passthrough of non-connector and pre-empted requests, enrichment of the pending intent to `exact` + connector id, WP_Error 403 when the enforcer blocks, allow when it doesn't, and fail-open when it throws. - Limit_Evaluator: assert `exact` outranks high and satisfies a high gate. Adds a protected Connector_Key_Index::get_connectors() seam so tests supply a fixed connector set without depending on core's connector registry (which has no registration filter and varies by WP version). No production behaviour change. Full suite: 80 tests, 243 assertions, green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Filip Ilic <ilic.filip@gmail.com>
`npm run lint:js` (CI js.yml) was failing on pre-existing prettier violations in App.js and Limits.js as well as the new dashboard panel. `npm run format` normalises both files; net -68 lines (collapses multi-line __() calls). No behaviour change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Filip Ilic <ilic.filip@gmail.com>
…nt point Sync CLAUDE.md with the connector-approval work: the new `exact` attribution tier (HTTP-guard credential match, above self-ID) and its confidence-gating rank; the second enforcement point at pre_http_request sharing the Gatekeeper's Enforcer; the new Capture/ + Interop/ files; and the GET /connector-approvals route. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Filip Ilic <ilic.filip@gmail.com>
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.
What & why
WordPress 7.0 ships AI in core behind a shared Connectors API with no spend controls. Our known weak point (per CLAUDE.md) is attribution confidence — today the best signal is a cooperating plugin's self-ID hook (high) or a
debug_backtracepath map (medium).The official WP AI plugin now ships an experimental "Connector Approval" feature that enforces at the
pre_http_requestfilter: it scans each outbound request for a configured connector credential, which identifies the exact connector leaving the site. This PR adopts that technique.Two properties make it worth adopting:
wp_ai_client_prompt()and call the provider API directly — traffic our prompt-hook path is structurally blind to.Changes
New
Capture\Connector_Key_Index— builds{credential => connector_id}from corewp_get_connectors()(no hard dependency on theaiplugin), scans request URL + headers (raw + rawurlencoded).Capture\Http_Guard— hookspre_http_request(pri 5). Enriches the Gatekeeper's pending intent toexactconfidence +connector_id, and optionally blocks via the shared Enforcer (translating its bool into aWP_Error403). Observe-only until a hard limit is configured; fails open on any error.Interop\Connector_Approval_Bridge— read-only view of theaiplugin's connector-approval experiment (its blocks never reach our capture path), exposed viaGET /connector-approvalsand a dashboard panel.exactattribution tierCaller_Resolver::CONFIDENCE_EXACTconst + docblock ladder (exact > high > medium > low).Usage_Recorderallowsexactthrough its confidence allowlist.Limit_Evaluatorranksexact(3) above high, so it satisfies everymin_confidencegate. (Without this an exact request would fail the gate likelow— the subtle bit.)Wiring —
Http_Guardis constructed inPlugin::register_runtime()reusing the sameCaller_ResolverandEnforceras the Gatekeeper, so both enforcement points evaluate limits identically.Invariants respected
Enforcer::should_block()returns true (which short-circuits to false with no enabled hard limits); the whole guard body fails open on any\Throwable. No double-block — for AI-Client calls the prompt hook runs first, and a prompt blocked there makes no HTTP request.aiplugin: credentials come from corewp_get_connectors(); interop only reads options when the experiment is detected active.function_exists('wp_get_connectors')etc.; passesphp -lwith the core connector API absent.Verification
Verified live on WordPress 7.0 with the real anthropic connector:
plugin_confidence=exactwith real tokens (input=16 output=4 estimated=0),provider=anthropic,model=claude-sonnet-5, cost computed, and surfaced on the dashboard (requests=1, cost_micros=108) — proving core's real outbound request carries the credential in the expected shape.Scope note
connector_idis kept as enrichment/enforcement-only (in-memory) for now — no new DB column this round, per the plan's "verify before widening the schema" decision. A follow-up can add the column + a connector breakdown on the dashboard.🤖 Generated with Claude Code