Skip to content

[P3] Prefix-matched demo detection silently withholds the API key from /v1/demographics and /v1/demo/../... #23

Description

@karlwaldman

Confirmed by execution on main (3feb505), PHP 8.5.8, 2026-09-13.

Client::request() decides whether to attach the API key with str_starts_with($path, '/v1/demo') (src/Client.php:241). Any path that merely begins with that string is treated as keyless demo traffic, so the Authorization header is dropped and the server answers 401 — and the SDK then tells the customer their key is invalid.

Repro

<?php
require 'vendor/autoload.php';
require 'tests/MockTransport.php';
use OilPriceAPI\Client;
use OilPriceAPI\Tests\MockTransport;

foreach (['/v1/demo/prices', '/v1/demographics', '/v1/demo/../prices/latest', '/v1/prices/latest'] as $p) {
    $t = new MockTransport();
    $t->queue(200, ['status' => 'success', 'data' => ['code' => 'X', 'price' => 1.0]]);
    (new Client('SECRET_KEY', 'https://api.oilpriceapi.com', 10.0, 0, $t))->raw()->get($p);
    printf("%-28s auth-header=%s\n", $p, var_export($t->requests[0]['headers']['Authorization'] ?? null, true));
}
/v1/demo/prices              auth-header=NULL
/v1/demographics             auth-header=NULL      <-- key silently withheld
/v1/demo/../prices/latest    auth-header=NULL      <-- key silently withheld
/v1/prices/latest            auth-header='Token SECRET_KEY'

Impact

A customer with a perfectly good key calls raw()->get() on a path that happens to start with /v1/demo, gets a 401, and the SDK raises AuthenticationException reading "Invalid API key. Create or manage an API key at https://www.oilpriceapi.com/auth/signup...". That is a support ticket whose premise is wrong. No data harm and no credential harm — the failure is in the safe direction — but the diagnosis handed to the user is false.

Suggested fix

Match the demo endpoint exactly rather than by prefix — $path === '/v1/demo/prices', or a segment-boundary check ($path === '/v1/demo' || str_starts_with($path, '/v1/demo/')) plus a rejection of .. segments in normalizeApiPath(), which would also make /v1/demo/../prices/latest an error instead of a confusing 401.

Found during the post-merge PHP expert review of #17/#18/#19.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Priority 3 - backlogbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions