Skip to content

fix(gba-validator): downgrade library-signature reject to warning under helper trust (fixes #7)#8

Closed
terafin wants to merge 1 commit into
joeblack2k:mainfrom
intarweb:feat/gba-signature-advisory-with-helper-trust
Closed

fix(gba-validator): downgrade library-signature reject to warning under helper trust (fixes #7)#8
terafin wants to merge 1 commit into
joeblack2k:mainfrom
intarweb:feat/gba-signature-advisory-with-helper-trust

Conversation

@terafin

@terafin terafin commented Jun 7, 2026

Copy link
Copy Markdown

🤖 Generated with Claude Code

…er helper trust

The GBA validator requires saves to carry one of the standard library
signature strings (EEPROM_V / SRAM_V / FLASH_V / FLASH1M_V / FLASH512_V)
or an AGB cartridge header. This assumption breaks for **RetroArch's
libretro-mGBA core** (the default GBA emulator on Steam Deck /
RetroDECK), which does NOT embed those signatures — confirmed against
both an EA Sports title (007 - Everything or Nothing) AND a canonical
mGBA-targeted save (Pokemon Emerald), neither of which contains any
library footer.

Net effect today: every legitimately-saved GBA file from a RetroDECK /
Steam Deck user is rejected with `HTTP 422 - "gba raw save is missing a
validated payload signature"`. See issue joeblack2k#7 for full evidence.

Fix
---

Add `SignatureAdvisoryWithHelperTrust bool` to
`strictRawSaveValidationProfile`. When set AND all of
`detection.Evidence.HelperTrusted` + non-empty `rom_sha1` + non-blank
payload hold, the signature check downgrades from reject to warning. The
warning surfaces in the inspection record so operators can see the
relaxation was applied. Anonymous uploads, uploads without rom_sha1, and
blank payloads continue to reject as before.

Set the flag on the GBA profile only. Other systems (Gameboy / NES /
SNES / NDS) are unaffected.

Trust rationale
---------------

The advisory downgrade only fires under all three conditions
simultaneously:
  1. HelperTrusted — the request came over the authenticated helper
     channel and the helper explicitly asserted the system slug.
  2. rom_sha1 present — the helper computed the ROM hash from a local
     ROM file (not strictly verifiable backend-side but a meaningful
     signal of provenance).
  3. Non-blank payload — rules out freshly-erased FLASH chips and
     truly-empty save buffers.

Random anonymous garbage cannot reach this path. Buggy or malicious
clients still fail #1 unless they hold a valid app password.

Tests
-----

`gba_signature_advisory_test.go` covers:
  - Regression guard: signature PRESENT — accepted, no advisory warning
  - The fix: signature missing + helper trust + rom_sha1 + non-blank — accepted with warning
  - Security guard: signature missing + helper trust but no rom_sha1 — REJECTED
  - Security guard: signature missing + rom_sha1 but no helper trust — REJECTED
  - Security guard: blank payload under helper trust — REJECTED

Full package test suite (15s) passes cleanly with no other regressions.

Fixes joeblack2k#7

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@terafin

terafin commented Jun 8, 2026

Copy link
Copy Markdown
Author

Consolidated into #11 (squashed with #9 since both touch the same validator files under the same libretro-vs-standalone lens). Closing this in favor of #11; no functional changes — same fix, cleaner review surface for the maintainer.

@terafin terafin closed this Jun 8, 2026
terafin added a commit to intarweb/RetroSaveManager that referenced this pull request Jun 8, 2026
… rtc)

Two related libretro-vs-standalone validator gaps in one batch — both fix
real data-loss paths reported by SGM-Helper users on Steam Deck (RetroDECK)
and SS1 (libretro cores), both stay strict for anonymous uploads, both
ship with happy-path + regression-guard tests.

## Part 1 — GBA library-signature advisory under helper trust  (was PR joeblack2k#8, fixes joeblack2k#7)

Standalone mGBA / VBA-M write a library version footer (`EEPROM_V`,
`SRAM_V`, `FLASH_V`, `FLASH1M_V`, `FLASH512_V`) into the .srm. RetroArch's
libretro-mGBA core does NOT — confirmed across both EA Sports games
(007 - Everything or Nothing, header `3500DNOB`) and canonical
mGBA-targeted titles (Pokemon Emerald, sparse FRAM data). RSM's
`hasGBASignature()` rejects every libretro GBA save → HTTP 422.

Adds `SignatureAdvisoryWithHelperTrust bool` to
`strictRawSaveValidationProfile`. When true, a failing `RequireSignature`
check is downgraded from reject → warning IFF (a) `HelperTrusted` AND
(b) `rom_sha1` present AND (c) payload non-blank. Anonymous uploads or
uploads without `rom_sha1` still hard-reject.

GBA's profile opts in. NES / SNES / Master System / Genesis are unchanged
because their `RequireSignature` is nil (no signature required at all).

## Part 2 — Game Boy .rtc sidecar size validation  (was PR joeblack2k#9)

RetroArch / libretro Game Boy cores write a small `.rtc` sidecar next
to the canonical `.srm` for cartridges with real-time clock state
(Pokemon Crystal, Pokemon Gold/Silver, Harvest Moon GBC). Observed
sizes 8–48 bytes. The strict gameboy raw-save profile's `AllowedSizes`
only listed canonical SRAM sizes (512..65536), so every `.rtc` upload
rejected with "game boy raw save size N is not recognized" → silent
data loss for clock state.

Adds optional `AllowedSizesByExt map[string]func(int) bool` to
`strictRawSaveValidationProfile`. When the incoming extension matches
a registered predicate, that predicate replaces the canonical size-set
check for that extension only. Other extensions fall through to
`AllowedSizes` unchanged. Gameboy opts in for `.rtc` with `1..=64` bytes.

`.sav` / `.srm` / `.ram` / `.gme` still validate against `strictRawGBSizes`
exactly as before — scoped relaxation, not a blanket loosening.

## Tests

- `TestNormalizeSaveInputAcceptsLibretroGBASaveUnderHelperTrust` — accepts
  libretro GBA save without library footer when HelperTrusted + rom_sha1
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutHelperTrust` —
  regression guard: anonymous upload still rejected
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutROMSHA1` — same
- `TestNormalizeSaveInputRejectsBlankLibretroGBASave` — blank payload
  still rejected even under helper trust
- `TestNormalizeSaveInputAcceptsTinyGameBoyRTCFile` — accepts 8/13/32/48/64
  byte gameboy `.rtc` payloads
- `TestNormalizeSaveInputRejectsTinyGameBoySRMFile` — regression guard:
  8-byte `.srm` still rejected, proves relaxation is `.rtc`-scoped
- `TestNormalizeSaveInputRejectsOversizedGameBoyRTCFile` — 65-byte
  `.rtc` rejected with new per-ext message

## Scope

- Only GBA opts into `SignatureAdvisoryWithHelperTrust`; only gameboy
  opts into `AllowedSizesByExt`. Every other system's behavior is
  byte-identical to before.
- Pre-existing trust / blank / executable-payload / text-noise checks
  still run — only the size predicate and signature requirement get
  the per-extension / per-trust-context overrides.

Consolidates and supersedes PR joeblack2k#8 (GBA advisory) + PR joeblack2k#9 (gameboy .rtc).
Both are scanner.rs/validator-relaxation fixes from the same lens
(libretro-vs-standalone format gaps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
terafin added a commit to intarweb/RetroSaveManager that referenced this pull request Jun 8, 2026
… rtc)

Two related libretro-vs-standalone validator gaps in one batch — both fix
real data-loss paths reported by SGM-Helper users on Steam Deck (RetroDECK)
and SS1 (libretro cores), both stay strict for anonymous uploads, both
ship with happy-path + regression-guard tests.

## Part 1 — GBA library-signature advisory under helper trust  (was PR joeblack2k#8, fixes joeblack2k#7)

Standalone mGBA / VBA-M write a library version footer (`EEPROM_V`,
`SRAM_V`, `FLASH_V`, `FLASH1M_V`, `FLASH512_V`) into the .srm. RetroArch's
libretro-mGBA core does NOT — confirmed across both EA Sports games
(007 - Everything or Nothing, header `3500DNOB`) and canonical
mGBA-targeted titles (Pokemon Emerald, sparse FRAM data). RSM's
`hasGBASignature()` rejects every libretro GBA save → HTTP 422.

Adds `SignatureAdvisoryWithHelperTrust bool` to
`strictRawSaveValidationProfile`. When true, a failing `RequireSignature`
check is downgraded from reject → warning IFF (a) `HelperTrusted` AND
(b) `rom_sha1` present AND (c) payload non-blank. Anonymous uploads or
uploads without `rom_sha1` still hard-reject.

GBA's profile opts in. NES / SNES / Master System / Genesis are unchanged
because their `RequireSignature` is nil (no signature required at all).

## Part 2 — Game Boy .rtc sidecar size validation  (was PR joeblack2k#9)

RetroArch / libretro Game Boy cores write a small `.rtc` sidecar next
to the canonical `.srm` for cartridges with real-time clock state
(Pokemon Crystal, Pokemon Gold/Silver, Harvest Moon GBC). Observed
sizes 8–48 bytes. The strict gameboy raw-save profile's `AllowedSizes`
only listed canonical SRAM sizes (512..65536), so every `.rtc` upload
rejected with "game boy raw save size N is not recognized" → silent
data loss for clock state.

Adds optional `AllowedSizesByExt map[string]func(int) bool` to
`strictRawSaveValidationProfile`. When the incoming extension matches
a registered predicate, that predicate replaces the canonical size-set
check for that extension only. Other extensions fall through to
`AllowedSizes` unchanged. Gameboy opts in for `.rtc` with `1..=64` bytes.

`.sav` / `.srm` / `.ram` / `.gme` still validate against `strictRawGBSizes`
exactly as before — scoped relaxation, not a blanket loosening.

## Tests

- `TestNormalizeSaveInputAcceptsLibretroGBASaveUnderHelperTrust` — accepts
  libretro GBA save without library footer when HelperTrusted + rom_sha1
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutHelperTrust` —
  regression guard: anonymous upload still rejected
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutROMSHA1` — same
- `TestNormalizeSaveInputRejectsBlankLibretroGBASave` — blank payload
  still rejected even under helper trust
- `TestNormalizeSaveInputAcceptsTinyGameBoyRTCFile` — accepts 8/13/32/48/64
  byte gameboy `.rtc` payloads
- `TestNormalizeSaveInputRejectsTinyGameBoySRMFile` — regression guard:
  8-byte `.srm` still rejected, proves relaxation is `.rtc`-scoped
- `TestNormalizeSaveInputRejectsOversizedGameBoyRTCFile` — 65-byte
  `.rtc` rejected with new per-ext message

## Scope

- Only GBA opts into `SignatureAdvisoryWithHelperTrust`; only gameboy
  opts into `AllowedSizesByExt`. Every other system's behavior is
  byte-identical to before.
- Pre-existing trust / blank / executable-payload / text-noise checks
  still run — only the size predicate and signature requirement get
  the per-extension / per-trust-context overrides.

Consolidates and supersedes PR joeblack2k#8 (GBA advisory) + PR joeblack2k#9 (gameboy .rtc).
Both are scanner.rs/validator-relaxation fixes from the same lens
(libretro-vs-standalone format gaps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot pushed a commit to intarweb/RetroSaveManager that referenced this pull request Jun 9, 2026
… rtc)

Two related libretro-vs-standalone validator gaps in one batch — both fix
real data-loss paths reported by SGM-Helper users on Steam Deck (RetroDECK)
and SS1 (libretro cores), both stay strict for anonymous uploads, both
ship with happy-path + regression-guard tests.

## Part 1 — GBA library-signature advisory under helper trust  (was PR joeblack2k#8, fixes joeblack2k#7)

Standalone mGBA / VBA-M write a library version footer (`EEPROM_V`,
`SRAM_V`, `FLASH_V`, `FLASH1M_V`, `FLASH512_V`) into the .srm. RetroArch's
libretro-mGBA core does NOT — confirmed across both EA Sports games
(007 - Everything or Nothing, header `3500DNOB`) and canonical
mGBA-targeted titles (Pokemon Emerald, sparse FRAM data). RSM's
`hasGBASignature()` rejects every libretro GBA save → HTTP 422.

Adds `SignatureAdvisoryWithHelperTrust bool` to
`strictRawSaveValidationProfile`. When true, a failing `RequireSignature`
check is downgraded from reject → warning IFF (a) `HelperTrusted` AND
(b) `rom_sha1` present AND (c) payload non-blank. Anonymous uploads or
uploads without `rom_sha1` still hard-reject.

GBA's profile opts in. NES / SNES / Master System / Genesis are unchanged
because their `RequireSignature` is nil (no signature required at all).

## Part 2 — Game Boy .rtc sidecar size validation  (was PR joeblack2k#9)

RetroArch / libretro Game Boy cores write a small `.rtc` sidecar next
to the canonical `.srm` for cartridges with real-time clock state
(Pokemon Crystal, Pokemon Gold/Silver, Harvest Moon GBC). Observed
sizes 8–48 bytes. The strict gameboy raw-save profile's `AllowedSizes`
only listed canonical SRAM sizes (512..65536), so every `.rtc` upload
rejected with "game boy raw save size N is not recognized" → silent
data loss for clock state.

Adds optional `AllowedSizesByExt map[string]func(int) bool` to
`strictRawSaveValidationProfile`. When the incoming extension matches
a registered predicate, that predicate replaces the canonical size-set
check for that extension only. Other extensions fall through to
`AllowedSizes` unchanged. Gameboy opts in for `.rtc` with `1..=64` bytes.

`.sav` / `.srm` / `.ram` / `.gme` still validate against `strictRawGBSizes`
exactly as before — scoped relaxation, not a blanket loosening.

## Tests

- `TestNormalizeSaveInputAcceptsLibretroGBASaveUnderHelperTrust` — accepts
  libretro GBA save without library footer when HelperTrusted + rom_sha1
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutHelperTrust` —
  regression guard: anonymous upload still rejected
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutROMSHA1` — same
- `TestNormalizeSaveInputRejectsBlankLibretroGBASave` — blank payload
  still rejected even under helper trust
- `TestNormalizeSaveInputAcceptsTinyGameBoyRTCFile` — accepts 8/13/32/48/64
  byte gameboy `.rtc` payloads
- `TestNormalizeSaveInputRejectsTinyGameBoySRMFile` — regression guard:
  8-byte `.srm` still rejected, proves relaxation is `.rtc`-scoped
- `TestNormalizeSaveInputRejectsOversizedGameBoyRTCFile` — 65-byte
  `.rtc` rejected with new per-ext message

## Scope

- Only GBA opts into `SignatureAdvisoryWithHelperTrust`; only gameboy
  opts into `AllowedSizesByExt`. Every other system's behavior is
  byte-identical to before.
- Pre-existing trust / blank / executable-payload / text-noise checks
  still run — only the size predicate and signature requirement get
  the per-extension / per-trust-context overrides.

Consolidates and supersedes PR joeblack2k#8 (GBA advisory) + PR joeblack2k#9 (gameboy .rtc).
Both are scanner.rs/validator-relaxation fixes from the same lens
(libretro-vs-standalone format gaps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot pushed a commit to intarweb/RetroSaveManager that referenced this pull request Jun 9, 2026
… rtc)

Two related libretro-vs-standalone validator gaps in one batch — both fix
real data-loss paths reported by SGM-Helper users on Steam Deck (RetroDECK)
and SS1 (libretro cores), both stay strict for anonymous uploads, both
ship with happy-path + regression-guard tests.

## Part 1 — GBA library-signature advisory under helper trust  (was PR joeblack2k#8, fixes joeblack2k#7)

Standalone mGBA / VBA-M write a library version footer (`EEPROM_V`,
`SRAM_V`, `FLASH_V`, `FLASH1M_V`, `FLASH512_V`) into the .srm. RetroArch's
libretro-mGBA core does NOT — confirmed across both EA Sports games
(007 - Everything or Nothing, header `3500DNOB`) and canonical
mGBA-targeted titles (Pokemon Emerald, sparse FRAM data). RSM's
`hasGBASignature()` rejects every libretro GBA save → HTTP 422.

Adds `SignatureAdvisoryWithHelperTrust bool` to
`strictRawSaveValidationProfile`. When true, a failing `RequireSignature`
check is downgraded from reject → warning IFF (a) `HelperTrusted` AND
(b) `rom_sha1` present AND (c) payload non-blank. Anonymous uploads or
uploads without `rom_sha1` still hard-reject.

GBA's profile opts in. NES / SNES / Master System / Genesis are unchanged
because their `RequireSignature` is nil (no signature required at all).

## Part 2 — Game Boy .rtc sidecar size validation  (was PR joeblack2k#9)

RetroArch / libretro Game Boy cores write a small `.rtc` sidecar next
to the canonical `.srm` for cartridges with real-time clock state
(Pokemon Crystal, Pokemon Gold/Silver, Harvest Moon GBC). Observed
sizes 8–48 bytes. The strict gameboy raw-save profile's `AllowedSizes`
only listed canonical SRAM sizes (512..65536), so every `.rtc` upload
rejected with "game boy raw save size N is not recognized" → silent
data loss for clock state.

Adds optional `AllowedSizesByExt map[string]func(int) bool` to
`strictRawSaveValidationProfile`. When the incoming extension matches
a registered predicate, that predicate replaces the canonical size-set
check for that extension only. Other extensions fall through to
`AllowedSizes` unchanged. Gameboy opts in for `.rtc` with `1..=64` bytes.

`.sav` / `.srm` / `.ram` / `.gme` still validate against `strictRawGBSizes`
exactly as before — scoped relaxation, not a blanket loosening.

## Tests

- `TestNormalizeSaveInputAcceptsLibretroGBASaveUnderHelperTrust` — accepts
  libretro GBA save without library footer when HelperTrusted + rom_sha1
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutHelperTrust` —
  regression guard: anonymous upload still rejected
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutROMSHA1` — same
- `TestNormalizeSaveInputRejectsBlankLibretroGBASave` — blank payload
  still rejected even under helper trust
- `TestNormalizeSaveInputAcceptsTinyGameBoyRTCFile` — accepts 8/13/32/48/64
  byte gameboy `.rtc` payloads
- `TestNormalizeSaveInputRejectsTinyGameBoySRMFile` — regression guard:
  8-byte `.srm` still rejected, proves relaxation is `.rtc`-scoped
- `TestNormalizeSaveInputRejectsOversizedGameBoyRTCFile` — 65-byte
  `.rtc` rejected with new per-ext message

## Scope

- Only GBA opts into `SignatureAdvisoryWithHelperTrust`; only gameboy
  opts into `AllowedSizesByExt`. Every other system's behavior is
  byte-identical to before.
- Pre-existing trust / blank / executable-payload / text-noise checks
  still run — only the size predicate and signature requirement get
  the per-extension / per-trust-context overrides.

Consolidates and supersedes PR joeblack2k#8 (GBA advisory) + PR joeblack2k#9 (gameboy .rtc).
Both are scanner.rs/validator-relaxation fixes from the same lens
(libretro-vs-standalone format gaps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot pushed a commit to intarweb/RetroSaveManager that referenced this pull request Jun 9, 2026
… rtc)

Two related libretro-vs-standalone validator gaps in one batch — both fix
real data-loss paths reported by SGM-Helper users on Steam Deck (RetroDECK)
and SS1 (libretro cores), both stay strict for anonymous uploads, both
ship with happy-path + regression-guard tests.

## Part 1 — GBA library-signature advisory under helper trust  (was PR joeblack2k#8, fixes joeblack2k#7)

Standalone mGBA / VBA-M write a library version footer (`EEPROM_V`,
`SRAM_V`, `FLASH_V`, `FLASH1M_V`, `FLASH512_V`) into the .srm. RetroArch's
libretro-mGBA core does NOT — confirmed across both EA Sports games
(007 - Everything or Nothing, header `3500DNOB`) and canonical
mGBA-targeted titles (Pokemon Emerald, sparse FRAM data). RSM's
`hasGBASignature()` rejects every libretro GBA save → HTTP 422.

Adds `SignatureAdvisoryWithHelperTrust bool` to
`strictRawSaveValidationProfile`. When true, a failing `RequireSignature`
check is downgraded from reject → warning IFF (a) `HelperTrusted` AND
(b) `rom_sha1` present AND (c) payload non-blank. Anonymous uploads or
uploads without `rom_sha1` still hard-reject.

GBA's profile opts in. NES / SNES / Master System / Genesis are unchanged
because their `RequireSignature` is nil (no signature required at all).

## Part 2 — Game Boy .rtc sidecar size validation  (was PR joeblack2k#9)

RetroArch / libretro Game Boy cores write a small `.rtc` sidecar next
to the canonical `.srm` for cartridges with real-time clock state
(Pokemon Crystal, Pokemon Gold/Silver, Harvest Moon GBC). Observed
sizes 8–48 bytes. The strict gameboy raw-save profile's `AllowedSizes`
only listed canonical SRAM sizes (512..65536), so every `.rtc` upload
rejected with "game boy raw save size N is not recognized" → silent
data loss for clock state.

Adds optional `AllowedSizesByExt map[string]func(int) bool` to
`strictRawSaveValidationProfile`. When the incoming extension matches
a registered predicate, that predicate replaces the canonical size-set
check for that extension only. Other extensions fall through to
`AllowedSizes` unchanged. Gameboy opts in for `.rtc` with `1..=64` bytes.

`.sav` / `.srm` / `.ram` / `.gme` still validate against `strictRawGBSizes`
exactly as before — scoped relaxation, not a blanket loosening.

## Tests

- `TestNormalizeSaveInputAcceptsLibretroGBASaveUnderHelperTrust` — accepts
  libretro GBA save without library footer when HelperTrusted + rom_sha1
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutHelperTrust` —
  regression guard: anonymous upload still rejected
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutROMSHA1` — same
- `TestNormalizeSaveInputRejectsBlankLibretroGBASave` — blank payload
  still rejected even under helper trust
- `TestNormalizeSaveInputAcceptsTinyGameBoyRTCFile` — accepts 8/13/32/48/64
  byte gameboy `.rtc` payloads
- `TestNormalizeSaveInputRejectsTinyGameBoySRMFile` — regression guard:
  8-byte `.srm` still rejected, proves relaxation is `.rtc`-scoped
- `TestNormalizeSaveInputRejectsOversizedGameBoyRTCFile` — 65-byte
  `.rtc` rejected with new per-ext message

## Scope

- Only GBA opts into `SignatureAdvisoryWithHelperTrust`; only gameboy
  opts into `AllowedSizesByExt`. Every other system's behavior is
  byte-identical to before.
- Pre-existing trust / blank / executable-payload / text-noise checks
  still run — only the size predicate and signature requirement get
  the per-extension / per-trust-context overrides.

Consolidates and supersedes PR joeblack2k#8 (GBA advisory) + PR joeblack2k#9 (gameboy .rtc).
Both are scanner.rs/validator-relaxation fixes from the same lens
(libretro-vs-standalone format gaps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot pushed a commit to intarweb/RetroSaveManager that referenced this pull request Jun 10, 2026
… rtc)

Two related libretro-vs-standalone validator gaps in one batch — both fix
real data-loss paths reported by SGM-Helper users on Steam Deck (RetroDECK)
and SS1 (libretro cores), both stay strict for anonymous uploads, both
ship with happy-path + regression-guard tests.

## Part 1 — GBA library-signature advisory under helper trust  (was PR joeblack2k#8, fixes joeblack2k#7)

Standalone mGBA / VBA-M write a library version footer (`EEPROM_V`,
`SRAM_V`, `FLASH_V`, `FLASH1M_V`, `FLASH512_V`) into the .srm. RetroArch's
libretro-mGBA core does NOT — confirmed across both EA Sports games
(007 - Everything or Nothing, header `3500DNOB`) and canonical
mGBA-targeted titles (Pokemon Emerald, sparse FRAM data). RSM's
`hasGBASignature()` rejects every libretro GBA save → HTTP 422.

Adds `SignatureAdvisoryWithHelperTrust bool` to
`strictRawSaveValidationProfile`. When true, a failing `RequireSignature`
check is downgraded from reject → warning IFF (a) `HelperTrusted` AND
(b) `rom_sha1` present AND (c) payload non-blank. Anonymous uploads or
uploads without `rom_sha1` still hard-reject.

GBA's profile opts in. NES / SNES / Master System / Genesis are unchanged
because their `RequireSignature` is nil (no signature required at all).

## Part 2 — Game Boy .rtc sidecar size validation  (was PR joeblack2k#9)

RetroArch / libretro Game Boy cores write a small `.rtc` sidecar next
to the canonical `.srm` for cartridges with real-time clock state
(Pokemon Crystal, Pokemon Gold/Silver, Harvest Moon GBC). Observed
sizes 8–48 bytes. The strict gameboy raw-save profile's `AllowedSizes`
only listed canonical SRAM sizes (512..65536), so every `.rtc` upload
rejected with "game boy raw save size N is not recognized" → silent
data loss for clock state.

Adds optional `AllowedSizesByExt map[string]func(int) bool` to
`strictRawSaveValidationProfile`. When the incoming extension matches
a registered predicate, that predicate replaces the canonical size-set
check for that extension only. Other extensions fall through to
`AllowedSizes` unchanged. Gameboy opts in for `.rtc` with `1..=64` bytes.

`.sav` / `.srm` / `.ram` / `.gme` still validate against `strictRawGBSizes`
exactly as before — scoped relaxation, not a blanket loosening.

## Tests

- `TestNormalizeSaveInputAcceptsLibretroGBASaveUnderHelperTrust` — accepts
  libretro GBA save without library footer when HelperTrusted + rom_sha1
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutHelperTrust` —
  regression guard: anonymous upload still rejected
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutROMSHA1` — same
- `TestNormalizeSaveInputRejectsBlankLibretroGBASave` — blank payload
  still rejected even under helper trust
- `TestNormalizeSaveInputAcceptsTinyGameBoyRTCFile` — accepts 8/13/32/48/64
  byte gameboy `.rtc` payloads
- `TestNormalizeSaveInputRejectsTinyGameBoySRMFile` — regression guard:
  8-byte `.srm` still rejected, proves relaxation is `.rtc`-scoped
- `TestNormalizeSaveInputRejectsOversizedGameBoyRTCFile` — 65-byte
  `.rtc` rejected with new per-ext message

## Scope

- Only GBA opts into `SignatureAdvisoryWithHelperTrust`; only gameboy
  opts into `AllowedSizesByExt`. Every other system's behavior is
  byte-identical to before.
- Pre-existing trust / blank / executable-payload / text-noise checks
  still run — only the size predicate and signature requirement get
  the per-extension / per-trust-context overrides.

Consolidates and supersedes PR joeblack2k#8 (GBA advisory) + PR joeblack2k#9 (gameboy .rtc).
Both are scanner.rs/validator-relaxation fixes from the same lens
(libretro-vs-standalone format gaps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot pushed a commit to intarweb/RetroSaveManager that referenced this pull request Jun 10, 2026
… rtc)

Two related libretro-vs-standalone validator gaps in one batch — both fix
real data-loss paths reported by SGM-Helper users on Steam Deck (RetroDECK)
and SS1 (libretro cores), both stay strict for anonymous uploads, both
ship with happy-path + regression-guard tests.

## Part 1 — GBA library-signature advisory under helper trust  (was PR joeblack2k#8, fixes joeblack2k#7)

Standalone mGBA / VBA-M write a library version footer (`EEPROM_V`,
`SRAM_V`, `FLASH_V`, `FLASH1M_V`, `FLASH512_V`) into the .srm. RetroArch's
libretro-mGBA core does NOT — confirmed across both EA Sports games
(007 - Everything or Nothing, header `3500DNOB`) and canonical
mGBA-targeted titles (Pokemon Emerald, sparse FRAM data). RSM's
`hasGBASignature()` rejects every libretro GBA save → HTTP 422.

Adds `SignatureAdvisoryWithHelperTrust bool` to
`strictRawSaveValidationProfile`. When true, a failing `RequireSignature`
check is downgraded from reject → warning IFF (a) `HelperTrusted` AND
(b) `rom_sha1` present AND (c) payload non-blank. Anonymous uploads or
uploads without `rom_sha1` still hard-reject.

GBA's profile opts in. NES / SNES / Master System / Genesis are unchanged
because their `RequireSignature` is nil (no signature required at all).

## Part 2 — Game Boy .rtc sidecar size validation  (was PR joeblack2k#9)

RetroArch / libretro Game Boy cores write a small `.rtc` sidecar next
to the canonical `.srm` for cartridges with real-time clock state
(Pokemon Crystal, Pokemon Gold/Silver, Harvest Moon GBC). Observed
sizes 8–48 bytes. The strict gameboy raw-save profile's `AllowedSizes`
only listed canonical SRAM sizes (512..65536), so every `.rtc` upload
rejected with "game boy raw save size N is not recognized" → silent
data loss for clock state.

Adds optional `AllowedSizesByExt map[string]func(int) bool` to
`strictRawSaveValidationProfile`. When the incoming extension matches
a registered predicate, that predicate replaces the canonical size-set
check for that extension only. Other extensions fall through to
`AllowedSizes` unchanged. Gameboy opts in for `.rtc` with `1..=64` bytes.

`.sav` / `.srm` / `.ram` / `.gme` still validate against `strictRawGBSizes`
exactly as before — scoped relaxation, not a blanket loosening.

## Tests

- `TestNormalizeSaveInputAcceptsLibretroGBASaveUnderHelperTrust` — accepts
  libretro GBA save without library footer when HelperTrusted + rom_sha1
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutHelperTrust` —
  regression guard: anonymous upload still rejected
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutROMSHA1` — same
- `TestNormalizeSaveInputRejectsBlankLibretroGBASave` — blank payload
  still rejected even under helper trust
- `TestNormalizeSaveInputAcceptsTinyGameBoyRTCFile` — accepts 8/13/32/48/64
  byte gameboy `.rtc` payloads
- `TestNormalizeSaveInputRejectsTinyGameBoySRMFile` — regression guard:
  8-byte `.srm` still rejected, proves relaxation is `.rtc`-scoped
- `TestNormalizeSaveInputRejectsOversizedGameBoyRTCFile` — 65-byte
  `.rtc` rejected with new per-ext message

## Scope

- Only GBA opts into `SignatureAdvisoryWithHelperTrust`; only gameboy
  opts into `AllowedSizesByExt`. Every other system's behavior is
  byte-identical to before.
- Pre-existing trust / blank / executable-payload / text-noise checks
  still run — only the size predicate and signature requirement get
  the per-extension / per-trust-context overrides.

Consolidates and supersedes PR joeblack2k#8 (GBA advisory) + PR joeblack2k#9 (gameboy .rtc).
Both are scanner.rs/validator-relaxation fixes from the same lens
(libretro-vs-standalone format gaps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot pushed a commit to intarweb/RetroSaveManager that referenced this pull request Jun 11, 2026
… rtc)

Two related libretro-vs-standalone validator gaps in one batch — both fix
real data-loss paths reported by SGM-Helper users on Steam Deck (RetroDECK)
and SS1 (libretro cores), both stay strict for anonymous uploads, both
ship with happy-path + regression-guard tests.

## Part 1 — GBA library-signature advisory under helper trust  (was PR joeblack2k#8, fixes joeblack2k#7)

Standalone mGBA / VBA-M write a library version footer (`EEPROM_V`,
`SRAM_V`, `FLASH_V`, `FLASH1M_V`, `FLASH512_V`) into the .srm. RetroArch's
libretro-mGBA core does NOT — confirmed across both EA Sports games
(007 - Everything or Nothing, header `3500DNOB`) and canonical
mGBA-targeted titles (Pokemon Emerald, sparse FRAM data). RSM's
`hasGBASignature()` rejects every libretro GBA save → HTTP 422.

Adds `SignatureAdvisoryWithHelperTrust bool` to
`strictRawSaveValidationProfile`. When true, a failing `RequireSignature`
check is downgraded from reject → warning IFF (a) `HelperTrusted` AND
(b) `rom_sha1` present AND (c) payload non-blank. Anonymous uploads or
uploads without `rom_sha1` still hard-reject.

GBA's profile opts in. NES / SNES / Master System / Genesis are unchanged
because their `RequireSignature` is nil (no signature required at all).

## Part 2 — Game Boy .rtc sidecar size validation  (was PR joeblack2k#9)

RetroArch / libretro Game Boy cores write a small `.rtc` sidecar next
to the canonical `.srm` for cartridges with real-time clock state
(Pokemon Crystal, Pokemon Gold/Silver, Harvest Moon GBC). Observed
sizes 8–48 bytes. The strict gameboy raw-save profile's `AllowedSizes`
only listed canonical SRAM sizes (512..65536), so every `.rtc` upload
rejected with "game boy raw save size N is not recognized" → silent
data loss for clock state.

Adds optional `AllowedSizesByExt map[string]func(int) bool` to
`strictRawSaveValidationProfile`. When the incoming extension matches
a registered predicate, that predicate replaces the canonical size-set
check for that extension only. Other extensions fall through to
`AllowedSizes` unchanged. Gameboy opts in for `.rtc` with `1..=64` bytes.

`.sav` / `.srm` / `.ram` / `.gme` still validate against `strictRawGBSizes`
exactly as before — scoped relaxation, not a blanket loosening.

## Tests

- `TestNormalizeSaveInputAcceptsLibretroGBASaveUnderHelperTrust` — accepts
  libretro GBA save without library footer when HelperTrusted + rom_sha1
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutHelperTrust` —
  regression guard: anonymous upload still rejected
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutROMSHA1` — same
- `TestNormalizeSaveInputRejectsBlankLibretroGBASave` — blank payload
  still rejected even under helper trust
- `TestNormalizeSaveInputAcceptsTinyGameBoyRTCFile` — accepts 8/13/32/48/64
  byte gameboy `.rtc` payloads
- `TestNormalizeSaveInputRejectsTinyGameBoySRMFile` — regression guard:
  8-byte `.srm` still rejected, proves relaxation is `.rtc`-scoped
- `TestNormalizeSaveInputRejectsOversizedGameBoyRTCFile` — 65-byte
  `.rtc` rejected with new per-ext message

## Scope

- Only GBA opts into `SignatureAdvisoryWithHelperTrust`; only gameboy
  opts into `AllowedSizesByExt`. Every other system's behavior is
  byte-identical to before.
- Pre-existing trust / blank / executable-payload / text-noise checks
  still run — only the size predicate and signature requirement get
  the per-extension / per-trust-context overrides.

Consolidates and supersedes PR joeblack2k#8 (GBA advisory) + PR joeblack2k#9 (gameboy .rtc).
Both are scanner.rs/validator-relaxation fixes from the same lens
(libretro-vs-standalone format gaps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot pushed a commit to intarweb/RetroSaveManager that referenced this pull request Jun 12, 2026
… rtc)

Two related libretro-vs-standalone validator gaps in one batch — both fix
real data-loss paths reported by SGM-Helper users on Steam Deck (RetroDECK)
and SS1 (libretro cores), both stay strict for anonymous uploads, both
ship with happy-path + regression-guard tests.

## Part 1 — GBA library-signature advisory under helper trust  (was PR joeblack2k#8, fixes joeblack2k#7)

Standalone mGBA / VBA-M write a library version footer (`EEPROM_V`,
`SRAM_V`, `FLASH_V`, `FLASH1M_V`, `FLASH512_V`) into the .srm. RetroArch's
libretro-mGBA core does NOT — confirmed across both EA Sports games
(007 - Everything or Nothing, header `3500DNOB`) and canonical
mGBA-targeted titles (Pokemon Emerald, sparse FRAM data). RSM's
`hasGBASignature()` rejects every libretro GBA save → HTTP 422.

Adds `SignatureAdvisoryWithHelperTrust bool` to
`strictRawSaveValidationProfile`. When true, a failing `RequireSignature`
check is downgraded from reject → warning IFF (a) `HelperTrusted` AND
(b) `rom_sha1` present AND (c) payload non-blank. Anonymous uploads or
uploads without `rom_sha1` still hard-reject.

GBA's profile opts in. NES / SNES / Master System / Genesis are unchanged
because their `RequireSignature` is nil (no signature required at all).

## Part 2 — Game Boy .rtc sidecar size validation  (was PR joeblack2k#9)

RetroArch / libretro Game Boy cores write a small `.rtc` sidecar next
to the canonical `.srm` for cartridges with real-time clock state
(Pokemon Crystal, Pokemon Gold/Silver, Harvest Moon GBC). Observed
sizes 8–48 bytes. The strict gameboy raw-save profile's `AllowedSizes`
only listed canonical SRAM sizes (512..65536), so every `.rtc` upload
rejected with "game boy raw save size N is not recognized" → silent
data loss for clock state.

Adds optional `AllowedSizesByExt map[string]func(int) bool` to
`strictRawSaveValidationProfile`. When the incoming extension matches
a registered predicate, that predicate replaces the canonical size-set
check for that extension only. Other extensions fall through to
`AllowedSizes` unchanged. Gameboy opts in for `.rtc` with `1..=64` bytes.

`.sav` / `.srm` / `.ram` / `.gme` still validate against `strictRawGBSizes`
exactly as before — scoped relaxation, not a blanket loosening.

## Tests

- `TestNormalizeSaveInputAcceptsLibretroGBASaveUnderHelperTrust` — accepts
  libretro GBA save without library footer when HelperTrusted + rom_sha1
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutHelperTrust` —
  regression guard: anonymous upload still rejected
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutROMSHA1` — same
- `TestNormalizeSaveInputRejectsBlankLibretroGBASave` — blank payload
  still rejected even under helper trust
- `TestNormalizeSaveInputAcceptsTinyGameBoyRTCFile` — accepts 8/13/32/48/64
  byte gameboy `.rtc` payloads
- `TestNormalizeSaveInputRejectsTinyGameBoySRMFile` — regression guard:
  8-byte `.srm` still rejected, proves relaxation is `.rtc`-scoped
- `TestNormalizeSaveInputRejectsOversizedGameBoyRTCFile` — 65-byte
  `.rtc` rejected with new per-ext message

## Scope

- Only GBA opts into `SignatureAdvisoryWithHelperTrust`; only gameboy
  opts into `AllowedSizesByExt`. Every other system's behavior is
  byte-identical to before.
- Pre-existing trust / blank / executable-payload / text-noise checks
  still run — only the size predicate and signature requirement get
  the per-extension / per-trust-context overrides.

Consolidates and supersedes PR joeblack2k#8 (GBA advisory) + PR joeblack2k#9 (gameboy .rtc).
Both are scanner.rs/validator-relaxation fixes from the same lens
(libretro-vs-standalone format gaps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot pushed a commit to intarweb/RetroSaveManager that referenced this pull request Jun 12, 2026
… rtc)

Two related libretro-vs-standalone validator gaps in one batch — both fix
real data-loss paths reported by SGM-Helper users on Steam Deck (RetroDECK)
and SS1 (libretro cores), both stay strict for anonymous uploads, both
ship with happy-path + regression-guard tests.

## Part 1 — GBA library-signature advisory under helper trust  (was PR joeblack2k#8, fixes joeblack2k#7)

Standalone mGBA / VBA-M write a library version footer (`EEPROM_V`,
`SRAM_V`, `FLASH_V`, `FLASH1M_V`, `FLASH512_V`) into the .srm. RetroArch's
libretro-mGBA core does NOT — confirmed across both EA Sports games
(007 - Everything or Nothing, header `3500DNOB`) and canonical
mGBA-targeted titles (Pokemon Emerald, sparse FRAM data). RSM's
`hasGBASignature()` rejects every libretro GBA save → HTTP 422.

Adds `SignatureAdvisoryWithHelperTrust bool` to
`strictRawSaveValidationProfile`. When true, a failing `RequireSignature`
check is downgraded from reject → warning IFF (a) `HelperTrusted` AND
(b) `rom_sha1` present AND (c) payload non-blank. Anonymous uploads or
uploads without `rom_sha1` still hard-reject.

GBA's profile opts in. NES / SNES / Master System / Genesis are unchanged
because their `RequireSignature` is nil (no signature required at all).

## Part 2 — Game Boy .rtc sidecar size validation  (was PR joeblack2k#9)

RetroArch / libretro Game Boy cores write a small `.rtc` sidecar next
to the canonical `.srm` for cartridges with real-time clock state
(Pokemon Crystal, Pokemon Gold/Silver, Harvest Moon GBC). Observed
sizes 8–48 bytes. The strict gameboy raw-save profile's `AllowedSizes`
only listed canonical SRAM sizes (512..65536), so every `.rtc` upload
rejected with "game boy raw save size N is not recognized" → silent
data loss for clock state.

Adds optional `AllowedSizesByExt map[string]func(int) bool` to
`strictRawSaveValidationProfile`. When the incoming extension matches
a registered predicate, that predicate replaces the canonical size-set
check for that extension only. Other extensions fall through to
`AllowedSizes` unchanged. Gameboy opts in for `.rtc` with `1..=64` bytes.

`.sav` / `.srm` / `.ram` / `.gme` still validate against `strictRawGBSizes`
exactly as before — scoped relaxation, not a blanket loosening.

## Tests

- `TestNormalizeSaveInputAcceptsLibretroGBASaveUnderHelperTrust` — accepts
  libretro GBA save without library footer when HelperTrusted + rom_sha1
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutHelperTrust` —
  regression guard: anonymous upload still rejected
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutROMSHA1` — same
- `TestNormalizeSaveInputRejectsBlankLibretroGBASave` — blank payload
  still rejected even under helper trust
- `TestNormalizeSaveInputAcceptsTinyGameBoyRTCFile` — accepts 8/13/32/48/64
  byte gameboy `.rtc` payloads
- `TestNormalizeSaveInputRejectsTinyGameBoySRMFile` — regression guard:
  8-byte `.srm` still rejected, proves relaxation is `.rtc`-scoped
- `TestNormalizeSaveInputRejectsOversizedGameBoyRTCFile` — 65-byte
  `.rtc` rejected with new per-ext message

## Scope

- Only GBA opts into `SignatureAdvisoryWithHelperTrust`; only gameboy
  opts into `AllowedSizesByExt`. Every other system's behavior is
  byte-identical to before.
- Pre-existing trust / blank / executable-payload / text-noise checks
  still run — only the size predicate and signature requirement get
  the per-extension / per-trust-context overrides.

Consolidates and supersedes PR joeblack2k#8 (GBA advisory) + PR joeblack2k#9 (gameboy .rtc).
Both are scanner.rs/validator-relaxation fixes from the same lens
(libretro-vs-standalone format gaps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot pushed a commit to intarweb/RetroSaveManager that referenced this pull request Jun 12, 2026
… rtc)

Two related libretro-vs-standalone validator gaps in one batch — both fix
real data-loss paths reported by SGM-Helper users on Steam Deck (RetroDECK)
and SS1 (libretro cores), both stay strict for anonymous uploads, both
ship with happy-path + regression-guard tests.

## Part 1 — GBA library-signature advisory under helper trust  (was PR joeblack2k#8, fixes joeblack2k#7)

Standalone mGBA / VBA-M write a library version footer (`EEPROM_V`,
`SRAM_V`, `FLASH_V`, `FLASH1M_V`, `FLASH512_V`) into the .srm. RetroArch's
libretro-mGBA core does NOT — confirmed across both EA Sports games
(007 - Everything or Nothing, header `3500DNOB`) and canonical
mGBA-targeted titles (Pokemon Emerald, sparse FRAM data). RSM's
`hasGBASignature()` rejects every libretro GBA save → HTTP 422.

Adds `SignatureAdvisoryWithHelperTrust bool` to
`strictRawSaveValidationProfile`. When true, a failing `RequireSignature`
check is downgraded from reject → warning IFF (a) `HelperTrusted` AND
(b) `rom_sha1` present AND (c) payload non-blank. Anonymous uploads or
uploads without `rom_sha1` still hard-reject.

GBA's profile opts in. NES / SNES / Master System / Genesis are unchanged
because their `RequireSignature` is nil (no signature required at all).

## Part 2 — Game Boy .rtc sidecar size validation  (was PR joeblack2k#9)

RetroArch / libretro Game Boy cores write a small `.rtc` sidecar next
to the canonical `.srm` for cartridges with real-time clock state
(Pokemon Crystal, Pokemon Gold/Silver, Harvest Moon GBC). Observed
sizes 8–48 bytes. The strict gameboy raw-save profile's `AllowedSizes`
only listed canonical SRAM sizes (512..65536), so every `.rtc` upload
rejected with "game boy raw save size N is not recognized" → silent
data loss for clock state.

Adds optional `AllowedSizesByExt map[string]func(int) bool` to
`strictRawSaveValidationProfile`. When the incoming extension matches
a registered predicate, that predicate replaces the canonical size-set
check for that extension only. Other extensions fall through to
`AllowedSizes` unchanged. Gameboy opts in for `.rtc` with `1..=64` bytes.

`.sav` / `.srm` / `.ram` / `.gme` still validate against `strictRawGBSizes`
exactly as before — scoped relaxation, not a blanket loosening.

## Tests

- `TestNormalizeSaveInputAcceptsLibretroGBASaveUnderHelperTrust` — accepts
  libretro GBA save without library footer when HelperTrusted + rom_sha1
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutHelperTrust` —
  regression guard: anonymous upload still rejected
- `TestNormalizeSaveInputRejectsLibretroGBASaveWithoutROMSHA1` — same
- `TestNormalizeSaveInputRejectsBlankLibretroGBASave` — blank payload
  still rejected even under helper trust
- `TestNormalizeSaveInputAcceptsTinyGameBoyRTCFile` — accepts 8/13/32/48/64
  byte gameboy `.rtc` payloads
- `TestNormalizeSaveInputRejectsTinyGameBoySRMFile` — regression guard:
  8-byte `.srm` still rejected, proves relaxation is `.rtc`-scoped
- `TestNormalizeSaveInputRejectsOversizedGameBoyRTCFile` — 65-byte
  `.rtc` rejected with new per-ext message

## Scope

- Only GBA opts into `SignatureAdvisoryWithHelperTrust`; only gameboy
  opts into `AllowedSizesByExt`. Every other system's behavior is
  byte-identical to before.
- Pre-existing trust / blank / executable-payload / text-noise checks
  still run — only the size predicate and signature requirement get
  the per-extension / per-trust-context overrides.

Consolidates and supersedes PR joeblack2k#8 (GBA advisory) + PR joeblack2k#9 (gameboy .rtc).
Both are scanner.rs/validator-relaxation fixes from the same lens
(libretro-vs-standalone format gaps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant