{"record":{"id":"b47626248f053f8d","repo":"BerriAI/litellm","slug":"invalid-identifier-identifier-r-contains-disall","errorCode":null,"errorMessage":"Invalid identifier {identifier!r}: contains disallowed characters","messagePattern":"Invalid identifier (.+?): contains disallowed characters","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/integrations/arize/arize_phoenix_client.py","lineNumber":14,"sourceCode":"\"\"\"\nArize Phoenix API client for fetching prompt versions from Arize Phoenix.\n\"\"\"\n\nimport urllib.parse\nfrom typing import Any, Final\n\nfrom litellm.llms.custom_httpx.http_handler import HTTPHandler\n\n\ndef _sanitize_id(identifier: str) -> str:\n    \"\"\"Reject path traversal characters and URL-encode the identifier.\"\"\"\n    if any(c in identifier for c in (\"/\", \"\\\\\", \"#\", \"?\")):\n        raise ValueError(f\"Invalid identifier {identifier!r}: contains disallowed characters\")\n    if \"..\" in identifier:\n        raise ValueError(f\"Invalid identifier {identifier!r}: path traversal detected\")\n    return urllib.parse.quote(identifier, safe=\"\")\n\n\nclass ArizePhoenixClient:\n    \"\"\"\n    Client for interacting with Arize Phoenix API to fetch prompt versions.\n\n    Supports:\n    - Authentication with Bearer tokens\n    - Fetching prompt versions\n    - Direct API base URL configuration\n    \"\"\"\n\n    def __init__(self, api_key: str | None = None, api_base: str | None = None):\n        \"\"\"\n        Initialize the Arize Phoenix client.","sourceCodeStart":1,"sourceCodeEnd":32,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/integrations/arize/arize_phoenix_client.py#L1-L32","documentation":"ArizePhoenixClient sanitizes every identifier (prompt_id, prompt_version_id) before placing it into a request URL path. `_sanitize_id` rejects identifiers containing '/', '\\\\', '#', or '?' because those characters would alter the URL path structure or query, then URL-encodes everything else. This is a guard against malformed or injected identifiers.","triggerScenarios":"Calling prompt operations with an identifier that contains a path separator or query character, e.g. prompt_id='workspace/prompts/123', 'my\\\\prompt', 'prompt#v2', or 'prompt?version=1'. The client is constructed via the Arize Phoenix prompt integration when loading or rendering that prompt.","commonSituations":"Pasting a full Phoenix URL or a prompt 'slug path' as the prompt_id instead of the bare ID; copy-pasting IDs with trailing URL fragments; user-supplied prompt IDs from an API request being passed through unvalidated.","solutions":["Use the bare prompt/prompt-version identifier with no '/', '\\\\', '#', '?' characters (e.g. 'UHJvbXB0OjE' style ID from the Phoenix UI)","If the ID came from a URL, strip everything except the last path segment and any query/fragment before passing it","Validate user-provided prompt IDs at your API boundary before they reach litellm's prompt manager"],"exampleFix":"# before\nprompt_id = \"https://app.phoenix.arize.com/prompts/UHJvbXB0OjE\"  # contains '/' -> ValueError\n\n# after\nprompt_id = \"UHJvbXB0OjE\"  # bare identifier only","handlingStrategy":"type-guard","validationCode":"import re\n\ndef safe_prompt_id(identifier: str) -> str:\n    if not re.fullmatch(r\"[A-Za-z0-9_\\-.]+\", identifier):\n        raise ValueError(f\"prompt_id {identifier!r} contains path/query characters\")\n    return identifier\n\nprompt_id = safe_prompt_id(user_supplied_prompt_id)","typeGuard":"def is_sanitizable_identifier(identifier: str) -> bool:\n    return isinstance(identifier, str) and not any(c in identifier for c in (\"/\", \"\\\\\", \"#\", \"?\")) and \"..\" not in identifier","tryCatchPattern":"try:\n    client.get_prompt_version(prompt_version_id)\nexcept ValueError as e:\n    if \"disallowed characters\" in str(e):\n        # strip to last path segment and retry once with the bare ID\n        bare = prompt_version_id.rstrip(\"/#?\").rsplit(\"/\", 1)[-1]\n        if bare and is_sanitizable_identifier(bare):\n            return client.get_prompt_version(bare)\n    raise","preventionTips":["Validate prompt IDs at your API boundary with an allow-list regex before they reach litellm","Store bare Phoenix IDs in config; never paste URLs"],"tags":["arize-phoenix","prompts","input-validation","url-sanitization","security"],"backgroundTag":"invalid-identifier","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}