Repository navigation
Leave the Composer routes open while SIMPLE_UPDATE_KEY is unset - #6
Conversation
Matches the update-checker route, so a Composer-built site can install from production before every legacy plugin bundles a key-aware checker. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015QYJYSm4FogA6Ds4TtgCZq
| return Wpup_ComposerResponse::error(503, 'The Composer repository is disabled: SIMPLE_UPDATE_KEY is not set.'); | ||
| } | ||
| if ( !$this->isAuthorized($headers) ) { | ||
| if ( $this->key !== '' && !$this->isAuthorized($headers) ) { |
There was a problem hiding this comment.
🔴 Anyone can now download premium plugin zips from /dist/reason-dev/.../*.zip whenever SIMPLE_UPDATE_KEY is unset. The old code fail-closed (503) specifically to keep "premium plugin code" (base branch includes/Wpup/ComposerRepository.php:13-14) behind this route when no key was set; that line is deleted and the gate at line 56 now only checks isAuthorized when $this->key !== ''. Composer-only/vnext premium plugins that never shipped via the legacy route were never exposed unauthenticated before this change. Fix: keep dist (or all Composer routes) fail-closed, or require a Composer-specific key, until the specific vnext premium packages involved are confirmed safe to leave open; don't rely solely on URL-guessing difficulty as the safeguard.
Why this was flagged
Trigger: SIMPLE_UPDATE_KEY unset in an environment, which per the PR author is production's state immediately after merge and deploy. Entry point: GET /dist/reason-dev//-.zip, routed via wpup_route_composer()->handle() in includes/Wpup/ComposerRepository.php:50-60. At line 56 the 401 check now only runs when $this->key !== '', so with no key the request falls through to serveDist() (line 111), which issues a 302 to a presigned S3 URL with no authorization at all. On the base branch this path answered 503 specifically because, per the deleted docblock comment (base branch lines 13-14), "premium plugin code sits behind these routes." The PR calls this the same exposure as the legacy packages/ zips, but that only holds for plugins also served by the legacy route; plugins distributed only via the new Composer/vnext path were never reachable unauthenticated before this diff. No other safeguard limits this, since valid slugs are guessable or discoverable via /packages.json and /p2/..., which are also open.
Verification: Base includes/Wpup/ComposerRepository.php returned 503 when $this->key === '', per the docblock keeping "premium plugin code" behind these routes. The diff deletes that check; the merged gate at line 56 skips authorization when the key is unset, letting GET /dist/reason-dev//-.zip fall through to serveDist() (line 111), which returns a 302 to a presigned S3 URL with no authentication.
Summary
The Composer routes now follow the update-checker route's rule: open while
SIMPLE_UPDATE_KEYis unset, bearer-gated once it is set. They used to answer 503 without a key.Why
No Asana task. One key gates both routes, and setting it in production makes the legacy update route require it too, which needs every legacy plugin to bundle a key-aware update checker first (a
composer updateacross the plugin repos that hasn't happened). With the 503, no Composer-built site could install from production until that fleet-wide work was done. This decouples the two: the first template site can build against production now, and the key goes on when the legacy fleet is ready.Changes
includes/Wpup/ComposerRepository.php: drop the 503 branch; the 401 check runs only when a key is configured.tests/composer-repository-test.php: replace the 503 test with open-mode tests (no header 200, stray bearer ignored, p2 served, unknown path still 404). The 401 tests with a key set are unchanged.serverless.yml,docs/runbook.md: comment and route table updated.Alternatives considered
Testing
Not run:
tests/acceptance/composer-smoke.sh(it asserts 401s, so it only applies to a stage with the key set, where behavior is unchanged) and a deploy.Not in this PR
bref deploy --env prod) after merge. The prod key stays unset.packages/zips already are: via a short-lived presigned URL handed out without auth. Same exposure as today's legacy route, now stated in the router docblock.docs/plugin-distribution.mdstill says prod Composer routes answer 503; fix it in PR Bump guzzlehttp/guzzle from 7.10.5 to 7.15.5 #3's rebase.🤖 Generated with Claude Code
https://claude.ai/code/session_015QYJYSm4FogA6Ds4TtgCZq