From 5d3dcafb2ff4f36f531658629c3b37bf64d13eab Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 13 Nov 2026 18:05:38 -0800 Subject: [PATCH] Avoid hardening work for absent environment store files --- .../runtime-environment-store-observation.ts | 27 ++++++++++++++----- .../runtime-environment-store-worker.test.ts | 18 ++++++++++++- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/shared/remote-runtime/host-store/runtime-environment-store-observation.ts b/src/shared/remote-runtime/host-store/runtime-environment-store-observation.ts index ebe4827ca27..154472ccaa4 100644 --- a/src/shared/remote-runtime/host-store/runtime-environment-store-observation.ts +++ b/src/shared/remote-runtime/host-store/runtime-environment-store-observation.ts @@ -41,15 +41,30 @@ export function readEnvironmentRegistryFile( if (observation?.has(path)) { return observation.get(path) ?? null } - let read: BoundedNodeFileRead | null = null + let read: BoundedNodeFileRead | null try { + read = readPresentEnvironmentRegistryFile(path, maxBytes) + } catch { + hardenExistingSecureFile(path) + read = readPresentEnvironmentRegistryFile(path, maxBytes) + } + if (read) { hardenExistingSecureFile(path) - read = readNodeFileSyncWithinLimit(path, maxBytes) - } catch (error) { - if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) { - throw error - } } observation?.set(path, read) return read } + +function readPresentEnvironmentRegistryFile( + path: string, + maxBytes: number +): BoundedNodeFileRead | null { + try { + return readNodeFileSyncWithinLimit(path, maxBytes) + } catch (error) { + if (error instanceof Error && 'code' in error && error.code === 'ENOENT') { + return null + } + throw error + } +} diff --git a/src/shared/remote-runtime/host-store/runtime-environment-store-worker.test.ts b/src/shared/remote-runtime/host-store/runtime-environment-store-worker.test.ts index 6f0b84e3a36..789e2ab597e 100644 --- a/src/shared/remote-runtime/host-store/runtime-environment-store-worker.test.ts +++ b/src/shared/remote-runtime/host-store/runtime-environment-store-worker.test.ts @@ -3,7 +3,7 @@ import { closeEnvironmentStore, environmentStoreWriteCount } from './runtime-environment-store-worker-client' -import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { chmodSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { Worker } from 'node:worker_threads' @@ -42,6 +42,22 @@ afterEach(() => { }) describe('asynchronous environment store owner', () => { + it.skipIf(process.platform === 'win32' || process.getuid?.() === 0)( + 'repairs owner read permission before returning an existing environment', + async () => { + const root = profile() + const saved = await addEnvironmentFromPairingCode(root, { + name: 'Private', + pairingCode: pairing(11), + now: 100 + }) + const path = getEnvironmentStorePath(root) + chmodSync(path, 0) + expect((await resolveEnvironment(root, saved.id)).name).toBe('Private') + expect(statSync(path).mode & 0o777).toBe(0o600) + } + ) + it('captures arguments before the caller can mutate them', async () => { const root = profile() const args = { name: 'Before', pairingCode: pairing(9), now: 100 }