Skip to content

GDPR batch A+B: SECURITY.md, /privacy, rate limit, temp-file fix, Delete All Data - #36

Merged
roznet merged 6 commits into
mainfrom
gdpr-batch-ab
Sep 27, 2026
Merged

roznet merged 6 commits into
mainfrom
gdpr-batch-ab

Conversation

@roznet

@roznet roznet commented Sep 26, 2026

Copy link
Copy Markdown
Owner

Works through Batches A and B of the outstanding items in legal/GDPR.md.

Backend

  • Rate limit: /generate + /prefill share a limit of 100 fills per user per rolling hour, counted over the existing usage log (the same approach as flyfun_common.auth.rate_limit). Over the limit: 429 + Retry-After. Skipped in dev mode. Closes SECURITY_AUDIT.md §17.
  • GET /privacy: public HTML rendered from the repo's PRIVACY.md (now copied into the image; new markdown dependency). Repo-relative links are rewritten to GitHub; a test guards the #passengers anchor that the in-app note links to.
  • SECURITY.md: private reporting channel + UK GDPR Art. 33–34 breach runbook. GitHub private vulnerability reporting has been enabled on the repo.

iOS / macOS

  • Temp-file regression fixed (SECURITY_AUDIT.md §16 had wrongly said FIXED since 6d5d0cc): GeneratedFormFiles owns tmp/forms/. iOS deletes the file on share-sheet close, or once the mail composer has the attachment. macOS keeps it after Open/Reveal/Mail/sharing services, then deletes it in the 15-minute sweep or at launch.
  • Settings → Privacy: policy link and a passenger privacy note (share sheet, GDPR Art. 14).
  • Delete All Data: every AppSchema model is deleted record by record, so CloudKit syncs the deletion (the confirmation says so); then the UI is rebuilt. Doesn't sign out.
  • Delete Account wording now says that on-device records stay.
  • New strings in fr/de/es are marked needs_review.

Android

  • Same Privacy card, passenger note (the backup rules were checked, so "not backed up or synced" is true) and Delete all data: Room clearAllTables + the forms/export cache + WebView storage.

Docs

  • PRIVACY.md: Android storage, passport scanning with the ML Kit metrics disclosure (Google's terms require it and document no opt-out), Passengers, and Deleting Your Data.
  • legal/GDPR.md: new §6a for Android; §1/§7/§9/§15 resolved.
  • designs/api.md and designs/ios-app.md updated.

Verification

  • Python: 211 passed.
  • iOS unit: 137 passed. macOS unit: 133 passed. iOS sim + macOS builds succeed.
  • Android: JVM tests 27 + 116 passed; instrumented data tests 22/22 on an emulator.
  • Not run: XCUI journeys; the new Settings UI hasn't been checked visually on any platform.

Before release

  • Deploy is a Docker rebuild (new dependency + PRIVACY.md in the image). Deploy before the app releases, since they link /privacy.
  • Play Console Data safety: declare ML Kit's diagnostics and device identifier as collected by an SDK.

🤖 Generated with Claude Code

@roznet

roznet commented Sep 26, 2026

Copy link
Copy Markdown
Owner Author

Added feat(android): start ML Kit on first scan, not at app launch: removes ML Kit's MlKitInitProvider from the merged manifest and initialises ML Kit in MrzScanner, so pilots who never scan never run ML Kit or send Google its metrics. The new instrumented MlKitLazyInitTest passes (1/1) and was checked to fail with the provider restored; data tests still pass (22/22). The disclosure in PRIVACY.md and GDPR.md §6a is narrowed to match. Not checked: a real passport scan (the emulator has no MRZ to read), so a device scan before release is worth doing.

🤖 Generated with Claude Code

roznet and others added 6 commits September 26, 2026 20:11
/generate and /prefill together allow 100 fills per user per rolling hour,
counted over the existing usage log (the flyfun_common.auth.rate_limit
strategy: no counter rows). Over the limit is a 429 with Retry-After,
checked before any template is filled. Closes SECURITY_AUDIT.md §17.

SECURITY.md adds the private reporting channel and the UK GDPR Art. 33-34
breach runbook, adapted from weather's, with the breach scenarios that
apply to a server holding no manifest data and the processor duty to tell
pilot-controllers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The apps need a privacy link, and the forms backend served no pages.
/privacy renders the repo's PRIVACY.md (baked into the image), so there is
one source; repo-relative links point at GitHub. Public, no auth. The
passenger note links /privacy#passengers, which a test guards.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Settings gains a Privacy card (policy link to /privacy, and a share-sheet
note a pilot can hand passengers, GDPR Art. 14) and a Delete all data
action: Room clearAllTables (tombstones and passenger links included,
then VACUUM), the forms/export cache folder, and web-form WebView storage.
It never signs out. Delete Account now says what it deletes and that the
on-phone records stay.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… All Data

