Skip to content

fix(ui): rotate model credentials in a dedicated modal so a normal save can't overwrite secrets - #28089

Merged
ryan-crabbe-berri merged 11 commits into
litellm_internal_stagingfrom
litellm_model_auth_fields
Jul 1, 2026
Merged

fix(ui): rotate model credentials in a dedicated modal so a normal save can't overwrite secrets#28089
ryan-crabbe-berri merged 11 commits into
litellm_internal_stagingfrom
litellm_model_auth_fields

Conversation

@ryan-crabbe-berri

@ryan-crabbe-berri ryan-crabbe-berri commented May 16, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Resolves LIT-3169

Summary

Adds a dedicated "Update API Key" modal so a model's key can be rotated without hand-editing raw litellm_params, and closes a silent credential-corruption bug in the model edit form

The bug this fixes

/v1/model/info redacts secrets by masking them (e.g. azur****BBCC), not by removing them. The edit form seeded the read-only LiteLLM Params textarea with the entire litellm_params blob, masked secrets included, and re-sent the whole blob on every save. Saving any unrelated field (TPM, tags, cost) therefore re-sent the masked placeholder, which the backend encrypted straight over the real secret. The masked-but-kept fields are azure_ad_token, aws_session_token, watsonx token/zen_api_key and the OCI key fields; api_key, client_secret, vertex_credentials and the AWS access/secret keys were safe only because the backend removes those from the snapshot entirely. An earlier revision of this PR added an inline auth section that sat on top of that broken blob path, which made the footgun easier to trigger

What changed

API-key rotation lives in UpdateModelCredentialsModal, opened from an "Update API Key" button on the model. It is a single field that PATCHes only { api_key }; the backend already merges partial litellm_params, so everything else on the deployment is untouched. The modal and its trigger button are antd, so the feature introduces no new tremor

The general edit form no longer touches secrets at all: masked values are stripped from both the textarea seed and the outbound payload, so a normal save can never carry a redacted secret. Two stray console.logs that echoed credentials (the provider upload handler and the model-update response) are removed, and a react-hooks/use-memo error that was failing the frontend-lint CI job is fixed

Screenshots / Proof of Fix

Driven through the Admin UI at http://localhost:4000/ui/models-and-endpoints, capturing the outbound PATCH from the browser and reading the stored secret back from the DB by decrypting the row (the API only ever shows it masked)

Screenshot 2026-06-30 at 4 26 17 PM Screenshot 2026-06-30 at 4 26 08 PM Screenshot 2026-06-30 at 4 26 04 PM

Before the fix, a plain save destroyed a masked-but-kept secret:

# azure model with azure_ad_token set to a known value
azure_ad_token (DB, decrypted) = azureADtoken_ORIGINAL...
# open the model, Edit Settings, Save (touch nothing)
# the browser PATCH carried "azure_ad_token": "azur****...BBCC"
azure_ad_token (DB, decrypted) = azur****************************BBCC   <- real secret gone

After the fix, the same plain save leaves it alone, and the modal rotates only the key:

# Edit Settings, Save (touch nothing)
# PATCH litellm_params no longer contains the masked secret at all  -> secret preserved
# Update API Key -> enter a new key -> submit
# PATCH body: {"litellm_params":{"api_key":"sk-rotated..."},"model_info":{"id":...}}
api_key (DB, decrypted)        = sk-rotated...     # rotated
azure_ad_token (DB, decrypted) = azureADtoken_ORIGINAL...   # untouched by the api-key-only PATCH

To re-capture screenshots: go to http://localhost:4000/ui/models-and-endpoints, open a DB model, click "Update API Key", enter a new key, submit; then Edit Settings and Save without touching anything and confirm the key still works. Browsers cache old JS chunks across rebuilds, so hard-refresh if the UI looks stale

Test plan

  • model_info_view.test.tsx: regression test asserting a masked secret in litellm_params is never present in the save PATCH (verified it fails when the strip guard is disabled)
  • update_model_credentials_modal.test.tsx: the modal sends a minimal { api_key } PATCH and is a no-op when the field is blank
  • provider_specific_fields.test.tsx plus the Add Model and credential-modal suites still pass (the shared component is back to its pre-PR render path)
  • End to end against a live proxy: corruption reproduced before the fix and gone after, and api-key rotation confirmed, captured from the browser PATCH and the decrypted DB row

Type

🐛 Bug Fix

🆕 New Feature

Changes

