Skip to content

security + code quality hardening (unrelated to #34)#35

Draft
kkolodziej39 wants to merge 2 commits intohenrygd:masterfrom
kkolodziej39:claude/api-hardening
Draft

security + code quality hardening (unrelated to #34)#35
kkolodziej39 wants to merge 2 commits intohenrygd:masterfrom
kkolodziej39:claude/api-hardening

Conversation

@kkolodziej39
Copy link
Copy Markdown
Contributor

Summary

Bundle of security fixes, input validation, URL encoding, and code cleanups — separated from #34 per reviewer feedback to keep the new feature PR focused.

Changes

Security:

  • Validate logo school slug with /^[a-z0-9-]+$/i (prevents path traversal)
  • Encode all GraphQL URL parameters with encodeURIComponent (previously raw string-interpolated)
  • Add AbortSignal.timeout(10000) to all external fetch calls
  • Add X-Content-Type-Options: nosniff and X-Frame-Options: DENY headers
  • Add res.ok check to /schools-index before parsing JSON
  • Fix getDivisionCode returning raw user input as fallback — now throws
  • Fix /schedule-alt crash on invalid sport key access
  • Year validation on /brackets (/^\d{4}$/ regex)
  • Wrap getTodayUrl() in try-catch in /scoreboard handler

Correctness:

  • Cache key collision fix: path + page could collide (e.g. /stats/foo1 vs /stats/foo?page=1 both produce /stats/foo1). Now uses ?page=N separator.
  • JSON.stringify(undefined) fix: /game/:id now returns 502 when upstream response shape is missing .data, instead of returning the literal string "undefined".
  • Hash-retry loop fix: replaced confusing hashes.length > 2 check with explicit index counter capped at maxAttempts.

Cleanups:

  • Remove dead commented-out code (cache_5m, shouldUseNewEndpoint, unused Contest game properties)
  • Log errors in all catch blocks instead of swallowing them
  • Remove redundant cache.has() check in catch-all route (already handled in onBeforeHandle)
  • Fix invalid OpenAPI parameter placement on /game/{id}/scoring-summary, /game/{id}/team-stats, /schedule-alt, and /logo (parameters were at path level instead of inside the get: operation)
  • Fix /logo dark query param schema type (booleanstring; HTTP query params are strings)

Draft status

Opened as draft because I'd like #34 to land first (feature). Happy to mark ready for review once you've merged that one, or any time you prefer.

Test plan

  • All 24 existing tests pass
  • Review security fixes
  • Verify no regressions against real NCAA.com endpoints

🤖 Generated with Claude Code

Unrelated to any new feature — bundle of security fixes, input
validation, URL encoding, and code cleanups discovered during review
of the repo.

Security:
- Validate logo school slug with /^[a-z0-9-]+$/i (prevents path traversal)
- Encode all GraphQL URL parameters with encodeURIComponent (previously
  raw string-interpolated into the URL)
- Add AbortSignal.timeout(10000) to all external fetch calls
- Add X-Content-Type-Options: nosniff and X-Frame-Options: DENY headers
- Add res.ok check to /schools-index before parsing JSON
- Fix getDivisionCode returning raw user input as fallback — now throws
- Fix /schedule-alt crash on invalid sport key access
- Year validation on /brackets (/^\d{4}$/ regex)
- Wrap getTodayUrl() in try-catch in /scoreboard handler

Correctness:
- Fix cache key collision: `path + page` could collide (e.g. /stats/foo1
  vs /stats/foo?page=1 both produce "/stats/foo1"). Now uses "?page=N"
  separator.
- Fix JSON.stringify(undefined) in /game/:id when upstream data missing:
  now returns 502 with a descriptive message
- Fix hash-retry loop guard: replace confusing `hashes.length > 2` check
  with explicit index counter capped at maxAttempts

Code cleanups:
- Remove dead commented-out code (cache_5m, shouldUseNewEndpoint,
  unused Contest game properties)
- Log errors in all catch blocks instead of swallowing them
- Remove redundant cache.has() check in catch-all route (already done
  in onBeforeHandle)
- Fix invalid OpenAPI parameter placement on /game/{id}/scoring-summary,
  /game/{id}/team-stats, /schedule-alt, and /logo (parameters were at
  path level instead of inside the `get:` operation object)
- Fix /logo `dark` query param schema type (boolean -> string; HTTP
  query params are strings and the handler compares as string)

Tests: all 24 existing tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@henrygd
Copy link
Copy Markdown
Owner

henrygd commented Apr 22, 2026

Sorry, haven't had time to look at this yet.

However, I had some bad actors abusing the public API and needed to add additional validations, which introduced merge conflicts. I can fix those when I have time to look at this, but I'm swamped right now so it may be a bit.

@kkolodziej39
Copy link
Copy Markdown
Contributor Author

@henrygd No worries at all, no rush

Sorry to hear that is happening and causing those extra issues/effort to prevent those bad actors. Hope all is resolved now!

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