T09 · Insecure Skill Coding Practices
- Location
scripts/checkpoint_tool.py:18- Finding
Path Traversal Enables Writes Outside the Checkpoint Directory
- Content
View full analysis
Vulnerability Details
File Location:
scripts/checkpoint_tool.py, lines 14-23, 37-57, and 145-147
Vulnerability Type: Path traversal and arbitrary file overwrite
Risk Level: HighVulnerable Code
python ROOT = Path.cwd() / "memory" / "checkpoints" ROOT.mkdir(parents=True, exist_ok=True) def cp_path(workflow_id: str) -> Path: return ROOT / f"{workflow_id}.json" def lock_path(workflow_id: str) -> Path: return ROOT / f"{workflow_id}.lock"python def cmd_start(args): p = cp_path(args.workflow) if p.exists() and not args.force: raise SystemExit(f"checkpoint exists: {p} (use --force)") obj = { "schemaVersion": "1.0", "workflowId": args.workflow, "agentId": args.agent, "status": "in_progress", "currentStep": args.step, "completedSteps": [], "remainingSteps": args.remaining, "handoffTo": args.handoff, "notes": args.notes or "", "idempotencyKey": args.idempotency, "updatedAt": now_iso(), "expiresAt": (datetime.now(timezone.utc) + timedelta(days=args.ttl_days)).isoformat(), } obj["checkpointHash"] = calc_hash({k: v for k, v in obj.items() if k != "checkpointHash"}) atomic_write(p, obj) print(p)python s.add_argument("--workflow", required=True) ... s.add_argument("--force", action="store_true")Technical Analysis
The value supplied through
--workflowis inserted directly into a filesystem path without validating that it is a simple identifier. Python'spathlibpreserves traversal components such as../; an absolute path component can also replace the intended base path during path composition.Consequently,
cp_path()does not guarantee that the resulting checkpoint path remains belowmemory/checkpoints. The same defect affectslock_path(). Theatomic_write()operation creates a temp ...[truncated 2049 chars]- Remediation
View remediation
Remediation Suggestions
-
Restrict workflow identifiers to a conservative allowlist, such as
^[A-Za-z0-9_-]+$. -
Explicitly reject absolute paths, path separators,
.and..components, and empty identifiers. -
Resolve every generated destination and verify that it remains under the resolved checkpoint root before reading or writing:
python import re WORKFLOW_ID = re.compile(r"^[A-Za-z0-9_-]+$") def safe_path(workflow_id: str, suffix: str) -> Path: if not WORKFLOW_ID.fullmatch(workflow_id): raise ValueError("Invalid workflow identifier") root = ROOT.resolve() destination = (root / f"{workflow_id}{suffix}").resolve() if destination.parent != root: raise ValueError("Checkpoint path escapes the checkpoint directory") return destination -
Apply the same validation to checkpoint and lock paths.
-
Revalidate
workflowIdvalues read from checkpoint files rather than trusting persisted data. -
Avoid exposing overwrite behavior unless it is operationally necessary. If replacement is needed, verify that the destination is a valid checkpoint owned by the expected workflow before replacing it.
-
Add tests using
../, nested paths, absolute paths, platform-specific separators, symbolic links, and existing external files.
-
