Skip to content

API key not attached on all requests → keyless 401 loop (auto-inject works on develop, stuck behind release + 3 stragglers) #264

Description

@deucebucket

Summary

Skaldleita-side log analysis shows 21,170 keyless 401 requests from 49 IPs over 14 days = 13.8% of all API traffic. These are Library Manager installs that registered, received an API key, but send requests without it — so every call 401s and the client retries forever (one IP: ~110 req/day, 100% 401, for 15+ days straight, zero backoff).

Cross-referencing the spam IPs to Skaldleita's registration DB: every offender already has a valid approved key on file. The bootstrap worked; the key just isn't being attached to outbound requests.

Root cause

The auto-inject is already correctly implemented on develop:

  • Wire 1 — register: app.py:10858 POSTs /api/register-instance
  • Wire 2 — save: app.py:10880 secrets['bookdb_api_key'] = result['api_key']
  • Wire 3 — attach: app.py:3089, app.py:11747 build headers['X-API-Key'] = secrets.get('bookdb_api_key') or BOOKDB_PUBLIC_KEY

So the spam is from old builds that predate this (betas 0.9.0-beta.134.152 and main-branch builds sending a python-requests/* User-Agent, i.e. no LibraryManager/ UA at all).

Action items

1. Ship it — developmain → release ⬅ biggest lever

The working auto-inject is on develop. The spamming users are on main/old betas. Merging developmain and cutting a release is what actually gets the fix to them. (Same blocker noted previously for the wynter242 /search 401 loop.)

2. Patch 3 remaining keyless call sites (even develop still 401s these)

Each has a stale # Uses public endpoint - no API key required comment — true before Skaldleita required keys on all endpoints (2026-06-02), false now:

  • app.py:11909GET /book/{book_id} (book hover cards) → no headers
  • app.py:11983GET /author/{author_id} (author hover cards) → no headers
  • app.py:2299GET /search (ebook verification path) → no headers

Fix: attach auth the same way the working paths do:

secrets = load_secrets()
api_key = secrets.get('bookdb_api_key') or BOOKDB_PUBLIC_KEY
headers = get_signed_headers() or {}
headers['X-API-Key'] = api_key
# ... requests.get(url, headers=headers, ...)

3. (Optional) Register the librarymanager://setkey?key=... URL scheme

Skaldleita's key-delivery email now includes a one-tap deep link (skaldleita PR #154). If LM registers a handler for librarymanager://setkey?key=<key> that writes the (URL-decoded) key into secrets['bookdb_api_key'], users get one-tap key injection from the email as a fallback when startup auto-register doesn't fire.

4. (Recommended) Enforce the version floor

/health returns min_library_manager_version. Having LM check it and hard-nag/block below the floor would stop old builds from silently looping in the future.

Evidence

  • 13.8% of Skaldleita traffic is keyless-401 spam (14-day window).
  • Spam IPs map to real registered users (betas .134/.150/.151/.152) — all with keys on file.
  • 401s are not rate-limited server-side, so broken clients loop indefinitely (Skaldleita is adding 401 backpressure separately).

Filed from a Skaldleita session; LM code referenced from the develop checkout.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2-highMajor functionality broken, no workaroundbugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions