Problem
api/v1/validate_api_key.php only accepts the API key as ?api_key= (query string) or in the POST body. Query-string credentials leak through several channels that header-based credentials don't:
- Web-server access logs — nginx/Apache log the full request URI by default, so
GET /api/v1/tickets/read.php?api_key=<secret> lands in /var/log/....
Referer headers — any outbound link from a page loaded with ?api_key= in the URL leaks the key to the destination.
- Browser history / bookmarks — if the URL is ever pasted into a browser tab.
- Client-side error trackers — Sentry and similar capture request URLs in breadcrumbs. They redact
Authorization headers automatically, but not query parameters.
I hit this writing a mobile client (Flutter, talking to v1 API) — Sentry breadcrumbs were exfiltrating the API key on every error report. Same risk applies to any third-party integration that logs requests.
Proposal
Also accept the key in a request header, with no breaking change to the existing query-string/body form:
Authorization: Bearer <key> (RFC 6750)
X-API-Key: <key>
Preference order on the server: header -> ?api_key= -> POST body. Existing clients keep working unchanged.
Patch
The change is small — one PHP file plus an .htaccess rule for the mod_php Authorization passthrough (FPM/CGI setups already pass it through). Branch on my fork:
I'd happily open this as a PR, but the repo restricts PR creation to collaborators. Options:
- A maintainer pulls the branch and reviews / merges directly
- Grant me temporary contributor access so I can open the PR through the normal review flow
- I can paste the diff into this issue if that's easier — let me know what you prefer
Test plan I ran
curl -H 'Authorization: Bearer <key>' .../api/v1/clients/read.php → 200, identical response shape to legacy
curl -H 'X-API-Key: <key>' .../api/v1/clients/read.php → 200
curl '.../api/v1/clients/read.php?api_key=<key>' → 200 (legacy unchanged)
curl .../api/v1/clients/read.php (no key) → 401 as before
- Invalid key in either form → 401 with existing JSON error body
Not in scope
This is the smallest change that stops credentials leaking through transport. Per-device tokens, scopes, token revocation, and /auth/whoami are separate concerns and belong in their own follow-ups — happy to discuss those in a separate issue if there's interest.
Problem
api/v1/validate_api_key.phponly accepts the API key as?api_key=(query string) or in the POST body. Query-string credentials leak through several channels that header-based credentials don't:GET /api/v1/tickets/read.php?api_key=<secret>lands in/var/log/....Refererheaders — any outbound link from a page loaded with?api_key=in the URL leaks the key to the destination.Authorizationheaders automatically, but not query parameters.I hit this writing a mobile client (Flutter, talking to v1 API) — Sentry breadcrumbs were exfiltrating the API key on every error report. Same risk applies to any third-party integration that logs requests.
Proposal
Also accept the key in a request header, with no breaking change to the existing query-string/body form:
Authorization: Bearer <key>(RFC 6750)X-API-Key: <key>Preference order on the server: header ->
?api_key=-> POST body. Existing clients keep working unchanged.Patch
The change is small — one PHP file plus an
.htaccessrule for the mod_php Authorization passthrough (FPM/CGI setups already pass it through). Branch on my fork:api/v1/validate_api_key.php,.htaccess(+32 / -6)I'd happily open this as a PR, but the repo restricts PR creation to collaborators. Options:
Test plan I ran
curl -H 'Authorization: Bearer <key>' .../api/v1/clients/read.php→ 200, identical response shape to legacycurl -H 'X-API-Key: <key>' .../api/v1/clients/read.php→ 200curl '.../api/v1/clients/read.php?api_key=<key>'→ 200 (legacy unchanged)curl .../api/v1/clients/read.php(no key) → 401 as beforeNot in scope
This is the smallest change that stops credentials leaking through transport. Per-device tokens, scopes, token revocation, and
/auth/whoamiare separate concerns and belong in their own follow-ups — happy to discuss those in a separate issue if there's interest.