UI only. New UpdateModelCredentialsModal (single api-key field, antd); model_info_view wires the antd trigger button, strips masked secrets on seed and on submit, and drops the inline auth section; provider_specific_fields loses the unused excludeKeys/disableRequired/useProviderAuthFieldKeys additions; networking drops a credential-echoing log

Provider API keys / auth could previously only be changed by hand-editing
the raw litellm_params JSON, so there was no first-class way to rotate a
model's key. Adds an Authentication section that renders the correct
provider-specific fields (reusing ProviderSpecificFields) keyed off the
model's custom_llm_provider; fields are blank ("leave blank to keep
current") so untouched secrets are preserved and only entered values are
PATCHed and encrypted at rest.

Resolves LIT-3169
@codecov

codecov Bot commented May 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a silent credential-corruption bug where the model edit form re-sent masked secret placeholders (e.g. azur****BBCC) on every save, causing the backend to encrypt the placeholder over the real secret. A dedicated UpdateModelCredentialsModal now handles API-key rotation as a minimal PATCH containing only the new key, while the general edit form strips any masked value before sending.

  • Corruption fix: isMaskedSecret (regex \\*{2,}) and stripMaskedSecrets are applied at both the textarea seed and the outbound PATCH payload; a regression test confirms masked values never reach the wire.
  • New modal: UpdateModelCredentialsModal sends exactly { litellm_params: { api_key }, model_info: { id } }, leaving all other deployment config untouched; antd Form validation blocks empty submission.
  • Cleanup: removes two console.log calls that echoed freshly-entered provider secrets from the modelPatchUpdateCall path and the vertex credential upload handler.

Confidence Score: 5/5

Safe to merge — the change is UI-only, tightly scoped to the model credential edit path, and introduces no new backend calls or auth boundary changes.

The corruption fix is correctly layered: masked secrets are stripped at both the form-seed stage and the final PATCH assembly, so neither a user typing nor a code-path regression can re-introduce the bug. The query-key invalidation (["models", "list"] prefix) correctly matches the useModelsInfo query. destroyOnHidden is valid in the project's antd 5.29.3. The regression test actively catches the masked-secret-on-wire scenario. No new network handlers or auth changes are introduced.

No files require special attention.

Important Files Changed

Filename Overview
ui/litellm-dashboard/src/components/model_info_view.tsx Core fix: adds isMaskedSecret/stripMaskedSecrets guards that strip masked credential placeholders from both the textarea seed and the PATCH payload, plus toolbar migration from Tremor to antd and wiring of the new credentials modal.
ui/litellm-dashboard/src/components/update_model_credentials_modal.tsx New modal that sends a minimal { litellm_params: { api_key }, model_info: { id } } PATCH for key rotation; uses antd Form with required validation, destroyOnHidden (valid in antd 5.29.3), and proper error handling.
ui/litellm-dashboard/src/components/update_model_credentials_modal.test.tsx New test suite covering the minimal-PATCH contract and the no-op-on-blank guard for the credentials modal; correctly mocks networking and notification dependencies.
ui/litellm-dashboard/src/components/model_info_view.test.tsx Adds a regression test asserting that a masked secret in litellm_params never appears in the save PATCH; the test is well-targeted and would catch re-introduction of the corruption bug.
ui/litellm-dashboard/src/components/networking.tsx Removes two console.log calls that echoed the full PATCH payload (including freshly-entered provider secrets) before and after the model update request.
ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx Removes debug console.log statements (vertex credential upload handler and upload onChange) that were added in an earlier revision; the component interface is simplified back to its pre-PR state.

Reviews (4): Last reviewed commit: "refactor(ui): convert the model detail t..." | Re-trigger Greptile

Comment thread ui/litellm-dashboard/src/components/model_info_view.tsx Outdated
Comment thread ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx Outdated
Comment thread ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx Outdated
@veria-ai

veria-ai Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

PR overview

Provider auth editing in model edit view

This PR adds provider-specific authentication fields to the model edit form, omits blank secret inputs from update payloads, and avoids copying newly entered auth fields into the local read-only params view after save. I checked the form merge order, credential-name branch, provider field key resolution, and PATCH helper for secret exposure or authorization issues.

Security review

  • No new security issues were flagged in the latest review.
  • 1 previously flagged issue(s) appear fixed in the latest changes.
  • No review issues remain open on this pull request.

Risk: 2/10

Comment thread ui/litellm-dashboard/src/components/model_info_view.tsx Outdated
Drop the onFieldsResolved/authFieldKeys round trip: the parent now resolves
provider auth field keys itself via the new useProviderAuthFieldKeys hook
(same metadata ProviderSpecificFields renders), removing the report-up effect
and its stable-reference footgun. ProviderSpecificFields keeps only
excludeKeys (real need: suppress duplicate visible inputs).

