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
- 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.
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
- 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.
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
- Invalid filename: would write outside the expected directory
- No chart generated
- No chart generated
- City '{city}' is not in the list of cities: {', '.join(citie
- Path '{path}' contains a dot-segment, which could lead to pa
AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13).
Data as JSON: /api/errors/45fbeeb54c99bb20.
Report an issue: GitHub.