T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/forge.py:45
- Finding
- Path Traversal Allows File Creation Outside the Skills Directory## Vulnerability Details **File Location**: `scripts/forge.py`, lines 45-85 **Vulnerability Type**: Unvalidated path traversal in the `create` command **Risk Level**: High ### Vulnerable Code ```python def create_skill(slug, display_name, description, emoji, steps, bins="python3"): """创建技能骨架""" skills_dir = Path(os.environ.get('OPENCLAW_WORKDIR', '.')) / 'skills' skill_dir = skills_dir / slug if skill_dir.exists(): print(f"⚠️ 技能已存在: {skill_dir}") cont = input("覆盖? (y/N): ") if cont.lower() != 'y': print("取消") return # 创建目录 (skill_dir / 'scripts').mkdir(parents=True, exist_ok=True) # 格式化步骤 steps_yaml = "" for i, step in enumerate(steps, 1): steps_yaml += f"{i}. **{step.strip()}** \n" # 格式化参数表 params_table = "| 参数 | 说明 |\n|------|------|\n| `<input>` | 输入内容 |\n| `<output>` | 输出目标 |\n" sample_usage = f"# 根据 {slug} 技能的具体功能填写使用示例" # 写入 SKILL.md content = SKILL_TEMPLATE.format( name=slug, display_name=display_name, description=description, emoji=emoji, bins=bins, sample_usage=sample_usage, steps_yaml=steps_yaml, params_table=params_table, ) (skill_dir / 'SKILL.md').write_text(content, encoding='utf-8') # 创建空脚本 scripts_dir = skill_dir / 'scripts' init_file = scripts_dir / '__init__.py' if not init_file.exists(): init_file.write_text('', encoding='utf-8') ``` ### Technical Analysis The `slug` value originates from `sys.argv[2]` and is appended directly to the configured skills directory without validation or a resolved-path containment check. Python's `pathlib` accepts both parent-directory components and absolute paths. Consequently, values such as `../../target` or `/tmp/target` cause `skill_dir` to refer to a location outside `${OPENCLAW_WORKDIR}/skills`. The f ...[truncated 1977 chars]
- Remediation
- ## Remediation Suggestions - Restrict slugs to a conservative identifier format, such as `^[a-z0-9][a-z0-9-]*$`. - Reject absolute paths, path separators, `.` components, and `..` components. - Resolve both the skills root and candidate destination, then verify that the destination is strictly contained beneath the root: ```python import re if not re.fullmatch(r"[a-z0-9][a-z0-9-]*", slug): raise ValueError("Invalid skill slug") skills_dir = ( Path(os.environ.get("OPENCLAW_WORKDIR", ".")) / "skills" ).resolve() skill_dir = (skills_dir / slug).resolve() if skill_dir.parent != skills_dir: raise ValueError("Skill path escapes the skills directory") ``` - Consider symlink-based escapes. Validate resolved paths immediately before file operations and avoid following untrusted symlinks where platform APIs permit. - Refuse replacement by default and require an explicit `--force` option rather than relying on an interactive prompt. - Where feasible, create files with exclusive-create semantics to reduce accidental overwrites and race conditions.
