microsoft/semantic-kernel · error · RuntimeError

Invalid filename: would write outside the expected directory

Error message

Invalid filename: would write outside the expected directory

What it means

Same path-traversal guard as error 626, in the streaming Bedrock sample. After streaming, the sample composes output_dir / binary_item.metadata['name'], resolves it, and raises RuntimeError if the resolved path is not within the script directory. It blocks writes that would escape via '..', absolute paths, or symlinked names.

Source

Thrown at python/samples/concepts/agents/bedrock_agent/bedrock_agent_with_code_interpreter_streaming.py:69

                binary_item = next((item for item in response.items if isinstance(item, BinaryContent)), None)
        print()
    finally:
        # Delete the agent
        await bedrock_agent.delete_agent()
        await thread.delete() if thread else None

    # Save the chart to a file
    if not binary_item:
        raise RuntimeError("No chart generated")

    # Securely assemble the file path and validate it's within the expected directory
    # This is a defense-in-depth measure against directory traversal attacks
    output_dir = Path(__file__).parent.resolve()
    file_path = (output_dir / binary_item.metadata["name"]).resolve()

    # Verify the resolved path is within the expected directory
    if not file_path.is_relative_to(output_dir):
        raise RuntimeError("Invalid filename: would write outside the expected directory")

    binary_item.write_to_file(file_path)
    print(f"Chart saved to {file_path}")

    # Sample output (using anthropic.claude-3-haiku-20240307-v1:0):
    # Response:
    # Here is the bar chart for the given data:
    # [A bar chart showing the following data:
    # Panda   5
    # Tiger   8
    # Lion    3
    # Monkey  6
    # Dolpin  2]
    # Chart saved to ...


if __name__ == "__main__":
    asyncio.run(main())

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Reduce the name to its basename with os.path.basename before joining.
  2. Regenerate the chart asking the model for a simple filename if it returned a path-like name.
  3. Write to a dedicated safe output subdir using a generated name.
  4. Keep the is_relative_to check as a secondary guard.

Example fix

# before
output_dir = Path(__file__).parent.resolve()
file_path = (output_dir / binary_item.metadata["name"]).resolve()
if not file_path.is_relative_to(output_dir):
    raise RuntimeError("Invalid filename: would write outside the expected directory")
# after
import os
file_path = (output_dir / os.path.basename(binary_item.metadata["name"])).resolve()
if not file_path.is_relative_to(output_dir):
    raise RuntimeError("Invalid filename: would write outside the expected directory")
Defensive patterns

Strategy: validation

Validate before calling

import os
from pathlib import Path

def safe_join(output_dir: Path, name: str) -> Path:
    safe = os.path.basename(str(name))
    target = (output_dir / safe).resolve()
    if not target.is_relative_to(output_dir.resolve()):
        raise RuntimeError(f"Refusing to write outside {output_dir}: {name!r}")
    return target

Type guard

def is_safe_filename(name: object) -> bool:
    s = str(name or "")
    return bool(s) and os.path.basename(s) == s and "/" not in s and "\\" not in s and ".." not in s

Try / catch

try:
    file_path = safe_join(output_dir, binary_item.metadata["name"])
    binary_item.write_to_file(file_path)
except RuntimeError as e:
    logger.error("Refused unsafe write: %s", e)

Prevention

When it happens

Trigger: binary_item.metadata['name'] (from the streamed BinaryContent) resolves outside Path(__file__).parent - e.g. '../../x', '/abs/path', or a name with path separators that climbs out of the directory.

Common situations: The model/code-interpreter returns a file name containing path separators or an absolute path; a crafted/malformed metadata name; or running the sample in an environment where __file__ resolves oddly.

Related errors


AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13). Data as JSON: /api/errors/63dc5a3a2d444aba. Report an issue: GitHub.