uttrflow / uttrflow/uttrflow-swift
The log privacy gate only matches variable names, so error descriptions are logged public, one of them with the home-folder path
- Dominant language
- Swift
- Stars
- 4
- Forks
- 17
- Avg merge
- 3h 32m
- Merged PRs (30d)
- 277
Description
## What happens
`Scripts/log_privacy_audit.py` (the gate from #361) decides whether a `privacy: .public` interpolation is user text by splitting the interpolated *identifier* into camelCase parts and matching them against a word list (`USER_TEXT`, `:21-25`; the check at `:125-140`). A value named `error`, `why`, `failure` or `front` never matches, whatever it contains. The gate also only recognises calls on `log` / `logger` / `*Log` (`:13`), not `print`, `NSLog`, `os_log` or `FileHandle.standardError`. None of those exist in app sources today.
Interpolations that pass the gate but can carry more than a fixed sentence:
- `Sources/Uttrflow/AppDelegate.swift:414`: `"the corpus would not open: \(String(describing: error), privacy: .public)"`. The error can be `PredictStoreError.cannotOpen(path)` (`Sources/UttrflowPredictStore/SQLite.swift:8`, thrown at `:34`), whose payload is the full database path under the home folder, which contains the account's user name. It can also be `.query(String)` with a raw SQLite message.
- `AppDelegate.swift:439`: `"the suggestion model did not load: \(String(describing: error), privacy: .public)"`. Model load and download errors from the hub library typically carry file paths and URLs.
- `AppDelegate.swift:1653`: `"store change failed: \(error.localizedDescription, privacy: .public)"`.
- `AppDelegate.swift:1083-1084`: `why` falls back to `String(describing: error)` for any error that is not an `UttrflowFailure`.
`log show` / sysdiagnose output is what people attach to bug reports, and public values are also retained in the unified log across reboots.
## Why it matters
The user name in a path is personal data under the project's own rules. More importantly, "named `error`, so it is safe" is the kind of gap that lets user text through later: an error type that embeds a clip, a field value or a transcript would pass silently.
## Acceptance criteria
- The four lines above log a fixed description (a case name, or a `userMessage`) publicly, and any payload with `privacy: .private`.
- The gate treats `String(describing:)`, `.localizedDescription` and `\(error)` interpolated as `.public` as findings unless allowlisted with a reason, the same way its existing exceptions are recorded.
- Tests for the gate (next to the script, or in the `make verify` step that runs it) cover a `String(describing: error)` public interpolation.
Contributor guide
Research direction
Start with Scripts/log_privacy_audit.py, the four AppDelegate.swift locations, and Sources/UttrflowPredictStore/SQLite.swift lines 8 and 34. Run the gate through the make verify step and inspect its existing exception tests, then add coverage for a public String(describing: error) interpolation. Done means the four reported lines expose only fixed descriptions publicly, keep payloads private, and the gate finds the unsafe interpolation unless it has a reasoned allowlist entry.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, swift
- Domain
- desktop, security, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 70/100