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
138 lines
5.9 KiB
Markdown
138 lines
5.9 KiB
Markdown
# 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:
|
|
```swift
|
|
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
|