From 67d8a539f6b9feb78f9ee83db82d2dd2b0fa5f1e Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Fri, 13 Nov 2026 19:46:14 -0500 Subject: [PATCH] fix(daemon): recheck consent after the access reader's lazy import The Full Disk Access reader started after its import resolved without asking again, and its catch swallowed the opt-out; ask after the import and let the opt-out end the capture so its claim is freed. --- .../daemon/permissions/tcc-denial-capture.ts | 17 ++++++++++++----- .../testing/tcc-denial-capture.test.ts | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/main/daemon/permissions/tcc-denial-capture.ts b/src/main/daemon/permissions/tcc-denial-capture.ts index 5172fb1b3b7..f389b9340b7 100644 --- a/src/main/daemon/permissions/tcc-denial-capture.ts +++ b/src/main/daemon/permissions/tcc-denial-capture.ts @@ -12,6 +12,7 @@ import { type HelperOrigin } from '../../../shared/daemon-tcc-denial-capture' import { LOCAL_BUILD_COMPATIBILITY_CONTRACT } from '../../../shared/local-build-compatibility-contract' +import type { DeveloperPermissionStatus } from '../../../shared/developer-permissions-types' import type { EventProps } from '../../../shared/telemetry-events' import { msSinceWakeSignal } from '../../power/wake-signal-clock' import { isTelemetryEnabled, track } from '../../telemetry/client' @@ -150,17 +151,23 @@ export function releaseTccDenialCapture(start: TccDenialCaptureStart): void { } async function readFullDiskAccess(): Promise { + let status: DeveloperPermissionStatus try { // Lazy: the status reader reaches the provider modules that import this capture. const { getTerminalHostsFullDiskAccessStatus } = await import('./daemon-full-disk-access-status') - const status = await getTerminalHostsFullDiskAccessStatus() - return status === 'granted' || status === 'denied' || status === 'unsupported' - ? status - : 'unknown' - } catch { + // The import is an await: consent may have gone, and the reader probes the host and disk. + requireTelemetryConsent() + status = await getTerminalHostsFullDiskAccessStatus() + } catch (error) { + if (error instanceof TelemetryOptedOutError) { + throw error + } return 'unknown' } + return status === 'granted' || status === 'denied' || status === 'unsupported' + ? status + : 'unknown' } function helperOriginOf(identity: DaemonEndpointIdentity): HelperOrigin { diff --git a/src/main/daemon/permissions/testing/tcc-denial-capture.test.ts b/src/main/daemon/permissions/testing/tcc-denial-capture.test.ts index a3e0f152322..4cca5c77c96 100644 --- a/src/main/daemon/permissions/testing/tcc-denial-capture.test.ts +++ b/src/main/daemon/permissions/testing/tcc-denial-capture.test.ts @@ -88,6 +88,21 @@ describe('tcc denial capture', () => { result: { identity: 'resolved', executablePath: null }, skipped: [mocks.logVerdict, mocks.signatures, mocks.fullDiskAccess] }, + { + // The access reader is lazily imported, so it starts after an await of its own. + stage: 'log reader, before the lazily imported access reader', + probe: mocks.logVerdict, + result: { + logAccess: 'ok', + tccdAsked: 'no', + authValue: 'none', + authReason: 'none', + service: 'none', + requirementFailure: 'no', + kernelDeny: 'no' + }, + skipped: [mocks.fullDiskAccess] + }, { stage: 'log, signature and access probes', probe: mocks.signatures,