Skip to content

fix(admin): redact saved model endpoint secrets - #615

Merged
hedhoud merged 8 commits into
refactor/hexagonalfrom
fix/redact-admin-secrets
Jul 3, 2026
Merged

fix(admin): redact saved model endpoint secrets#615
hedhoud merged 8 commits into
refactor/hexagonalfrom
fix/redact-admin-secrets

Conversation

@hedhoud

@hedhoud hedhoud commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #614 by redacting saved secrets from admin-facing read responses while keeping the real values available server-side.

Model endpoint responses now hide secret values from extra and expose has_api_key so the Admin UI can show that a key exists without receiving it. The service also preserves existing stored secrets when an endpoint update omits the key, which prevents the edit form from accidentally clearing private endpoint credentials.

The /config response now redacts known secret fields before returning the settings object to the Admin UI.

Why

The Admin UI needs to manage model endpoints and runtime config, but raw API keys, tokens, and passwords should not be sent back to the browser after they are saved. At the same time, saved model endpoint keys are still required by backend validation and indexing, so this keeps storage/internal use intact.

Validation

  • uv run --no-env-file pytest tests/unit/api/routers/admin/test_phase14_admin_routers.py tests/unit/services/orchestrators/test_model_endpoint_service.py tests/unit/api/test_secret_redaction.py tests/unit/api/test_main_proxy_headers.py -q
  • uv run --no-env-file ruff check openrag/core/utils/redaction.py openrag/api/schemas/admin/model_endpoint_schemas.py openrag/api/main.py openrag/services/orchestrators/model_endpoint_service.py tests/unit/api/test_secret_redaction.py tests/unit/api/routers/admin/test_phase14_admin_routers.py tests/unit/services/orchestrators/test_model_endpoint_service.py tests/integration/api/test_model_endpoints.py
  • git diff --check
  • cd ui && npm run lint && npm run test && npm run build
  • Local API smoke test: create/list/detail/update model endpoint with a fake API key; no read response contained the fake key, and Postgres still preserved the stored key after update without api_key.
  • Playwright check against local Admin UI: edit dialog hides the key, shows the preservation hint, keeps Update enabled for an unchanged saved endpoint, and System config does not expose the fake key.

Notes

I also ran tests/integration/api/test_model_endpoints.py against the local CPU stack. Three tests passed; the CRUD validation test failed on the existing local setup because it expects a mock vLLM service at http://vllm:8000/v1, which this CPU stack does not start.

TestSprite preflight passed, but I did not run a TestSprite suite because this change is only deployed on localhost and TestSprite requires a publicly reachable target URL.

Summary by CodeRabbit

  • New Features
    • Added admin support to reveal a server-stored endpoint API key on demand.
    • Enhanced the model endpoint dialog to reuse an existing API key or enter a new one, including show/hide and copy actions.
    • Endpoint details now indicate whether an API key exists without exposing its full value.
  • Bug Fixes
    • Sensitive configuration values and endpoint extra secret data are now redacted in API responses.
    • Updating endpoint details preserves previously saved API keys when not changed.
    • Draft endpoint validation can reuse stored credentials and now rejects invalid URLs more reliably.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Secrets are redacted in admin config and model endpoint responses, model endpoint updates preserve stored API keys unless replaced, and the admin UI can reveal, reuse, and submit endpoint API keys while keeping read responses masked.

Changes

Admin config and secret redaction

Layer / File(s) Summary
Config response redaction
openrag/api/main.py
GET /config now redacts serialized settings before returning them.
Redaction utilities
openrag/core/utils/redaction.py, tests/unit/api/test_secret_redaction.py
New helpers mask secret fields, redact nested payloads, and preserve existing secrets during merges, with unit tests covering each behavior.

Model endpoint API-key flow

