{"record":{"id":"ee352ecbf2df3d2f","repo":"shareAI-lab/learn-claude-code","slug":"invalid-memory-filename-filename","errorCode":null,"errorMessage":"Invalid memory filename: {filename}","messagePattern":"Invalid memory filename: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"s09_memory/code.py","lineNumber":90,"sourceCode":"        return {}, text\n    parts = text.split(\"---\", 2)\n    if len(parts) < 3:\n        return {}, text\n    try:\n        metadata = yaml.safe_load(parts[1]) or {}\n    except yaml.YAMLError:\n        return {}, text\n    if not isinstance(metadata, dict):\n        return {}, text\n    return metadata, parts[2].lstrip()\n\ndef memory_slug(name: str) -> str:\n    slug = re.sub(r\"[^\\w]+\", \"-\", name.lower()).strip(\"-_\")\n    return slug or \"memory\"\n\ndef memory_path(filename: str, allow_index: bool = False) -> Path:\n    if Path(filename).name != filename:\n        raise ValueError(f\"Invalid memory filename: {filename}\")\n    if filename == MEMORY_INDEX.name and not allow_index:\n        raise ValueError(\"The memory index is not a memory record\")\n\n    root = MEMORY_DIR.resolve()\n    if not root.is_relative_to(WORKDIR.resolve()):\n        raise ValueError(\"Memory directory escapes the workspace\")\n    path = (root / filename).resolve()\n    if not path.is_relative_to(root):\n        raise ValueError(f\"Memory path escapes the store: {filename}\")\n    return path\n\ndef _memory_slug(name: str) -> str:\n    return memory_slug(name)\n\ndef _normalized_memory_text(value: str) -> str:\n    return \" \".join(value.lower().split())\n\ndef should_store_memory(candidate: dict, existing: list[dict]) -> bool:","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s09_memory/code.py#L72-L108","documentation":"Raised by memory_path() in s09_memory/code.py when the supplied filename contains a path separator — Path(filename).name != filename detects any directory component. Filenames must be bare names because memory records are stored flat inside MEMORY_DIR; a separator would allow writing outside the store. Both '../' traversal and innocent subdirectory paths like 'notes/2024.md' are rejected.","triggerScenarios":"Calling a memory tool with filename=\"../index.py\", filename=\"subdir/note.md\", or an absolute path \"/tmp/x.md\" (its .name differs). On Windows, backslash-containing names also fail this check.","commonSituations":"LLMs trying to organize memories into folders; paths copied wholesale from other tool output; traversal attempts by prompt-injected content reaching the memory tool.","solutions":["Flatten the name: use memory_slug() or manual replacement of '/' with '-' to get a bare filename","Store only the record name; if you need hierarchy, encode it in the name ('notes-2024.md')","Validate with Path(f).name == f before calling the memory tool"],"exampleFix":"// before\nmemory_write(filename=\"notes/2024-08.md\", ...)\n// after\nmemory_write(filename=\"notes-2024-08.md\", ...)","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef flat_filename(name: str) -> str:\n    return Path(name).name if Path(name).name == name else name.replace('/', '-').replace('\\\\', '-')\n\nassert Path(fn).name == fn, 'filename must be bare (no directories)'\nmemory_write(filename=flat_filename(user_name), ...)","typeGuard":"def is_flat_filename(filename: object) -> bool:\n    return isinstance(filename, str) and bool(filename) and Path(filename).name == filename","tryCatchPattern":"try:\n    memory_path(fn)\nexcept ValueError as e:\n    if 'Invalid memory filename' in str(e):\n        fn = fn.replace('/', '-')\n        memory_path(fn)\n    else:\n        raise","preventionTips":["Store records as flat names; encode hierarchy with dashes","Run memory_slug() over free-form names before use","Never pass directory listings or absolute paths as memory filenames"],"tags":["memory","security","path-validation","filesystem"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}