Skip to content

feat(stereo-camera): delegation hardening — signer match, per-user first-use notice, DECLINE_DELEGATION (spec v3) - #1838

Merged
dfattal merged 1 commit into
mainfrom
feat/stereo-camera-delegation-hardening
Oct 7, 2026
Merged

dfattal merged 1 commit into
mainfrom
feat/stereo-camera-delegation-hardening

Conversation

@dfattal

@dfattal dfattal commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

What

Three hardening items for the ADR-043 stereo-camera consent model (normative text: spec §2a, §7.1 row 6, new §7.1.1; ADR-043 Amendment 3).

  1. Signer match for user-writable delegating entries (Windows). A delegating entry (HKLM or HKCU Software\DisplayXR\CameraConsent\Delegating) whose executable is not under %ProgramFiles%, %ProgramFiles(x86)% or %SystemRoot% (SHGetKnownFolderPath, ASCII case-insensitive, separator boundary, any .. component = not under) is applied only if the entry records a signer CN, as a sibling REG_SZ named <valueName>.signer, and the exe has a valid Authenticode signature whose leaf subject CN matches it (case-insensitive, trimmed). The check is WinVerifyTrust with WINTRUST_ACTION_GENERIC_VERIFY_V2, no UI, WTD_REVOKE_NONE and WTD_CACHE_ONLY_URL_RETRIEVAL, so it never touches the network. If the check fails, step 6 is skipped exactly as if the exe were not delegating: stored decision, then prompt. One WARN per exe per service run names the reason. Admin-protected paths keep path-only trust. POSIX stays path-only (u_camera_consent_path_user_writable() returns false; documented).
  2. First-use notice for per-user delegation. The first time a USER-level entry allows an exe in a service run, the service logs a WARN and shows a tray balloon ("3D camera: per-user trust used"). This goes through the new ipc_server_stereo_camera_set_notice_provider, which service_tray_win.c implements. It fires once per exe per run. HKLM entries stay silent. macOS logs the WARN only for now.
  3. XR_STEREO_CAMERA_CLIENT_DECLINE_DELEGATION_BIT_DXR (0x2), spec v2 → v3. The header text matches the brief exactly. The flag travels XrStereoCameraClientInfoDXR.flags → xrt_application_info.stereo_camera_client_flags (IPC wire) → consent_evaluate() → u_camera_consent_evaluate(..., flags, ...), which then skips step 6. Unknown bits are ignored.

Design notes

  • The store op is_delegating is replaced by get_delegation (scope SYSTEM/USER + signer). The policy gets the platform facts through the env vtable (path_user_writable, exe_signer, user_delegation_notice), so it stays pure and testable with fakes. If path_user_writable is NULL, every path counts as user-writable (fails closed).
  • decision.delegating still means registered, and the RAW refusal keys on it, so an untrusted or declined entry can only narrow what a client gets. The new decision.delegated / stream.delegated means "allowed by delegation". Only those streams keep the foreground-rule exemption. A registered client that declined, or whose entry was untrusted, is treated as an ordinary window-bearing app.
  • xrt_application_info grows one uint64_t, which changes the client↔service wire layout. Client and service ship together, and the git-tag gate already enforces that.
  • Known limit, documented in §7.1.1: some folders under %SystemRoot% (Temp, Tasks) and any install directory whose ACL an installer loosened are writable anyway.
  • Guide fixes made along the way: installer rule 8 had the pre-fix(stereo-camera): delegation never outranks the OS camera switch or a stored Deny #1836 step order, and rule 4 described the old ANSI registry reader. Both now match the current code.

CLI

  • camera trust <exe>|--self [--signer "<CN>"] writes or replaces the .signer companion and warns when the entry will not apply. untrust removes both values.
  • camera signer <exe>|--self prints the path class and the valid Authenticode signer. Exit 3 means it is not validly signed.
  • camera list|calib|probe ... --decline-delegation declares the new bit, so the owner can test without a browser.
  • camera consent shows each delegating entry's signer.

