{"record":{"id":"9b3036a8e33a7204","repo":"bytedance/deer-flow","slug":"cannot-set-env-key-k-to-masked-value-pr","errorCode":null,"errorMessage":"Cannot set env key '{k}' to masked value '***'; provide a real value.","messagePattern":"Cannot set env key '(.+?)' to masked value '\\*\\*\\*'; provide a real value\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"backend/app/gateway/routers/mcp.py","lineNumber":680,"sourceCode":"    GET (masked) → modify enabled → PUT (masked values sent back).\n    This function ensures masked values (``***``) are replaced with the\n    real secrets from the current on-disk config.\n\n    ``***`` is only accepted for keys that already exist in *existing*.\n    New keys must provide a real value.\n\n    For OAuth secrets, ``None`` means \"preserve the existing stored value\"\n    so masked GET responses can be safely round-tripped. To explicitly clear\n    a stored secret, clients may send an empty string, which is converted\n    to ``None`` before persisting.\n    \"\"\"\n    merged_env = {}\n    for k, v in incoming.env.items():\n        if v == _MASKED_VALUE:\n            if k in existing.env:\n                merged_env[k] = existing.env[k]\n            else:\n                raise HTTPException(\n                    status_code=400,\n                    detail=f\"Cannot set env key '{k}' to masked value '***'; provide a real value.\",\n                )\n        else:\n            merged_env[k] = v\n\n    merged_headers = {}\n    for k, v in incoming.headers.items():\n        if v == _MASKED_VALUE:\n            if k in existing.headers:\n                merged_headers[k] = existing.headers[k]\n            else:\n                raise HTTPException(\n                    status_code=400,\n                    detail=f\"Cannot set header '{k}' to masked value '***'; provide a real value.\",\n                )\n        else:\n            merged_headers[k] = v","sourceCodeStart":662,"sourceCodeEnd":698,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/mcp.py#L662-L698","documentation":"400 raised when an MCP config update sends the masked sentinel '***' for an env key that does not exist in the currently stored server config. Masked GET responses are meant to round-trip: existing keys sent back as '***' keep their stored value. A masked value for a brand-new key has nothing to preserve, so it is rejected.","triggerScenarios":"PUT to /api/mcp/config adding env: {\"API_KEY\": \"***\"} when the stored server has no API_KEY; re-submitting a masked GET body but renaming or adding env keys; creating a new server by templating another server's masked response.","commonSituations":"Config round-trip edit flows where the user adds a new secret field but leaves the masked placeholder; automation that merges masked snapshots into new server definitions.","solutions":["Provide the real secret value for any env key not already stored","Only send '***' for keys that exist in the stored config (those are preserved on merge)","Maintain the authoritative unmasked config outside the API and diff against it, not against masked GET output"],"exampleFix":"# before\nenv: {\"EXISTING_KEY\": \"***\", \"NEW_KEY\": \"***\"}  # NEW_KEY -> 400\n# after\nenv: {\"EXISTING_KEY\": \"***\", \"NEW_KEY\": \"real-secret-value\"}","handlingStrategy":"validation","validationCode":"function resolveMaskedEnv(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 env key '${k}' needs a real value, not the mask`); out[k] = existing[k]; } else out[k] = v; } return out; }","typeGuard":"function isMasked(v: unknown): v is '***' { return v === '***'; }","tryCatchPattern":"null","preventionTips":["Fetch the unmasked truth from your secret store, use GET output only as structure reference","Send '***' only for keys you verified exist server-side","When adding env keys in a UI, blank the masked placeholder instead of submitting it"],"tags":["mcp","secrets","masking","env","http-400"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}