bytedance/deer-flow · error · HTTPException
Cannot set header '{k}' to masked value '***'; provide a rea
Error message
Cannot set header '{k}' to masked value '***'; provide a real value. What it means
400 raised under the same masked-value merge logic as env keys, but for HTTP headers: an update sends '***' for a header name that has no stored value on the existing server. Existing masked headers round-trip (stored value preserved); new masked headers cannot.
Source
Thrown at backend/app/gateway/routers/mcp.py:693
for k, v in incoming.env.items():
if v == _MASKED_VALUE:
if k in existing.env:
merged_env[k] = existing.env[k]
else:
raise HTTPException(
status_code=400,
detail=f"Cannot set env key '{k}' to masked value '***'; provide a real value.",
)
else:
merged_env[k] = v
merged_headers = {}
for k, v in incoming.headers.items():
if v == _MASKED_VALUE:
if k in existing.headers:
merged_headers[k] = existing.headers[k]
else:
raise HTTPException(
status_code=400,
detail=f"Cannot set header '{k}' to masked value '***'; provide a real value.",
)
else:
merged_headers[k] = v
merged_oauth = incoming.oauth
if incoming.oauth is not None and existing.oauth is not None:
# None = preserve (masked round-trip), "" = explicitly clear, else = new value
merged_client_secret = existing.oauth.client_secret if incoming.oauth.client_secret is None else (None if incoming.oauth.client_secret == "" else incoming.oauth.client_secret)
merged_refresh_token = existing.oauth.refresh_token if incoming.oauth.refresh_token is None else (None if incoming.oauth.refresh_token == "" else incoming.oauth.refresh_token)
merged_oauth = incoming.oauth.model_copy(
update={
"client_secret": merged_client_secret,
"refresh_token": merged_refresh_token,
}
)
update = {View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Send the actual header value (e.g. the real bearer token) for new header keys
- Reserve '***' strictly for keys already present in the stored config
- Keep the source of truth for secrets in a vault and inject real values at submit time
Example fix
# before
headers: {"X-Api-Key": "***"} # new header -> 400
# after
headers: {"X-Api-Key": "actual-key"} Defensive patterns
Strategy: validation
Validate before calling
function resolveMaskedHeaders(incoming: Record<string, string>, existing: Record<string, string> | undefined): Record<string, string> { const out: Record<string, string> = {}; for (const [k, v] of Object.entries(incoming)) { if (v === '***') { if (!existing || !(k in existing)) throw new Error(`new header '${k}' needs a real value, not the mask`); out[k] = existing[k]; } else out[k] = v; } return out; } Type guard
function isMasked(v: unknown): v is '***' { return v === '***'; } Prevention
- Same rule as env keys: masks may round-trip only onto existing stored keys
- Inject real tokens from a vault at submit time
- Never template new servers from another server's masked GET response
When it happens
Trigger: PUT to /api/mcp/config with headers: {"Authorization": "***"} on a server that never had an Authorization header; adding a new header while copying masked values from a GET response.
Common situations: Adding auth headers to a previously anonymous HTTP/SSE MCP server via a masked round-trip payload; UI forms pre-filling '***' into new header inputs.
Related errors
- Cannot set extra config key '{key}' to masked value '***'; p
- Cannot set env key '{k}' to masked value '***'; provide a re
- Missing X-GitHub-Event header
- MCP server '{server_name}' with stdio transport requires a c
- MCP server '{server_name}' command must be a single executab
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/e4d616041a3193aa.
Report an issue: GitHub.