Fix the stale Authentication branch: derive it from the live
litellm_credential_name form value (Form.useWatch) instead of the server
snapshot, so clearing/adding a credential mid-edit shows the right UI. Also
skip inline auth updates entirely when a named credential is selected, so we
never submit a credential name and raw inline auth together.
@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor Author

@greptile re review

Comment thread ui/litellm-dashboard/src/components/model_info_view.tsx Outdated
…sole

The auth values a user types are still sent in the PATCH request, but:
- strip them from the locally-stored litellm_params after save so the
  read-only LiteLLM Params JSON doesn't render the plaintext key
- remove the debug console.log in modelPatchUpdateCall that dumped the
  full update payload (incl. api_key / vertex_credentials) to the browser
  console on every model update

Backend stores these encrypted and returns them masked on refetch.
Auth fields render blank ('leave blank to keep'), but required metadata
(e.g. OpenAI api_key) added a required validation rule that blocked
onFinish entirely — making it impossible to save any unrelated edit
without re-entering the secret. Add a disableRequired prop to
ProviderSpecificFields and set it in the model edit Authentication section.
@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor Author

@greptileai re review

…itellm_model_auth_fields

# Conflicts:
#	ui/litellm-dashboard/src/components/add_model/provider_specific_fields.test.tsx
#	ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx
@codspeed-hq

codspeed-hq Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 30 untouched benchmarks


Comparing litellm_model_auth_fields (f9cfb9b) with litellm_internal_staging (d4c33b2)

Open in CodSpeed

…ve can't overwrite secrets

The model edit form seeded the read-only LiteLLM Params textarea with the whole
litellm_params blob and re-sent all of it on every save. Because /model/info
redacts secrets by masking them ("azur****BBCC") rather than removing them, any
save re-encrypted the asterisk mask over the real value and silently destroyed
credentials such as azure_ad_token, aws_session_token, watsonx token/zen_api_key
and the OCI key fields. api_key, client_secret, vertex_credentials and the AWS
access/secret keys were safe only because the backend strips those entirely

Credential rotation now lives in a dedicated UpdateModelCredentialsModal that
PATCHes only the fields the user types, decoupled from the params blob; the
backend already merges partial litellm_params, so the rest of the deployment is
left untouched. The general edit form drops masked values from both the textarea
seed and the outbound payload, so a normal save can never carry a redacted secret

Also removes the now-unused inline auth section and its excludeKeys and
useProviderAuthFieldKeys plumbing, strips secret-leaking console.logs from the
provider upload handler and the model-update response, and fixes a
react-hooks/use-memo error that was failing the frontend-lint CI job
@ryan-crabbe-berri ryan-crabbe-berri changed the title feat(ui): add provider auth editing to the model edit view fix(ui): rotate model credentials in a dedicated modal so a normal save can't overwrite secrets Jun 30, 2026
Removing the credential-echoing console.log (and its info: any param) from the
provider upload handler dropped the tracked count by one; update the committed
baseline so the Check lint budgets CI step is not stale
Narrows UpdateModelCredentialsModal to a single API Key field. On submit it
PATCHes only { api_key }, so the backend merge leaves every other deployment
param untouched; a model authed via azure_ad_token, AWS keys, or a Vertex JSON
won't have anything to rotate here yet, which is the intended scope for now.

Drops the multi-field provider rendering this added earlier, which also removes
the now-unused disableRequired prop from ProviderSpecificFields and reverts that
shared component to its prior shape. The "Update API Key" trigger button is now
an antd Button rather than a TremorButton, so the feature introduces no tremor.
… antd

Switches Test Connection, Re-use Credentials and Delete Model to antd Button so
the toolbar matches the Update API Key button and no longer mixes libraries;
Delete Model uses antd's danger styling instead of hand-rolled red classes
@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor Author

@greptileai review

Reworked credential editing into a dedicated api-key rotation modal that PATCHes only the new key, stopped the model edit form from re-sending masked secrets (the corruption fix), and converted the model detail toolbar to antd

@ryan-crabbe-berri
ryan-crabbe-berri merged commit 833406a into litellm_internal_staging Jul 1, 2026
122 checks passed
@ryan-crabbe-berri
ryan-crabbe-berri deleted the litellm_model_auth_fields branch July 1, 2026 00:20
duanhongyi pushed a commit to duanhongyi/litellm that referenced this pull request Jul 2, 2026
…ve can't overwrite secrets (BerriAI#28089)

