Files
FrenoCorp/agents/security-reviewer/reviews/FRE-5134-security-review.md
Michael Freno 727a160987 FRE-5186: CTO Recovery - FRE-5134 pipeline reassignment to Security Reviewer
FRE-5134 was approved by Code Reviewer but reassignment to Security Reviewer
was never completed via API. FRE-5186 (recovery issue) resolved and FRE-5134
reassigned to Security Reviewer for security audit.

- FRE-5186 marked DONE with recovery plan
- FRE-5134 reassigned from Code Reviewer to Security Reviewer (036d6925-3aac-4939-a0f0-22dc44e618bc)
- FRE-5134 status set to in_progress for security audit
2026-05-12 10:59:54 -04:00

5.9 KiB

Security Review: FRE-5134 - Local Race Discovery Feature

Reviewer: Security Reviewer (036d6925-3aac-4939-a0f0-22dc44e618bc) Engineer: Founding Engineer (d20f6f1c-1f24-4405-a122-2f93e0d6c94a) Date: 2026-05-12 Status: APPROVED with minor findings


Scope

File Lines Purpose
Nessa/Services/RaceDiscoveryService.swift 318 Core discovery service with rate limiting
Nessa/Features/Races/Views/RaceDiscoveryView.swift 165 SwiftUI race discovery interface
Nessa/Features/Races/ViewModels/RaceDiscoveryViewModel.swift 105 View model with business logic
Nessa/Services/RaceService.swift 136 HTTP service layer (shared)
Nessa/Models/Race.swift 186 Data models and filters
NessaTests/RaceDiscoveryViewModelTests.swift 282 Unit test coverage

STRIDE Analysis

Threat Component Risk Mitigation
Spoofing Auth token Low Bearer token via RaceService, optional nil for unauthenticated reads
Tampering API requests Low Protocol-based service, JSON-encoded filters, URL query params validated server-side
Repudiation Race registration Low Server-side registration via registerForRace(id:), audit trail on server
Info Disclosure Error messages Medium print() statements in ViewModel may leak internal error details
DoS Rate limiting Low Client-side rate limiting (5 req/60s) provides defense-in-depth
Elevation of Priv Save/Register Low Auth token required on server-side for mutations

Findings

Medium: Console Log Data Leakage

Location: RaceDiscoveryViewModel.swift:29,48,69,81,95

Five print() statements log generic error descriptions to the console:

print("Failed to fetch races: \(error)")
print("Failed to get race: \(error)")
print("Failed to toggle save race: \(error)")
print("Failed to register for race: \(error)")
print("Failed to fetch saved races: \(error)")

Impact: In production builds, these could expose internal error details (e.g., API endpoints, stack traces, auth failure reasons) to device console logs. An attacker with physical device access or a crash reporting tool could infer API structure.

Remediation: Replace print() with a structured logger at DEBUG level or use a dedicated error reporting service with log-level filtering.


Low: Missing locationService Property (Compilation Bug)

Location: RaceDiscoveryService.swift:166-172

The getUserCurrentLocation(_:) method references locationService.getLastKnownLocation(for:) but locationService is never declared as a property on the actor. The method is also never called by any public API.

Impact: Compilation error if the method is ever invoked. Currently dead code.

Remediation: Either declare private let locationService: LocationServiceProtocol on the actor, or remove the method if unused.


Low: MatchReason.isUpcoming Enum Mismatch (Compilation Bug)

Location: RaceDiscoveryService.swift:256-258

The determineMatchReasons(race:request:) method appends .isUpcoming, but the MatchReason enum (line 53-60) defines .newEvent instead. No .isUpcoming case exists.

Impact: Compilation error when this code path is exercised.

Remediation: Change .isUpcoming to .newEvent on line 258.


Informational: Client-Side Rate Limiting

Location: RaceDiscoveryService.swift:71-94

Rate limiting (5 requests per 60 seconds) is enforced client-side via an in-memory array. This provides defense-in-depth but is not a substitute for server-side rate limiting.

Assessment: Acceptable for a mobile app. Server-side rate limiting (HTTP 429) is already handled by RaceService.validateResponse().


Informational: Optional Auth Token

Location: RaceService.swift:17,85-87

The authToken property is optional (String?). When nil, requests are sent without the Authorization header.

Assessment: Acceptable for read-only endpoints. Mutations (saveRace, registerForRace) should require server-side auth validation. Current implementation defers auth enforcement to the server, which is the correct pattern.


Informational: URL Scheme Validation

Location: Race.swift:17

The registrationUrl: String? field is stored but not validated for URL scheme. If displayed as a Link in SwiftUI, an attacker-controlled URL with javascript: or custom scheme could execute code.

Assessment: Currently not rendered as a clickable link in the UI. If registrationUrl is used in a Link view in the future, add scheme validation (allow https:// only).


Security Controls Assessment

Control Status Notes
Authentication Bearer token pattern, optional for reads
Authorization Server-side enforcement via HTTP 401/403
Input Validation Codable models, URL query params
Rate Limiting Client-side (5 req/60s) + server-side (429)
Error Handling ⚠️ print() statements leak details
Concurrency Safety Actor-based isolation
Data Encoding Codable, JSON, ISO8601 dates
Secrets Management Token passed via header, no hardcoded secrets

Verdict

APPROVED - Ready for production with minor follow-ups.

Summary: No critical or high security vulnerabilities found. The implementation follows solid security patterns: protocol-based service architecture, Bearer token authentication, actor-based concurrency, and defense-in-depth rate limiting.

Two compilation bugs should be fixed before merge:

  1. Missing locationService property (dead code)
  2. MatchReason.isUpcoming vs .newEvent enum mismatch

One medium finding should be addressed in next sprint:

  • Replace print() statements with structured logging