Skip to content

security: non-constant-time compare for PACKRAT_API_KEY and admin basic-auth password #2163

@andrew-bierman

Description

@andrew-bierman

Discovered during review of #2083.

Files:

  • packages/api/src/utils/auth.ts:121apiKeyHeader === PACKRAT_API_KEY
  • packages/api/src/routes/admin/index.ts:24password === env.ADMIN_PASSWORD, username === env.ADMIN_USERNAME

Both comparisons use plain === against a secret. An attacker with enough samples can derive the secret byte-by-byte via response-time timing differences.

Fix: use a constant-time compare. Cloudflare Workers has WebCrypto:

function timingSafeEqual(a: string, b: string): boolean {
  const ab = new TextEncoder().encode(a);
  const bb = new TextEncoder().encode(b);
  if (ab.byteLength !== bb.byteLength) return false;
  let out = 0;
  for (let i = 0; i < ab.byteLength; i++) out |= ab[i] ^ bb[i];
  return out === 0;
}

Length-equalize before compare to avoid leaking length differences.

Related: #2083 (middleware was touched by the rewrite; admin/index.ts was reduced 906→580 LOC — HTMX refactor tracked elsewhere, this timing issue is separate and still present)

Test requirement: cannot directly unit-test timing, but lint/CI can assert no === comparison against env secrets.

Metadata

Metadata

Assignees

No one assigned

    Labels

    apibugSomething isn't workingweb

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions