Provider API keys never leave AIService, and only MANAGE_AI_SETTINGS can change a provider

FixAIService
Shipped
September 17, 2026 at 3:33 AM UTC
Author
Kamo
Commit
cb7395f

GET /api/ai/providers and GET /api/ai/providers/{id} answered with each provider's DECRYPTED API key. The list is what every member's AI chat model picker loads (and kamo-js / the mobile app), so any member of an organization reorder) needed only a session, so any member could also point a provider's base URL at their own server and press Test, which sends the decrypted key there. The settings screen already demanded MANAGE_AI_SETTINGS; the server did not. - Both reads now answer hasApiKey and maskedApiKey (the last four characters; a short or undecryptable key shows none of itself). The unused maskApiKey, which masked the ciphertext, is replaced. - Create, update, delete, test and reorder answer 403 without MANAGE_AI_SETTINGS (God bypasses, as everywhere). - Update keeps the stored key unless a non-blank one is sent. The edit form was filled from the returned key, so a row seeded in plaintext (decrypts to "") had its key overwritten with an encrypted blank on any save. AiProviderKeyExposureTest drives the controller through MockMvc with the real SessionHelper: no key in either read, the key's state reported, blank keeps / typed replaces, and all five changes refused for a member without the right with nothing saved and no adapter called. Five of its six tests fail against the previous controller. The kamo-internal settings form stops expecting the key in the same change set.

All changes

Like what you see shipping?

All of it arrives in your workspace on its own. Start on the free plan and read this page again in a month.

Start Free ForeverView Pricing