Skip to content

AUDIT-17 — Low — API key comparison is not constant-time and not normalised #74

Closed

Description

@getdownandcode

Summary

Severity: Low
Area: Security / auth
Affected files: agentkit/api/auth.py:22-30, agentkit/config.py:127-130
Suggested branch: fix/api-key-constant-time-compare
Suggested PR title: fix(auth): compare API keys in constant time

This is finding AUDIT-17 of 18 from a consolidated audit of agentkit/. It is deliberately self-contained so it can be fixed on its own branch with its own PR.

Related findings: Independent of the other findings.


AUDIT-17 — Low — API key comparison is not constant-time and not normalised

File — agentkit/api/auth.py:22-30

async def verify_api_key(
    api_key: str | None = Security(api_key_header),
    settings: Settings = Depends(get_current_settings),
) -> str:
    """Validate client API key from X-API-Key header against configured authorized keys."""
    if not api_key or api_key not in settings.api_keys_list:
        logger.warning("Unauthorized API request with missing or invalid key.")
        raise AuthenticationError("Invalid or missing API key.")
    return api_key

Why this is a bug

in on a list is a short-circuiting, timing-variable comparison — a classic timing oracle for recovering a valid key byte-by-byte. Additionally, configured keys are stripped (Settings.api_keys_list, config.py:127-130) but the presented key is not, so " key " fails while "key" succeeds (an inconsistency that also weakens logging hygiene, since the raw key is used as the rate-limit key).

Pass cases

  • Key comparison uses a constant-time comparison (hmac.compare_digest) over all configured keys.
  • The presented key is normalised (stripped) the same way as configured keys, and the normalised value is what is used for rate limiting and logging.
  • Existing auth tests (missing key, wrong key, valid key) still pass; a test asserts " key " and "key" behave identically.


Definition of done (per AGENTS.md)

  • New/updated unit and integration tests covering the pass cases above, all passing (pytest).
  • ruff check ., ruff format --check . and mypy agentkit clean.
  • Docstrings updated where behaviour changed; docs/PROGRESS.md updated (status, PR link, commit count, notes).
  • A new ADR in docs/DECISIONS.md if an architectural decision was made.
  • Branch pushed and PR opened (never commit directly to main).

Baseline at the time of writing: pytest is green on main (all tests pass; tests/real/* skipped unless AGENTKIT_REAL_INFRA=1).

Activity

  1. added
    bugSomething isn't working
    securitySecurity vulnerability or hardening item
    on Oct 20, 2026
  2. getdownandcode commented on Oct 20, 2026

    @getdownandcode
    OwnerAuthor

    Audit index (18 separate issues, fix one by one)

    ID Issue Severity
    AUDIT-01 #58 Critical
    AUDIT-02 #59 Critical
    AUDIT-03 #60 High
    AUDIT-04 #61 High
    AUDIT-05 #62 High
    AUDIT-06 #63 High/Medium
    AUDIT-07 #64 Medium-High
    AUDIT-08 #65 Medium-High
    AUDIT-09 #66 Medium-High
    AUDIT-10 #67 Medium-High
    AUDIT-11 #68 Medium
    AUDIT-12 #69 Medium
    AUDIT-13 #70 Medium
    AUDIT-14 #71 Medium
    AUDIT-15 #72 Medium
    AUDIT-16 #73 Medium
    AUDIT-17 #74 Low
    AUDIT-18 #75 Low/Deployment

    Each issue is self-contained: current code, reproduction, pass cases, suggested branch and PR title. Reference the relevant one with Fixes #NN in your PR.

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

    bugSomething isn't workingsecuritySecurity vulnerability or hardening item

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions