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

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.

Source

Thrown at python/samples/concepts/agents/bedrock_agent/bedrock_agent_with_code_interpreter.py:67

            if not binary_item:
                binary_item = next((item for item in response.items if isinstance(item, BinaryContent)), None)
    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. Sanitize the name before composing the path: take os.path.basename(binary_item.metadata['name']) and strip any separators.
  2. Confirm the metadata name is a plain filename; if the model emitted a path, regenerate requesting a simple name.
  3. Keep the is_relative_to check as the secondary guard after sanitizing.
  4. If legitimate, write to a dedicated safe subdir under output_dir using a generated name.

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 - strip to basename first
import os
safe_name = os.path.basename(binary_item.metadata["name"])
file_path = (output_dir / safe_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))              # strip any path components
    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 "")
    if not s or s.startswith(("/", "\\")) or ":" in s[:3]:
        return False
    return os.path.basename(s) == s and ".." not in s.split(os.sep)

Try / catch

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

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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