Use the deploy-time file size configuration for model debugger uploads
instead of a hardcoded 20 MB cap, and validate file size on the frontend
before sending the request.
Align product copy, API docs, Swagger descriptions, and i18n strings
with workspace terminology while keeping internal tenant identifiers
and headers unchanged for API compatibility.
Allow platform system administrators to update builtin model parameters
and credentials from the management UI while keeping secrets masked in API
responses. UI edits clear YAML ownership so startup reconciliation no
longer overwrites runtime overrides.
Prevent low-privileged tenant members from reading raw API keys and
infrastructure credentials via tenant profile, KV config, model/MCP/web
search listings, and KB initialization config responses.
Let admins send real requests against configured chat, embedding, rerank,
VLM, and ASR models from settings, with thinking controls shown only when
the model supports them.
Test-connection's "连接失败" was nearly useless for debugging because
two layers conspired to swallow the actual upstream error:
Backend swallow
---------------
checkChatModelConnection and CheckASRModel pattern-matched on the
upstream error string (401 / 404 / timeout / ...) and replaced it with
a fixed Chinese hint. The original SDK message (request URL, response
body, provider's error code description) was discarded.
Refactor: extract classifyConnectionError(errMsg) — returns just the
short Chinese hint. Callers combine "<hint>:<raw err>" so the hint
indicates "where to start looking" and the raw error shows "what
actually happened". The status-400 special-case (endpoint reachable,
parameter mismatch only) still returns success.
Frontend swallow
----------------
ModelEditorDialog.testConnection ignored result.message on success
paths (fine), but also overwrote it with a generic i18n string on
failure paths and 4xx rejections — even though axios's interceptor
already lifts the backend's error.message to error.message. Failure
branch now uses error?.message || result.message as the toast content;
i18n fallback only when neither is present.
SSRF allowlist hint
-------------------
SSRF validation failures previously returned
"Base URL 未通过安全校验: <reason>" with no mention of how to recover
(the allowlist is server-side, not configurable in the UI). Operators
hit it and were stuck.
New utils.FormatSSRFError(label, rawURL, err) wraps the error with
guidance to add the host to SSRF_WHITELIST_EXTRA (the operator-facing
append-only escape hatch — SSRF_WHITELIST itself is the project's
baseline). The hint echoes the failed host so it can be copy-pasted
straight into the env var:
Base URL 未通过安全校验:DNS resolution failed for hostname
test.local.com ... 如该地址确实可信,请联系运维在服务端环境变量
SSRF_WHITELIST_EXTRA 中加入该主机(支持精确域名 / *.example.com
通配 / IP / CIDR),示例:SSRF_WHITELIST_EXTRA=test.local.com,
*.example.com,10.0.0.0/8
All SSRF call sites switch to the helper:
initialization (CheckRemoteModel / TestEmbedding / CheckRerank /
CheckASR / TestMultimodal / batch validation), model CRUD,
mcp_service CRUD, knowledge URL ingest, system ReconnectDocReader.
Replace the legacy "redacted placeholder + Clear* boolean" pattern with
dedicated per-resource credential subresources across MCP services,
Models, Web search providers, and Data sources.
Why
---
The previous design had three problems:
1. Main PUT body carried secret fields. The frontend echoed back a
redacted "***" placeholder, and a fragile MergeUpdate / IsRedactedOrEmpty
defense in the service layer tried to detect "user did not change this"
vs "user wants to clear this". A regression in that defense (or a new
frontend forgetting it) silently overwrites the stored secret with the
placeholder.
2. The "remove this credential" UX was a red checkbox under a pre-filled
password input. Three intents (preserve / replace / clear) collapsed
onto one field, and credential changes were bundled with unrelated
config edits in the same submit. Users wiped working keys by mistake.
3. Secret presence was inferred from "did the response come back with a
'***' placeholder", which couples the contract to a magic string.
Design
------
Each of the four resources now exposes:
PUT /{resource}/{id}/credentials # write one or more fields
DELETE /{resource}/{id}/credentials/{field} # clear a single field
"Is this configured?" metadata travels on the main resource response as
a typed map (dto.MCPServiceResponse.Credentials etc.) — no separate GET
endpoint. The frontend reads the configured boolean from the main GET
and never sees secret values at all.
Main PUT endpoints now ignore any secret fields in the request body and
log a deprecation warning if they appear, so legacy clients fail loudly
rather than overwriting silently.
Frontend
--------
- New reusable <CredentialResource> component renders a three-state card
(unconfigured / configured / editing) and drives the dedicated
endpoints. Used by MCP, Model, Web search; DataSource has a bespoke
card with the same behaviour because its credentials are a single
atomic per-connector map.
- Cancel from edit mode now restores state synchronously from the meta
prop. The previous async refresh() was a no-op while state was still
'editing', leaving the input frozen open.
- Remove is single-click + toast. The danger-themed button is the
deterrent; a modal confirm adds friction without adding safety (the
plaintext is irrecoverable client-side either way — recovery means
re-typing).
DTOs (internal/handler/dto/) are deliberately separate from the GORM
models so "no secret in response" is a compile-time invariant: a future
contributor cannot leak a secret without explicitly adding the field to
the DTO, which is review-able in a single diff.
Storage is unchanged — credentials still live in the existing jsonb /
parameters columns. No schema migration.
Cleanup
-------
- types.MCPService / types.Model / types.WebSearchProviderEntity /
types.DataSourceConfig: drop ClearAPIKey / ClearToken / ClearAppSecret
/ ClearCredentials boolean fields, MergeUpdate(), RedactSensitiveData().
- utils/types/secret.go: drop PreserveIfRedacted / IsRedactedOrEmpty.
RedactedSecretPlaceholder constant is retained because VectorStore
still uses the old pattern and is out of scope here.
- Frontend hasExistingApiKey / clearApiKey / convertToLegacyFormat
redaction handling removed; i18n keys renamed secret -> credential.
Closes#988
Unify the "write-only secrets" pattern across MCP services, Models,
WebSearch providers, and DataSources. Fix two related defects:
1. GET responses leaked plaintext credentials — GetMCPServiceByID
while List masked, all Model / WebSearchProvider / DataSource GET
responses unconditionally.
2. When the frontend echoed a masked placeholder back on save, the
update path overwrote the stored secret with the mask — silently
wiping the real credential. MCP surfaced this most visibly:
ListMCPServices masked Token to "first4****last4" and
McpServiceDialog round-tripped that masked string back into the DB.
- internal/types/secret.go introduces the shared sentinel
RedactedSecretPlaceholder = "***" and two helpers:
- IsRedactedOrEmpty(s): true for "" or the sentinel
- PreserveIfRedacted(incoming, existing): merge predicate
- Each resource adds a RedactSensitiveData (mutates-in-place) or
Redacted (returns-a-copy) method and a MergeUpdate contract:
empty or placeholder preserves, any other value replaces, and a
write-only Clear* bool flag (gorm:"-", json omitempty) wipes with
precedence over preserve / replace. Clear* never persists.
- Every GET / List / Create / Update response path now redacts.
VectorStore's pre-existing "***" literal is refactored onto the
shared constant for consistency.
All four dialogs — McpServiceDialog, ModelEditorDialog,
WebSearchSettings, DataSourceEditorDialog — never pre-fill secret
inputs. The stored-state signal is collapsed into the input
placeholder: "•••••••• (Enter new value to replace)" when the server
reports a stored value, falling back to the connector-specific
placeholder otherwise. The input itself carries the "something is
there" cue, matching how password dots render.
An opt-in "Remove this credential" checkbox appears in edit mode when
a value is stored; checking it disables the input and triggers a
danger-themed confirmation dialog before submit. DataSource rolls the
clear up to a connector-level "Remove all credentials" flag since its
credentials are per-connector atomic bundles (e.g. Feishu app_id +
app_secret).
i18n updates across en-US, zh-CN, ko-KR, ru-RU:
secret.storedPlaceholder / clearHint / confirmClearTitle /
confirmClearBody.
- MCP update handler's manual map-to-struct projection was silently
dropping clear_api_key / clear_token / custom_headers on partial
updates. Pull them off the map explicitly.
- MCP configChanged detection now snapshots URL / StdioConfig /
TransportType before the merge so connection recycling still
triggers on real transport changes (not just whenever AuthConfig
is present).
- MCP CustomHeaders: nil preserves existing, non-nil replaces. Was
always overwriting, silently wiping headers on any auth-bearing
update.
- GetMCPServiceByID now redacts (was plaintext while List masked).
- WebSearchProvider update preserves Name / Description when empty
instead of clobbering them.
- WebSearchProvider TestProviderRaw rejects the "***" placeholder up
front with an actionable message rather than forwarding it to the
upstream provider API as a literal key.
- Model builtin hideSensitiveInfo now preserves non-secret metadata
(SupportsVision, Provider, ExtraConfig, InterfaceType,
ParameterSize, EmbeddingParameters) that were previously zeroed
out for builtin model cards.
- Replace local equalStringSlices / equalStringMaps helpers with
stdlib slices.Equal / maps.Equal (Go 1.21+).
- internal/types/{secret,mcp,model,web_search_provider,datasource}_test.go:
table-driven coverage of merge, redact, clear, and write-only flag
invariants for every resource.
- internal/application/service/mcp_service_test.go: full service layer
with a fake repo; covers ListMCPServices / GetMCPServiceByID
redaction, UpdateMCPService partial-update honoring of Clear*,
CustomHeaders preserve-on-nil / replace-on-non-nil, and
clear-flag precedence over submitted values.
Additive only. No changes to request or response JSON shape beyond
the write-only Clear* flags (omitted by default via omitempty). No DB
schema changes. No SDK struct field removals. Existing clients that
don't know about the new flags continue to function unchanged.
- DataSource.Config jsonb at-rest encryption. Pre-existing residual
risk; tracked as a follow-up.
- Renaming VectorStore.MaskSensitiveFields to match the new
RedactSensitiveData / Redacted naming convention. Left alone to
minimize diff; a follow-up rename PR can unify.
- Implemented a new function to check ASR model connectivity via the /v1/audio/transcriptions endpoint.
- Updated ModelEditorDialog.vue to integrate ASR model checks and adjust UI elements accordingly.
- Enhanced localization files to reflect changes in ASR terminology and descriptions.
- Added ASR configuration options to the knowledge base and updated related migration scripts.
- Refactored model handling to support ASR as a distinct model type in the application.
- Simplified model filtering logic by removing the deduplication function and directly filtering models based on type.
- Updated SSRF validation to preserve backend-managed fields when parameters are not provided by the frontend.
- Clarified comments and improved code readability in the ModelEditorDialog and ModelSettings components.
- Added timezone and language settings to the environment configuration.
- Introduced built-in agent configurations with multilingual support for various agents.
- Updated Docker Compose to utilize new environment variables for timezone and language.
- Created new prompt templates for question generation, summary generation, and keywords extraction.
- Eliminated the 'isDefault' field from model settings and related service logic, simplifying model management.
- Updated UI components to remove references to default models, ensuring a cleaner user experience.
- Adjusted backend model creation and update processes to reflect the removal of default model selection logic.
- Introduced `is_builtin` property in the model configuration to identify builtin models.
- Updated frontend components to display a tag for builtin models in the ModelSelector and ModelSettings.
- Enhanced the ModelEditorDialog to prevent editing and deletion of builtin models.
- Implemented logic in the backend to hide sensitive information for builtin models and restrict their modification.
- Added informative UI elements to clarify the nature of builtin models in the settings and editor views.
- Replaced the dialog component with a custom modal layout for better user experience.
- Enhanced form structure with custom styles and validation for required fields.
- Updated model source selection and added dynamic placeholders based on model type.
- Improved API validation logic for model saving in both ModelEditorDialog and ModelSettings.
- Refactored styles for better alignment and responsiveness across components.