Remediate the 2026-09-03 feature-test findings

This commit is contained in:
2026-09-03 17:15:03 -04:00
parent cf95daf3bb
commit fd513c08eb
112 changed files with 5182 additions and 857 deletions

View File

@@ -2,6 +2,7 @@
import glob
import hashlib
import re
import os
from flask import Blueprint, request, current_app
@@ -20,6 +21,7 @@ from shopdb.utils.responses import (
)
from shopdb.utils.pagination import get_pagination_params, paginate_query
from shopdb.utils.import_mode import apply_import_timestamps
from shopdb.utils.datevalues import DateValueError, parse_date
# Uploads live in the instance dir, like model images and part photos, so a
@@ -326,6 +328,24 @@ def create_version(app_id: int):
if not data or not data.get('version'):
return error_response(ErrorCodes.VALIDATION_ERROR, 'version is required')
# A digest is a claim the delivery path CHECKS against, so a wrong one is
# worse than none: an over-long value was silently truncated to the column
# width and came back as 64 plausible hex characters, indistinguishable
# from a real SHA-256. Either it is a SHA-256 or it is refused.
digest = (data.get('sha256') or '').strip().lower()
if digest and not re.fullmatch(r'[0-9a-f]{64}', digest):
return error_response(
ErrorCodes.VALIDATION_ERROR,
'sha256 must be 64 hexadecimal characters')
# Refused rather than stored raw: a malformed date used to reach MySQL,
# which (before STRICT_TRANS_TABLES) wrote 0000-00-00 and then broke every
# read of this application.
try:
releasedate = parse_date(data.get('releasedate'), 'releasedate')
except DateValueError as bad:
return error_response(ErrorCodes.VALIDATION_ERROR, str(bad))
if AppVersion.query.filter_by(appid=app_id, version=data['version']).first():
return error_response(
ErrorCodes.CONFLICT,
@@ -336,9 +356,9 @@ def create_version(app_id: int):
version = AppVersion(
appid=app_id,
version=data['version'],
releasedate=data.get('releasedate'),
releasedate=releasedate,
notes=data.get('notes'),
sha256=data.get('sha256'),
sha256=digest or None,
silentargs=data.get('silentargs')
)
@@ -558,9 +578,20 @@ def upload_application_image(app_id: int):
@applications_bp.route('/image/<path:filename>', methods=['GET'])
@require_permission('applications.view')
def serve_application_image(filename):
"""Serve an uploaded application image (public - lists and tiles read it)."""
"""Serve an uploaded application image.
Ungated, like every other image route in the product (the branding logo,
the map blueprint, an employee photo, a printed-part picture). A browser
fetching <img src="/api/applications/image/application-3.png"> sends no
Authorization header, so a permission check here does not protect the file
- it just guarantees the picture never appears. The tile fell back to its
broken-image handler on every page, including for an admin.
What is exposed is an application icon to somebody who already knows the
filename, which is the same posture as its four siblings. Registered in
tests/test_core/test_public_read_surface.py with that reasoning.
"""
return send_upload(_appimage_dir(), filename)