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.
This commit is contained in:
Jinwoo-H
2026-11-13 19:46:14 -05:00
parent 332612dc9c
commit 67d8a539f6
2 changed files with 27 additions and 5 deletions
@@ -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<FullDiskAccessValue> {
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 {
@@ -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,