Skip to content

Accept the key as the HTTP Basic password on the Composer routes - #7

Merged
justinmaurerdotdev merged 1 commit into
masterfrom
feat/composer-basic-auth
Nov 5, 2026
Merged

justinmaurerdotdev merged 1 commit into
masterfrom
feat/composer-basic-auth

Conversation

@justinmaurerdotdev

Copy link
Copy Markdown
Member

Summary

The Composer routes (/packages.json, /p2/…, /dist/…) now also accept the update key as the password in HTTP Basic auth, with any username. Bearer works exactly as before, and the key is still never accepted from the query string.

Why

No Asana task.

SIMPLE_UPDATE_KEY is now enforced in production. Dependabot's composer jobs in plugin and theme repos (for example reason-dot-com-theme) resolve reason-dev/* packages from packages.reason.com. Dependabot's composer-repository registry can only send username and password, with no token option (options reference), so those jobs now get a 401 every time. Plugin CI and site image builds are unaffected; they already send Bearer.

Changes

  • includes/Wpup/ComposerRepository.php: isAuthorized() accepts Bearer <key> (unchanged) or Basic base64(<user>:<key>). The username is ignored. The password is compared to the key with hash_equals, as for Bearer. Base64 is decoded strictly, a value with no : is refused, and any other scheme is refused. The class docblock says so. The WWW-Authenticate: Bearer challenge is unchanged.
  • tests/composer-repository-test.php: the old basic scheme: 401 check is replaced by checks for Basic with the key as the password (200), an empty username (200), a lowercase scheme (200), a wrong password, the key as the username, no colon, extra text after the key, non-base64, the dist route, and an unknown scheme (all 401).
  • docs/runbook.md: the routes section mentions Basic, and the rotation step for the org secret now says it lives in both the Actions and the Dependabot secret stores. A new "Dependabot in plugin and theme repos" section gives the registries block to copy.

Alternatives considered

  • Leave Bearer-only and turn off Dependabot's composer updates in those repos. Rejected: it gives up automated update PRs for reason-dev/* and third-party Composer dependencies alike.
  • Require a fixed username. Rejected: it adds a second value to keep in step and no security, since the key is the whole secret.
  • Accept the key in the query string. Rejected for the reason the class already gives: Composer writes download URLs into composer.lock.

Testing

  • php tests/composer-repository-test.php and php tests/index-routing-test.php: all pass (exit 0).
  • With ComposerRepository.php reverted, the three "Basic should be accepted" checks fail. So the new tests do exercise the change.
  • php -l on the changed class: clean.
  • Not run: a deploy, or a real Dependabot job against it.

Review: self-review by @justinmaurerdotdev (repo admin)

Not in this PR

  • Deploying to the dev stage and then production (manual, as before). After the production deploy, a quick check: curl -s -o /dev/null -w '%{http_code}' -u "x:$KEY" https://packages.reason.com/packages.json should print 200.
  • The registries block in each repo's dependabot.yml: follow-up PRs after this is deployed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LzLdtrTXNxMS97RQ7NiMLh

Dependabot's composer-repository registry can only send a username and
password, so its Composer jobs could not reach reason-dev/* packages once
SIMPLE_UPDATE_KEY was enforced. Any username is accepted; the password
must equal the key. Bearer is unchanged; the query string is still refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LzLdtrTXNxMS97RQ7NiMLh
@justinmaurerdotdev justinmaurerdotdev self-assigned this Nov 5, 2026

@claude claude Bot 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.

I reviewed this PR and didn't find any bugs. It widens what counts as a valid credential on the key-protected Composer routes, so a human look is still worthwhile before it merges.

What was reviewed:

  • isAuthorized() in includes/Wpup/ComposerRepository.php: Bearer handling is unchanged. Basic uses strict base64 decoding, requires a colon, splits on the first colon only (so a password containing : still works), and compares the password with hash_equals. Any other scheme or a non-string header is rejected.
  • The Basic header should reach the code under the Bref php-84-fpm runtime, because Wpup_Headers reads HTTP_* keys from $_SERVER.
  • The new tests cover the accept and reject cases. The runbook adds the Dependabot secret-store rotation step.
Extended reasoning...

The change touches authentication on the Composer routes (/packages.json, /p2, /dist). It now accepts SIMPLE_UPDATE_KEY as an HTTP Basic password with any username, alongside the existing Bearer token. The rest of the PR adds tests and runbook documentation. The code is small, follows the existing hash_equals pattern and has good tests, and no bugs were found. It still changes which credentials the auth check accepts, so per the approval guidelines a human should confirm the change rather than having it auto-approved.

@justinmaurerdotdev
justinmaurerdotdev merged commit f0fc6e6 into master Nov 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant