Skip to content

API keys are exposed in plain text in Models and Notification settings #98

@him0

Description

@him0

Summary

API keys and other sensitive credentials are currently displayed in plain text in the Web UI's provider settings screens. This applies to:

  1. Custom Models modal — The API key input field uses type="text", and the backend GET endpoints return the full API key in JSON responses.
  2. Notification Channels modal — Sensitive config fields like Discord webhook URLs are similarly displayed as plain text inputs.

This means API keys are visible on screen, in browser DevTools network logs, and in any client-side state.

Current Behavior

  • ModelsModal.tsx renders the API key in a type="text" input (line 391)
  • The GET /api/custom-models endpoint returns the full api_key value in the JSON response (toModelAPI in custom_models.go)
  • When editing an existing model, the full key is loaded from the API response into the form
  • Discord webhook URLs in NotificationsModal.tsx are rendered as regular text inputs

Suggested Approach

Here's one possible direction — happy to discuss alternatives:

Backend

  • Mask API keys in GET responses: Modify toModelAPI() to return a masked version of the key (e.g. sk-a••••xZ9f — first 4 + last 4 characters). This prevents the full key from ever reaching the client on read operations.
  • Preserve existing keys on update: The update handler already supports keeping the existing key when api_key is empty. This could be extended to also detect when the masked value is sent back unchanged.
  • Notification channels: Add a sensitive flag to ConfigField and apply the same masking pattern to sensitive config values (e.g. Discord webhook URLs). The update handler would need to merge sensitive fields so that masked values sent back from the client are replaced with the stored originals.

Frontend

  • Use type="password" for sensitive inputs: Switch the API key input to type="password". When editing, start with the field empty and use a placeholder like "Leave blank to keep current key".
  • Relax edit-mode validation: Currently handleSave requires api_key even when editing. This could be changed to only require it for new models, since the backend preserves the existing key when the field is empty.
  • Notification channels: Use the sensitive flag from ConfigField metadata to dynamically render sensitive fields as password inputs with the same empty-on-edit pattern.

Existing patterns that help

  • handleUpdateModel already preserves the existing key when api_key is empty (line 216-220 of custom_models.go)
  • handleTestModel already accepts model_id to look up the stored key server-side, so testing works without sending the key from the client
  • handleDuplicateModel copies the key server-side — the frontend never sends it

Affected Files

  • server/custom_models.go
  • server/notification_channels.go
  • ui/src/components/ModelsModal.tsx
  • ui/src/components/NotificationsModal.tsx
  • ui/src/services/api.ts

Thank you for building Shelley.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions