# 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 | grep -iE "192\.168|100\.120|client_secret|api_key" ```