{"record":{"id":"5d7904018bfada67","repo":"langflow-ai/langflow","slug":"invalid-filename","errorCode":null,"errorMessage":"Invalid filename","messagePattern":"Invalid filename","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"src/backend/base/langflow/api/utils/flow_utils.py","lineNumber":171,"sourceCode":"    Called from any endpoint that accepts caller-supplied file references\n    under a public-access boundary.\n    \"\"\"\n    if not files:\n        return\n    expected_flow_id = str(source_flow_id).lower()\n    for entry in files:\n        if not isinstance(entry, str) or not entry:\n            raise HTTPException(status_code=400, detail=\"Invalid file entry\")\n        if any(token in entry for token in _PUBLIC_FILE_REJECTED_SUBSTRINGS):\n            raise HTTPException(status_code=400, detail=\"Invalid file path\")\n        match = _PUBLIC_FILE_PATH_RE.match(entry)\n        if not match:\n            raise HTTPException(status_code=400, detail=\"Invalid file path format\")\n        flow_id_segment, basename = match.group(1), match.group(2)\n        if flow_id_segment.lower() != expected_flow_id:\n            raise HTTPException(status_code=400, detail=\"File not in this flow's namespace\")\n        if basename in (\".\", \"..\"):\n            raise HTTPException(status_code=400, detail=\"Invalid filename\")\n\n\ndef compute_virtual_flow_id(\n    identifier: str | uuid.UUID,\n    flow_id: uuid.UUID,\n    *,\n    principal_type: Literal[\"user\", \"client\"] | None = None,\n) -> uuid.UUID:\n    \"\"\"Compute a deterministic virtual flow ID for session/message isolation.\n\n    Args:\n        identifier: A unique identifier (user_id for authenticated users, client_id for anonymous).\n        flow_id: The original flow ID.\n        principal_type: Optional identity domain for public-flow callers. Authenticated\n            user IDs and anonymous client IDs must never share a UUID namespace.\n\n    Returns:\n        A deterministic UUID v5 derived from the identifier and flow_id.","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/utils/flow_utils.py#L153-L189","documentation":"validate_public_files() rejects a file reference whose basename is exactly '.' after the path already passed the format and namespace checks. It is part of the hardening for GHSA-rcjh-r59h-gq37, which restricted unauthenticated public-flow builds to file paths of the form {source_flow_id}/{basename}. Although the '..' substring is already rejected earlier, a lone '.' basename (the current directory) is blocked here as an extra guard so anonymous callers cannot address the flow's directory itself.","triggerScenarios":"A caller of a public-flow build/upload endpoint passes a files list entry like '3f0b.../.' (flow UUID matching the public flow, basename exactly '.'). Note that entries containing '..' anywhere, backslashes, NUL bytes, non-UUID prefixes, or a foreign flow UUID fail earlier with 'Invalid file path'/'Invalid file path format'/'File not in this flow's namespace' instead.","commonSituations":"Client code that builds file paths by string concatenation (os.path.join or f-strings) and accidentally appends an empty segment; scripts replaying captured upload requests after path normalization (os.path.normpath turns './' into '.'); crafted probes against a shared public flow trying to escape the {flow_id}/ namespace.","solutions":["Fix the client to send only plain basenames under the flow's own UUID, e.g. '{flow_id}/report.pdf' — never '.' or '..'.","If you construct paths programmatically, split on '/' and drop empty components instead of using normpath before sending to the API.","If you legitimately need directory-like grouping, upload under a flattened name (e.g. 'folder_report.pdf'); the API only accepts a single path segment.","If you operate the server and see floods of these, treat them as probes of GHSA-rcjh-r59h-gq37 and rate-limit the endpoint."],"exampleFix":"# before\nfiles = [f\"{flow_id}/{os.path.normpath(rel_path)}\"]  # normpath('....') can yield '.'\n\n# after\nrel = rel_path.strip('/').split('/')[-1]  # keep basename only\nif rel in ('.', '..'):\n    raise ValueError(f\"bad file path: {rel_path}\")\nfiles = [f\"{flow_id}/{rel}\"]","handlingStrategy":"validation","validationCode":"import re\nPATH_RE = re.compile(r'^([0-9a-fA-F-]{36})/([^/\\\\]+)$')\n\ndef safe_public_files(files, flow_id):\n    fid = str(flow_id).lower()\n    out = []\n    for f in files or []:\n        m = PATH_RE.match(f)\n        if (not m or m.group(1).lower() != fid or m.group(2) in ('.', '..')\n                or any(t in f for t in ('..', '\\\\', '\\x00'))):\n            raise ValueError(f'unsafe file entry: {f!r}')\n        out.append(f)\n    return out","typeGuard":null,"tryCatchPattern":"try:\n    validate_public_files(files, flow_id)\nexcept HTTPException as e:\n    if e.status_code == 400 and e.detail == 'Invalid filename':\n        # strip '.'/'..' entries client-side and rebuild the list\n        ...\n    raise","preventionTips":["Build file references as f'{flow_id}/{basename}' from a trusted basename only.","Never run os.path.normpath on entries before sending — it can produce '.'.","Reject '.', '..' and empty segments client-side before the request."],"tags":["security","path-traversal","public-flow","validation","http-400"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}