T09 · Insecure Skill Coding Practices
- Location
scripts/run.py:36- Finding
Workspace Path Traversal Enables Unauthorized File Reads and Writes
- Content
View full analysis
1 else workspace / "GOAL.md" out_path = workspace / outputs[0] if out_path.exists() and out_path.stat().st_size > 0: if not _is_placeholder(out_path.read_text(encoding="utf-8", errors="ignore")): return 0 taxonomy = load_yaml(taxonomy_path) if taxonomy_path.exists() else None if not isinstance(taxonomy, list) or not taxonomy: raise SystemExit(f"Invalid taxonomy in {taxonomy_path}") spec = load_workspace_pipeline_spec(workspace) target_h3 = int(getattr(spec, "core_chapter_h3_target", 0) or 3) goal_line = "" if goal_path.exists(): for raw in goal_path.read_text(encoding="utf-8", errors="ignore").splitlines(): line = raw.strip() if line and not line.startswith("#"): goal_line = line break skeleton: list[dict[str, Any]] = [] section_no = 3 for topic in taxonomy: if not isinstance(topic, dict): continue title = str(topic.get("name") or "").strip() if not title: continue desc = str(topic.get("description") or "").strip() children = topic.get("children") or [] seed_topics = [ str(child.get("name") or "").strip() for child in children if isinstance(child, dict) and str(child.get("name") or "").strip() ][: max(3, target_h3)] rationale = desc or (f"retrieval-informed chapter for {title}" if not goal_line else f"{title} within {goal_line}") skeleton.append( { "id": str(section_no), "title": title, ...[truncated 3295 chars]- Remediation
View remediation
Path: relative = Path(value) if relative.is_absolute(): raise ValueError(f"Absolute paths are not allowed: {value}") candidate = (workspace / relative).resolve() if not candidate.is_relative_to(workspace): raise ValueError(f"Path escapes workspace: {value}") return candidate ``` 2. Apply this validation consistently: ```python workspace = Path(args.workspace).resolve() taxonomy_path = confined_path(workspace, inputs[0]) goal_path = confined_path( workspace, inputs[1] if len(inputs) > 1 else "GOAL.md", ) out_path = confined_path(workspace, outputs[0]) ``` 3. Prefer an explicit output allowlist because the Skill declares only one output: ```python allowed_output = (workspace / "outline/chapter_skeleton.yml").resolve() if out_path != allowed_output: raise ValueError("Output must be outline/chapter_skeleton.yml") ``` 4. Restrict input paths to the documented files unless configurable inputs are essential: - `outline/taxonomy.yml` - `GOAL.md` 5. Validate resolved paths immediately before each filesystem operation to reduce time-of-check/time-of-use risk. In environments where untrusted users can modify workspace symlinks concurrently, use descriptor-based or sandbox-enforced filesystem confinement. 6. Add regression tests covering: - Absolute input and output paths. - `../` traversal. - Symlinks resolving outside the workspace. - Valid workspace-relative paths. - Attempts to replace existing empty or placeholder files outside the workspace. ]]>
