Skip to content

Fix 13 bugs found during the test build-out - #8

Merged
JeremiahM37 merged 5 commits into
mainfrom
fix/bug-sweep
Jul 11, 2026
Merged

Fix 13 bugs found during the test build-out#8
JeremiahM37 merged 5 commits into
mainfrom
fix/bug-sweep

Conversation

@JeremiahM37

Copy link
Copy Markdown
Owner

Fixes every bug surfaced while writing the unit/e2e test suites. Each fix carries a regression test that fails on the old code.

Correctness

  • /api/register admin branch was unreachable — auth-exempt paths skipped context enrichment, so ctxUserRole was never admin. Exempt paths now get best-effort identity resolution (API key / session) that never rejects; admins can register users without an invite, anonymous behavior unchanged.
  • JobStore data raceGet/Items/Values returned live references to maps mutated by worker goroutines; they now return copies (race-test proven under -race).
  • Deluge re-auth never fired on real session expiry — expiry arrives as JSON-RPC Not authenticated, not a transport error; now re-logins and retries once.
  • Watcher double-importhasMatchingJob used exact title equality while job tracking matched fuzzily; both now share titlesMatch.
  • RAWG platform mapping was nondeterministic — random Go map iteration made Xbox 360 / Wii U / SNES / Sega Genesis / PS2 resolve differently per call; now deterministic longest-match-first.
  • Sega Master System queried RAWG as Game Boy — ID 26 → 74.

Error handling

  • Oversized request bodies return 413 (was generic 400) via Content-Length short-circuit + MaxBytesError mapping.
  • Six handlers no longer ignore JSON decode errors (400 with a message on garbage bodies).
  • DDL staging failures (MkdirAll/Create) are logged and surfaced in the job error with the actual cause instead of a bare "Download failed".

Polish

  • RAWG description truncation no longer splits UTF-8 runes.
  • Versioned PC dir names (Game.v1.0-FITGIRL) clean correctly (extension parsing skipped for directories).
  • Scheduler's inter-item wait is interruptible by Stop() and applies on continue paths.
  • Self-heal container name configurable via QB_CONTAINER_NAME (default qbittorrent).
  • Ships favicon.svg — no more 404 on every page load.
  • Removed dead err var and an unreachable dead_letter guard.

Verification

  • gofmt / go vet / staticcheck clean
  • go test ./... -race -count=1 — all packages pass
  • Hermetic Playwright e2e — 9/9 pass

api:
- /api/register admin branch was unreachable: exempt paths now get
  best-effort identity resolution (API key/session) without ever
  rejecting, so admins can register users without an invite
- Oversized bodies return 413 (Content-Length short-circuit +
  MaxBytesError mapping in a shared decodeJSONBody helper)
- Six handlers no longer ignore JSON decode errors (400 on garbage)

db:
- JobStore.Get/Items/Values return copies instead of live references
  to maps concurrently mutated by workers (data race)

download:
- Deluge re-auths and retries once on JSON-RPC "Not authenticated"
  (previously only transport errors triggered re-login)
- DDL staging failures (MkdirAll/Create) are logged and surfaced in
  the job error instead of a bare "Download failed"
- Watcher uses the same fuzzy title match as job tracking so
  tracker-renamed torrents aren't double-imported
- Removed dead err var and unreachable dead_letter guard

metadata:
- RAWG platform mapping is deterministic longest-match-first (was
  random map iteration: Xbox 360/Wii U/SNES/Genesis/PS2 flapped)
- Sega Master System uses RAWG ID 74 (was 26 = Game Boy)
- Description truncation no longer splits UTF-8 runes

organize:
- Versioned PC directory names (Game.v1.0-FITGIRL) are cleaned as
  extensionless names instead of misparsing .0-FITGIRL as an extension

scheduler:
- Inter-item rate-limit wait is interruptible by Stop and applies on
  continue paths too

monitor:
- Self-heal container name configurable via QB_CONTAINER_NAME

web:
- Ship favicon.svg (kills the 404 on every page load)

Every fix carries a regression test that fails on the old code.
return sourcePath, err
}

fi, err := os.Stat(sourcePath)
Comment thread internal/download/manager.go Fixed
Comment thread internal/download/manager.go Fixed
Comment thread internal/download/manager.go Fixed
- DDL downloads: filenames from Content-Disposition/URL are reduced to
  a single safe path component and joined via a containment check
  (safeChild), closing a path traversal from a malicious server
- Backup restore: zip entry names are Base()'d before joining into the
  data dir (zip-slip); backup names Base()'d after sanitizing
- platform slugs from download requests are sanitized before building
  ROM library paths; organizePC keeps cleaned names single-component
- copyDir/extractArchives verify children stay inside their roots
- externally supplied values (URLs, filenames, usernames) are stripped
  of newlines before logging (log forging)

Adds sanitizeFilename/safeChild/sanitizeLog helpers with unit tests and
an end-to-end regression test proving a filename="../../pwned.zip"
response cannot escape the staging dir.
Comment thread internal/download/manager.go Fixed
Comment thread internal/download/manager.go Fixed
sanitizeFilename/safeChild now gate on filepath.IsLocal (the sanitizer
CodeQL's go/path-injection query recognizes), and torrent content
basenames + backup names are routed through them. Closes the alerts
tied to sanitizable filename components.
Clears the two remaining CodeQL path-injection alerts on the backup
create path (name was already restricted to [A-Za-z0-9_-] but CodeQL
didn't track the guard through Sprintf; safeJoin gates the final
component on filepath.IsLocal, the recognized barrier).
waitFor/waitJobStatus polled 5-10s deadlines that a worker finishes in
milliseconds locally, but under the CI -race build on a throttled shared
runner the deadline could occasionally lose (one timeout on
TestDownloadDDL). Floor the poll timeout at 45s; a passing test still
returns the instant its condition holds, so green runs are unaffected.
@JeremiahM37
JeremiahM37 merged commit 90052ad into main Jul 11, 2026
4 of 5 checks passed
@JeremiahM37
JeremiahM37 deleted the fix/bug-sweep branch July 11, 2026 07:01
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.

2 participants