{"record":{"id":"b7f4382574c71d13","repo":"TauricResearch/TradingAgents","slug":"ticker-contains-characters-not-allowed-in-a-filesy","errorCode":null,"errorMessage":"ticker contains characters not allowed in a filesystem path: {value!r}","messagePattern":"ticker contains characters not allowed in a filesystem path: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"tradingagents/dataflows/utils.py","lineNumber":34,"sourceCode":"\ndef safe_ticker_component(value: str, *, max_len: int = 32) -> str:\n    \"\"\"Validate ``value`` is safe to interpolate into a filesystem path.\n\n    Tickers come from user CLI input or from LLM tool calls, both of which\n    can be influenced by attacker-controlled content (e.g. prompt injection\n    embedded in fetched news). Without validation, a value like\n    ``\"../../../etc/foo\"`` flows into ``os.path.join`` / ``Path /`` and\n    escapes the configured cache, checkpoint, or results directory.\n\n    Returns ``value`` unchanged when it matches the allowed pattern; raises\n    ``ValueError`` otherwise.\n    \"\"\"\n    if not isinstance(value, str) or not value:\n        raise ValueError(f\"ticker must be a non-empty string, got {value!r}\")\n    if len(value) > max_len:\n        raise ValueError(f\"ticker exceeds {max_len} chars: {value!r}\")\n    if not _TICKER_PATH_RE.fullmatch(value):\n        raise ValueError(\n            f\"ticker contains characters not allowed in a filesystem path: {value!r}\"\n        )\n    # The regex above allows '.', so values like '.', '..', '...' would pass,\n    # and as a path component they traverse the parent directory. Reject any\n    # value that's only dots.\n    if set(value) == {\".\"}:\n        raise ValueError(f\"ticker cannot consist solely of dots: {value!r}\")\n    return value\n\n\ndef save_output(data: pd.DataFrame, tag: str, save_path: SavePathType = None) -> None:\n    if save_path:\n        data.to_csv(save_path, encoding=\"utf-8\")\n        print(f\"{tag} saved to {save_path}\")\n\n\ndef get_current_date():\n    return date.today().strftime(\"%Y-%m-%d\")","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/TauricResearch/TradingAgents/blob/a33fd4c0f134485a43553a2c23a63cb14adbd88f/tradingagents/dataflows/utils.py#L16-L52","documentation":"Raised by safe_ticker_component() in tradingagents/dataflows/utils.py when the string contains characters outside _TICKER_PATH_RE = ^[A-Za-z0-9._\\-^=+]+$. That allowlist deliberately excludes slashes, spaces, colons, percent, etc., so the value can never traverse directories when interpolated into cache/checkpoint/result paths. Legit symbol punctuation (dot, dash, caret for indices, '=' for futures, '+' for forex) is permitted.","triggerScenarios":"Values like '../../../etc/passwd', 'AAPL/BTC', 'BRK B' (space instead of dot), 'AAPL:US' (colon), URL-encoded or prompt-injected strings containing '/' or '%'. Any path separator or exotic punctuation triggers it.","commonSituations":"Prompt injection embedded in fetched news/newsletter text reaching ticker fields; users typing 'BRK B' or exchange-suffixed forms; values URL-decoded late; pipe-delimited lists passed as one symbol.","solutions":["Use canonical Yahoo-style symbols with allowed punctuation only: 'AAPL', 'BRK-B'/'BRK.B', '^GSPC', 'GC=F', 'XAUUSD+'","Replace disallowed separators before passing: ' ' -> '-' or '.', strip ':EXCHANGE' suffixes","Treat this error as a security signal: if it fires from LLM tool output, audit the pipeline for prompt injection instead of sanitizing blindly","Pre-validate with the same pattern: re.fullmatch(r'[A-Za-z0-9._\\-^=+]+', value)"],"exampleFix":"# before\nsafe_ticker_component(\"../../../etc/passwd\")   # or \"AAPL/BTC\"\n# -> ValueError: ticker contains characters not allowed in a filesystem path: ...\n\n# after\nsafe_ticker_component(\"AAPL\")\n# pre-validate\nimport re\nok = bool(re.fullmatch(r\"[A-Za-z0-9._\\-^=+]\", value)) and set(value) != {\".\"}\nsafe_ticker_component(value) if ok else reject()","handlingStrategy":"validation","validationCode":"import re\nfrom tradingagents.dataflows.utils import safe_ticker_component\n\ndef sanitize_ticker(raw: str) -> str | None:\n    \"\"\"Return a path-safe ticker or None; mirrors safe_ticker_component's rules.\"\"\"\n    if not isinstance(raw, str):\n        return None\n    raw = raw.strip().replace(\" \", \"-\")          # 'BRK B' -> 'BRK-B'\n    raw = raw.split(\":\")[0]                        # strip 'AAPL:US' suffixes\n    if not re.fullmatch(r\"[A-Za-z0-9._\\-^=+]+\", raw) or set(raw) == {\".\"}:\n        return None\n    return raw","typeGuard":"import re\n\ndef is_path_safe_ticker(v) -> bool:\n    return (isinstance(v, str) and bool(re.fullmatch(r\"[A-Za-z0-9._\\-^=+]+\", v))\n            and set(v) != {\".\"})","tryCatchPattern":"try:\n    safe_ticker_component(ticker)\nexcept ValueError as e:\n    if \"not allowed in a filesystem path\" in str(e):\n        # SECURITY signal: input may be prompt-injected; log & reject, don't sanitize blindly\n        security_log(f\"rejected suspicious ticker {ticker!r}\")\n        raise\n    raise","preventionTips":["Never interpolate raw LLM output into paths — always run it through safe_ticker_component or an allowlist regex","Treat '/' and '..' in ticker fields as prompt-injection attempts and alert, not just sanitize","Use canonical Yahoo forms ('BRK-B', '^GSPC', 'GC=F') instead of vendor-specific suffixed symbols"],"tags":["security","path-traversal","validation","prompt-injection","symbols"],"backgroundTag":null,"analyzedSha":"a33fd4c0f134485a43553a2c23a63cb14adbd88f","analyzedAt":"2026-08-14T19:45:16.920Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}