AI settings API with key management #80

Member

Maestro task: 176b2a30-6e90-4339-b430-5b8967b2fd32 (Feature 11 - Settings Home and Admin AI Assistant).

Done-condition

GET /api/v1/settings/ai returns provider, base_url, model, temperature, system_prompt, api_key_last4 and key_verified_at; PATCH validates provider against the presets, temperature 0-1 in 0.1 steps, system_prompt <= 8000 chars; PUT /settings/ai/api-key stores the key with secretbox and records last4; POST /settings/ai/verify calls Provider.Verify and stores key_verified_at on success or returns the provider's typed error; GET /settings/ai/models returns the live model list; POST /settings/ai/reset-prompt restores the default persona from CTXD PROMPTS.MD section 4. A test scans every response body and asserts the full key never appears; a harness test with the Fake provider proves a PATCHed model and temperature are used by the next skill run. Go quality floor passes.

Security hardening (second commit)

  • PATCH /settings/ai refuses to change provider or base_url while a key is stored (400 "remove the key first"), so a stored key can never be re-pointed at an attacker's server.
  • A key is only accepted for a custom base_url that passes secureurl.Check (https, or plain http only to localhost) - no key over plain http to a LAN host.
  • Tests cover the endpoint lock, the redirect-with-key case, the secure-URL rule, and the response-body key scan.

Evidence

  • gofmt -l . empty; go vet ./...; CGO_ENABLED=0 go build ./...; go test -race -count=1 ./... all pass; no new dependency; no sqlc change.
  • docs/ARCHITECTURE.md updated.

Notes recorded, not acted on: no admin role exists yet (single-role by design); blocking local/cloud-metadata base_urls would break the normal Ollama case, and the key-leak it guarded against is already closed by the endpoint lock.

Maestro task: 176b2a30-6e90-4339-b430-5b8967b2fd32 (Feature 11 - Settings Home and Admin AI Assistant). ## Done-condition GET /api/v1/settings/ai returns provider, base_url, model, temperature, system_prompt, api_key_last4 and key_verified_at; PATCH validates provider against the presets, temperature 0-1 in 0.1 steps, system_prompt <= 8000 chars; PUT /settings/ai/api-key stores the key with secretbox and records last4; POST /settings/ai/verify calls Provider.Verify and stores key_verified_at on success or returns the provider's typed error; GET /settings/ai/models returns the live model list; POST /settings/ai/reset-prompt restores the default persona from CTXD PROMPTS.MD section 4. A test scans every response body and asserts the full key never appears; a harness test with the Fake provider proves a PATCHed model and temperature are used by the next skill run. Go quality floor passes. ## Security hardening (second commit) - PATCH /settings/ai refuses to change provider or base_url while a key is stored (400 "remove the key first"), so a stored key can never be re-pointed at an attacker's server. - A key is only accepted for a custom base_url that passes secureurl.Check (https, or plain http only to localhost) - no key over plain http to a LAN host. - Tests cover the endpoint lock, the redirect-with-key case, the secure-URL rule, and the response-body key scan. ## Evidence - gofmt -l . empty; go vet ./...; CGO_ENABLED=0 go build ./...; go test -race -count=1 ./... all pass; no new dependency; no sqlc change. - docs/ARCHITECTURE.md updated. Notes recorded, not acted on: no admin role exists yet (single-role by design); blocking local/cloud-metadata base_urls would break the normal Ollama case, and the key-leak it guarded against is already closed by the endpoint lock.
dev_agent added 2 commits 2026-10-06 08:55:33 -04:00
GET/PATCH /api/v1/settings/ai return and change provider, base_url,
model, temperature and system_prompt, with read-only api_key_last4 and
key_verified_at. PATCH requires a preset provider, temperature 0-1 in
0.1 steps and a system_prompt of at most 8000 characters.
PUT /settings/ai/api-key seals the key with secretbox and records its
last four characters; POST /settings/ai/verify calls Provider.Verify and
records key_verified_at or returns the provider's typed error;
GET /settings/ai/models lists the live models; POST
/settings/ai/reset-prompt restores household.DefaultPersonaPrompt.

