4.1 KiB
4.1 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. The app deliberately does not include thelocationvalue inUIBackgroundModes; optional alerts use CoreLocation region monitoring/significant-change behaviour rather than persistent real-time background tracking.- 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"