validate_url_for_http_request previously skipped DNS resolution, so
attacker-controlled domains that resolve to private/loopback/link-local
addresses (e.g. 127.0.0.1.nip.io) passed validation and the subsequent
httpx GET hit internal services. Resolve the hostname via getaddrinfo
and reject any result whose IP is private, loopback, link-local,
reserved, multicast, or unspecified. Unresolvable hostnames are
rejected as well.
https://claude.ai/code/session_01T335ZvA825YhuzPctmYzUy
Standard media fields (url_cover, url_manual, url_screenshots) were downloaded
using the stored credential-less URLs, causing them to count against the anonymous
IP quota instead of the user's SS account. Apply add_ss_auth_to_url() at each
download call site in the scan and ROM update paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(screenscraper): guard add_ss_auth_to_url against non-SS URLs
Only inject ssid/sspassword into screenscraper.fr URLs to prevent
leaking user credentials to third-party sources (IGDB, LaunchBox, etc.)
when url_cover/url_manual/url_screenshots originate from other providers.
Add tests for the non-SS no-op and empty-string edge cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
test(screenscraper): verify SS credentials injected for all media download paths
- TestAddSsAuthToUrl: add guards for non-SS URLs (IGDB, LaunchBox) and
empty string inputs
- test_update_rom: verify ssid/sspassword appear in url_cover and
url_manual args passed to get_cover/get_manual for screenscraper.fr
URLs; verify IGDB URLs are NOT decorated with SS credentials
- TestScanCredentialInjection: verify the scan-path ternary pattern
correctly applies add_ss_auth_to_url to cover and screenshot URLs,
and that a None cover URL passes through without error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
test(screenscraper): empirical audit — every SS request carries ssid/sspassword
Intercepts both HTTP clients at the transport/session level to verify
that every outgoing screenscraper.fr request is decorated with the user's
ssid and sspassword credentials:
aiohttp (API calls via auth_middleware):
- jeuInfos.php, jeuRecherche.php, ssinfraInfos.php, ssuserInfos.php
httpx (media downloads via FSResourcesHandler):
- get_cover → url_cover
- get_manual → url_manual
- get_rom_screenshots → url_screenshots (each URL)
- store_media_file → extra media (fanart, bezel, etc.)
Also verifies the domain guard: IGDB URLs passed through add_ss_auth_to_url
are NOT decorated with SS credentials.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Starlette versions 0.8.3-1.0.0 are vulnerable to improper Host header
validation when reconstructing request.url, which can cause
request.url.path to differ from the actual requested path and bypass
path-based security checks. Patched in 1.0.1.
Bumps fastapi to 0.134.0 since 0.121.x pins starlette<0.50.0.
https://claude.ai/code/session_01P9NtqqFN9dVW1c5Uno6oRC
atob() decodes base64 to a JS string, which r.return() then re-encodes
as UTF-8. For filenames with non-ASCII characters (e.g. Pokémon), bytes
above 0x7F get double-encoded — serving different content than what the
backend computed the CRC32 over, causing mod_zip to report CRC failure
on the .m3u file.
Buffer.from(value, 'base64') decodes directly to a byte array and
r.return() sends it verbatim, matching the CRC exactly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The paginated ROM list eager-loaded sibling_roms via selectinload, which
hydrated full Rom ORM instances (including heavy JSON metadata columns)
for every sibling even though only an existence/count check was needed
on the frontend. On large collections this dominated request latency.
Split sibling handling by response shape:
- SimpleRomSchema (list): siblings is now list[int]; populated per page
by a single SELECT against the sibling_roms view projecting only
(rom_id, sibling_rom_id) — no Rom row hydration.
- DetailedRomSchema (detail): keeps full SiblingRomSchema objects, with
load_only on (id, name, fs_name_no_tags, fs_name_no_ext) so sibling
rows stop dragging in JSON metadata.
Frontend usage already only consumes siblings.length on list views; the
detail-page VersionSwitcher continues to receive the richer schema.
Drop the migration and the multi_file / top_level_file_count columns on
roms; express both as deferred column_property correlated subqueries
against rom_files instead. The gallery list and detail queries opt in
via undefer, so they get the values computed in the same SELECT via
indexed subqueries (rom_id index already in place); other code paths
that don't read the flags pay nothing.
This keeps the gallery perf win (no rom_files load for cards) without
introducing schema state that has to stay in sync with rom_files at
write time.
ASGI spec only allows headers on the http.response.start message;
appending Set-Cookie to body messages is out-of-spec and may break on
some servers. Early-return for non-start messages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All callers declare a fresh `local -a wrap=()` before invoking, so the
in-function reset is unnecessary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collapse `otel_prefix` and `otel_prefix_str` into a single nameref-based
helper. Watchfiles call sites embed the array as a shell-quoted prefix
via `${wrap[*]@Q}`, which also fixes a quoting bug where an
`OTEL_SERVICE_NAME_PREFIX` containing a single quote would produce an
invalid command string and break the watcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collapse the duplicated OTEL_SDK_DISABLED / opentelemetry-instrument
branches in run_startup, start_bin_gunicorn, start_bin_watcher, and
start_bin_sync_watcher into two small helpers:
- otel_prefix: emits the wrapper as NUL-delimited argv tokens (for
direct process invocation).
- otel_prefix_str: emits the wrapper as a shell-string prefix (for
embedding inside `watchfiles --target-type command`).
Each call site becomes a single command instead of a 2- or 3-way
branch with a fully duplicated command body. As a side effect, the
watcher functions now also gain the `command -v opentelemetry-instrument`
fallback that the gunicorn/startup paths added.
The gallery list endpoint was eager-loading every rom_file row for each
paginated ROM via selectinload, then re-joining each row back to its
parent rom for the is_top_level computation. For platforms with extracted
multi-file ROMs (Xbox 360 ~1394 files/ROM, Switch ~199 files/ROM), this
made /api/roms time out at 120s even with a rom_id index.
Cards never displayed individual files — only the has_simple_single_file
/ has_nested_single_file / has_multiple_files booleans that derive from
the file list. Denormalize the underlying state onto roms as multi_file
(folder-based vs single-file) and top_level_file_count, recompute the
booleans from those columns, drop the selectinload from filter_roms, and
move the files field from SimpleRomSchema to DetailedRomSchema so the
gallery payload no longer ships file rows.
Also drop the redundant joinedload(RomFile.rom) and switch the relation
to lazy="select" so subsequent file.rom accesses resolve from the
session identity map instead of re-JOINing the parent rom per file row.
ShowQRCode.vue's folder-based DS/3DS fallback now fetches the detailed
rom on demand, since SimpleRom no longer carries files.