You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
No API key concept exists. We need opaque random keys with two tiers (admin / client), hashed at rest, and a safe first-run bootstrap.
Goal
A keys table, sk-cap-… generator, and a first-run flow that mints exactly one admin key.
Tasks
Migration 002_keys.sql:
CREATETABLEkeys (
id TEXTPRIMARY KEY,
hash TEXT UNIQUE NOT NULL,
tier TEXTNOT NULLCHECK(tier IN ('admin','client')),
label TEXT,
allowed_models TEXTNOT NULL DEFAULT '["*"]', -- JSON array
rate_limit_override INTEGER, -- NULL = use global; capped (see below)
debug_enabled INTEGERNOT NULL DEFAULT 0,
created_at INTEGERNOT NULL,
revoked_at INTEGER
);
CREATEINDEXidx_keys_hashON keys(hash);
generateKey() using crypto.randomBytes(32) → sk-cap- + base32 (RFC4648 no padding) = 7 + 52 = 59 char total
hashKey(plain) = sha256 hex
Bootstrap (per security S3, S4):
Condition: features.auth === true && admin-tier key count === 0 (NOT just total key count — preserves recovery semantics)
Generate one admin key
Write plaintext to ~/.local/share/copilot-api/admin.key.txt with mode 0600
Print to stdout: only when isatty(1) is true. In non-TTY (Docker logs / journald), print only the file path + sha256 prefix Admin key written to /path/admin.key.txt (sha256:abc1234…). Read & delete this file.
On next boot, if admin.key.txt exists, refuse to start with a clear "operator must read+delete the bootstrap file" message
Recovery CLI (per security S4): copilot-api admin recover — requires read access to data dir as proof of operator identity, mints a fresh admin key, audit-logs the action (see F2.D for audit log)
Rate limit override safety (per security S8): treat 0 / negative / NULL as "use default", NOT unlimited. Hard cap override at 10× the configured global default; reject values above cap with explicit error.
Part of #23. Depends on F2.A.
Background
No API key concept exists. We need opaque random keys with two tiers (admin / client), hashed at rest, and a safe first-run bootstrap.
Goal
A
keystable,sk-cap-…generator, and a first-run flow that mints exactly one admin key.Tasks
002_keys.sql:generateKey()usingcrypto.randomBytes(32)→sk-cap-+ base32 (RFC4648 no padding) = 7 + 52 = 59 char totalhashKey(plain)= sha256 hexfeatures.auth === true && admin-tier key count === 0(NOT just total key count — preserves recovery semantics)~/.local/share/copilot-api/admin.key.txtwith mode0600isatty(1)is true. In non-TTY (Docker logs / journald), print only the file path + sha256 prefixAdmin key written to /path/admin.key.txt (sha256:abc1234…). Read & delete this file.admin.key.txtexists, refuse to start with a clear "operator must read+delete the bootstrap file" messagecopilot-api admin recover— requires read access to data dir as proof of operator identity, mints a fresh admin key, audit-logs the action (see F2.D for audit log)0/ negative / NULL as "use default", NOT unlimited. Hard cap override at 10× the configured global default; reject values above cap with explicit error.createKey(tier, label, allowed_models, override?),revokeKey(id),listKeys(),findKeyByHash(hash),setDebugEnabled(id, bool)Acceptance criteria
admin recover)File pointers
src/lib/migrations/002_keys.sql,src/services/keys.ts,src/cli/admin-recover.ts,tests/keys.test.tssrc/start.ts,src/main.ts(citty subcommand)Dependencies
Depends on F2.A. Blocks F2.C, F2.D, F3.A, F4.A.