Skip to content

Commit 1230205

Browse files
committed
fix(hosted-e2e): isolate auth drain socket
1 parent 662d998 commit 1230205

4 files changed

Lines changed: 40 additions & 7 deletions

File tree

test/e2e/hosted-v1/harness.test.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { execFile, spawn as spawnChild } from 'node:child_process';
22
import { createHash, createHmac, createPublicKey, verify } from 'node:crypto';
33
import { EventEmitter } from 'node:events';
4-
import { chmod, lstat, mkdir, mkdtemp, readFile, realpath, rm, writeFile } from 'node:fs/promises';
4+
import { chmod, lstat, mkdir, mkdtemp, readdir, readFile, realpath, rm, writeFile } from 'node:fs/promises';
55
import { createConnection, type Socket } from 'node:net';
66
import { tmpdir } from 'node:os';
77
import { join } from 'node:path';
@@ -546,15 +546,19 @@ describe('hosted v1 browser E2E sandbox', () => {
546546
const socketRoot = await mkdtemp('/tmp/hv1-r5-');
547547
roots.push(socketRoot);
548548
const lifecycleSocket = join(socketRoot, 'orchestrator-lifecycle.sock');
549-
const drainSocket = join(socketRoot, 'auth-drain.sock');
550-
const proofPath = join(sandbox.fakeRuntimeStateDir, 'auth-drain', 'drain-proof.json');
549+
// Keep the independently owned test socket below Darwin's Unix-domain path limit.
550+
// Production uses the likewise short /run/agent-teams-auth-drain mount.
551+
const authDrainRoot = await mkdtemp('/tmp/hv1-ad-');
552+
roots.push(authDrainRoot);
553+
const drainSocket = join(authDrainRoot, 'auth-drain.sock');
554+
const proofPath = join(authDrainRoot, 'drain-proof.json');
551555
const runtimeEnvironment = {
552556
...process.env,
553557
E2E_SEED_APP_DATA_ROOT: sandbox.appDataDir,
554558
E2E_SEED_CLAUDE_ROOT: sandbox.claudeDir,
555559
E2E_FAKE_RUNTIME_STATE_ROOT: sandbox.fakeRuntimeStateDir,
556560
E2E_LIFECYCLE_RUN_ROOT: socketRoot,
557-
E2E_AUTH_DRAIN_ROOT: join(sandbox.fakeRuntimeStateDir, 'auth-drain'),
561+
E2E_AUTH_DRAIN_ROOT: authDrainRoot,
558562
E2E_LIFECYCLE_TRUST_ROOT: sandbox.lifecycleTrustDir,
559563
E2E_LIFECYCLE_LAUNCHER_ROOT: sandbox.lifecycleLauncherDir,
560564
E2E_AUTH_DRAIN_INDETERMINATE_ONCE: '1',
@@ -590,6 +594,10 @@ describe('hosted v1 browser E2E sandbox', () => {
590594
waitForPath(drainSocket),
591595
waitForPath(join(socketRoot, 'lifecycle-owner-admission.json')),
592596
]);
597+
expect((await readdir(socketRoot)).sort()).toEqual([
598+
'lifecycle-owner-admission.json',
599+
'orchestrator-lifecycle.sock',
600+
]);
593601
const manifestEnvelope = JSON.parse(
594602
await readFile(join(socketRoot, 'lifecycle-owner-admission.json'), 'utf8')
595603
) as { payload: string };
@@ -2562,6 +2570,7 @@ describe('hosted v1 browser E2E sandbox', () => {
25622570
readFile('test/fixtures/hosted-v1/seedContainer.ts', 'utf8'),
25632571
readFile('.github/workflows/ci.yml', 'utf8'),
25642572
]);
2573+
const securitySpec = await readFile('test/e2e/hosted-v1/phase-6-security.spec.ts', 'utf8');
25652574
expect(compose).toContain('dockerfile: docker/Dockerfile');
25662575
expect(compose).not.toContain('docker/e2e/Dockerfile');
25672576
expect(compose).toContain('COMPOSE_PROJECT_NAME');
@@ -2583,6 +2592,18 @@ describe('hosted v1 browser E2E sandbox', () => {
25832592
expect(compose).toContain(
25842593
'AUTH_DRAIN_EVIDENCE_FILE: /run/agent-teams-auth-drain/drain-proof.json'
25852594
);
2595+
expect(seed).toContain(
2596+
'const AUTH_DRAIN_SOCKET_PATH = `${AUTH_DRAIN_ROOT}/auth-drain.sock`;'
2597+
);
2598+
expect(seed).not.toContain(
2599+
'const AUTH_DRAIN_SOCKET_PATH = `${LIFECYCLE_RUN_ROOT}/auth-drain.sock`;'
2600+
);
2601+
expect(securitySpec).toContain(
2602+
"createConnection('/run/agent-teams-auth-drain/auth-drain.sock')"
2603+
);
2604+
expect(securitySpec).not.toContain(
2605+
"createConnection('/run/agent-teams-orchestrator/auth-drain.sock')"
2606+
);
25862607
expect(compose).toContain('E2E_LIFECYCLE_TRUST_DIR');
25872608
expect(compose).toContain('agent-teams-lifecycle-trust-init:');
25882609
expect(compose).toContain(

test/e2e/hosted-v1/phase-6-security.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ async function authDrainControl(
6666
): Promise<void> {
6767
const script = `
6868
const { createConnection } = require('node:net');
69-
const socket = createConnection('/run/agent-teams-orchestrator/auth-drain.sock');
69+
const socket = createConnection('/run/agent-teams-auth-drain/auth-drain.sock');
7070
const request = JSON.parse(process.argv[1]);
7171
let body = '';
7272
socket.setEncoding('utf8');

test/fixtures/hosted-v1/seedContainer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const LIFECYCLE_TRUST_ROOT =
6060
const LIFECYCLE_LAUNCHER_ROOT =
6161
process.env.E2E_LIFECYCLE_LAUNCHER_ROOT ?? '/run/agent-teams-lifecycle-launcher';
6262
const LIFECYCLE_SOCKET_PATH = `${LIFECYCLE_RUN_ROOT}/orchestrator-lifecycle.sock`;
63-
const AUTH_DRAIN_SOCKET_PATH = `${LIFECYCLE_RUN_ROOT}/auth-drain.sock`;
63+
const AUTH_DRAIN_SOCKET_PATH = `${AUTH_DRAIN_ROOT}/auth-drain.sock`;
6464
const AUTH_DRAIN_EVIDENCE_PATH = `${AUTH_DRAIN_ROOT}/drain-proof.json`;
6565
const LIFECYCLE_OWNER_MANIFEST_PATH = `${LIFECYCLE_RUN_ROOT}/lifecycle-owner-admission.json`;
6666
const LIFECYCLE_TRUST_ANCHOR_PATH = `${LIFECYCLE_TRUST_ROOT}/trust-anchor`;

test/main/composition/hosted/hostedLifecycleProductionOwnerAdmission.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,9 @@ describe('hosted lifecycle production owner admission', () => {
398398
}
399399
});
400400

401-
it('rejects a stale socket identity and an unexpected authority-layout entry', async () => {
401+
it('rejects a stale socket identity and unexpected authority-layout files or sockets', async () => {
402402
const input = await fixture();
403+
let unexpectedSocket: Server | null = null;
403404
try {
404405
const stale = structuredClone(input.payload);
405406
const ownerBinding = stale.ownerBinding as Record<string, unknown>;
@@ -413,7 +414,18 @@ describe('hosted lifecycle production owner admission', () => {
413414
mode: 0o400,
414415
});
415416
expect(admitHostedLifecycleProductionOwner(input.environment, input.options)).toBeNull();
417+
418+
await rm(join(input.runDirectory, 'unexpected'));
419+
unexpectedSocket = createServer();
420+
const unexpectedSocketPath = join(input.runDirectory, 'auth-drain.sock');
421+
await listen(unexpectedSocket, unexpectedSocketPath);
422+
await chmod(unexpectedSocketPath, 0o600);
423+
expect(admitHostedLifecycleProductionOwner(input.environment, input.options)).toBeNull();
416424
} finally {
425+
if (unexpectedSocket !== null) {
426+
const socket = unexpectedSocket;
427+
await new Promise<void>((resolve) => socket.close(() => resolve()));
428+
}
417429
await input.close();
418430
}
419431
});

0 commit comments

Comments
 (0)