81 lines
4.4 KiB
JavaScript
81 lines
4.4 KiB
JavaScript
// Deliberately executes a fixed fake program, never Pi or caller-supplied code.
|
|
// PI_CODING_AGENT_DIR matches the statically verified 0.85.1 auth boundary.
|
|
import { spawn } from 'node:child_process';
|
|
import { mkdtemp, mkdir, writeFile, open, rm } from 'node:fs/promises';
|
|
import { constants } from 'node:fs';
|
|
import { validId } from './records.mjs';
|
|
import { exact, refuse, validateFixtureCredential } from './execution.mjs';
|
|
const fake = `
|
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
import { join } from 'node:path';
|
|
const [command, subcommand, flag, provider] = process.argv.slice(1);
|
|
if (command !== 'auth' || subcommand !== 'check' || flag !== '--provider') process.exit(2);
|
|
if (Object.keys(process.env).some(k => !['HOME','PI_CODING_AGENT_DIR','FIXTURE_MODE'].includes(k))) process.exit(2);
|
|
const path = join(process.env.PI_CODING_AGENT_DIR, 'auth.json');
|
|
const mode = process.env.FIXTURE_MODE;
|
|
if (mode === 'timeout') await new Promise(() => setInterval(() => {}, 1000));
|
|
if (mode === 'failure') { process.stderr.write('FIXTURE_PRIVATE_DIAGNOSTIC'); process.exit(1); }
|
|
if (mode === 'malformed') { await writeFile(path, '{', { mode: 0o600 }); process.exit(0); }
|
|
const auth = JSON.parse(await readFile(path, 'utf8'));
|
|
const c = auth[provider];
|
|
if (mode === 'rotate' && c.type === 'oauth') {
|
|
c.access = 'FIXTURE_ROTATED_ACCESS'; c.refresh = 'FIXTURE_ROTATED_REFRESH';
|
|
c.expires = Date.now() + 3600000;
|
|
}
|
|
await writeFile(path, JSON.stringify(auth), { mode: 0o600 });
|
|
process.stdout.write('ready');
|
|
`;
|
|
export function validateRefreshOptions(options = {}) {
|
|
exact(options, [], ['mode', 'timeoutMs']);
|
|
const mode = options.mode ?? 'rotate', timeoutMs = options.timeoutMs ?? 2000;
|
|
if (!['rotate', 'unchanged', 'failure', 'timeout', 'malformed'].includes(mode) ||
|
|
!Number.isInteger(timeoutMs) || timeoutMs < 10 || timeoutMs > 10000) refuse('invalid-refresh-option');
|
|
return { mode, timeoutMs };
|
|
}
|
|
export async function refreshFixtureCredential(provider, credential, options = {}) {
|
|
if (!validId(provider)) refuse('invalid-provider');
|
|
const { mode, timeoutMs } = validateRefreshOptions(options);
|
|
const input = validateFixtureCredential(credential, credential?.type);
|
|
const root = await mkdtemp('/tmp/mosaic-refresh-fixture-');
|
|
try {
|
|
const agent = `${root}/agent`, home = `${root}/home`, cwd = `${root}/cwd`;
|
|
for (const dir of [agent, home, cwd]) await mkdir(dir, { mode: 0o700 });
|
|
const file = `${agent}/auth.json`;
|
|
await writeFile(file, JSON.stringify({ [provider]: input }), { mode: 0o600, flag: 'wx' });
|
|
const outcome = await new Promise(resolve => {
|
|
let timedOut = false, spawnFailed = false;
|
|
const child = spawn(process.execPath, ['--input-type=module', '-e', fake, 'auth', 'check', '--provider', provider], {
|
|
cwd, env: { HOME: home, PI_CODING_AGENT_DIR: agent, FIXTURE_MODE: mode },
|
|
// Child output is discarded, never buffered, parsed, logged or returned.
|
|
stdio: 'ignore', shell: false,
|
|
});
|
|
const timer = setTimeout(() => { timedOut = true; child.kill('SIGKILL'); }, timeoutMs);
|
|
child.on('error', () => { spawnFailed = true; });
|
|
child.on('close', (code, signal) => {
|
|
clearTimeout(timer); resolve({ code, signal, timedOut, spawnFailed });
|
|
});
|
|
});
|
|
if (outcome.timedOut) refuse('refresh-timeout');
|
|
if (outcome.spawnFailed || outcome.code !== 0 || outcome.signal) refuse('refresh-failed');
|
|
const h = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
let result;
|
|
try {
|
|
const s = await h.stat();
|
|
if (!s.isFile() || s.uid !== process.getuid() || (s.mode & 0o777) !== 0o600 || s.size > 4096) refuse('invalid-refresh-output');
|
|
const buf = Buffer.alloc(4097); let size = 0;
|
|
while (size < buf.length) {
|
|
const { bytesRead } = await h.read(buf, size, buf.length - size, null);
|
|
if (!bytesRead) break;
|
|
size += bytesRead;
|
|
}
|
|
if (size > 4096) refuse('invalid-refresh-output');
|
|
try { result = JSON.parse(buf.subarray(0, size).toString('utf8')); }
|
|
catch { refuse('invalid-refresh-output'); }
|
|
} finally { await h.close(); }
|
|
exact(result, [provider]);
|
|
const output = validateFixtureCredential(result[provider], input.type);
|
|
if (output.type === 'oauth' && output.expires <= Date.now() + 300000) refuse('refresh-not-ready');
|
|
return output;
|
|
} finally { await rm(root, { recursive: true, force: true }); }
|
|
}
|