T09 · Insecure Skill Coding Practices
- Location
scripts/queue_task.py:44- Finding
Unsanitized Task Slug Allows Path Traversal and Arbitrary Filesystem Modification
- Content
View full analysis
Vulnerability Details
File Location:
scripts/queue_task.py, lines 44-47; filesystem write sinks at lines 76-104 and 148-149
Vulnerability Type: Path traversal / arbitrary directory targeting
Risk Level: HighVulnerable Code
python def resolve_dir(cfg: Dict[str, str], slug: str) -> pathlib.Path: ws = pathlib.Path(cfg['WORKSPACE_DIR']).expanduser().resolve() tasks_dir = cfg.get('TASKS_DIR', 'tasks') return ws / tasks_dir / slugThe returned path is subsequently used for filesystem creation and modification:
python def ensure_files(base: pathlib.Path, slug: str, with_task_docs: bool = False) -> Dict[str, pathlib.Path]: base.mkdir(parents=True, exist_ok=True) if with_task_docs: ensure_task_docs_if_missing(base, slug) p = { 'queue': base / 'queue.jsonl', 'progress': base / 'progress.json', 'done': base / 'done.jsonl', 'failed': base / 'failed.jsonl', 'lock': base / 'lock.json', } for k in ('queue', 'done', 'failed'): if not p[k].exists(): p[k].write_text('', encoding='utf-8') if not p['progress'].exists(): write_json(p['progress'], { 'task': slug, 'status': 'init', 'updatedAt': now_iso(), 'cursor': None, 'counts': {'processed': 0, 'done': 0, 'failed': 0, 'skipped': 0}, 'blocking_reason': None, 'layout': 'task-folder' if (base / 'TASK.md').exists() else 'standalone', }) if not p['lock'].exists(): p['lock'].write_text('', encoding='utf-8') return pThe stale-lock command can also truncate a targeted
lock.json:python except Exception: lock.write_text('', encoding='utf-8') print('cleared_unparsable_lock')Technical Analysis
The positional
slugargument is controlled by the command-line caller an ...[truncated 3168 chars]- Remediation
View remediation
Remediation Suggestions
Treat the slug as an identifier rather than a path:
-
Reject empty values, absolute paths, path separators,
.components, and..components. -
Apply a restrictive allowlist, such as letters, digits, underscores, and hyphens:
python import re SLUG_PATTERN = re.compile(r'^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$') def validate_slug(slug: str) -> str: if not SLUG_PATTERN.fullmatch(slug): raise ValueError('Invalid task slug') return slug -
Resolve the complete candidate path and verify that it remains beneath the resolved task root:
python def resolve_dir(cfg: Dict[str, str], slug: str) -> pathlib.Path: slug = validate_slug(slug) workspace = pathlib.Path(cfg['WORKSPACE_DIR']).expanduser().resolve() task_root = (workspace / cfg.get('TASKS_DIR', 'tasks')).resolve() candidate = (task_root / slug).resolve() if not candidate.is_relative_to(task_root): raise ValueError('Task directory escapes the configured task root') return candidate -
Validate
TASKS_DIRas well, particularly if configuration files may be modified by less-trusted users. Resolve the intended task root before performing containment checks. -
Refuse to follow symbolic links in task directories or state files where feasible. Use secure file-opening primitives and verify file type and ownership before truncating
lock.json. -
Do not clear an unparsable lock automatically. Report the error and require an explicit, separately authorized force option.
-
Add tests covering absolute slugs,
../traversal, nested separators, symbolic links, malformed locks, and paths exactly outside the configured task root. -
Run the skill under a least-privileged account with filesystem access limited to the configured task root as defense in depth.
-
