From 7dc7caee268b6b26092d660fcfa145596f87fead Mon Sep 17 00:00:00 2001 From: Mischa Date: Sun, 21 Jun 2026 17:24:22 +0200 Subject: [PATCH] fix: sanitise trip slug on input, escape single quotes in YAML frontmatter Fix D: apply _sanitise_slug() to grav_trip_slug in POST /select before storing in TripState, preventing path traversal via ../sequences. Fix E: add _yaml_str() helper that doubles single quotes; apply to title, location_city, and location_country in both run_export and overwrite_export frontmatter blocks, preventing invalid YAML for values like Xi'an. Co-Authored-By: Claude Sonnet 4.6 --- services/travel-memories/app/routes/albums.py | 9 ++++++++- services/travel-memories/app/routes/export.py | 20 +++++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/services/travel-memories/app/routes/albums.py b/services/travel-memories/app/routes/albums.py index f82e554..96441e2 100644 --- a/services/travel-memories/app/routes/albums.py +++ b/services/travel-memories/app/routes/albums.py @@ -1,3 +1,4 @@ +import re from pathlib import Path from flask import Blueprint, current_app, redirect, render_template, request @@ -7,6 +8,12 @@ from app.state import TripState, Photo, load_state, save_state bp = Blueprint("albums", __name__) +def _sanitise_slug(s: str) -> str: + s = s.strip().lower() + s = re.sub(r'[^a-z0-9-]+', '-', s) + return s.strip('-') + + def _client(): return ImmichClient(current_app.config["IMMICH_URL"], current_app.config["IMMICH_API_KEY"]) @@ -31,7 +38,7 @@ def index(): @bp.post("/select") def select(): album_ids = request.form.getlist("album_ids[]") - grav_trip_slug = request.form["grav_trip_slug"].strip() + grav_trip_slug = _sanitise_slug(request.form["grav_trip_slug"]) start_over = request.form.get("start_over") == "1" if len(album_ids) == 1: diff --git a/services/travel-memories/app/routes/export.py b/services/travel-memories/app/routes/export.py index da32ab4..0cec923 100644 --- a/services/travel-memories/app/routes/export.py +++ b/services/travel-memories/app/routes/export.py @@ -16,6 +16,10 @@ def slugify(text: str) -> str: return re.sub(r"[\s_-]+", "-", text).strip("-") +def _yaml_str(s: str) -> str: + return s.replace("'", "''") + + def _client(): return ImmichClient( current_app.config["IMMICH_URL"], @@ -98,19 +102,19 @@ def run_export(): if group.entry_type == "journal": frontmatter = ( f"---\n" - f"title: '{group.title}'\n" + f"title: '{_yaml_str(group.title)}'\n" f"date: '{date_str}'\n" f"template: {template}\n" f"published: true\n" - f"location_city: '{group.location_city}'\n" - f"location_country: '{group.location_country}'\n" + f"location_city: '{_yaml_str(group.location_city)}'\n" + f"location_country: '{_yaml_str(group.location_country)}'\n" f"hero_image: {hero_filename or ''}\n" f"---\n" ) else: frontmatter = ( f"---\n" - f"title: '{group.title}'\n" + f"title: '{_yaml_str(group.title)}'\n" f"date: '{date_str}'\n" f"template: {template}\n" f"published: true\n" @@ -192,19 +196,19 @@ def overwrite_export(): if group.entry_type == "journal": frontmatter = ( f"---\n" - f"title: '{group.title}'\n" + f"title: '{_yaml_str(group.title)}'\n" f"date: '{date_str}'\n" f"template: {template}\n" f"published: true\n" - f"location_city: '{group.location_city}'\n" - f"location_country: '{group.location_country}'\n" + f"location_city: '{_yaml_str(group.location_city)}'\n" + f"location_country: '{_yaml_str(group.location_country)}'\n" f"hero_image: {hero_filename or ''}\n" f"---\n" ) else: frontmatter = ( f"---\n" - f"title: '{group.title}'\n" + f"title: '{_yaml_str(group.title)}'\n" f"date: '{date_str}'\n" f"template: {template}\n" f"published: true\n"