{"record":{"id":"8d09751c21aa8709","repo":"unslothai/unsloth","slug":"provider-public-key-is-missing","errorCode":null,"errorMessage":"Provider public key is missing.","messagePattern":"Provider public key is missing\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"studio/frontend/src/features/chat/api/providers-api.ts","lineNumber":114,"sourceCode":"let cachedPublicKeyPem: string | null = null;\nlet cachedForgeKey: forge.pki.rsa.PublicKey | null = null;\n\nexport function clearProviderPublicKeyCache(): void {\n  cachedPublicKeyPem = null;\n  cachedForgeKey = null;\n}\n\nasync function importProviderPublicKey(\n  forceRefresh = false,\n): Promise<forge.pki.rsa.PublicKey> {\n  if (!forceRefresh && cachedForgeKey) {\n    return cachedForgeKey;\n  }\n  const response = await authFetch(\"/api/providers/public-key\");\n  const body = await parseJsonOrThrow<{ public_key: string }>(response);\n  const publicKeyPem = body.public_key?.trim();\n  if (!publicKeyPem) {\n    throw new Error(\"Provider public key is missing.\");\n  }\n  if (!forceRefresh && cachedPublicKeyPem === publicKeyPem && cachedForgeKey) {\n    return cachedForgeKey;\n  }\n  const forgeKey = forge.pki.publicKeyFromPem(publicKeyPem);\n  cachedPublicKeyPem = publicKeyPem;\n  cachedForgeKey = forgeKey;\n  return forgeKey;\n}\n\nexport async function encryptProviderApiKey(\n  plaintextApiKey: string,\n  forceRefresh = false,\n): Promise<string> {\n  const key = await importProviderPublicKey(forceRefresh);\n  const encrypted = key.encrypt(plaintextApiKey, \"RSA-OAEP\", {\n    md: forge.md.sha256.create(),\n    mgf1: { md: forge.md.sha256.create() },","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/frontend/src/features/chat/api/providers-api.ts#L96-L132","documentation":"Thrown by importProviderPublicKey when GET /api/providers/public-key succeeded (2xx) but the returned public_key field is missing, empty, or whitespace-only after trim. This blocks the RSA key import (forge.pki.publicKeyFromPem) used to encrypt provider API keys client-side; without it no provider key can be saved.","triggerScenarios":"Server returns {public_key: ''} or omits the field — misconfigured backend that failed to generate/load its RSA keypair at startup.","commonSituations":"Backend started without generating a keypair (missing write permission for the key file, first-run setup incomplete); key migration/rotation left the config empty; a custom deployment skipping an init step.","solutions":["Fix the backend so /api/providers/public-key returns a valid PEM — check server startup logs for keypair generation errors.","Verify the server has write access to persist its generated keypair so it is stable across restarts (a rotating key would also break decryption of stored keys).","Retry saving the provider once the endpoint returns a non-empty key."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const probe = await authFetch('/api/providers/public-key');\nconst key = (await probe.json()).public_key;\nif (!key || !key.trim()) throw new Error('Backend keypair not configured — fix server setup');","typeGuard":null,"tryCatchPattern":"try { await encryptProviderApiKey(apiKey); }\ncatch (e) {\n  if (e.message === 'Provider public key is missing.') showServerSetupHint();\n  else throw e;\n}","preventionTips":["Add a backend startup check that fails fast when the RSA keypair is missing.","Persist the generated keypair so restarts don't rotate it.","Probe the public-key endpoint during app init to surface misconfiguration early."],"tags":["providers","encryption","rsa","server-config"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}