{"record":{"id":"45fbeeb54c99bb20","repo":"microsoft/semantic-kernel","slug":"invalid-filename-would-write-outside-the-expected","errorCode":null,"errorMessage":"Invalid filename: would write outside the expected directory","messagePattern":"Invalid filename: would write outside the expected directory","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"python/samples/concepts/agents/bedrock_agent/bedrock_agent_with_code_interpreter.py","lineNumber":67,"sourceCode":"            if not binary_item:\n                binary_item = next((item for item in response.items if isinstance(item, BinaryContent)), None)\n    finally:\n        # Delete the agent\n        await bedrock_agent.delete_agent()\n        await thread.delete() if thread else None\n\n    # Save the chart to a file\n    if not binary_item:\n        raise RuntimeError(\"No chart generated\")\n\n    # Securely assemble the file path and validate it's within the expected directory\n    # This is a defense-in-depth measure against directory traversal attacks\n    output_dir = Path(__file__).parent.resolve()\n    file_path = (output_dir / binary_item.metadata[\"name\"]).resolve()\n\n    # Verify the resolved path is within the expected directory\n    if not file_path.is_relative_to(output_dir):\n        raise RuntimeError(\"Invalid filename: would write outside the expected directory\")\n\n    binary_item.write_to_file(file_path)\n    print(f\"Chart saved to {file_path}\")\n\n    # Sample output (using anthropic.claude-3-haiku-20240307-v1:0):\n    # Response:\n    # Here is the bar chart for the given data:\n    # [A bar chart showing the following data:\n    # Panda   5\n    # Tiger   8\n    # Lion    3\n    # Monkey  6\n    # Dolpin  2]\n    # Chart saved to ...\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/microsoft/semantic-kernel/blob/c028a0c7dc4f0814cdcbaba9d998f187a41197bf/python/samples/concepts/agents/bedrock_agent/bedrock_agent_with_code_interpreter.py#L49-L85","documentation":"A defense-in-depth RuntimeError in the Bedrock code-interpreter sample: it composes a file path from binary_item.metadata['name'], resolves it, and refuses to write unless the result stays within the script's own directory (Path(__file__).parent). It fires when the metadata-supplied name contains a traversal ('../', absolute paths, or symlinks) that would escape output_dir, blocking a directory-traversal write.","triggerScenarios":"binary_item.metadata['name'] resolves to something outside the script directory: e.g. '../../etc/passwd', an absolute path '/tmp/x', or a name with enough '..' segments to climb above output_dir.","commonSituations":"The model/code-interpreter returns a file name with path separators, a fully-qualified name, or '..' sequences; or a test that injects a crafted metadata name to validate the guard. Normally the model returns a bare filename, so this firing indicates either a malicious/odd file name or an environment where __file__ resolves unusually.","solutions":["Sanitize the name before composing the path: take os.path.basename(binary_item.metadata['name']) and strip any separators.","Confirm the metadata name is a plain filename; if the model emitted a path, regenerate requesting a simple name.","Keep the is_relative_to check as the secondary guard after sanitizing.","If legitimate, write to a dedicated safe subdir under output_dir using a generated name."],"exampleFix":"# before\noutput_dir = Path(__file__).parent.resolve()\nfile_path = (output_dir / binary_item.metadata[\"name\"]).resolve()\nif not file_path.is_relative_to(output_dir):\n    raise RuntimeError(\"Invalid filename: would write outside the expected directory\")\n# after - strip to basename first\nimport os\nsafe_name = os.path.basename(binary_item.metadata[\"name\"])\nfile_path = (output_dir / safe_name).resolve()\nif not file_path.is_relative_to(output_dir):\n    raise RuntimeError(\"Invalid filename: would write outside the expected directory\")","handlingStrategy":"validation","validationCode":"import os\nfrom pathlib import Path\n\ndef safe_join(output_dir: Path, name: str) -> Path:\n    safe = os.path.basename(str(name))              # strip any path components\n    target = (output_dir / safe).resolve()\n    if not target.is_relative_to(output_dir.resolve()):\n        raise RuntimeError(f\"Refusing to write outside {output_dir}: {name!r}\")\n    return target","typeGuard":"def is_safe_filename(name: object) -> bool:\n    s = str(name or \"\")\n    if not s or s.startswith((\"/\", \"\\\\\")) or \":\" in s[:3]:\n        return False\n    return os.path.basename(s) == s and \"..\" not in s.split(os.sep)","tryCatchPattern":"try:\n    file_path = safe_join(output_dir, binary_item.metadata[\"name\"])\n    binary_item.write_to_file(file_path)\nexcept RuntimeError as e:\n    # surface the unsafe name but do not write\n    logger.error(\"Refused unsafe write: %s\", e)","preventionTips":["Always reduce user/model-supplied filenames to os.path.basename before joining.","Keep an is_relative_to check as a secondary guard after resolving.","Write generated files into a dedicated, empty subdir to limit blast radius."],"tags":["python","sample","path-traversal","security","bedrock"],"backgroundTag":null,"analyzedSha":"c028a0c7dc4f0814cdcbaba9d998f187a41197bf","analyzedAt":"2026-08-13T13:48:05.040Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}