Long-lived account billing keys are stored as raw text in application state and preserved by default
Source references: 4The implementation puts submitted `sk-...` values directly into a canister map or single `Text` slot. It describes preservation as the default upgrade policy for almost all apps, while also stating that each key is long-lived, unscoped, and able to spend the account balance.
This increases both the exposure surface and retention period of the credential. If a future endpoint, upgrade, state-handling flaw, or administrative access exposes the value, the affected asset is the OpenAI account and its budget, not merely one session.
The source does store the raw key as `Text` in canister state and calls preservation across upgrades the default. This extends the retention of a powerful, long-lived billing credential; compromise of canister state, upgrades, or dependencies could affect the whole OpenAI account. The skill also forbids getters, logging, and frontend readback, so the evidence does not show ordinary users can directly retrieve keys. Users can ask about at-rest protection, rotation, and deletion policies.
- Long-lived, no expiry. Spends the entire OpenAI account balance on every call.- No scoped permissions — there is no "tweet.read"-style narrowing. Every key has full account access.- OpenAI rate-limits per-key per-minute; treat the key like a billing credential, not a session token.- **Never returned by any `query` or `shared` function.** Never logged. Never sent to the frontend. Never put in a stable variable that another endpoint with a weaker gate could read.Show 3 other places
// Per-user OpenAI keys. Never iterated except by the calling principal. let openAIKeys : Map.Map<Principal, Text>; include MixinOpenAIChat(openAIKeys);}; public shared ({ caller }) func setMyOpenAIApiKey(key : Text) : async () { if (caller.isAnonymous()) { Runtime.trap("Sign in to use this feature"); }; openAIKeys.add(caller, key); };- **Anonymous callers must not store keys.** `caller.isAnonymous()` short-circuits before any `openAIKeys.add` — otherwise everyone reading the canister via `2vxsx-fae` shares one key slot.- **`stable var` / migration.** The `Map<Principal, Text>` lives in stable memory like any other actor field; on upgrade, decide whether to preserve, rotate, or drop the keys. The default (preserve) is correct for almost all apps. If you ever rotate, drop the whole map — never partially.