158 lines
9.6 KiB
Markdown
158 lines
9.6 KiB
Markdown
# Privacy & Security redesign — all eight tables
|
|
|
|
Approved mock: `home-mocks/privacy.html` (scratchpad, :8642). Spec wins over mock on conflict.
|
|
|
|
## Goals
|
|
|
|
1. **One subject for eyes and ears**: live PipeWire tiles + portal permission rows unified —
|
|
camera, microphone, screen sharing (screencast table), remote desktop — plus the background
|
|
table (7 live rows) as its own card. The sandboxing honesty note stays.
|
|
2. **Traces clear natively**: recent files, thumbnails, trash (reusing Disks) — the
|
|
`openGnomePanel("privacy")` punt dies and privacy becomes Panama-OWNED.
|
|
3. **Secrets polish**: per-action confirm state (Copy stops doubling as Cancel), copy feedback,
|
|
friendly item descriptions.
|
|
4. **SSH Keys complete**: generation with a pty-fed passphrase, agent remove wired (with the
|
|
keyring-agent honesty as prose), Fix-permissions action, copy feedback, visible empty states,
|
|
refresh rows on both pages.
|
|
5. The duplicated Screen-lock card is replaced by an "Elsewhere" pointer card (Power & Lock,
|
|
Notifications).
|
|
|
|
Non-goals: location (geoclue absent — render the section only if the table has entries),
|
|
notifications portal table (NotificationsPage owns the subject), documents-portal grants,
|
|
telemetry/USB-protection gsettings (inert without their GNOME daemons — the trap the page's own
|
|
header warns about), known-hosts hashed-entry removal, keyring collection management.
|
|
|
|
## Helpers (pinned)
|
|
|
|
**`scripts/panama-permissions`** — generalized from 3 hardcoded devices to tables:
|
|
- `snapshot` → `{ tables: { camera: [...], microphone: [...], screencast: [...],
|
|
"remote-desktop": [...], background: [...], location: [...] }, available, error }`; each row
|
|
`{ app, allowed }`. camera/microphone stay the `devices` table's simple yes/no; screencast /
|
|
remote-desktop values are structured GVariants — those rows report presence only and support
|
|
**revoke only** (`DeletePermission`), never Set (pinned: the page must not offer a toggle it
|
|
cannot honor). background is plain yes/no (toggleable). location read-only listing.
|
|
- `set TABLE APP true|false` (only for simple-valued tables: camera, microphone, background),
|
|
`forget TABLE APP` (all tables). Table and app ids validated.
|
|
|
|
**NEW `scripts/panama-privacy`** — traces:
|
|
- `traces` → `{ recents: { bytes, entries }, thumbnails: { bytes }, error }` (du-based,
|
|
budgeted).
|
|
- `clear-recents` — truncate `~/.local/share/recently-used.xbel` to an empty valid xbel
|
|
document (not delete — GTK recreates but an empty valid file takes effect instantly).
|
|
- `clear-thumbnails` — guarded removal inside `~/.cache/thumbnails` only (resolve, refuse
|
|
symlink escape — the panama-disks guard pattern).
|
|
- Trash is NOT here — the page reuses `Disks.clean("trash")` / its cleanable byte count.
|
|
|
|
**`scripts/panama-ssh-keys`**:
|
|
- `generate NAME COMMENT` — ed25519 only; NAME validated `^[A-Za-z0-9_.-]{1,64}$`, confined to
|
|
`~/.ssh`, refuses overwrite; **passphrase read from stdin by the helper, handed to ssh-keygen
|
|
over a pty — never argv, never a temp file** (empty passphrase allowed but the UI requires
|
|
non-empty; helper accepts empty only with an explicit `--no-passphrase` flag the UI never
|
|
passes). Returns the fresh snapshot.
|
|
- `fix-permissions NAME` — chmod 600, same confinement, returns fresh snapshot.
|
|
- `agent-remove` exists; unchanged.
|
|
|
|
## Services (A)
|
|
|
|
- **Permissions.qml**: `tables` model per the snapshot; `setPermission(table, app, allowed)`,
|
|
`revoke(table, app)`; per-table helpers the UI needs (`simpleTables`, `revokeOnlyTables`).
|
|
- **NEW `Traces.qml`**: `recentsBytes/entries`, `thumbnailsBytes`, `measured`, `measure()`,
|
|
`clearRecents()`, `clearThumbnails()`, `busy/lastError`; seam `PANAMA_PRIVACY_HELPER`.
|
|
- **SshKeys.qml**: `generate(name, comment, passphrase)` (passphrase via Process stdin),
|
|
`removeFromAgent(path)` (wires the existing verb; surfaces the durableRemoval refusal
|
|
message), `fixPermissions(name)`, copy feedback (`copiedKey` cleared by a timer, the Keyring
|
|
`copiedPath` pattern), `refresh()` exposed for a page row.
|
|
- **Keyring.qml**: no changes expected; the confirm-state fix is page-side.
|
|
|
|
## UI (B)
|
|
|
|
**PrivacyPage.qml** rebuilt (gains `objectName: "privacy"`): unified Camera/mic/screen card
|
|
(live tiles from PrivacyState with the in-use warn tone; grouped sections per table with
|
|
uppercase labels + counts; camera/mic rows Ask-again + toggle; screencast/remote-desktop rows
|
|
detail-explained with two-stage Revoke; empty-section honesty lines; the "not sandboxed and
|
|
never ask" note); Run in the background card (all rows, toggles); Saved passwords & secrets
|
|
(unlock state row + lazy Saved-items with per-row copy feedback and per-row confirm state —
|
|
separate `confirmingItem` from the copy path); Traces card (recents/thumbnails from Traces,
|
|
trash from Disks cleanables with "the same Trash Storage cleans" detail, clipboard-history
|
|
pointer row); Device security card + "Check again" refresh row; Elsewhere card (Power & Lock,
|
|
Notifications pointers). Location section only when the table is non-empty. The Screen-lock
|
|
card is DELETED. No `openGnomePanel` calls remain.
|
|
|
|
**SshKeysPage.qml** rebuilt: error rows into cards; Your keys card (rows + copy feedback +
|
|
generate flow — name/comment LiveFieldRows, two SecretFieldRows with match validation,
|
|
Create key disabled until valid + matching); Agent card (held keys with Remove, the
|
|
design-not-a-bug prose when gnome-keyring); over-permissive warning card gains Fix
|
|
permissions; Known hosts card always visible with an empty state; a refresh row.
|
|
|
|
## Search & docs (C)
|
|
|
|
New entries: Background apps, Screen sharing permission, Remote desktop permission, Clear
|
|
recent files, Thumbnails, Application permissions (privacy) → privacy; Generate an SSH key,
|
|
Fix key permissions, Remove from agent → ssh-keys. Docs regen only if schema changes (none —
|
|
verify).
|
|
|
|
## Contracts (C — write; hermetic runs only)
|
|
|
|
- `permissions-contract`: tables model; the revoke-only rule for structured tables (no Set path
|
|
for screencast/remote-desktop anywhere — AST pin); the page never claims more than the portal
|
|
enforces (kept); validated table/app ids.
|
|
- NEW `privacy-traces-contract`: hermetic — clear-recents writes a valid empty xbel (never
|
|
deletes), clear-thumbnails guarded (symlink escape refused), no urgency language in the
|
|
Traces card copy (the anti-racket stance), trash reuses Disks (no second trash
|
|
implementation — grep pin).
|
|
- `secrets-contract`: extend — per-row confirm state separated from copy (the collision pin),
|
|
copy feedback present; all existing pins kept.
|
|
- `ssh-keys-contract`: extend — generate's passphrase never in argv (AST + runtime with a
|
|
recording stub), pty usage pinned, name confinement + overwrite refusal, fix-permissions
|
|
confinement, agent-remove reachable from QML now + the honesty prose, copy feedback; all
|
|
existing pins kept (private keys never read, passphrase rule).
|
|
- `gnome-handoff-contract`: `privacy` becomes OWNED; verify no page hands off to it.
|
|
- `lock-screen-settings-contract`: reconcile with the lock card's removal from PrivacyPage.
|
|
- Backlog Phase 12; README count line (172 → 173 expected).
|
|
|
|
## Agent ownership (parallel)
|
|
|
|
- **A**: `scripts/panama-permissions`, NEW `scripts/panama-privacy`, `scripts/panama-ssh-keys`,
|
|
`services/Permissions.qml`, NEW `services/Traces.qml`, `services/SshKeys.qml`.
|
|
- **B**: `modules/settings/PrivacyPage.qml`, `SshKeysPage.qml`, new components (+ qmldir).
|
|
- **C**: `services/SettingsSearch.qml`, contracts above, backlog, README count line.
|
|
|
|
## As built (A) — refinements to the pinned APIs
|
|
|
|
Read the real permission store before finalizing, and two things there were not
|
|
what the spec assumed:
|
|
|
|
- **screencast / remote-desktop ids are opaque restore tokens**, one per
|
|
remembered session, not the table name. So the helper `List`s the table,
|
|
`Lookup`s each token, and folds the result **by application** — one row per
|
|
app, with `grants` counting the stored sessions behind it. `forget TABLE APP`
|
|
drops every one of them, which keeps the pinned `revoke(table, app)`
|
|
signature honest.
|
|
- **`speakers` is gone.** The spec names six tables and speakers is not among
|
|
them; the old three-device model is replaced wholesale.
|
|
|
|
Additive to the pinned shapes (nothing removed):
|
|
|
|
- `snapshot` rows carry `grants` (int) and `raw` (string) beside `app`/`allowed`;
|
|
the payload carries `simpleTables` and `revokeOnlyTables` so the service never
|
|
hardcodes a list the helper could change.
|
|
- `set TABLE APP true|false` (not `allow|deny` — the old CLI's words).
|
|
- `traces` sub-objects carry `path`, `present`, and (thumbnails) `measured`,
|
|
which is false when the walk hit its budget and the byte count is a floor.
|
|
- `Traces` does **not** measure on startup. `measured` is false until the page
|
|
calls `measure()`.
|
|
- `Permissions.devices` survives as a derived camera/microphone view, because
|
|
ApplicationsPage reads it to answer "does this app have a privacy rule".
|
|
- `panama-ssh-keys generate` runs ssh-keygen with `SSH_ASKPASS_REQUIRE=never`
|
|
and no `DISPLAY`: this desktop sets `SSH_ASKPASS_REQUIRE=prefer`, which made
|
|
ssh-keygen open a graphical dialog and ignore the terminal entirely.
|
|
- Helper seams: `PANAMA_PRIVACY_HELPER`, `PANAMA_PERMISSIONS_HELPER`,
|
|
`PANAMA_SSH_KEYS_HELPER`. Fixture seams: `PANAMA_PRIVACY_RECENTS`,
|
|
`PANAMA_PRIVACY_THUMBNAILS` (both still confined to `HOME`, so a hermetic run
|
|
points `HOME` at a scratch directory).
|
|
|
|
Hard rules: NO live mutations — no portal Set/Delete, no keyring writes, no ssh-keygen runs
|
|
against the real ~/.ssh, no chmod, no truncating the real recents, no thumbnail deletion.
|
|
Read-only probes and hermetic stubs only. Valid QML/Python at every save. B programs against
|
|
the pinned APIs; A updates this spec before changing them.
|