Skip to content

Featherframe Cloud keeps the owner's AI key at the front door, never in the server's data (W-937) - #377

Merged
wr merged 1 commit into
mainfrom
wells/w-937-ai-key-at-front-door
Oct 20, 2026
Merged

wr merged 1 commit into
mainfrom
wells/w-937-ai-key-at-front-door

Conversation

@wr

@wr wr commented Oct 20, 2026

Copy link
Copy Markdown
Owner

What changes

On Featherframe Cloud, the owner's AI keys (the image key and the text key) are no longer in the household's database. That database is copied to R2 after every update and onto every Container that starts.

  • The front door keeps the key, sealed with AES-GCM under a new Worker secret (AI_KEY_SECRET) and bound to the household and the key's slot. The database keeps a stand-in (held:<mask>:<fingerprint>) so the page still shows sk-pr…wxyz.
  • The server's AI requests go to <vendor>.vendor.internal. HouseholdServer intercepts that name on its Container (ctx.container.interceptOutboundHttp) and hands it to VendorGate, a Worker entrypoint with the household in its props. The gate adds the key, but only for the paths Featherframe calls, and for a paid call only while the spend gate holds that purchase open. Its own refusals carry X-FF-Gate.
  • Why a Worker and not the front door: a Durable Object is billed while it waits on a fetch, and an image takes minutes. Each AI call adds one Worker request and one front-door request.
  • Saving a key on Cloud: the key passes through the server once, in the settings POST, and goes straight to the front door. If the front door does not take it, nothing is saved and the page says so.
  • Existing keys move at a start. A key moves only once the vendor answers it through the gate the same way it answers directly. If not, the key stays put and keeps working, and the next start tries again. The database is then VACUUMed, because a SQLite backup copies free pages. The move counts as the same key, so it clears no cooldown and buys no collage again.
  • Cloud no longer offers the Self-hosted image provider or the Local text provider, since a Container can't reach the owner's machine.
  • Self-hosted is unchanged.

Test plan

  • make test: server suite, including the new tests/test_keys.py (routing for every model class, the save path, the move, compaction, the Cloud page)
  • hosted: npx tsc, npx vitest run, including test/gate.test.ts (allowed paths, header stripping, seal and unseal, the purchase check, the gate end to end)
  • After deploy: Wells's household moves its key, the DB in R2 holds only the stand-in, and a generation works through the gate

Refs W-937

…in the server's data (W-937)

The key sat in plain text in the household's database, which is copied to R2
after every update and onto every Container that starts. Now the front door
keeps it, sealed under a Worker secret, and the database keeps only a
stand-in with the key's mask. The server's AI requests go to
<vendor>.vendor.internal, which Cloudflare's outbound interception hands to
a gate in the Worker: it adds the key, only for the calls Featherframe makes
and, for a paid call, only while the spend gate holds that purchase open.

A key from before moves at a start, and only once the vendor answers it
through the gate as it does directly, so a gate that does not work leaves the
key where it is, working. The database is then compacted, since SQLite keeps
a replaced value in its free pages. Cloud no longer offers the Self-hosted
image provider or the Local text provider, which a Container cannot reach.
Self-hosted is unchanged.

Refs: W-937
@linear-code

linear-code Bot commented Oct 20, 2026

Copy link
Copy Markdown

W-937

@wr
wr merged commit 9278a94 into main Oct 20, 2026
6 checks passed
@wr
wr deleted the wells/w-937-ai-key-at-front-door branch October 20, 2026 03:12
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