T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/update_daily_log.py:157
- Finding
- Unvalidated Date Argument Enables Path Traversal and Arbitrary Markdown File Modification## Vulnerability Details **File Location**: `scripts/update_daily_log.py`, lines 157 and 170-178 **Vulnerability Type**: Path traversal caused by insufficient input validation **Risk Level**: Medium ### Vulnerable Code ```python def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Append timestamped entries to an Obsidian daily note.") parser.add_argument("--date", required=True, help="Date in YYYY-MM-DD") parser.add_argument("--time", action="append", required=True, help="Time for one entry (repeat for multiple entries)") parser.add_argument("--text", action="append", required=True, help="Activity text for one entry (repeat in same order as --time)") parser.add_argument("--location", action="append", help="Optional location for one entry (repeat in same order)") parser.add_argument("--tags", action="append", help="Optional tags for one entry (repeat in same order)") parser.add_argument("--mode", choices=["bullets", "table"], default="bullets") parser.add_argument("--daily-dir", default=str(DEFAULT_DAILY_DIR)) parser.add_argument("--template", default=str(DEFAULT_TEMPLATE)) return parser.parse_args() ``` ```python def main() -> int: args = parse_args() entries = build_entries(args) note_path = Path(args.daily_dir) / f"{args.date}.md" template_path = Path(args.template) ensure_note(note_path, args.date, template_path) content = note_path.read_text(encoding="utf-8") updated = update_timeline(content, entries, args.mode) note_path.write_text(updated, encoding="utf-8") print(str(note_path)) return 0 ``` The validation bypass is enabled by `scripts/update_daily_log.py`, lines 92-95: ```python def ensure_note(note_path: Path, date_str: str, template_path: Path) -> None: if note_path.exists(): return note_path.parent.mkdir(parents=True, exist_ok=True) note_path.write_text(load_template(date_str, template_path), encoding="utf-8") ` ...[truncated 2882 chars]
- Remediation
- ## Remediation Suggestions 1. Validate the date unconditionally before any filesystem operation: ```python parsed_date = datetime.strptime(args.date, "%Y-%m-%d") safe_date = parsed_date.strftime("%Y-%m-%d") ``` 2. Construct the filename only from the normalized value: ```python note_path = Path(args.daily_dir) / f"{safe_date}.md" ``` 3. Resolve the base directory and destination and enforce containment: ```python daily_dir = Path(args.daily_dir).resolve() note_path = (daily_dir / f"{safe_date}.md").resolve() if note_path.parent != daily_dir: raise ValueError("Daily note path must remain inside the daily-note directory") ``` 4. Reject values containing path separators, absolute paths, parent-directory components, or any input that does not exactly match the required date representation. 5. Perform validation before checking whether the destination exists so an existing file cannot bypass validation. 6. If callers do not need configurable paths, remove or restrict `--daily-dir` and `--template`. Otherwise, validate them against an explicit allowlisted vault root. 7. Add regression tests covering: - Valid `YYYY-MM-DD` dates. - `../` and `..\` traversal attempts. - Absolute-path input. - Traversal to existing and nonexistent files. - Encoded or mixed-separator path variants. - Verification that the resolved destination is a direct child of the configured daily-note directory.
