fix: code review — security fixes, dedup, cleanup

- setup: validate roles against users.VALID_ROLES (Supervisor was missing)
- files: fix path traversal prefix-match edge case (is_relative_to),
  dedupe path validation into resolve_upload_path(), use logging not print
- measurements: extract shared _build_measurement_filters() helper
- client app: prevent open redirect via Referer on /set-language
- maker: guard resp.json() in parse-technical-sheet proxy
- measure/maker: extract shared file proxy into services/file_proxy.py
- measure: localize supervisor validation error messages
- annotation-editor: remove global keydown listener in destroy()

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-11 12:16:29 +00:00
parent 25a788f430
commit fd571c479e
8 changed files with 141 additions and 134 deletions
+11 -15
View File
@@ -1,12 +1,13 @@
"""Maker blueprint - recipe creation and editing."""
import requests as http_requests
from flask import Blueprint, Response, flash, jsonify, redirect, render_template, request, session, url_for
from flask import Blueprint, flash, jsonify, redirect, render_template, request, session, url_for
from flask_babel import gettext as _
from blueprints.auth import login_required, role_required
from config import Config
from services.api_client import api_client
from services.file_proxy import proxy_file
maker_bp = Blueprint("maker", __name__, url_prefix="/maker")
@@ -380,19 +381,7 @@ def api_upload_file():
@login_required
def api_get_file(file_path: str):
"""Proxy: Serve file from API server (browser can't send X-API-Key)."""
api_key = session.get("api_key", "")
base_url = Config.API_SERVER_URL.rstrip("/")
resp = http_requests.get(
f"{base_url}/api/files/{file_path}",
headers={"X-API-Key": api_key},
timeout=30,
)
if resp.status_code != 200:
return Response(resp.text, status=resp.status_code)
return Response(
resp.content,
content_type=resp.headers.get("content-type", "application/octet-stream"),
)
return proxy_file(file_path)
@maker_bp.route("/api/files/<path:file_path>", methods=["DELETE"])
@@ -444,4 +433,11 @@ def api_parse_technical_sheet(recipe_id: int):
timeout=90,
)
return jsonify(resp.json()), resp.status_code
try:
payload = resp.json()
except ValueError:
payload = {
"error": True,
"detail": resp.text or f"HTTP {resp.status_code}",
}
return jsonify(payload), resp.status_code