bytedance/deer-flow · error · HTTPException

Cannot set extra config key '{key}' to masked value '***'; p

Error message

Cannot set extra config key '{key}' to masked value '***'; provide a real value.

What it means

400 raised when an MCP configuration update sends the literal masked value '***' for a sensitive extra-config key that does not already exist in the stored configuration. GET responses mask sensitive values; when updating, the server preserves existing values for masked round-trips, but a masked value with no stored original cannot be resolved, so it is rejected.

Source

Thrown at backend/app/gateway/routers/mcp.py:461


def _is_sensitive_extra_key(key: str) -> bool:
    return bool(_SENSITIVE_EXTRA_KEY_RE.search(_normalize_config_key(key)))


def _mask_sensitive_extra_value(value: Any) -> Any:
    if isinstance(value, dict):
        return {key: _MASKED_VALUE if _is_sensitive_extra_key(str(key)) else _mask_sensitive_extra_value(nested) for key, nested in value.items()}
    if isinstance(value, list):
        return [_mask_sensitive_extra_value(item) for item in value]
    return value


def _merge_extra_value_preserving_masked(key: str, incoming_value: Any, existing_value: Any, *, existing_present: bool) -> Any:
    if incoming_value == _MASKED_VALUE and _is_sensitive_extra_key(key):
        if existing_present:
            return existing_value
        raise HTTPException(
            status_code=400,
            detail=f"Cannot set extra config key '{key}' to masked value '***'; provide a real value.",
        )

    if isinstance(incoming_value, dict) and isinstance(existing_value, dict):
        merged: dict[str, Any] = {}
        for nested_key, nested_value in incoming_value.items():
            nested_present = nested_key in existing_value
            merged[nested_key] = _merge_extra_value_preserving_masked(
                str(nested_key),
                nested_value,
                existing_value.get(nested_key),
                existing_present=nested_present,
            )
        return merged

    if isinstance(incoming_value, list) and isinstance(existing_value, list) and len(incoming_value) == len(existing_value):
        return [_merge_extra_value_preserving_masked(key, nested_value, existing_value[index], existing_present=True) for index, nested_value in enumerate(incoming_value)]

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Send a real secret value instead of '***' for any sensitive key that is newly added
  2. Keep an unmasked source of truth (secret manager or local file) and merge masked GET output with real values before submitting
  3. When round-tripping an existing server's config, leave sensitive keys with '***' only if they already exist in stored config (then they are preserved)

Example fix

# before
extra: {"api_token": "***"}   # new key, masked value -> 400
# after
extra: {"api_token": "sk-real-secret-from-vault"}
Defensive patterns

Strategy: validation

Validate before calling

function assertNoMaskedNewKeys(incomingExtra: Record<string, unknown>, existingExtra: Record<string, unknown> | undefined) { for (const [k, v] of Object.entries(incomingExtra)) { if (v === '***' && !(existingExtra && k in existingExtra)) throw new Error(`provide a real value for new key ${k}`); if (typeof v === 'object' && v !== null) assertNoMaskedNewKeys(v as any, existingExtra?.[k] as any); } }

Type guard

function isMaskedSentinel(v: unknown): v is '***' { return v === '***'; }

Try / catch

try { await putMcpConfig(payload); } catch (e) { if (e.status === 400 && /masked value/.test(e.detail)) { /* replace *** for the named key with the real secret and retry once */ } throw e; }

Prevention

When it happens

Trigger: PUT to the MCP config endpoint where extra config contains {'api_token': '***'} for a key that was never set before; constructing a config payload by copying a masked GET response for a brand-new server entry; adding a new sensitive nested key whose value came from another server's masked response.

Common situations: Config-as-code workflows that fetch the live (masked) config and re-submit it as a template for new servers; UI edits that pre-fill masked placeholders into create forms.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/c90529c437429697. Report an issue: GitHub.