- 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
4.0 KiB
4.0 KiB
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
NSAllowsArbitraryLoadsexception 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.swiftand OAuth documentation comments inFuelPriceProvider.swift). - Relay:
CLIENT_ID/CLIENT_SECRETread from environment only (app/main.py:31-32);.envis gitignored;__pycache__/*.pycuntracked;.env.exampleships empty placeholders. - Binary scan: only the relay IP,
maps.apple.com, andgroup.com.apt.fuelboardstrings found. No credentials.
3. Price data hardening — PASS (three layers)
- Relay rejects out-of-band rows at the edge (
_fuel_price50–500 pence/litre). - App drops out-of-band grades at decode (
FuelPriceProvider.decodeStations, previouslyc1edd4d). - 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.sharedlisted FIRST (default access group), thencom.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
NSAllowsArbitraryLoadsanywhere (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+NSLocationAlwaysAndWhenInUseUsageDescriptionboth present with clear copy;UIBackgroundModes: locationjustified 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
- Source secret scan (app, widget, shared, relay, config)
- Git history scan (all 43 commits, both repos)
- Built binary string scan (app + widget)
- Entitlements (app groups, keychain groups, ordering)
- Info.plist (ATS, permissions, background modes)
- Relay env handling + gitignore + pycache hygiene
- Data validation layers (relay band, app decode guard, sanitizer)
Recurring checks
Run before every release:
# 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"