Tests

  • tests_camera_consent has 25 test cases / 272 assertions, all pass (MSVC Release). The new cases cover: DECLINE (falls to stored Allow / Allow once / prompt, stored Deny still wins, unknown bits ignored, unregistered exe unaffected), user-writable entry without a signer / unsigned / wrong signer → not delegated, matching signer → delegated, admin path → path-only (signer never queried), host without classifiers fails closed, user-level notice once per exe (none for SYSTEM / declined / Deny), seen-set bounded, path_is_under, signer_equal.
  • Mutation check. Each of 7 mutations of the new rules makes at least one case fail: decline ignored, user-writable ignored, no-signer accepted, mismatch accepted, unsigned accepted, notice dropped, notice for SYSTEM.
  • MSVC is warning-clean except the known oxr_session.c:5562 C4090.
  • gen_extensions_index.py --check passes. The index.json summary is unchanged.
  • The MinGW check was not run: there is no mingw-w64 cross compiler on this box.
  • Smoke test of the real platform functions (camera signer): C:\Windows\explorer.exe is admin-protected, signed "Microsoft Windows". %LOCALAPPDATA%\Programs\cursor\Cursor.exe is USER-WRITABLE, signed "Anysphere, Inc.". The dev _package\bin\displayxr-cli.exe is USER-WRITABLE and unsigned (exit 3).

Not hardware-verified. The owner session runs the panel / service checks.

🤖 Generated with Claude Code

…rst-use notice, DECLINE_DELEGATION (spec v3)

Three hardening items for the ADR-043 consent model (spec §2a, §7.1, §7.1.1):

- Signer match for user-writable delegating entries (Windows). An entry whose
  executable is not under %ProgramFiles%, %ProgramFiles(x86)% or %SystemRoot%
  (SHGetKnownFolderPath, case-insensitive, separator boundary, no "..") is
  applied only when it records a signer CN (sibling REG_SZ "<valueName>.signer")
  and the executable has a valid Authenticode signature by that signer
  (WinVerifyTrust GENERIC_VERIFY_V2, no UI, WTD_REVOKE_NONE, cache-only URL
  retrieval). Otherwise step 6 is skipped as if not delegating, with one WARN
  per exe per run. Admin-protected paths keep path-only trust; POSIX is
  path-only for now (documented).
- First-use notice for USER-level (HKCU / user JSON) delegation: WARN + tray
  balloon (new ipc_server_stereo_camera_set_notice_provider), once per exe per
  service run. HKLM stays silent.
- XR_STEREO_CAMERA_CLIENT_DECLINE_DELEGATION_BIT_DXR (0x2), spec v2 -> v3,
  plumbed XrStereoCameraClientInfoDXR -> xrt_application_info
  .stereo_camera_client_flags -> IPC -> u_camera_consent_evaluate(flags).

A skipped delegation also drops the foreground-rule exemption (new
decision.delegated / stream.delegated) but never the RAW refusal, which keys
on registration alone. Store op is_delegating -> get_delegation (scope +
signer); the policy takes path_user_writable / exe_signer /
user_delegation_notice through the env vtable (NULL path classifier fails
closed), so it stays unit-testable with fakes.

CLI: `camera trust <exe> [--signer CN]`, `camera signer <exe>|--self`
(exit 3 = not validly signed), `list|calib|probe --decline-delegation`.

Tests: tests_camera_consent 25 cases / 272 assertions; 7 mutations of the new
rules each fail at least one case.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@dfattal
dfattal marked this pull request as ready for review October 7, 2026 03:09
@dfattal
dfattal requested a review from a team as a code owner October 7, 2026 03:09
@dfattal
dfattal merged commit a1d8a32 into main Oct 7, 2026
40 checks passed
@dfattal
dfattal deleted the feat/stereo-camera-delegation-hardening branch October 7, 2026 03:09
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