564 lines
24 KiB
JavaScript
564 lines
24 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: [{
|
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
name: null,
|
|
type: null,
|
|
model: null,
|
|
address: null,
|
|
siteId: null,
|
|
deviceGroupId: null,
|
|
displayStatus: null,
|
|
localStorage: null,
|
|
}],
|
|
});
|
|
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('runtime discovers and projects hierarchy while metadata failures preserve cameras', async () => {
|
|
const deviceId = '550e8400-e29b-41d4-a716-446655440000';
|
|
const runtime = new AppRuntime({
|
|
sessionStore: createSessionStore(),
|
|
altaClient: {
|
|
getDevices: async () => [{
|
|
guid: deviceId,
|
|
name: 'Camera',
|
|
server_group_id: 'site-1',
|
|
secret: 'drop-me',
|
|
}],
|
|
getDeviceSites: async () => { throw Object.assign(new Error('tenant secret'), { code: 'ALTA_HTTP_ERROR' }); },
|
|
getDeviceGroups: async () => [{ id: 'group-1', name: 'Lobby', parent_id: null }],
|
|
},
|
|
proxyManager: { listTrackedProxies: () => [] },
|
|
});
|
|
|
|
const result = await runtime.getDeviceHierarchy();
|
|
assert.equal(result.success, true);
|
|
assert.deepEqual(result.hierarchy.devices.map(({ id }) => id), [deviceId]);
|
|
assert.deepEqual(result.hierarchy.sites, []);
|
|
assert.deepEqual(result.hierarchy.groups, [{
|
|
id: 'group-1', name: 'Lobby', parentId: null, pendingDeletionStart: null,
|
|
}]);
|
|
assert.equal(result.hierarchy.diagnostics.sites.error, 'Device sites unavailable');
|
|
assert.doesNotMatch(JSON.stringify(result), /drop-me|tenant secret/);
|
|
});
|
|
|
|
test('runtime enforces a bounded whole-discovery deadline', async () => {
|
|
const runtime = new AppRuntime({
|
|
sessionStore: createSessionStore(),
|
|
altaClient: {
|
|
getDevices: async () => new Promise(() => {}),
|
|
getDeviceSites: async () => [],
|
|
getDeviceGroups: async () => [],
|
|
},
|
|
proxyManager: { listTrackedProxies: () => [] },
|
|
discoveryTimeoutMs: 25,
|
|
});
|
|
const result = await runtime.getDeviceHierarchy();
|
|
assert.deepEqual(result, {
|
|
success: false,
|
|
hierarchy: { devices: [], sites: [], groups: [] },
|
|
message: 'Alta device discovery timed out',
|
|
});
|
|
});
|
|
|
|
test('newest hierarchy discovery owns the launch allowlist when completions arrive out of order', async () => {
|
|
const sessionStore = createSessionStore();
|
|
sessionStore.establish('https://customer.avasecurity.com', 'synthetic-cookie');
|
|
const deviceA = '550e8400-e29b-41d4-a716-44665544000a';
|
|
const deviceB = '550e8400-e29b-41d4-a716-44665544000b';
|
|
const pending = [];
|
|
const launches = [];
|
|
const runtime = new AppRuntime({
|
|
sessionStore,
|
|
altaClient: {
|
|
getDevices: () => new Promise((resolve) => pending.push(resolve)),
|
|
getDeviceSites: async () => [],
|
|
getDeviceGroups: async () => [],
|
|
},
|
|
proxyManager: {
|
|
launchProxy(request) {
|
|
launches.push(request.deviceId);
|
|
return { processId: 5000 + launches.length, deviceId: request.deviceId, status: 'running' };
|
|
},
|
|
listTrackedProxies: () => [],
|
|
},
|
|
});
|
|
|
|
const staleA = runtime.getDeviceHierarchy();
|
|
const freshB = runtime.getDeviceHierarchy();
|
|
pending[1]([{ guid: deviceB }]);
|
|
assert.equal((await freshB).success, true);
|
|
assert.equal((await runtime.launchProxy(deviceB)).success, true);
|
|
pending[0]([{ guid: deviceA }]);
|
|
assert.deepEqual(await staleA, {
|
|
success: false,
|
|
stale: true,
|
|
hierarchy: { devices: [], sites: [], groups: [] },
|
|
message: 'Alta device discovery result is stale',
|
|
});
|
|
assert.equal((await runtime.launchProxy(deviceA)).success, false);
|
|
assert.deepEqual(launches, [deviceB]);
|
|
});
|
|
|
|
test('disconnect and session changes invalidate pending discovery without repopulating the allowlist', async () => {
|
|
const deviceId = '550e8400-e29b-41d4-a716-44665544000c';
|
|
for (const invalidate of ['disconnect', 'session-change']) {
|
|
const sessionStore = createSessionStore();
|
|
sessionStore.establish('https://first.avasecurity.com', 'synthetic-cookie');
|
|
let resolveDevices;
|
|
const runtime = new AppRuntime({
|
|
sessionStore,
|
|
altaClient: {
|
|
getDevices: () => new Promise((resolve) => { resolveDevices = resolve; }),
|
|
getDeviceSites: async () => [],
|
|
getDeviceGroups: async () => [],
|
|
},
|
|
proxyManager: { listTrackedProxies: () => [] },
|
|
});
|
|
const discovery = runtime.getDeviceHierarchy();
|
|
if (invalidate === 'disconnect') {
|
|
assert.equal((await runtime.disconnect()).success, true);
|
|
} else {
|
|
sessionStore.establish('https://second.avasecurity.com', 'replacement-cookie');
|
|
runtime.onSessionChanged();
|
|
}
|
|
resolveDevices([{ guid: deviceId }]);
|
|
assert.equal((await discovery).stale, true, invalidate);
|
|
assert.equal(runtime.allowedDeviceIds.size, 0, invalidate);
|
|
}
|
|
});
|
|
|
|
test('launch allowlist remains bound to the session origin that produced it', async () => {
|
|
const deviceId = '550e8400-e29b-41d4-a716-44665544000d';
|
|
const sessionStore = createSessionStore();
|
|
sessionStore.establish('https://first.avasecurity.com', 'synthetic-cookie');
|
|
let launches = 0;
|
|
const runtime = new AppRuntime({
|
|
sessionStore,
|
|
altaClient: {
|
|
getDevices: async () => [{ guid: deviceId }],
|
|
getDeviceSites: async () => [],
|
|
getDeviceGroups: async () => [],
|
|
},
|
|
proxyManager: {
|
|
listTrackedProxies: () => [],
|
|
launchProxy() { launches += 1; },
|
|
},
|
|
});
|
|
assert.equal((await runtime.getDeviceHierarchy()).success, true);
|
|
sessionStore.establish('https://second.avasecurity.com', 'replacement-cookie');
|
|
assert.equal((await runtime.launchProxy(deviceId)).success, false);
|
|
assert.equal(launches, 0);
|
|
});
|
|
|
|
test('runtime rejects discovery deadlines above 60 seconds', () => {
|
|
assert.throws(() => new AppRuntime({ discoveryTimeoutMs: 60_001 }), /discovery timeout/i);
|
|
});
|
|
|
|
|
|
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 = await 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', 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 }];
|
|
const runtime = new AppRuntime({
|
|
sessionStore,
|
|
altaClient: {},
|
|
proxyManager: {
|
|
listTrackedProxies() { return tracked.slice(); },
|
|
stopProxy() { return { success: false, processId: 4101, status: 'permission-denied' }; },
|
|
},
|
|
});
|
|
const state = await runtime.disconnect();
|
|
assert.equal(state.success, false);
|
|
assert.equal(state.connected, true);
|
|
assert.deepEqual(state.activeProxies.map((entry) => entry.processId), [4101]);
|
|
});
|
|
|
|
test('kill request without exit keeps proxy tracked and session connected until confirmed exit', async () => {
|
|
const { EventEmitter } = require('node:events');
|
|
const { createProxyManager } = require('../src/proxy-launch');
|
|
const sessionStore = createSessionStore();
|
|
sessionStore.establish('https://customer.avasecurity.com', 'synthetic-cookie');
|
|
const deviceId = '550e8400-e29b-41d4-a716-446655440000';
|
|
const child = new EventEmitter();
|
|
child.pid = 4101;
|
|
child.exitCode = null;
|
|
child.signalCode = null;
|
|
child.kill = () => true;
|
|
let timeoutCallback;
|
|
const proxyManager = createProxyManager({
|
|
appDirectory: 'C:\\Program Files\\Alta Proxy Tool',
|
|
fs: { existsSync: () => true },
|
|
spawn: () => child,
|
|
platform: 'win32',
|
|
stopTimeoutMs: 10,
|
|
setTimeout(callback) { timeoutCallback = callback; return 1; },
|
|
clearTimeout() {},
|
|
});
|
|
proxyManager.launchProxy({ deploymentHost: 'customer.avasecurity.com', cookie: 'synthetic-cookie', deviceId });
|
|
const runtime = new AppRuntime({ sessionStore, altaClient: {}, proxyManager });
|
|
|
|
const firstDisconnect = runtime.disconnect();
|
|
assert.equal(proxyManager.listTrackedProxies()[0].status, 'stopping');
|
|
timeoutCallback();
|
|
const failed = await firstDisconnect;
|
|
assert.equal(failed.success, false);
|
|
assert.equal(failed.connected, true);
|
|
assert.deepEqual(failed.activeProxies, [{ deviceId, processId: 4101 }]);
|
|
|
|
child.exitCode = 0;
|
|
child.emit('exit', 0, null);
|
|
const disconnected = await runtime.disconnect();
|
|
assert.equal(disconnected.success, true);
|
|
assert.equal(disconnected.connected, false);
|
|
assert.deepEqual(disconnected.activeProxies, []);
|
|
});
|
|
|
|
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', 'getDeviceGroups', 'getDeviceHierarchy', '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.match(preload, /launchProxy:\s*\(deviceId\)/);
|
|
assert.doesNotMatch(preload, /launchProxy:\s*\([^)]*(?:cookie|origin)/i);
|
|
assert.doesNotMatch(renderer, /cookieValue|sessionData\.cookies|cookies\s*:/);
|
|
assert.match(renderer, /state\.connected\s*&&\s*\(!wasConnected\s*\|\|\s*state\.origin\s*!==\s*previousOrigin\)/);
|
|
assert.match(read('main.js'), /onConnectionStateChanged:\s*\(\)\s*=>\s*\{\s*runtime\.onSessionChanged\(\)/);
|
|
assert.doesNotMatch(html, /id="cookieKey"|id="altaUsername"|type="password"|updateProgress|Install Update/);
|
|
assert.match(html, /paired Chrome extension session/i);
|
|
assert.match(html, /id="pairingSection"[^>]*hidden/);
|
|
assert.match(html, /id="managePairingBtn"/);
|
|
assert.match(renderer, /shouldShowPairingOnboarding/);
|
|
assert.match(renderer, /openFixedReleasesPage/);
|
|
assert.match(html, /Bridge Pairing/);
|
|
});
|
|
|
|
test('Electron quit waits for confirmed cleanup and reports a safe failure instead of disposing', () => {
|
|
const main = read('main.js');
|
|
const beforeQuit = main.slice(main.indexOf("app.on('before-quit'"), main.indexOf("app.on('window-all-closed'"));
|
|
const preventIndex = beforeQuit.indexOf('event.preventDefault()');
|
|
const disconnectIndex = beforeQuit.indexOf('await runtime.disconnect()');
|
|
const activeCheckIndex = beforeQuit.indexOf('state.activeProxies.length > 0');
|
|
const disposeIndex = beforeQuit.indexOf('runtime.sessionStore.dispose()');
|
|
const confirmedQuitIndex = beforeQuit.indexOf('allowConfirmedQuit = true');
|
|
const quitIndex = beforeQuit.indexOf('app.quit()');
|
|
|
|
assert.ok(preventIndex >= 0 && preventIndex < disconnectIndex);
|
|
assert.ok(disconnectIndex < activeCheckIndex && activeCheckIndex < disposeIndex);
|
|
assert.ok(disposeIndex < confirmedQuitIndex && confirmedQuitIndex < quitIndex);
|
|
assert.match(main, /dialog\.showMessageBox/);
|
|
assert.match(main, /could not confirm that every proxy process exited/i);
|
|
assert.doesNotMatch(beforeQuit, /taskkill|pkill|wmic/i);
|
|
});
|
|
|
|
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/);
|
|
});
|
|
|
|
test('production source and documentation are GitPeji-only with no active GitHub workflow', () => {
|
|
assert.equal(fs.existsSync(path.join(ROOT, '.github', 'workflows', 'deploy-pages.yml')), false);
|
|
const sourceAndDocs = [
|
|
'main.js', 'preload.js', 'renderer.js', 'renderer-controller.js', 'index.html',
|
|
'src/electron-runtime.js', 'src/proxy-launch.js', 'README.md', 'CLAUDE.md',
|
|
'docs/security/2026-08-security-baseline.md',
|
|
'docs/plans/2026-08-19-apt-security-foundation.md',
|
|
].map(read).join('\n');
|
|
assert.doesNotMatch(sourceAndDocs, /github/i);
|
|
assert.match(sourceAndDocs, /GitPeji/);
|
|
});
|