T09 · Insecure Skill Coding Practices
- Location
scripts/add_branch.py:12- Finding
Path Traversal Allows Arbitrary File Overwrite and Modification
- Content
View full analysis
Vulnerability Details
File Location:
scripts/add_branch.py, lines 12-48
Vulnerability Type: Path traversal and unrestricted filesystem write
Risk Level: HighVulnerable Code
python base_path = "/root/.openclaw/workspace/memory" if type == "domain": # Create a new domain file file_path = f"{base_path}/domains/{name.lower()}.md" content = f"# 🌐 DOMAIN: {name.capitalize()}\n\nDetails about {name}.\n\n---\n*Back to Root:* `MEMORY.md`" link_line = f"\n- **[{name.capitalize()}]** -> `memory/domains/{name.lower()}.md`" root_file = "/root/.openclaw/workspace/MEMORY.md" # Create directory if it doesn't exist os.makedirs(os.path.dirname(file_path), exist_ok=True) # Write the new markdown file with open(file_path, "w") as f: f.write(content) # Append a link to the root memory map with open(root_file, "a") as f: f.write(link_line) elif type == "project": # Create a new project file file_path = f"{base_path}/projects/{name.lower()}.md" content = f"# 🏗️ PROJECT: {name.capitalize()}\n\nStatus and details for {name}.\n\n---\n*Back to Domain:* `{parent_file}`" link_line = f"\n1. **[{name.capitalize()}]** -> `memory/projects/{name.lower()}.md`" # Create directory if it doesn't exist os.makedirs(os.path.dirname(file_path), exist_ok=True) # Write the new markdown file with open(file_path, "w") as f: f.write(content) # If a parent domain file is specified, append a link to it if parent_file: full_parent_path = f"/root/.openclaw/workspace/{parent_file}" if os.path.exists(full_parent_path): with open(full_parent_path, "a") as f: f.write(link_line)Technical Analysis
The command-line arguments
nameandparent_fileare incorporated directly into filesystem paths without ...[truncated 2960 chars]- Remediation
View remediation
Remediation Suggestions
-
Restrict branch names to a safe filename grammar. For example, accept only ASCII letters, digits, underscores, and hyphens:
python import re if not re.fullmatch(r"[A-Za-z0-9_-]+", name): raise ValueError("Branch name contains unsupported characters") -
Build and resolve paths with
pathlib.Path, then verify that the resolved result remains under its designated base directory:python from pathlib import Path branch_root = Path(base_path, "domains").resolve() file_path = (branch_root / f"{name.lower()}.md").resolve() try: file_path.relative_to(branch_root) except ValueError: raise ValueError("Branch path escapes the permitted directory") -
Apply an equivalent containment check to project paths.
-
Do not accept arbitrary workspace-relative parent paths. Resolve parents exclusively beneath the known domain directory:
python domain_root = Path(base_path, "domains").resolve() parent_path = (domain_root / parent_file).resolve() try: parent_path.relative_to(domain_root) except ValueError: raise ValueError("Parent must be inside memory/domains") -
Reject absolute paths,
.and..components, path separators in logical branch names, and parent files that do not have the expected.mdextension. -
Use exclusive creation mode (
"x") when a new branch must not replace an existing file. If replacement is an intended feature, require an explicit overwrite option and perform the same containment validation first. -
Consider rejecting symlink targets by inspecting path components and opening files using platform protections such as
O_NOFOLLOWwhere available. -
Run the helper under a least-privileged account with write access limited to the intended memory directories rather than relying on root-owned hardcoded paths.
-