* feat(ui): add provider auth editing to the model edit view

Provider API keys / auth could previously only be changed by hand-editing
the raw litellm_params JSON, so there was no first-class way to rotate a
model's key. Adds an Authentication section that renders the correct
provider-specific fields (reusing ProviderSpecificFields) keyed off the
model's custom_llm_provider; fields are blank ("leave blank to keep
current") so untouched secrets are preserved and only entered values are
PATCHed and encrypted at rest.

Resolves LIT-3169

* refactor(ui): simplify model auth editing; fix stale credential branch

Drop the onFieldsResolved/authFieldKeys round trip: the parent now resolves
provider auth field keys itself via the new useProviderAuthFieldKeys hook
(same metadata ProviderSpecificFields renders), removing the report-up effect
and its stable-reference footgun. ProviderSpecificFields keeps only
excludeKeys (real need: suppress duplicate visible inputs).

Fix the stale Authentication branch: derive it from the live
litellm_credential_name form value (Form.useWatch) instead of the server
snapshot, so clearing/adding a credential mid-edit shows the right UI. Also
skip inline auth updates entirely when a named credential is selected, so we
never submit a credential name and raw inline auth together.

* fix(ui): don't leak freshly-entered model auth secrets to display/console

The auth values a user types are still sent in the PATCH request, but:
- strip them from the locally-stored litellm_params after save so the
  read-only LiteLLM Params JSON doesn't render the plaintext key
- remove the debug console.log in modelPatchUpdateCall that dumped the
  full update payload (incl. api_key / vertex_credentials) to the browser
  console on every model update

Backend stores these encrypted and returns them masked on refetch.

* fix(ui): don't require blank auth fields in model edit context

Auth fields render blank ('leave blank to keep'), but required metadata
(e.g. OpenAI api_key) added a required validation rule that blocked
onFinish entirely — making it impossible to save any unrelated edit
without re-entering the secret. Add a disableRequired prop to
ProviderSpecificFields and set it in the model edit Authentication section.

* fix(ui): rotate model credentials in a dedicated modal so a normal save can't overwrite secrets

The model edit form seeded the read-only LiteLLM Params textarea with the whole
litellm_params blob and re-sent all of it on every save. Because /model/info
redacts secrets by masking them ("azur****BBCC") rather than removing them, any
save re-encrypted the asterisk mask over the real value and silently destroyed
credentials such as azure_ad_token, aws_session_token, watsonx token/zen_api_key
and the OCI key fields. api_key, client_secret, vertex_credentials and the AWS
access/secret keys were safe only because the backend strips those entirely

Credential rotation now lives in a dedicated UpdateModelCredentialsModal that
PATCHes only the fields the user types, decoupled from the params blob; the
backend already merges partial litellm_params, so the rest of the deployment is
left untouched. The general edit form drops masked values from both the textarea
seed and the outbound payload, so a normal save can never carry a redacted secret

Also removes the now-unused inline auth section and its excludeKeys and
useProviderAuthFieldKeys plumbing, strips secret-leaking console.logs from the
provider upload handler and the model-update response, and fixes a
react-hooks/use-memo error that was failing the frontend-lint CI job

* chore(ui): ratchet no-explicit-any lint metric to 2013

Removing the credential-echoing console.log (and its info: any param) from the
provider upload handler dropped the tracked count by one; update the committed
baseline so the Check lint budgets CI step is not stale

* refactor(ui): scope the model credential modal to api-key rotation only

Narrows UpdateModelCredentialsModal to a single API Key field. On submit it
PATCHes only { api_key }, so the backend merge leaves every other deployment
param untouched; a model authed via azure_ad_token, AWS keys, or a Vertex JSON
won't have anything to rotate here yet, which is the intended scope for now.

Drops the multi-field provider rendering this added earlier, which also removes
the now-unused disableRequired prop from ProviderSpecificFields and reverts that
shared component to its prior shape. The "Update API Key" trigger button is now
an antd Button rather than a TremorButton, so the feature introduces no tremor.

* refactor(ui): convert the model detail toolbar buttons from tremor to antd

Switches Test Connection, Re-use Credentials and Delete Model to antd Button so
the toolbar matches the Update API Key button and no longer mixes libraries;
Delete Model uses antd's danger styling instead of hand-rolled red classes

* style(ui): make the api-key modal submit button primary and drop the Need Help link
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.

2 participants