Filled forms (passport data) were left in tmp since QuickLook's cleanup
went with QuickLook in 6d5d0cc. GeneratedFormFiles now owns their
lifetime under tmp/forms/: iOS deletes on share-sheet close and once the
mail composer has the attachment; macOS deletes on Done/close/Save and
keeps the file only while Open/Reveal/Mail/sharing services still read
it, relying on a 15-minute sweep before the next form and a clear at
launch.

Settings gains Privacy (policy link, passenger note share, GDPR Art. 14)
and Delete All Data: every AppSchema model deleted record by record so
CloudKit syncs it, then the UI is rebuilt; the account and Keychain are
untouched. Delete Account now says the on-device records stay.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rasure

PRIVACY.md (now also served at /privacy) gains Android storage, passport
scanning with the ML Kit metrics disclosure Google's terms require, a
Passengers section (the #passengers anchor the in-app note links), and
Deleting Your Data; temp-file wording matches the new behaviour.
SECURITY_AUDIT.md §16 records the regression and re-fix, §17 the rate
limit. legal/GDPR.md marks §1, §7, §9, §15 resolved and adds §6a for the
Android client.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ML Kit sends Google usage metrics once running, with no opt-out, and its
MlKitInitProvider started it at every launch. The manifest now removes
the provider, and both scan paths - the camera (MrzScanner) and photo/PDF
(ImageMrzReader) - call startMlKit() first, so a pilot who never scans
never runs ML Kit.

startMlKit() guards MlKit.initialize() to once per process: it is not
idempotent and throws "already initialized" on a second call, which
would crash the second scan.

MlKitLazyInitTest asserts ML Kit is uninitialised after app start,
started by a scan, and survives each path twice; it fails with the
provider restored, so a dependency bump that re-adds it is caught.
PRIVACY.md and GDPR.md §6a narrow the disclosure to pilots who scan.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@roznet

roznet commented Sep 26, 2026

Copy link
Copy Markdown
Owner Author

Rebased onto main (6 new Android commits from PR 35). Two things came out of it:

  • ImageMrzReader (scan from photo/PDF, new on main) called ML Kit directly. With the startup provider removed, it would have crashed with "MlKitContext has not been initialized". Both scan paths now go through one startMlKit().
  • MlKit.initialize() is not idempotent: a second call throws "already initialized". The earlier version of the lazy-init commit would have crashed the second scan in a session. startMlKit() now guards it to once per process. MlKitLazyInitTest runs each scan path twice and passes; before the fix it failed with exactly that exception.

The ML Kit commit was amended to include both fixes (force-pushed). Checks after the rebase: Android build, JVM tests, instrumented scan 1/1 and data 23/23, Python 211 passed.

🤖 Generated with Claude Code

@claude

claude Bot commented Sep 26, 2026

Copy link
Copy Markdown

Code Review

Reviewed the full diff (Python API, iOS/macOS, Android) against designs/api.md and designs/ios-app.md.

Checked closely:

  • rate_limit.py — sliding-window count over usage rows, shared by /generate + /prefill, dev-mode bypass, 429 + Retry-After. Logic and window math check out; matches the design doc and SECURITY_AUDIT.md §17.
  • privacy.py — path resolution (parents[3]) lines up with the Dockerfile's COPY PRIVACY.md . into /app; anchor (#passengers) and link-rewriting are covered by tests.
  • iOS GeneratedFormFiles/LocalDataEraser — directory-scoped delete-safety (path traversal, "outside root" guards), the sweep/removeAll lifecycle, and the macOS keepFile handoff logic (Open/Reveal/Mail vs Done/Save) are internally consistent and match the documented per-platform cleanup behavior.
  • Android MlKitStart/ImageMrzReader/MrzScanner — double-checked-locking guard around the non-idempotent MlKit.initialize, both scan entry points call it first; LocalDataEraser erase order (DB → files → best-effort WebView clear) matches its own doc comment.

No bugs, CLAUDE.md violations (none present in repo), or design-doc deviations found with high confidence. Design docs (api.md, ios-app.md) and legal/GDPR.md/SECURITY_AUDIT.md were updated consistently with the code changes.

Approving.

🤖 Generated with Claude Code

@roznet
roznet merged commit 0341889 into main Sep 27, 2026
3 checks passed
@roznet
roznet deleted the gdpr-batch-ab branch September 27, 2026 04:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant