Skip to content

feat(auth): add read-only API keys (API_KEYS_READONLY) - #69

Merged
howethomas merged 1 commit into
mainfrom
feat/readonly-api-keys
Aug 19, 2026
Merged

feat(auth): add read-only API keys (API_KEYS_READONLY)#69
howethomas merged 1 commit into
mainfrom
feat/readonly-api-keys

Conversation

@howethomas

Copy link
Copy Markdown
Contributor

Problem

API_KEYS was a flat comma-separated list and every token in it was equal: full read, write and delete over the REST API. MCP_TOOLS_PROFILE=readonly gates only MCP tool listing, not REST routes. So any valid token authorized POST /vcons, POST /vcons/batch, PUT|DELETE on tags, and DELETE /vcons/{uuid} — one request could destroy a corpus. There was no safe credential to hand an external consumer of a hosted dataset (e.g. the public IETF demo set).

Change

Add API_KEYS_READONLY. Those tokens authenticate but:

Surface Allowed Rejected
REST GET, HEAD, OPTIONS every POST/PUT/PATCH/DELETE -> 403 Forbidden
MCP read, schema, analytics, infra tools write tools (not listed; tools/call fails)

Design notes:

  • Separate env var, not inline tok:ro scoping. Colons are legal inside tokens, so parsing them would break existing API_KEYS values.
  • REST enforcement is method-based, in the single existing auth middleware, so every current and future mutating route is covered without a per-route allowlist.
  • The read-only MCP tool set reuses the existing category metadata (restrictToReadonly drops write from whatever MCP_TOOLS_PROFILE / MCP_ENABLED_CATEGORIES resolved) rather than inventing a second classification. MCP_DISABLED_* still applies on top.
  • API_KEYS keeps full access for backward compatibility; the server logs a startup warning when no read-only keys are configured.
  • A token listed in both variables is treated as read-only (deny wins).
  • An MCP session is pinned to the scope of the key that opened it — a request carrying a session ID created under a different scope gets 403, so a read-only token cannot ride a read/write session.
  • Plugin tools carry no category, so read-only sessions drop them rather than guess whether they mutate.

Tests

New tests/api/readonly-keys.test.ts (11 tests): read-only key gets 200 on GET /api/v1/vcons, 403 on POST /vcons and DELETE /vcons/{uuid} (handlers never invoked), full-access key still 201, unknown key 401; scope resolution incl. both-lists and legacy-API_KEYS cases; MCP create_vcon rejected via tools/call and absent from tools/list when read-only.

Full suite: 845 tests, 831 passed / 14 skipped. npm run build clean.

Docs

docs/guide/configuration.md (new "API Keys and Read-Only Access" section), docs/guide/installation.md, docs/var/02-installation-guide.md, docs/var/03-configuration-guide.md, docs/api/rest-api.md, .env.example.

Known gap

POST /database/analyze (query plans) is read-ish but blocked for read-only keys by the method rule — documented; the equivalent analyze_query MCP tool still works.

Follow-up (other repo)

VCONIC/vconic-datasets docs/INSTALL.md still tells readers the token grants full write ("Connecting an MCP client" and Limitations sections); update once this lands.

🤖 Generated with Claude Code

API_KEYS was a flat list where every token granted full read, write and
delete over the REST API, so there was no safe credential to hand an
external consumer of a hosted dataset.

Add API_KEYS_READONLY: those tokens authenticate but are limited to
GET/HEAD/OPTIONS on REST (403 otherwise) and to non-write MCP tools. The
read-only tool set reuses the existing MCP_TOOLS_PROFILE category
metadata (drop 'write') rather than a second classification.

- API_KEYS keeps full access for backward compatibility; the server warns
  at startup when no read-only keys are configured
- a token listed in both variables is read-only (deny wins)
- an MCP session is pinned to the scope of the key that opened it, so a
  read-only token cannot reuse a read/write session
- plugin tools have no category, so read-only sessions drop them

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@howethomas
howethomas merged commit 026fdcc into main Aug 19, 2026
6 checks passed
@howethomas
howethomas deleted the feat/readonly-api-keys branch August 19, 2026 14:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant