# Independent Plan Review: Remozio implementation plan, revision 3

**Scope:** Plan-only review of `reviews/round-3-plan.md`. No tools were used, and no testing, browsing, or source inspection was performed. Platform behaviour is not asserted beyond what the plan states. Where a question is already an explicit feasibility gate, it is not reported as a defect.

**Overall:** The plan is internally coherent on its core controls:

- authority-signed requests, independently re-verified by the root service and the GUI adapter;
- separate biometric and decision keys;
- a durable consumption ledger with an `unknown` outcome and no automatic retry;
- full argv rendering and stdin-source disclosure;
- Refresh-based recovery for expired dialogs;
- all three adapters kept in scope.

The remaining findings concern precise scope classification for biometric-free actions, same-user hardening of the GUI adapter, phone-side protections for the non-biometric decision key, and validation coverage for allow-once.

---

## Concrete planning defects

### F1 — Medium: GUI adapter compromise is excluded, but no same-user hardening makes that exclusion hold
**Sections:** §2 (threat model: "malicious unprivileged requesting process"), §3 ("compromise of that signed process is outside the stated assumption"), §10 acceptance list.

**Problem:** The per-user GUI adapter holds three valuable things:
- Accessibility permission;
- the ability to request dispatch permits;
- plaintext credentials during unlock.

It runs as the same user as the in-scope malicious caller. §2 assumes the "installed signed app" is uncompromised, but the plan never requires the properties that stop a same-user process from subverting it:
- hardened runtime;
- library validation;
- no `get-task-allow`, `disable-library-validation`, or DYLD-environment entitlements;
- an administrator-owned install location for the bundle.

Only the transport fallback gets an explicit administrator-controlled directory requirement.

**Failure scenario:** The adapter bundle lives in a user-writable location, or ships with a debugging or DYLD-tolerant entitlement. A same-user process then injects into it or alters it. It can then:
- receive a certified recipient key for a real unlock request and read the released password; or
- fabricate AX "assertions" that the root service endorses as adapter provenance.

The root service's audit-token and designated-requirement check limits this only if dynamic code validity is enforced and injection is structurally prevented. The plan does not state either requirement.

**Minimal correction:**
- Require hardened runtime and library validation for the GUI adapter and the frontend.
- Forbid the entitlements listed above in release builds.
- Install the bundle in a root/admin-owned location.
- Have the root service check dynamic code validity on every XPC request, not only at launch.
- Add acceptance tests: same-user dylib/DYLD injection into the adapter fails, and a same-user modification of the bundle fails.

**UX impact:** None.

### F2 — Medium: the "internal bookkeeping" clause weakens the one-time vs reusable classification
**Sections:** §1 table, §9 ("Internal bookkeeping for the current request does not by itself make an action a reusable rule"), §9 Little Snitch.

**Problem:** The biometric-free exception depends on the effect being confined to the current request. The plan never defines "current request" operationally. The bookkeeping clause invites classifying some target-app behaviours as one-time when they are not, such as a transient rule matching the same process, host, or port for a period, or until the connection or process ends. Whether Little Snitch's "Once" behaves that way is exactly what §9 says must be validated. The plan should not pre-excuse it.

**Failure scenario:** An adapter capability mapping marks an action "once" because the app labels it that way. The app actually creates a short-lived internal rule. A malicious caller then makes repeated connections within that window, all covered by one biometric-free allow-once.

**Minimal correction:** State a testable criterion. A biometric-free decision may affect only the connection attempt(s) captured in that specific alert. Any effect on a later attempt, even briefly or by internal mechanism, counts as reusable and requires biometrics.

Bookkeeping that cannot match a later attempt is harmless and may be ignored. Validate this per app version with a second-connection test. If Little Snitch's one-time option fails the criterion, record it as a feasibility result and consult the user, per §9's existing rule.

**UX impact:** None if "Once" is truly one-time. If it is not, any change is a user decision, not a silent downgrade.

