Files
fuelboard/SECURITY.md
FuelBoard Contributor 2ed18d81a3 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
2026-08-11 23:14:28 +01:00

95 lines
4.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 50500 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"
```