{"record":{"id":"882d408829afb866","repo":"TauricResearch/TradingAgents","slug":"ticker-exceeds-max-len-chars-value-r","errorCode":null,"errorMessage":"ticker exceeds {max_len} chars: {value!r}","messagePattern":"ticker exceeds (.+?) chars: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"tradingagents/dataflows/utils.py","lineNumber":32,"sourceCode":"_TICKER_PATH_RE = re.compile(r\"^[A-Za-z0-9._\\-\\^=+]+$\")\n\n\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","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/TauricResearch/TradingAgents/blob/a33fd4c0f134485a43553a2c23a63cb14adbd88f/tradingagents/dataflows/utils.py#L14-L50","documentation":"Raised by safe_ticker_component() in tradingagents/dataflows/utils.py when the string is longer than max_len (default 32). It bounds the ticker before it is used as a filesystem path component — the guard exists because overlong or hostile values flow into os.path.join/Path from user or LLM input.","triggerScenarios":"Passing full company names, sentences, pasted text blobs, or prompt-injected content as a ticker; concatenating exchange prefixes/suffixes beyond 32 chars (rare for real tickers, which are short).","commonSituations":"LLM passing the company name instead of the symbol ('Apple Inc.' style, or worse, whole phrases); upstream schemas losing validation; users pasting ISINs plus descriptions.","solutions":["Pass the actual exchange ticker (almost always < 10 chars)","Validate/truncate LLM tool output before it reaches dataflows: extract the symbol with a regex like ^[A-Za-z0-9._^=+-]{1,16}$","Raise max_len only if you genuinely support longer path components (call-site kwarg), not to paper over bad input"],"exampleFix":"# before\nsafe_ticker_component(\"Apple Incorporated Class A Common Stock\")\n# -> ValueError: ticker exceeds 32 chars: ...\n\n# after\nsafe_ticker_component(\"AAPL\")\n# or constrain LLM output first\nimport re\nm = re.search(r\"\\b[A-Z]{1,5}(?:\\.[A-Z]{1,2})?\\b\", raw)\nsafe_ticker_component(m.group(0)) if m else reject()","handlingStrategy":"validation","validationCode":"def ticker_length_ok(value: str, max_len: int = 32) -> bool:\n    return isinstance(value, str) and 0 < len(value) <= max_len","typeGuard":"def looks_like_ticker(v) -> bool:\n    import re\n    return isinstance(v, str) and bool(re.fullmatch(r\"[A-Za-z0-9._\\-^=+]{1,16}\", v)) and set(v) != {\".\"}","tryCatchPattern":"try:\n    safe_ticker_component(raw)\nexcept ValueError as e:\n    if \"exceeds\" in str(e):\n        m = re.search(r\"[A-Za-z0-9._\\-^=+]{1,16}\", raw)   # extract the embedded symbol\n        raw = m.group(0) if m else None\n    raise","preventionTips":["Constrain LLM tool output with a regex extracting the exchange symbol from free text","Map company names to symbols via a lookup table before calling data APIs","Real tickers are short — anything over ~16 chars is almost certainly not a ticker"],"tags":["validation","security","path-traversal","llm-input","symbols"],"backgroundTag":null,"analyzedSha":"a33fd4c0f134485a43553a2c23a63cb14adbd88f","analyzedAt":"2026-08-14T19:45:16.920Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}