Layer / File(s) Summary
Backend endpoint contracts
openrag/api/schemas/admin/model_endpoint_schemas.py, openrag/api/routers/admin/model_endpoints.py, openrag/services/orchestrators/model_endpoint_service.py
Model endpoint responses now expose has_api_key and redact extra, draft validation can reuse stored API-key references, a reveal endpoint returns the stored key, and endpoint updates preserve existing secrets while validation rejects invalid URLs.
Backend tests
tests/integration/api/test_model_endpoints.py, tests/unit/api/routers/admin/test_phase14_admin_routers.py, tests/unit/services/orchestrators/test_model_endpoint_service.py
Tests cover masked read responses, reveal-key behavior, stored-key reuse and mismatch handling, secret preservation on update, and invalid URL validation cases.

UI API client and admin dialog

Layer / File(s) Summary
UI client and admin page
ui/src/lib/api/models.ts, ui/src/lib/api/models.test.ts, ui/src/pages/admin/models.tsx
The model API client adds reveal and secret-handling helpers, and the admin model dialog adds API-key reveal, reuse, validation, and submit handling with matching test coverage.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AdminUI
  participant ApiRouter
  participant ModelEndpointService
  participant Store

  AdminUI->>ApiRouter: POST /model-endpoints/{type}/{name}/reveal-api-key
  ApiRouter->>Store: load endpoint record
  Store-->>ApiRouter: endpoint.extra.api_key
  ApiRouter-->>AdminUI: RevealApiKeyResponse

  AdminUI->>ApiRouter: POST /model-endpoints/validate
  ApiRouter->>Store: load stored endpoint by type/name
  Store-->>ApiRouter: stored endpoint.extra.api_key
  ApiRouter->>ModelEndpointService: validate_endpoint(url, api_key)
  ModelEndpointService-->>ApiRouter: validation result
  ApiRouter-->>AdminUI: validation response
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: redacting saved admin model endpoint secrets.
Linked Issues check ✅ Passed The PR redacts admin config and model endpoint read responses, preserves stored secrets on update, and exposes has_api_key as required by #614.
Out of Scope Changes check ✅ Passed The added route, UI support, helpers, and tests are all directly related to secret redaction and safe key reuse.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/redact-admin-secrets

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hedhoud
hedhoud force-pushed the fix/redact-admin-secrets branch from fb56fd5 to ffc83bc Compare July 2, 2026 12:18

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fb56fd5c68

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openrag/core/utils/redaction.py Outdated
Comment thread ui/src/pages/admin/models.tsx Outdated
@hedhoud
hedhoud force-pushed the fix/redact-admin-secrets branch from ffc83bc to 76ce3d8 Compare July 2, 2026 12:29

@andyne13 andyne13 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this — the write-only handling is clean and extending redaction to /config is a good catch. A few points from reviewing the diff:

Blocker — draft validation can send a stored key to an arbitrary URL

validate_endpoint_draft (api/routers/admin/model_endpoints.py) now reuses a stored endpoint's api_key (via stored_api_key_name / stored_api_key_model_type) while probing body.endpoint, which is fully caller-controlled. validate_endpoint (services/orchestrators/model_endpoint_service.py) then sends Authorization: Bearer <stored key> to {url}/models with no binding between url and the stored endpoint and no URL safety check.

So a caller can set endpoint to a server they control, reference any saved endpoint by name/type, and read the bearer token off the wire — recovering the key this PR otherwise makes write-only. The same unbound URL also lets the probe reach internal addresses (SSRF).

Suggestion: when a stored key is reused, bind the probe to that endpoint's saved URL (or require body.endpoint's host to match it); if the URL differs, require the key to be passed explicitly rather than reused. An is_safe_url-style guard on the probe URL with follow_redirects=False would close the SSRF angle too.

Should-fix — /config redaction is a denylist, not safe-by-default

redact_secrets redacts by exact field name against SECRET_FIELD_NAMES. It covers everything sensitive in the settings today, but because it's exact-match, a future secret field with an unlisted name (e.g. access_key, private_key, signing_key) would be returned in clear by default.

Two options: build the /config payload from an explicit allowlist of safe fields (new fields default to hidden), or keep the denylist but add a guard test that fails when a new str field is added to the settings model without being classified — so it can't silently rot.

Minor

  • token and secret as exact names will redact any field literally named that, secret or not. Fine for the current settings; just flagging in case a benign field named token ever appears.

The redaction helper, has_api_key, and preserve-on-update all look correct otherwise. 👍

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/unit/api/routers/admin/test_phase14_admin_routers.py (1)

