From d80ec4657ebee60e14d4dcd38d05a60683b9acd9 Mon Sep 17 00:00:00 2001 From: chen Date: Tue, 26 May 2026 14:51:26 +0800 Subject: [PATCH 1/3] fix: validate vintage evidence images --- tests/test_validate_vintage_submission.py | 60 ++++++++--- tools/validate_vintage_submission.py | 123 +++++++++++++++------- 2 files changed, 130 insertions(+), 53 deletions(-) diff --git a/tests/test_validate_vintage_submission.py b/tests/test_validate_vintage_submission.py index db4783bf4..4a80dabb8 100644 --- a/tests/test_validate_vintage_submission.py +++ b/tests/test_validate_vintage_submission.py @@ -1,5 +1,7 @@ # SPDX-License-Identifier: MIT import importlib.util +import struct +import zlib from pathlib import Path @@ -11,6 +13,24 @@ SubmissionValidator = validate_vintage_submission.SubmissionValidator +def write_png(path: Path, width: int = 640, height: int = 480) -> None: + def chunk(kind: bytes, data: bytes) -> bytes: + return ( + struct.pack(">I", len(data)) + + kind + + data + + struct.pack(">I", zlib.crc32(kind + data) & 0xFFFFFFFF) + ) + + raw_rows = b"".join(b"\x00" + (b"\xff\xff\xff" * width) for _ in range(height)) + path.write_bytes( + b"\x89PNG\r\n\x1a\n" + + chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0)) + + chunk(b"IDAT", zlib.compress(raw_rows)) + + chunk(b"IEND", b"") + ) + + def test_wallet_validation_accepts_rtc1_alphanumeric_range(): validator = SubmissionValidator() @@ -49,32 +69,42 @@ def test_attestation_log_json_requires_core_fields(tmp_path): assert result["checks"]["json_valid"] is True -def test_photo_validation_preserves_size_and_format_warnings(tmp_path): +def test_photo_validation_rejects_non_image_content(tmp_path): validator = SubmissionValidator() - photo_path = tmp_path / "photo.txt" - photo_path.write_bytes(b"tiny") + photo_path = tmp_path / "photo.jpg" + photo_path.write_bytes(b"not an image" * 1000) result = validator.validate_photo(str(photo_path)) - assert result["status"] == "WARN" - assert "too small" in result["message"] - assert "Unusual photo format" in result["message"] - assert result["checks"]["file_size_bytes"] == 4 - assert result["checks"]["format"] == ".txt" - assert "Photo file is unusually small" in validator.warnings + assert result["status"] == "FAIL" + assert "not a valid image" in result["message"] + assert result["checks"]["file_size_bytes"] == 12000 + assert result["checks"]["format"] == ".jpg" + + +def test_photo_validation_accepts_real_png_with_dimensions(tmp_path): + validator = SubmissionValidator() + photo_path = tmp_path / "photo.png" + write_png(photo_path) + + result = validator.validate_photo(str(photo_path)) + assert result["status"] == "PASS" + assert result["checks"]["image_type"] == "png" + assert result["checks"]["width"] == 640 + assert result["checks"]["height"] == 480 -def test_screenshot_validation_preserves_small_file_warning(tmp_path): + +def test_screenshot_validation_rejects_non_image_content(tmp_path): validator = SubmissionValidator() screenshot_path = tmp_path / "screenshot.png" - screenshot_path.write_bytes(b"tiny") + screenshot_path.write_bytes(b"not an image" * 1000) result = validator.validate_screenshot(str(screenshot_path)) - assert result["status"] == "WARN" - assert "too small" in result["message"] - assert result["checks"]["file_size_bytes"] == 4 - assert "Screenshot file is unusually small" in validator.warnings + assert result["status"] == "FAIL" + assert "not a valid image" in result["message"] + assert result["checks"]["file_size_bytes"] == 12000 def test_validate_submission_extracts_arch_and_bounty_from_valid_log(tmp_path): diff --git a/tools/validate_vintage_submission.py b/tools/validate_vintage_submission.py index d8ee26d11..a392ab6ec 100644 --- a/tools/validate_vintage_submission.py +++ b/tools/validate_vintage_submission.py @@ -17,6 +17,7 @@ import argparse import json import os +import struct import sys from datetime import datetime from typing import Dict, Any, Optional @@ -29,6 +30,84 @@ def __init__(self): self.checks: Dict[str, Dict[str, Any]] = {} self.errors: list = [] self.warnings: list = [] + + def _read_image_metadata(self, image_path: str) -> Dict[str, Any]: + """Read basic image type and dimensions without optional dependencies.""" + with open(image_path, "rb") as f: + header = f.read(32) + + if header.startswith(b"\x89PNG\r\n\x1a\n") and header[12:16] == b"IHDR": + width, height = struct.unpack(">II", header[16:24]) + return {"image_type": "png", "width": width, "height": height} + + if header[:6] in (b"GIF87a", b"GIF89a"): + width, height = struct.unpack("H", length_bytes)[0] + if segment_length < 2: + break + + if marker in b"\xc0\xc1\xc2\xc3\xc5\xc6\xc7\xc9\xca\xcb\xcd\xce\xcf": + segment = f.read(5) + if len(segment) != 5: + break + height, width = struct.unpack(">HH", segment[1:5]) + return {"image_type": "jpeg", "width": width, "height": height} + + f.seek(segment_length - 2, os.SEEK_CUR) + + raise ValueError("not a valid image") + + def _validate_image_file(self, image_path: str, label: str) -> Dict[str, Any]: + file_size = os.path.getsize(image_path) + ext = os.path.splitext(image_path)[1].lower() + checks = { + "file_exists": True, + "file_size_bytes": file_size, + "format": ext, + } + + try: + metadata = self._read_image_metadata(image_path) + except Exception as exc: + return { + "status": "FAIL", + "message": f"{label} is not a valid image: {exc}", + "checks": checks, + } + + checks.update(metadata) + warnings = [] + if metadata["width"] < 640 or metadata["height"] < 480: + warnings.append(f"{label} resolution is too small: {metadata['width']}x{metadata['height']}") + + return { + "status": "WARN" if warnings else "PASS", + "message": "; ".join(warnings) if warnings else f"{label} is a valid image", + "checks": checks, + } def validate_photo(self, photo_path: str) -> Dict[str, Any]: """Validate photo evidence""" @@ -43,37 +122,20 @@ def validate_photo(self, photo_path: str) -> Dict[str, Any]: result["message"] = f"Photo file not found: {photo_path}" return result - warning_messages = [] - - # Check file size (should be reasonable) - file_size = os.path.getsize(photo_path) - if file_size < 10000: # Less than 10KB - warning_messages.append(f"Photo file seems too small: {file_size} bytes") - self.warnings.append("Photo file is unusually small") - # Check file extension ext = os.path.splitext(photo_path)[1].lower() if ext not in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']: - warning_messages.append(f"Unusual photo format: {ext}") self.warnings.append(f"Unusual photo format: {ext}") - + # In production, would check: # - EXIF timestamp # - Image content (machine + monitor) # - Metadata consistency - - if warning_messages: + + result = self._validate_image_file(photo_path, "Photo file") + if ext not in ['.jpg', '.jpeg', '.png', '.gif', '.bmp'] and result["status"] == "PASS": result["status"] = "WARN" - result["message"] = "; ".join(warning_messages) - else: - result["status"] = "PASS" - result["message"] = "Photo file exists and appears valid" - result["checks"] = { - "file_exists": True, - "file_size_bytes": file_size, - "format": ext - } - + result["message"] = f"Unusual photo format: {ext}" return result def validate_screenshot(self, screenshot_path: str) -> Dict[str, Any]: @@ -89,22 +151,7 @@ def validate_screenshot(self, screenshot_path: str) -> Dict[str, Any]: result["message"] = f"Screenshot file not found: {screenshot_path}" return result - # Check file size - file_size = os.path.getsize(screenshot_path) - if file_size < 1000: # Less than 1KB - result["status"] = "WARN" - result["message"] = f"Screenshot file seems too small: {file_size} bytes" - self.warnings.append("Screenshot file is unusually small") - else: - result["status"] = "PASS" - result["message"] = "Screenshot file exists" - - result["checks"] = { - "file_exists": True, - "file_size_bytes": file_size - } - - return result + return self._validate_image_file(screenshot_path, "Screenshot file") def validate_attestation_log(self, log_path: str) -> Dict[str, Any]: """Validate server-side attestation log""" From 272b190d1cb5347cabf01406887c04647ff952b5 Mon Sep 17 00:00:00 2001 From: chen Date: Tue, 26 May 2026 18:46:14 +0800 Subject: [PATCH 2/3] fix: accept BMP vintage evidence images --- tests/test_validate_vintage_submission.py | 25 +++++++++++++++++++++++ tools/validate_vintage_submission.py | 6 ++++++ 2 files changed, 31 insertions(+) diff --git a/tests/test_validate_vintage_submission.py b/tests/test_validate_vintage_submission.py index 4a80dabb8..02bf1da9e 100644 --- a/tests/test_validate_vintage_submission.py +++ b/tests/test_validate_vintage_submission.py @@ -31,6 +31,18 @@ def chunk(kind: bytes, data: bytes) -> bytes: ) +def write_bmp(path: Path, width: int = 640, height: int = 480) -> None: + row_stride = ((width * 3 + 3) // 4) * 4 + pixel_data_size = row_stride * height + file_size = 14 + 40 + pixel_data_size + path.write_bytes( + b"BM" + + struct.pack(" Dict[str, Any]: width, height = struct.unpack("= 26: + dib_size = struct.unpack("= 40: + width, height = struct.unpack(" Date: Tue, 26 May 2026 19:11:58 +0800 Subject: [PATCH 3/3] fix: summarize low resolution evidence warnings --- tests/test_validate_vintage_submission.py | 12 ++++++++++++ tools/validate_vintage_submission.py | 1 + 2 files changed, 13 insertions(+) diff --git a/tests/test_validate_vintage_submission.py b/tests/test_validate_vintage_submission.py index 02bf1da9e..9b1115219 100644 --- a/tests/test_validate_vintage_submission.py +++ b/tests/test_validate_vintage_submission.py @@ -120,6 +120,18 @@ def test_photo_validation_accepts_real_bmp_with_dimensions(tmp_path): assert result["checks"]["height"] == 480 +def test_low_resolution_image_warning_is_in_summary(tmp_path): + validator = SubmissionValidator() + photo_path = tmp_path / "photo.png" + write_png(photo_path, width=320, height=240) + + result = validator.validate_photo(str(photo_path)) + + assert result["status"] == "WARN" + assert "resolution is too small" in result["message"] + assert validator.warnings == [result["message"]] + + def test_screenshot_validation_rejects_non_image_content(tmp_path): validator = SubmissionValidator() screenshot_path = tmp_path / "screenshot.png" diff --git a/tools/validate_vintage_submission.py b/tools/validate_vintage_submission.py index 087ec22c7..7ec007f04 100644 --- a/tools/validate_vintage_submission.py +++ b/tools/validate_vintage_submission.py @@ -108,6 +108,7 @@ def _validate_image_file(self, image_path: str, label: str) -> Dict[str, Any]: warnings = [] if metadata["width"] < 640 or metadata["height"] < 480: warnings.append(f"{label} resolution is too small: {metadata['width']}x{metadata['height']}") + self.warnings.extend(warnings) return { "status": "WARN" if warnings else "PASS",