Appearance
Apple Build Scan Privacy And Retention
Date: 18 October 2026.
Scope And Evidence Rules
This register addresses the build's scan-photo/statistics behaviour (point 2) and retention of scan photos/results, logs, support and backups (point 3). Email vendor, hosting region and international-transfer verification (point 4) belongs to a separate owner and is deliberately not assessed here.
The product owner confirmed on 18 October that the Apple build has not yet been created. There is no submitted IPA to certify in this task. The build controls below apply to the next release after the matching PRs are integrated; the resulting immutable build still needs its own verification before submission.
Keep these four statements distinct:
- Implemented: source code and tests specify behaviour on an identified commit.
- Built: an immutable build ID and source/configuration evidence identify the IPA.
- Deployed: the running server's selected non-secret configuration was checked.
- Enforced: successful retention jobs and restoration/deletion tests demonstrate that the selected period is actually applied.
A source flag is not proof about an uploaded IPA. A dry-run report is not proof that files were deleted. A stopped recording flag does not remove old archives.
Apple defines collection for its App Privacy disclosures as transmission off-device that allows access longer than necessary to service a request in real time. Persisting photos for diagnostics is not the same as transient scan inference. Account-linked scan counts are not automatically anonymous statistics.
Sources: Apple App Privacy details, CNIL retention principles.
Store Disclosure Decision
Until the server guard is deployed and verified, do not describe ordinary scans as transient-only: the current runtime retains photo/result archives. A release client blocking its optional diagnostic endpoint cannot override that behaviour.
If account/day/outcome counters remain enabled with a bounded retention period, they still contain an account identifier and represent usage data. Do not label them anonymous simply because there is no email address in the counter table or because the app has no advertising SDK. Prepare the App Privacy answers from the verified processing purpose and actual data types; assess SDK/provider flows with the separate point-4 owner rather than asserting they do not exist.
Retention Target Policy
Engineering target selected on 18 October after the product owner delegated the choice. These are service choices, not current settings, universal legal obligations, legal sign-off or proof of an active purge. Production operations still require separate approval of the dry run.
| Data | Proposed purpose/limit | After account deletion | Required enforcement |
|---|---|---|---|
| Ordinary scan photo and inference response | Request lifetime only, no diagnostic archive | No persistent scan archive should exist for new ordinary requests | Verify every scan route and effective runtime configuration |
| On-device scan history | Explicit user-facing history criteria must be documented separately from server diagnostics | Clear associated local history on this device; do not claim remote device wiping | Current source stores at most 50 entries with result/photo-path references, not a time-based expiry |
| Explicitly supplied diagnostic photo/result | Diagnose a reported issue, 30 days | Erase attributable cases unless a specific documented exception applies | Safe file identification, bounded age-based purge and a verifiable owner/deletion link |
| Account-linked daily scan counters | Service usage, 90 days | Erase the account's rows, not merely hide them | Bounded SQL retention and actual account cascade verification |
| Operational/debug logs | Troubleshooting, 30 days | Restricted access; expire according to the selected log purpose | Rotation is not sufficient by itself; verify time-based expiry in each sink |
| Security/access audit events | Security investigation, 6 months | Minimize and retain only justified evidence until expiry | Separate security purpose, redaction, bounded retention and exception handling |
| Support correspondence | Resolve requests, 12 months after closure | Erase/anonymize unnecessary identity; document any justified exception | Identify the actual support record owner and closure/deletion process |
| Rolling recovery backups | Recovery, 30 days | Old copies expire within the approved window; reapply erasure before recovered service is exposed | Verified inventory/scheduler/expiry, restricted access and a restoration rehearsal |
The CNIL requires the controller to determine a proportionate, justified duration or criteria. Its security-event journal guidance recommends a rolling period between six months and one year in the stated context, with justified exceptions; it does not impose one period for every debug log, scan or support message.
Treat the operational/diagnostic durations above as maximum engineering windows, not a reason to keep data that is no longer needed. Do not extend Loki's existing shorter window merely to match a 30-day maximum. Dedicated security evidence has its own justified purpose and policy. Record purge cadence and the maximum operational delay explicitly; an age cutoff alone does not promise instantaneous physical deletion or a running scheduler.
Source: CNIL security logging guidance.
Release Evidence Checklist
- Record the exact EAS build ID, source commit and selected release profile.
- Record resolved production scan/debug flags without credentials or complete environment dumps. Distinguish development/preview builds from Apple distribution.
- Inspect automatic scan archival separately from an explicit diagnostic upload.
- Inspect the effective backend archive and scan-statistics switches. A client setting cannot disable an unconditional server-side archive.
- List retained categories, timestamps/expiry and safe count-only observations. Do not inspect user photo contents, tokens, email addresses or holdings for QA.
- Validate purge with disposable fixtures, cutoff boundaries, retries, concurrent writes and unsafe path/symlink cases. Review the production dry run separately.
- Record deployment and scheduler activation as their own authorized operations.
- Test recovery without resurrecting erased identities and only then publish a claim about backup erasure timing.
- Align store disclosures and privacy copy with the verified combination of submitted build and running server, not a future branch or desired configuration.
Current Validation Limits
Read-only backend inspection on 18 October established the following state:
| Category | Verified observation | What it does not establish |
|---|---|---|
| Automatic archive | Production API selected flags had automatic archiving enabled; dated archive directories include 28 July and 27 September | Exact capture dates, archive contents, ownership or expiry were not inspected |
| Statistics | Account-linked scan statistics enabled; no implemented TTL | Anonymous-only collection or automatic age-based expiry |
| API and proxy Docker logs | Per-container json-file rotation options absent; no host-wide rotation override found | No universal statement about all sinks or external logging |
| Loki | Retention configured to 168 hours, with a 2-hour compactor deletion delay | Physical deletion verified at exactly 170 hours, or coverage of original Docker logs |
| Backups | Manual dated copies present; no applicable application timer/root cron identified in inspected scope | Complete backup inventory, offsite/provider retention or verified deletion replay |
| Support | No support-message persistence identified in backend source; the mobile help label is not an implemented message form | Absence of correspondence in external inboxes/ticket tools |
The production storage guard in backend PR #499 requires an explicit production diagnostic opt-in in addition to the historical auto-archive switch. It is a source change, not a deployed setting at this observation. It does not remove old archives or change statistics/log retention. Its repository audit records the selected flags and inspection limits without user contents.
Mobile PR #735 blocks new diagnostic history writes and diagnostic upload callbacks in release builds via the existing compile-time development boundary. Development tooling remains available. Its local lint/typecheck and 2,638 tests passed; Maestro/native device validation was unavailable. The patch does not erase existing local entries or OS-managed image cache files and cannot switch off server-side archival or account-linked statistics. It is separate from the pending account-deletion flow.
On the observed commits, mobile PR #735 passed its CI lint/typecheck/test job and backend PR #499 passed all six CI jobs, including complete API E2E and Compose smoke. These are integration checks, not deployment or physical-retention proof.
No uploaded Apple binary is certified by this document merely because its source configuration was inspected. No production purge, backup deletion, service restart or provider/hosting audit is authorized by creating this register. The companion implementation PRs and dated read-only observations supply the actual state.
Production Activation And Recovery
Backend PR #503 minimizes production scan.engine events before Docker receives them. It keeps request correlation, version/mode, result/OCR statuses, durations and boolean/count fields, without OCR text, photo hashes/dimensions, provider detail or card/match payloads. Development diagnostics and the actual scan response remain unchanged. Its local API suite passed 1,193 tests, including route-level production and development logging checks. This is minimization for one event, not a claim that every log stream is sanitized or that old files have expired.
Backend PR #501 adds a bounded maintenance command for scan archives and account statistics. It defaults to simulation, requires an explicit store, duration and database/root target, and requires --apply for writes. Archive application additionally requires quiesced writers, an exclusive lock, a canonical dedicated root and identity rechecks after moving a case into private quarantine. It rejects unsafe/unknown paths and does not use recursive deletion. Changed or interrupted cases require operator review. Statistics use a bounded parameterized query with row locks and freshness checks.
The selected arguments are 30 days for diagnostics and 90 days for statistics, using conservative completed UTC-day boundaries. That can preserve the cutoff day (up to one additional day) and does not include scheduler/batch completion delay. These are explicit cleanup criteria, not an exact physical-TTL guarantee. The command does not install a scheduler or operate on logs, support or backups.
Its local verification passed 1,203 API tests, 553 ingest tests, 12 focused retention tests and two real disposable PostgreSQL tests. The latter cover freshness, completed-day boundaries, idempotence, locked rows and a concurrent update. Full local Docker E2E was unavailable; CI status is tracked on the PR.
- Approve the purpose, duration, expiry anchor and exception owner for each category. Keep an operational-debug sink distinct from a security audit sink; a single shared sink cannot simultaneously promise two incompatible periods.
- Review a bounded, count-only dry run against explicitly identified storage roots/tables. Check recent/live references, cutoff boundaries, incomplete writes and unsafe paths. Do not infer that every unknown folder is disposable.
- Obtain separate authorization for runtime changes, deployment, scheduler installation and the reviewed production purge. Preserve source/runtime rollback information without creating indefinite copies of erased data.
- Publish a retention claim only after observing successful expiry over time. Record failures/retries and alert on missing jobs, not just successful exit codes. A size-limited rotating log is bounded in bytes, not necessarily days.
- Inventory backup creation and expiry, including manual release/incident copies. Restrict them from normal use and track relevant erasure requests until every potentially affected backup has expired. Do not call a backup anonymous merely because access is restricted or its filenames are pseudonymous.
- Restore into an isolated environment. Reapply outstanding erasures and relevant expiry rules before reopening API, mail, shares, pushes or background jobs. Test the absence of the deleted account and associated data with disposable fixtures. A restore that succeeds technically but resurrects deleted users does not pass the privacy recovery gate.
Until these operational checks pass, report the corresponding row as not verified/enforced, even if a retention setting or a purge helper exists in Git.