Files
Alta-Proxy-Tool/test/runtime-contract.test.js
T

330 lines
14 KiB
JavaScript

'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { PassThrough } = require('node:stream');
const {
AppRuntime,
PairingController,
createBridgeHandler,
loadPairingEnvelope,
} = require('../src/electron-runtime');
const {
APT_EXTENSION_ORIGIN,
BridgeAuth,
computeCookieProof,
createRequestLimiter,
} = require('../src/bridge-auth');
const { createSessionStore } = require('../src/session-store');
const ROOT = path.join(__dirname, '..');
const read = (name) => fs.readFileSync(path.join(ROOT, name), 'utf8');
function responseHarness() {
return {
statusCode: 0,
headers: {},
body: '',
setHeader(name, value) { this.headers[name.toLowerCase()] = value; },
writeHead(statusCode, headers = {}) {
this.statusCode = statusCode;
for (const [name, value] of Object.entries(headers)) this.setHeader(name, value);
},
end(chunk = '') { this.body += chunk; },
};
}
function requestHarness({ method = 'POST', origin = APT_EXTENSION_ORIGIN, url = '/cookie', body = '{}' } = {}) {
const request = new PassThrough();
request.method = method;
request.url = url;
request.headers = { origin };
process.nextTick(() => request.end(body));
return request;
}
test('runtime keeps Alta credentials in main-owned modules and exposes only non-secret state', async () => {
const sessionStore = createSessionStore();
sessionStore.establish('https://customer.avasecurity.com', 'top-secret-cookie');
const calls = [];
const tracked = [];
const proxyManager = {
launchProxy(request) {
calls.push(request);
tracked.push({ processId: 91, deviceId: request.deviceId, status: 'running', startedAt: 1 });
return { success: true, processId: 91, deviceId: request.deviceId, status: 'running' };
},
stopProxy(processId) {
calls.push({ processId });
tracked.splice(0, tracked.length);
return { success: true, processId, status: 'stop-requested' };
},
listTrackedProxies() { return tracked.slice(); },
};
const runtime = new AppRuntime({
sessionStore,
altaClient: {
getDevices: async () => [{ guid: '550e8400-e29b-41d4-a716-446655440000' }],
getDeviceSites: async () => [],
getAuthInfo: async () => ({ user: 'safe' }),
},
proxyManager,
checkForUpdate: async ({ currentVersion }) => ({ status: 'up-to-date', currentVersion }),
currentVersion: '1.0.0',
openExternal: async () => {},
});
assert.deepEqual(await runtime.getDevices(), { success: true, devices: [{ guid: '550e8400-e29b-41d4-a716-446655440000' }] });
const launched = await runtime.launchProxy('550e8400-e29b-41d4-a716-446655440000');
assert.equal(launched.success, true);
assert.deepEqual(calls[0], {
deploymentHost: 'customer.avasecurity.com',
cookie: 'top-secret-cookie',
deviceId: '550e8400-e29b-41d4-a716-446655440000',
});
assert.deepEqual(runtime.getConnectionState(), {
connected: true,
origin: 'https://customer.avasecurity.com',
activeProxies: [{ deviceId: '550e8400-e29b-41d4-a716-446655440000', processId: 91 }],
});
assert.equal(JSON.stringify(launched).includes('top-secret-cookie'), false);
assert.equal(JSON.stringify(runtime.getConnectionState()).includes('top-secret-cookie'), false);
assert.equal((await runtime.stopProxy('550e8400-e29b-41d4-a716-446655440000')).success, true);
assert.deepEqual(calls[1], { processId: 91 });
assert.equal((await runtime.stopProxy(999)).success, false);
});
test('bridge authenticates itself before accepting a one-time HMAC cookie request', async () => {
const protect = (value) => Buffer.from(`protected:${value}`);
const unprotect = (value) => Buffer.from(value).toString().slice('protected:'.length);
const auth = new BridgeAuth({ protect, unprotect });
const secret = auth.rotate().secret;
const sessionStore = createSessionStore();
let stateNotifications = 0;
const handler = createBridgeHandler({
bridgeAuth: auth,
sessionStore,
onConnectionStateChanged: () => { stateNotifications += 1; },
});
const unknownPreflight = requestHarness({ method: 'OPTIONS', origin: 'chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' });
const unknownResponse = responseHarness();
await handler(unknownPreflight, unknownResponse);
assert.equal(unknownResponse.statusCode, 403);
assert.equal(unknownResponse.headers['access-control-allow-origin'], undefined);
const allowedPreflight = requestHarness({ method: 'OPTIONS' });
const allowedResponse = responseHarness();
await handler(allowedPreflight, allowedResponse);
assert.equal(allowedResponse.statusCode, 204);
assert.equal(allowedResponse.headers['access-control-allow-origin'], APT_EXTENSION_ORIGIN);
assert.doesNotMatch(allowedResponse.headers['access-control-allow-headers'], /X-APT-Pairing/i);
const clientNonce = Buffer.alloc(32, 3).toString('base64url');
const challengeResponse = responseHarness();
await handler(requestHarness({ url: '/challenge', body: JSON.stringify({ clientNonce }) }), challengeResponse);
assert.equal(challengeResponse.statusCode, 200);
const challenge = JSON.parse(challengeResponse.body);
assert.equal(JSON.stringify(challenge).includes(secret), false);
const cookieBody = {
clientNonce,
serverNonce: challenge.serverNonce,
deploymentUrl: 'https://customer.avasecurity.com',
cookieValue: 'valid-cookie',
};
cookieBody.proof = computeCookieProof(secret, cookieBody);
const acceptedResponse = responseHarness();
await handler(requestHarness({ body: JSON.stringify(cookieBody) }), acceptedResponse);
assert.equal(acceptedResponse.statusCode, 200);
assert.deepEqual(sessionStore.describe(), { connected: true, origin: 'https://customer.avasecurity.com' });
assert.equal(stateNotifications, 1);
assert.equal(acceptedResponse.body.includes('valid-cookie'), false);
const replayResponse = responseHarness();
await handler(requestHarness({ body: JSON.stringify(cookieBody) }), replayResponse);
assert.equal(replayResponse.statusCode, 403);
});
test('bridge limiter encloses body reads and HMAC authentication under forged floods', async () => {
const auth = new BridgeAuth({ protect: (value) => Buffer.from(value), unprotect: (value) => Buffer.from(value).toString() });
auth.rotate();
const limiter = createRequestLimiter({ maxConcurrent: 1 });
const handler = createBridgeHandler({ bridgeAuth: auth, sessionStore: createSessionStore(), limiter, deadlineMs: 50 });
const held = new PassThrough();
held.method = 'POST';
held.url = '/challenge';
held.headers = { origin: APT_EXTENSION_ORIGIN };
const first = handler(held, responseHarness());
await new Promise((resolve) => setImmediate(resolve));
assert.equal(limiter.active, 1);
const rejected = responseHarness();
await handler(requestHarness({ url: '/challenge', body: JSON.stringify({ clientNonce: 'A'.repeat(43) }) }), rejected);
assert.equal(rejected.statusCode, 429);
held.end('{}');
await first;
});
test('pairing envelope persists encrypted with restrictive permissions and secrets are returned once', () => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'apt-pairing-'));
const envelopePath = path.join(directory, 'bridge-pairing.json');
const protect = (value) => Buffer.from(`dpapi:${value}`);
const unprotect = (value) => Buffer.from(value).toString().slice('dpapi:'.length);
const controller = new PairingController({ envelopePath, protect, unprotect });
const first = controller.initialize();
assert.equal(first.paired, true);
assert.match(first.secret, /^[A-Za-z0-9_-]{43}$/);
assert.deepEqual(controller.getStatus(), { paired: true });
const persisted = loadPairingEnvelope(envelopePath, { protect, unprotect });
assert.equal(Object.values(persisted).includes(first.secret), false);
assert.equal(fs.readFileSync(envelopePath, 'utf8').includes(first.secret), false);
if (process.platform !== 'win32') assert.equal(fs.statSync(envelopePath).mode & 0o777, 0o600);
const rotated = controller.rotate();
assert.match(rotated.secret, /^[A-Za-z0-9_-]{43}$/);
assert.notEqual(rotated.secret, first.secret);
assert.deepEqual(controller.getStatus(), { paired: true });
controller.revoke();
assert.deepEqual(controller.getStatus(), { paired: false });
assert.equal(fs.existsSync(envelopePath), false);
});
test('pairing fails closed and reports unavailable without secure storage', () => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'apt-pairing-unavailable-'));
const controller = new PairingController({ envelopePath: path.join(directory, 'bridge-pairing.json') });
assert.deepEqual(controller.initialize(), { paired: false, unavailable: true });
assert.deepEqual(controller.getStatus(), { paired: false, unavailable: true });
assert.throws(() => controller.rotate(), (error) => error.code === 'SECURE_STORAGE_UNAVAILABLE');
});
test('runtime reconciles exited children and permits relaunch for the same device', async () => {
const sessionStore = createSessionStore();
sessionStore.establish('https://customer.avasecurity.com', 'synthetic-cookie');
const deviceId = '550e8400-e29b-41d4-a716-446655440000';
const tracked = [];
let nextPid = 4101;
const runtime = new AppRuntime({
sessionStore,
altaClient: { getDevices: async () => [{ guid: deviceId }] },
proxyManager: {
launchProxy() {
const entry = { processId: nextPid++, deviceId, status: 'running', startedAt: 1 };
tracked.push(entry);
return { success: true, ...entry };
},
stopProxy() { throw new Error('unused'); },
listTrackedProxies() { return tracked.slice(); },
},
});
await runtime.getDevices();
assert.equal((await runtime.launchProxy(deviceId)).processId, 4101);
tracked.length = 0;
assert.deepEqual(runtime.getConnectionState().activeProxies, []);
assert.equal((await runtime.launchProxy(deviceId)).processId, 4102);
});
test('disconnect stops every owned proxy before clearing the Alta session', async () => {
const sessionStore = createSessionStore();
sessionStore.establish('https://customer.avasecurity.com', 'synthetic-cookie');
const tracked = [
{ processId: 4101, deviceId: '550e8400-e29b-41d4-a716-446655440000', status: 'running', startedAt: 1 },
{ processId: 4102, deviceId: '550e8400-e29b-41d4-a716-446655440001', status: 'running', startedAt: 1 },
];
const stopped = [];
const runtime = new AppRuntime({
sessionStore,
altaClient: {},
proxyManager: {
listTrackedProxies() { return tracked.slice(); },
stopProxy(processId) {
stopped.push(processId);
tracked.splice(tracked.findIndex((entry) => entry.processId === processId), 1);
return { success: true, processId, status: 'stop-requested' };
},
},
});
const state = runtime.disconnect();
assert.deepEqual(stopped, [4101, 4102]);
assert.equal(state.connected, false);
assert.deepEqual(state.activeProxies, []);
});
test('disconnect reports failure truthfully and retains session when an owned proxy cannot stop', () => {
const sessionStore = createSessionStore();
sessionStore.establish('https://customer.avasecurity.com', 'synthetic-cookie');
const tracked = [{ processId: 4101, deviceId: '550e8400-e29b-41d4-a716-446655440000', status: 'running', startedAt: 1 }];
const runtime = new AppRuntime({
sessionStore,
altaClient: {},
proxyManager: {
listTrackedProxies() { return tracked.slice(); },
stopProxy() { return { success: false, processId: 4101, status: 'permission-denied' }; },
},
});
const state = runtime.disconnect();
assert.equal(state.success, false);
assert.equal(state.connected, true);
assert.deepEqual(state.activeProxies.map((entry) => entry.processId), [4101]);
});
test('update runtime is check-only and opens only the fixed GitPeji releases page', async () => {
const opened = [];
const runtime = new AppRuntime({
sessionStore: createSessionStore(),
altaClient: {},
proxyManager: {},
checkForUpdate: async ({ currentVersion }) => ({ status: 'update-available', currentVersion, latestVersion: '1.1.0' }),
currentVersion: '1.0.0',
openExternal: async (url) => { opened.push(url); },
});
assert.deepEqual(await runtime.checkForUpdates(), {
success: true,
status: 'update-available',
currentVersion: '1.0.0',
latestVersion: '1.1.0',
});
assert.deepEqual(await runtime.openFixedReleasesPage(), { success: true });
assert.deepEqual(opened, ['https://git.pejicorp.com/peji/Alta-Proxy-Tool/releases']);
});
test('preload and renderer expose only narrow, credential-free contracts', () => {
const preload = read('preload.js');
const renderer = read('renderer.js');
const html = read('index.html');
const expectedMethods = [
'getDevices', 'getDeviceSites', 'getAuthInfo', 'launchProxy', 'stopProxy', 'disconnect',
'getConnectionState', 'checkForUpdates', 'openFixedReleasesPage', 'rotatePairing',
'revokePairing', 'getPairingStatus', 'onConnectionStateChanged',
];
for (const method of expectedMethods) assert.match(preload, new RegExp(`\\b${method}\\b`));
assert.doesNotMatch(preload, /downloadAndInstall|download-and-install|onUpdateDownloadProgress|onExtensionCookie/);
assert.doesNotMatch(renderer, /cookieValue|sessionData\.cookies|cookies\s*:/);
assert.doesNotMatch(html, /id="cookieKey"|updateProgress|Install Update/);
assert.match(renderer, /openFixedReleasesPage/);
assert.match(html, /Bridge Pairing/);
});
test('source policy removes legacy credential IPC, shell launch, broad kill, and executable updater', () => {
const production = ['main.js', 'preload.js', 'renderer.js', 'index.html', 'src/electron-runtime.js']
.map(read).join('\n');
const forbidden = [
/sanitizeBatchInput/, /taskkill/i, /\.bat\b/i, /download-and-install-update/,
/httpsGetFollowRedirects/, /api\.github\.com/i, /github releases/i,
/apt-local-bridge-token/, /X-APT-Token/, /cookie-proxy-/,
];
for (const pattern of forbidden) assert.doesNotMatch(production, pattern);
assert.doesNotMatch(read('main.js'), /axios/);
assert.match(read('main.js'), /event\.sender === webContents/);
assert.match(read('main.js'), /frame === webContents\.mainFrame/);
assert.match(read('main.js'), /pathToFileURL\(path\.join\(__dirname, 'index\.html'\)\)/);
assert.match(read('main.js'), /127\.0\.0\.1/);
assert.match(read('main.js'), /18247/);
assert.match(read('main.js'), /shell\.openExternal/);
});