{
"title": "[SECURITY] Vintage hardware submission validator accepts any file as photo/screenshot evidence — no real image validation",
"body": "## Summary\n\nThe validate_photo() and validate_screenshot() methods in tools/validate_vintage_submission.py skip actual image content validation. They check file existence and size but return SKIP with "not implemented" for real validation. Any file (text, binary, malware, garbage) passes as valid evidence.\n\n## Affected File\n\ntools/validate_vintage_submission.py\n\n- validate_photo() at line 33 — stubs with: Photo validation requires image processing (not implemented)\n- validate_screenshot() at line 79 — stubs with: Screenshot validation requires image processing (not implemented)\n\n## Impact\n\nBounty #2314 (Vintage Hardware Submission) uses this validator to approve submissions that trigger payouts. An attacker can:\n\n1. Submit any non-image file as "photo evidence" — it passes with PASS status as long as it exists\n2. Submit a screenshot that doesn't show actual mining output — only file size is checked\n3. Bypass the entire evidence requirement by providing a 1KB+ junk file\n\nNo EXIF validation, no image dimension checks, no content type verification, no hash matching against on-chain attestations.\n\n## Severity\n\nMEDIUM — Evidence authentication gap. Bounty payouts can process without verified proof of work.\n\n## Suggested Fix\n\nAdd Pillow-based image validation:\n\npython\nfrom PIL import Image\n\ndef validate_image_file(path: str) -> Dict[str, Any]:\n try:\n img = Image.open(path)\n img.verify() # Verify it's a real image\n width, height = img.size\n return {\"valid\": True, \"width\": width, \"height\": height}\n except Exception as e:\n return {\"valid\": False, \"error\": str(e)}\n\n\nAlso add:\n- Minimum resolution check (e.g., 640x480)\n- EXIF timestamp validation against submission window\n- Content hash matching against the attestation log's photo_hash field (see machine_passport_api.py)\n\n## Found by\n\n@waefrebeorn (RustChain bug hunt — Row S, cells S4/S5)\n\n## Related\n\n- S3: beacon_api.py mock LLM response (#6287)\n- S1: claims_settlement.py sign_and_broadcast stub (#6286)"
}
{
"title": "[SECURITY] Vintage hardware submission validator accepts any file as photo/screenshot evidence — no real image validation",
"body": "## Summary\n\nThe
validate_photo()andvalidate_screenshot()methods intools/validate_vintage_submission.pyskip actual image content validation. They check file existence and size but returnSKIPwith "not implemented" for real validation. Any file (text, binary, malware, garbage) passes as valid evidence.\n\n## Affected File\n\ntools/validate_vintage_submission.py\n\n-validate_photo()at line 33 — stubs with:Photo validation requires image processing (not implemented)\n-validate_screenshot()at line 79 — stubs with:Screenshot validation requires image processing (not implemented)\n\n## Impact\n\nBounty #2314 (Vintage Hardware Submission) uses this validator to approve submissions that trigger payouts. An attacker can:\n\n1. Submit any non-image file as "photo evidence" — it passes withPASSstatus as long as it exists\n2. Submit a screenshot that doesn't show actual mining output — only file size is checked\n3. Bypass the entire evidence requirement by providing a 1KB+ junk file\n\nNo EXIF validation, no image dimension checks, no content type verification, no hash matching against on-chain attestations.\n\n## Severity\n\nMEDIUM — Evidence authentication gap. Bounty payouts can process without verified proof of work.\n\n## Suggested Fix\n\nAdd Pillow-based image validation:\n\npython\nfrom PIL import Image\n\ndef validate_image_file(path: str) -> Dict[str, Any]:\n try:\n img = Image.open(path)\n img.verify() # Verify it's a real image\n width, height = img.size\n return {\"valid\": True, \"width\": width, \"height\": height}\n except Exception as e:\n return {\"valid\": False, \"error\": str(e)}\n\n\nAlso add:\n- Minimum resolution check (e.g., 640x480)\n- EXIF timestamp validation against submission window\n- Content hash matching against the attestation log's photo_hash field (seemachine_passport_api.py)\n\n## Found by\n\n@waefrebeorn (RustChain bug hunt — Row S, cells S4/S5)\n\n## Related\n\n- S3: beacon_api.py mock LLM response (#6287)\n- S1: claims_settlement.py sign_and_broadcast stub (#6286)"}