### F3 — Medium: allow-once is missing from the per-version validation rule and from acceptance tests
**Sections:** §6 ("A target-app cancel or deny-once … must use the dispatch ledger"), §9 ("Cancel and deny-once bypass biometrics only when their actual effect is confined…"), §10 acceptance lists.

**Problem:** The user-confirmed exception covers cancel, deny-once and allow-once. Two sentences that define the confinement obligation name only cancel and deny-once:
- §9's per-app/version effect validation;
- §6's explicit ledger sentence.

§10 tests that "cancel and true deny-once work without access to the biometric key." It has no test that:
- allow-once works with the decision key; or
- allow-once is confined to the captured Little Snitch request.

There is also no negative test that the decision key is rejected for command execution, 1Password access/unlock, credential release, and enrollment. The acceptance list covers only reusable rules. That leaves the "do not widen to command/1Password" boundary unverified.

**Failure scenario:** An implementation follows §9 literally. It validates confinement for deny-once but not allow-once. It ships an allow-once path whose true effect was never checked. Separately, a verifier bug accepts decision-key signatures for a command purpose, and no acceptance test catches it.

**Minimal correction:**
- Add allow-once to the §9 confinement/validation sentence and the §6 ledger sentence.
- Add acceptance cases:
  - Little Snitch allow-once succeeds with the decision key and affects only the captured request;
  - the decision key is rejected, at both the root service and the adapter, for command approval, 1Password access/unlock, credential release, and enrollment;
  - an allow-once signature cannot be replayed onto a different Little Snitch alert.

**UX impact:** None.

### F4 — Medium: behaviour on unrecognized target-app versions is undefined, which affects both security and UX
**Sections:** §6 ("trusted adapter capability mapping"), §9 ("Validate this effect for each supported app/version"; "Unsupported dialogs retain a visible pending notification and a manual remote-control route").

**Problem:** Classification relies on a per-version mapping, and target apps update independently. The plan does not say what happens when the observed version, or the AX structure, is not in the mapping. Two readings are possible:
- **Fail open:** keep using a stale mapping. Biometric-free once-actions may carry an unvalidated effect.
- **Fail fully closed:** treat the dialog as unsupported. Remote Little Snitch and 1Password handling silently disappears after every app update. That is a reduction in remote functionality.

**Minimal correction:**
- State that biometric-free classification is never applied to an unmapped version or structure.
- Require read-only detection and notification to continue.
- Record the fallback for actions as a pending user decision. Options include:
  - (a) unsupported with a manual route;
  - (b) available, but all target-app actions require biometrics until the version is validated;
  - (c) another user-chosen option.
- Add an acceptance test for an unknown version.

**UX flag:** Options (a) and (b) worsen UX (reduced functionality, or extra biometrics). They require user consultation and must not be silently adopted.

### F5 — Low/Medium: phone-side protections for the non-biometric decision key are unspecified
**Sections:** §2 (threat model), §6 (decision key), §7 ("explicit foreground-tap rules"), §9 ("An operator with access to the unlocked phone app…").

**Problem:** The accepted tradeoff assumes the actor is someone using the unlocked phone app. The plan does not require either of the following:
- that the decision key be usable only while the device is unlocked;
- that the approval activity refuse to show over the keyguard.

It also never states whether other, unprivileged Android apps are in the phone-side threat model. If they are, a single in-app tap on "Allow once" is the only consent, and overlay or tapjacking attacks become relevant. Biometric actions are protected by the system BiometricPrompt; decision-key actions are not.

**Failure scenario:** An approval activity displayed over the lock screen lets someone holding a locked phone perform a Little Snitch allow-once. Alternatively, an overlay app induces a tap on "Allow once."

**Minimal correction:**
- Require an unlocked-device constraint on the decision key where the platform supports it. Keep the app from showing approval actions over the keyguard.
- Ignore obscured or overlaid touches on decision buttons, or hide overlays on supported API levels.
- State explicitly whether malicious phone apps are in scope.

