T09 · Insecure Skill Coding Practices
- Location
scripts/update_daily_log.py:157- Finding
Unvalidated Date Argument Enables Path Traversal and Arbitrary Markdown File Modification
- Content
View full analysis
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: MediumVulnerable 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 0The 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
View remediation
Remediation Suggestions
-
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") -
Construct the filename only from the normalized value:
python note_path = Path(args.daily_dir) / f"{safe_date}.md" -
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") -
Reject values containing path separators, absolute paths, parent-directory components, or any input that does not exactly match the required date representation.
-
Perform validation before checking whether the destination exists so an existing file cannot bypass validation.
-
If callers do not need configurable paths, remove or restrict
--daily-dirand--template. Otherwise, validate them against an explicit allowlisted vault root. -
Add regression tests covering:
- Valid
YYYY-MM-DDdates. ../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.
- Valid
-
