Backlog, tests, logo compression, security audit, App Store icon
- BACKLOG.md: P0-P3 roadmap incl. govuk API switch (blocked on credentials) - APPSTORE.md: review-gate analysis (2 blockers: LAN data source, Always-location notes) - SECURITY.md: audit - no secrets, no ATS holes, one MEDIUM (relay IP in binary, planned fix) - FuelBoardTests/: SPM package, 28 tests on real Shared/ sources (sanitizer, price guard, RAG, sort, distance, brands) - Logos: proper 2x/3x pairs, palette-quantized, 75% smaller (315KB -> 79KB) - App icon: 1024px fuelpump on gradient (was missing entirely -> instant rejection) - decodeStations(from:) exposed for testability; relay fetch reuses it
This commit is contained in:
+94
@@ -0,0 +1,94 @@
|
||||
# FuelBoard Security Audit
|
||||
|
||||
Audit date: 2026-08-11 · Scope: app, widget, relay, build pipeline, git history.
|
||||
|
||||
## Summary
|
||||
|
||||
No critical issues. No secrets in source, git history, or binaries. One
|
||||
architecture note (relay IP baked into the app) is scheduled for removal by
|
||||
the govuk-API switch (BACKLOG P0). Findings below are ordered by severity.
|
||||
|
||||
## Findings
|
||||
|
||||
### 1. LAN IP baked into app binary — MEDIUM (planned fix)
|
||||
|
||||
- `Shared/FuelPriceProvider.swift:52` — `baseURL = URL(string: "http://192.168.1.131:8788")!`
|
||||
- Confirmed present in the built app binary (`strings`).
|
||||
- **Why it works**: ATS exempts raw numeric IP addresses (documented Apple
|
||||
behaviour), so no `NSAllowsArbitraryLoads` exception exists in the plist —
|
||||
the plist is clean.
|
||||
- **Risk**: the relay is a private LAN host; the app is unusable off the
|
||||
user's network, and the URL is effectively a hardcoded endpoint.
|
||||
- **Fix**: the govuk API switch (BACKLOG P0) replaces this with the HTTPS
|
||||
gov.uk endpoint via the relay. For any future public build, the app should
|
||||
talk to an HTTPS domain, never a raw IP.
|
||||
|
||||
### 2. No secrets in source / git / binary — PASS
|
||||
|
||||
- App + widget sources: no API keys, tokens, or passwords (only keychain
|
||||
API usage in `FuelStore.swift` and OAuth *documentation* comments in
|
||||
`FuelPriceProvider.swift`).
|
||||
- Relay: `CLIENT_ID`/`CLIENT_SECRET` read from environment only
|
||||
(`app/main.py:31-32`); `.env` is gitignored; `__pycache__`/`*.pyc`
|
||||
untracked; `.env.example` ships empty placeholders.
|
||||
- Binary scan: only the relay IP, `maps.apple.com`, and `group.com.apt.fuelboard`
|
||||
strings found. No credentials.
|
||||
|
||||
### 3. Price data hardening — PASS (three layers)
|
||||
|
||||
- Relay rejects out-of-band rows at the edge (`_fuel_price` 50–500 pence/litre).
|
||||
- App drops out-of-band grades at decode (`FuelPriceProvider.decodeStations`,
|
||||
previously `c1edd4d`).
|
||||
- Station names title-cased at decode + on cache load (`aee890e`).
|
||||
- Covered by unit tests (`FuelBoardTests` — `PriceGuardTests`).
|
||||
|
||||
### 4. Keychain / app-group hygiene — PASS
|
||||
|
||||
- Keychain access groups: `com.apt.fuelboard.shared` listed FIRST
|
||||
(default access group), then `com.apt.fuelboard` — matches the proven
|
||||
WidgetBoard pattern for free-account keychain sharing.
|
||||
- Stations stored in app-group UserDefaults only (~2.9 MB exceeds keychain
|
||||
limits; documented in `FuelStore.swift`). Stored data is public price data —
|
||||
not sensitive.
|
||||
- `kSecAttrAccessibleAfterFirstUnlock` — widget can read after reboot without
|
||||
unlock. Correct for background widget refresh.
|
||||
|
||||
### 5. Transport security — PASS for LAN dev, MUST CHANGE for public
|
||||
|
||||
- No `NSAllowsArbitraryLoads` anywhere (confirmed by plist + pbxproj grep).
|
||||
- Cleartext HTTP only to the LAN relay IP (ATS-exempt). Once the relay moves
|
||||
behind an HTTPS domain (govuk switch), no ATS changes are needed.
|
||||
|
||||
### 6. Privacy / permissions — review for App Store
|
||||
|
||||
- `NSLocationWhenInUseUsageDescription` + `NSLocationAlwaysAndWhenInUseUsageDescription`
|
||||
both present with clear copy; `UIBackgroundModes: location` justified by the
|
||||
alert feature (geofenced cheapest-station notifications).
|
||||
- Location is the only sensitive permission requested. No contacts, photos,
|
||||
camera, mic.
|
||||
- No third-party SDKs → no SDK privacy manifests required.
|
||||
|
||||
## What was checked
|
||||
|
||||
- [x] Source secret scan (app, widget, shared, relay, config)
|
||||
- [x] Git history scan (all 43 commits, both repos)
|
||||
- [x] Built binary string scan (app + widget)
|
||||
- [x] Entitlements (app groups, keychain groups, ordering)
|
||||
- [x] Info.plist (ATS, permissions, background modes)
|
||||
- [x] Relay env handling + gitignore + pycache hygiene
|
||||
- [x] Data validation layers (relay band, app decode guard, sanitizer)
|
||||
|
||||
## Recurring checks
|
||||
|
||||
Run before every release:
|
||||
|
||||
```bash
|
||||
# secrets in working tree
|
||||
grep -rniE "api[_-]?key|client[_-]?secret|password|token" FuelBoard/ Shared/ FuelBoardWidgets/ --include="*.swift"
|
||||
|
||||
# secrets in git history (both repos)
|
||||
git grep -ilE "client_secret|api_key|password" $(git rev-list --all) | head
|
||||
|
||||
# binary leak check (after build)
|
||||
strings <app-binary> | grep -iE "192\.168|100\.120|client_secret|api_key"
|
||||
```
|
||||
Reference in New Issue
Block a user