The new internal/ai/aisettings service also builds the harness.Config
for each skill run from the saved settings. presets.ForAccount builds a
provider's ListModels and Verify without a model, so a key can be
checked and a model picked before one is chosen.

Tests scan every response body from these routes for the full key, and
a harness run with the Fake provider uses the PATCHed model and
temperature.

Maestro task: 176b2a30-6e90-4339-b430-5b8967b2fd32

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the stored AI key on the endpoint it was stored for
CI / ci (push) Successful in 1m59s
CI / ci (pull_request) Successful in 1m59s
94632e0950
PATCH /settings/ai refused nothing when the base URL or provider changed
with a key stored, so a session could point base_url at another server
and have POST /settings/ai/verify send it the key. Both changes are now
refused while a key is stored: remove the key, change the endpoint, then
store the new key. A preset without a fixed base URL takes a key only
when its base URL passes secureurl.Check, so the key never crosses the
network in plain text.

Maestro task: 176b2a30-6e90-4339-b430-5b8967b2fd32

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
overseer added 2 commits 2026-10-06 08:55:34 -04:00
GET/PATCH /api/v1/settings/ai return and change provider, base_url,
model, temperature and system_prompt, with read-only api_key_last4 and
key_verified_at. PATCH requires a preset provider, temperature 0-1 in
0.1 steps and a system_prompt of at most 8000 characters.
PUT /settings/ai/api-key seals the key with secretbox and records its
last four characters; POST /settings/ai/verify calls Provider.Verify and
records key_verified_at or returns the provider's typed error;
GET /settings/ai/models lists the live models; POST
/settings/ai/reset-prompt restores household.DefaultPersonaPrompt.

The new internal/ai/aisettings service also builds the harness.Config
for each skill run from the saved settings. presets.ForAccount builds a
provider's ListModels and Verify without a model, so a key can be
checked and a model picked before one is chosen.

Tests scan every response body from these routes for the full key, and
a harness run with the Fake provider uses the PATCHed model and
temperature.

Maestro task: 176b2a30-6e90-4339-b430-5b8967b2fd32

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the stored AI key on the endpoint it was stored for
CI / ci (push) Successful in 1m59s
CI / ci (pull_request) Successful in 1m59s
94632e0950
PATCH /settings/ai refused nothing when the base URL or provider changed
with a key stored, so a session could point base_url at another server
and have POST /settings/ai/verify send it the key. Both changes are now
refused while a key is stored: remove the key, change the endpoint, then
store the new key. A preset without a fixed base URL takes a key only
when its base URL passes secureurl.Check, so the key never crosses the
network in plain text.

Maestro task: 176b2a30-6e90-4339-b430-5b8967b2fd32

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
review_agent requested changes 2026-10-06 08:58:20 -04:00
Dismissed
review_agent left a comment
Member

Independent Codex gpt-6-sol review: CHANGES_REQUIRED for this exact commit.

Independent Codex gpt-6-sol review: CHANGES_REQUIRED for this exact commit.
overseer added 1 commit 2026-10-06 09:06:22 -04:00
Redact the AI key from model lists and fit the longest prompt
CI / ci (push) Successful in 2m6s
CI / ci (pull_request) Successful in 2m7s
799e7b62d0
GET /settings/ai/models now removes the stored API key from every model
ID and display name the provider returns, so a provider that echoes the
key in a successful list cannot disclose it.

PATCH /settings/ai gets its own body limit, sized for 8,000 characters at
12 bytes of JSON each (a \u-escaped surrogate pair) plus the other
fields. A valid prompt of three- or four-byte characters was refused by
the shared 16 KiB limit; the 8,000-character rule stays the only limit
on the prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
review_agent left a comment
Member

Independent Codex gpt-6-sol review: APPROVED for this exact commit.

Independent Codex gpt-6-sol review: APPROVED for this exact commit.
approver_agent merged commit 8855091b30 into feature/b28d9979-0f7a-4e81-bdff-2f16f3db2053-settings-admin-ai 2026-10-06 09:16:10 -04:00
Sign in to join this conversation.
No Reviewers
No labels
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Maniacs_Mansion/MealQuest#80