250-260: 🔒 Security & Privacy | 🔵 Trivial

Consider covering audit logging for secret reveal.

This test confirms the reveal endpoint returns the raw stored secret to an authorized caller, which matches the intended design. Since revealing a raw credential is a sensitive, high-value action, consider whether the production route should emit a structured audit log entry (via get_logger()), and if so, add a test asserting that log emission alongside this response check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/api/routers/admin/test_phase14_admin_routers.py` around lines 250
- 260, The reveal-api-key test currently only verifies the response from the
model-endpoint reveal flow, but it does not cover the expected audit logging for
this sensitive action. Update the test around
test_model_endpoint_reveal_api_key_returns_stored_secret to also assert that the
route emits a structured audit log via get_logger() when the secret is revealed,
while keeping the existing response assertion for the raw api_key. Use the
existing reveal endpoint path and the FakeModelEndpointService/_build_app setup
to locate the production flow and verify both the returned secret and the log
emission.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openrag/services/orchestrators/model_endpoint_service.py`:
- Around line 344-345: The URL validation in model_endpoint_service should
handle malformed inputs before urlsplit() can raise ValueError. Update the
validation path around the parsed urlsplit(url) logic to catch parsing errors
and return the same structured absolute HTTP(S) validation response instead of
letting an exception bubble out. Keep the fix localized to the URL-checking flow
in the relevant validation method so existing scheme/netloc checks still run for
valid inputs.

In `@ui/src/pages/admin/models.tsx`:
- Around line 271-273: The revealed API-key cache in the admin models dialog is
currently a single string, so a late response from one endpoint can be reused
for a different one. Update the state and related logic in the models page
(including the editing/reveal flow around revealedApiKey, fetchStoredApiKey, and
the reveal handlers) to store the cached secret together with its { modelType,
name } identity, and only return or display it when it matches the current
editing target. Ensure any reveal/reset logic also clears or ignores stale
cached values when editing switches to a different endpoint.

---

Nitpick comments:
In `@tests/unit/api/routers/admin/test_phase14_admin_routers.py`:
- Around line 250-260: The reveal-api-key test currently only verifies the
response from the model-endpoint reveal flow, but it does not cover the expected
audit logging for this sensitive action. Update the test around
test_model_endpoint_reveal_api_key_returns_stored_secret to also assert that the
route emits a structured audit log via get_logger() when the secret is revealed,
while keeping the existing response assertion for the raw api_key. Use the
existing reveal endpoint path and the FakeModelEndpointService/_build_app setup
to locate the production flow and verify both the returned secret and the log
emission.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b492b3f4-639c-4a31-91b2-674675d953c8

📥 Commits

Reviewing files that changed from the base of the PR and between ffc83bc and ace434b.

📒 Files selected for processing (12)
  • openrag/api/main.py
  • openrag/api/routers/admin/model_endpoints.py
  • openrag/api/schemas/admin/model_endpoint_schemas.py
  • openrag/core/utils/redaction.py
  • openrag/services/orchestrators/model_endpoint_service.py
  • tests/integration/api/test_model_endpoints.py
  • tests/unit/api/routers/admin/test_phase14_admin_routers.py
  • tests/unit/api/test_secret_redaction.py
  • tests/unit/services/orchestrators/test_model_endpoint_service.py
  • ui/src/lib/api/models.test.ts
  • ui/src/lib/api/models.ts
  • ui/src/pages/admin/models.tsx

Comment thread openrag/services/orchestrators/model_endpoint_service.py Outdated
Comment thread ui/src/pages/admin/models.tsx Outdated

@andyne13 andyne13 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround — re-reviewed the latest commits.

Resolved ✅

  • Draft-validate key reuse is now bound to the saved URL via _same_endpoint_url (400 otherwise). Checked it for bypasses — the compare is strict (case differences only make it stricter/fail-closed), so a stored key can't be redirected to an arbitrary host. follow_redirects=False on the probe is a nice addition too.
  • Denylist robustnessSECRET_FIELD_SUFFIXES now catches compound names (*_api_key, *_token, *_password, *_secret, *_access_key, …), so things like minio_secret_key / smtp_password are covered, and the max_tokens false-positive test is a good guard. 👍