**UX flag:** Requiring device unlock matches the stated flow (notification → details), and the plan already hides content on the lock screen, so it is expected to be neutral. If the user wants lock-screen once-actions, that is a user decision.

### F6 — Low: authority request and phone decision fields are conflated in the envelope spec
**Section:** §6 ("The envelope includes … phone key ID, … action choice").

**Problem:** The authority creates the immutable request before the phone chooses an action. The authority therefore cannot know which key the user will use (biometric or decision) or which action they will pick. It also cannot know which phone will act, if more than one is enrolled.

Implementations may then diverge. One may bind a key ID in the request and break allow-once. Another may loosely accept any key ID and weaken the key-purpose check.

**Minimal correction:** Specify two structures:
1. **Authority-signed request:** request/Mac/account binding, challenge, expiry, payload, and captured action/scope choices.
2. **Phone-signed decision:** request digest, selected action/scope, signing key ID, key purpose, and phone identity.

Verifiers derive the required key class from the retained action semantics. They then check the signing key's enrolled purpose against that class.

**UX impact:** None.

### F7 — Low: applying a phone-selected scope to the dialog is an unpermitted pre-dispatch mutation
**Section:** §9 ("Apply and re-read the selected scope on the same verified dialog before dispatch"), §6 (permit and unknown handling).

**Problem:** Changing the dialog's scope control is itself an external UI action. The plan requires a permit only for the final dispatch. It does not say:
- whether scope application happens under the permit; or
- what happens if revalidation fails after the control was changed.

**Failure scenario:** The adapter switches Forever to Once, and revalidation then fails. The dialog is left in a Remozio-modified state. A later Refresh captures the modified value as the "existing default," which violates the §9 rule to reflect the user's own defaults. A crash between applying the scope and clicking also lacks a defined outcome.

**Minimal correction:**
- Perform scope application inside the same one-time permit.
- Treat any failure after the control is changed as `unknown`.
- Mark refreshed requests whose captured default may be Remozio-modified.
- Add the race to the §10 tests.

**UX impact:** None.

---

## Tentative UX choices requiring explicit user confirmation
These are not design defects. They are choices the plan makes, or implies, without recorded user consent. §1 says such choices must be listed as pending decisions.

- **U1 — Forced re-enrollment on biometric change** (§5, §7). "Invalidation on biometric enrollment changes" means that adding a fingerprint or face forces full re-enrollment, including Mac administrator authorization. §1 lists "force repeated enrollment" as consultation-required, and this choice is not recorded as user-confirmed.
  - The plan also leaves a partial state undefined. After invalidation, the decision key, which is not biometric-bound, apparently still works, so once-actions keep working while protected actions fail.
  - The plan should state whether this partial state is intended, and how the app presents it.
  - Invalidation is the more protective option. Relaxing it should not be proposed silently in either direction.
- **U2 — Fallback for unknown app versions** (see F4).
- **U3 — Lock-screen availability of once-actions** (see F5), if the user expects it.

## Items correctly treated as feasibility gates (not defects)
The following have scheduled user consultation on failure:
- single-prompt credential release (§7);
- descriptor-based or atomic execution (§8);
- sudoers policy compatibility (§8);
- pre-login transport key custody (§3);
- secure-field targeting (§9);
- Little Snitch true one-time behaviour (§9);
- Android background delivery (§5);
- Flux licensing (§4).

Note one interaction: §7's "no authentication grace period" invariant may conflict with the only single-prompt credential-release design. Any time-bound key would count as "weaker key protection" and already triggers consultation, so this is covered.

---

**Conclusion: SUBSTANTIVE FINDINGS REMAIN.** F1–F4 are medium; F5–F7 are low or low/medium. U1–U3 need explicit user decisions before convergence can be claimed.