🔴 New regression to fix before merge — /config now leaks the first 3 chars of every secret

redact_secrets (used by /config, main.py:333) no longer fully redacts — it now calls mask_secret_value, which for any secret ≥ 8 chars returns value[:3] + "********" (redaction.py:58-60). Your own test confirms it: redact_secrets(...)["websearch"]["api_token"] == "sea********".

So /config returns the first 3 characters of rdb.password, the OIDC token_encryption_key (session-encryption Fernet key), oidc.client_secret, every api_key, and websearch.api_token. That's a partial-disclosure regression from the full <redacted> this PR started with.

The prefix hint makes sense for a user-supplied endpoint API key (to identify which key) — but mask_secret_value is only ever reached via /config (the endpoint path drops the key through redact_secret_mapping + has_api_key). So the masking applies exclusively to infrastructure secrets, where it has no UI purpose: revealing the first 3 chars of a human-chosen DB password aids guessing, and exposing any bytes of the session-encryption key is best avoided.

Suggestion: keep /config on full <redacted> (or omit) and don't route it through mask_secret_value — e.g. a full-redact variant for the config path, leaving masking (if you want it) for endpoint-key display only.

Minor (unchanged): the validate probe still has no is_safe_url guard, so it can reach internal addresses with an admin-supplied key. Admin-gated and follow_redirects=False covers the redirect pivot, so a should-fix rather than a blocker.

Comment thread openrag/core/utils/redaction.py Outdated
Comment thread ui/src/pages/admin/models.tsx
@hedhoud

hedhoud commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator Author

Fixed the /config redaction regression in 1637c17. Config secrets now return full <redacted> values again, so we no longer expose the first 3 chars of DB/OIDC/API tokens. I kept the short prefix mask only for the model-endpoint API-key display path, where it helps identify the stored key without exposing the full value.\n\nI also updated the redaction test so this specific regression is covered. I left the internal-address validate guard as a separate hardening follow-up since it is a different path and the stored-key reuse leak is already closed by the saved-URL binding.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 07b4e11ecc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openrag/core/utils/redaction.py Outdated
Comment thread ui/src/pages/admin/models.tsx Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c6587e174b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ui/src/lib/api/models.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f2ef2b727c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ui/src/pages/admin/models.tsx Outdated
Comment thread openrag/core/utils/redaction.py Outdated

@Ahmath-Gadji Ahmath-Gadji left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — all substantive findings are resolved and I validated the branch locally at 9b7bca0e.

Findings closed

  • Endpoint extra data-loss (c6587e17) — redact_secret_mapping now masks secrets in place instead of whitelisting, so non-secret config (temperature, enable_thinking, nested auth/headers) survives the read→edit→save round-trip. Locked in by test_redact_secret_mapping_keeps_non_secret_endpoint_extra_shape.
  • /config prefix leak (1637c174) — redact_secrets returns full <redacted> for all secret fields; no more first-3-char disclosure of DB password / Fernet key / client secret.
  • URL-edit UX (c6587e17, f2ef2b72) — changing an endpoint URL with a hidden key now gives a clear "reveal or enter the API key" prompt instead of a cryptic 400, a revealed key feeds validation, and explicit key-clearing is supported.

Validation run locally

  • pytest redaction + model-endpoint-service + admin-routers → 59 passed
  • vitest run src/lib/api/models.test.ts20 passed
  • CI green (api-tests, tests, milvus-integration, lint, layer-import-guard)

Non-blocking follow-up: the draft-validate probe still has no private-range/is_safe_url guard, so an admin-supplied URL can reach internal addresses — admin-gated and mitigated by follow_redirects=False, so fine to land and harden later.

@hedhoud
hedhoud merged commit 00a3a4c into refactor/hexagonal Jul 3, 2026
6 checks passed
@hedhoud
hedhoud deleted the fix/redact-admin-secrets branch July 3, 2026 10:17
@Ahmath-Gadji Ahmath-Gadji added the fix Fix issue label Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix Fix issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants