feat: release passwordless hierarchical APT v1.2.5
APT build checks / build-checks (push) Successful in 37s
APT build checks / build-checks (pull_request) Successful in 37s

This commit is contained in:
2026-08-22 16:20:12 +00:00
parent c08d2ad381
commit ace4568f81
27 changed files with 209 additions and 145 deletions
+2 -2
View File
@@ -11,7 +11,7 @@ const ROOT = path.join(__dirname, '..');
const read = (name) => fs.readFileSync(path.join(ROOT, name), 'utf8');
const readJson = (name) => JSON.parse(read(name));
function writeKit(root, { version = '1.2.0', legacy = false } = {}) {
function writeKit(root, { version = '1.2.5', legacy = false } = {}) {
const extension = path.join(root, 'chrome-extension');
fs.mkdirSync(extension, { recursive: true });
fs.writeFileSync(path.join(root, 'AltaCameraProxy.exe'), 'synthetic executable');
@@ -31,7 +31,7 @@ test('application and extension versions and supported dependencies stay coordin
const lock = readJson('package-lock.json');
const manifest = readJson('chrome-extension/manifest.json');
assert.equal(pkg.version, '1.2.0');
assert.equal(pkg.version, '1.2.5');
assert.equal(manifest.version, pkg.version);
assert.equal(lock.version, pkg.version);
assert.equal(lock.packages[''].version, pkg.version);
+52 -6
View File
@@ -6,6 +6,7 @@ const {
MAX_PROJECTED_PAYLOAD_BYTES,
projectDeviceHierarchy,
} = require('../src/device-projection');
const { buildDeviceTree } = require('../device-tree');
function uuid(index) {
return `00000000-0000-4000-8000-${index.toString(16).padStart(12, '0')}`;
@@ -18,7 +19,7 @@ function rawDevice(index, overrides = {}) {
type: 'camera',
model: 'Synthetic',
address: `10.0.${Math.floor(index / 255)}.${index % 255}`,
server_group_id: 'site-1',
server_group_id: 'server-group-1',
device_group_id: 'group-1',
capabilities: { localStorage: index % 2 === 0, secretCapability: 'drop-me' },
live: { display_status: 'online', private: 'drop-me' },
@@ -29,25 +30,68 @@ function rawDevice(index, overrides = {}) {
test('projects only allowlisted fields and honestly diagnoses ineligible devices', () => {
const payload = projectDeviceHierarchy({
devices: [rawDevice(1), rawDevice(2, { guid: 'not-a-uuid' })],
devices: [
rawDevice(1),
rawDevice(2, { guid: 'not-a-uuid' }),
rawDevice(4),
],
sites: [{ id: 'site-1', name: 'HQ', pending_deletion_start: null, secret: 'drop-me' }],
groups: [{ id: 'group-1', name: 'Lobby', parent_id: null, pending_deletion_start: null, secret: 'drop-me' }],
});
assert.deepEqual(payload.devices, [{
id: uuid(1), name: 'Camera 1', type: 'camera', model: 'Synthetic', address: '10.0.0.1',
siteId: 'site-1', deviceGroupId: 'group-1', displayStatus: 'online', localStorage: false,
siteId: null, deviceGroupId: 'group-1', displayStatus: 'online', localStorage: false,
}]);
assert.deepEqual(payload.sites, [{ id: 'site-1', name: 'HQ', pendingDeletionStart: null }]);
assert.deepEqual(payload.groups, [{ id: 'group-1', name: 'Lobby', parentId: null, pendingDeletionStart: null }]);
assert.deepEqual(payload.diagnostics.devices, { received: 2, eligible: 1, invalid: 1 });
assert.deepEqual(payload.diagnostics.devices, {
received: 3, eligible: 1, invalid: 1, cloudNativeExcluded: 1,
});
assert.doesNotMatch(JSON.stringify(payload), /drop-me|cookie|secretCapability|private/);
});
test('infers camera sites from device-group parents instead of server groups', () => {
const payload = projectDeviceHierarchy({
devices: [rawDevice(3, {
name: 'Front Door',
server_group_id: 'cloud-connector-server-group',
device_group_id: 'entrances',
})],
sites: [{ id: 'test-site', name: 'Test Site' }],
groups: [{ id: 'entrances', name: 'Entrances', parent_id: 'test-site' }],
});
const model = buildDeviceTree(payload);
const camera = [...model.cameraByKey.values()][0];
assert.equal(payload.devices[0].siteId, null);
assert.equal(camera.site.canonicalId, 'test-site');
assert.equal(camera.group.canonicalId, 'entrances');
assert.equal(camera.hierarchyStatus, 'inferred-site');
assert.deepEqual(model.diagnostics, []);
});
test('excludes cloud-native cameras while retaining Cloud Connector and unknown-storage devices', () => {
const payload = projectDeviceHierarchy({
devices: [
rawDevice(10, { name: 'Cloud Native', capabilities: { localStorage: true } }),
rawDevice(11, { name: 'Cloud Connector', capabilities: { localStorage: false } }),
rawDevice(12, { name: 'Unknown Storage', capabilities: {} }),
],
});
assert.deepEqual(payload.devices.map((device) => device.name), ['Cloud Connector', 'Unknown Storage']);
assert.deepEqual(payload.diagnostics.devices, {
received: 3, eligible: 2, invalid: 0, cloudNativeExcluded: 1,
});
});
test('projects synthetic 1,500 and 10,000 camera deployments below the 8 MiB IPC cap', () => {
for (const count of [1_500, 10_000]) {
const payload = projectDeviceHierarchy({
devices: Array.from({ length: count }, (_, index) => rawDevice(index)),
devices: Array.from({ length: count }, (_, index) => rawDevice(index, {
capabilities: { localStorage: false },
})),
sites: [],
groups: [],
});
@@ -80,6 +124,7 @@ test('repairs lone UTF-16 surrogates without changing valid Unicode or hiding ca
address: 'Hall \ud800\udc00 / \udc00',
server_group_id: 'site-\ud800',
device_group_id: 'group-\udc00',
capabilities: { localStorage: false },
})],
sites: [{ id: 'site-\ud800', name: 'Valid \ud83d\udcf7 \udc00', pending_deletion_start: '\ud800' }],
groups: [{ id: 'group-\udc00', name: 'Group \ud83d\udcf7 \ud800', parent_id: 'site-\ud800' }],
@@ -90,7 +135,7 @@ test('repairs lone UTF-16 surrogates without changing valid Unicode or hiding ca
assert.equal(payload.devices[0].name, 'Front \ufffd camera');
assert.equal(payload.devices[0].model, 'Model \ufffd');
assert.equal(payload.devices[0].address, 'Hall \ud800\udc00 / \ufffd');
assert.equal(payload.devices[0].siteId, 'site-\ufffd');
assert.equal(payload.devices[0].siteId, null);
assert.equal(payload.devices[0].deviceGroupId, 'group-\ufffd');
assert.deepEqual(payload.sites, [{ id: 'site-\ufffd', name: 'Valid \ud83d\udcf7 \ufffd', pendingDeletionStart: '\ufffd' }]);
assert.deepEqual(payload.groups, [{ id: 'group-\ufffd', name: 'Group \ud83d\udcf7 \ufffd', parentId: 'site-\ufffd', pendingDeletionStart: null }]);
@@ -101,6 +146,7 @@ test('repairs lone UTF-16 surrogates without changing valid Unicode or hiding ca
test('rejects a projected hierarchy payload above 8 MiB', () => {
const devices = Array.from({ length: 10_000 }, (_, index) => rawDevice(index, {
name: `Camera ${index} ${'x'.repeat(900)}`,
capabilities: { localStorage: false },
}));
assert.throws(
() => projectDeviceHierarchy({ devices, sites: [], groups: [] }),
+7 -5
View File
@@ -39,10 +39,11 @@ function makeElement() {
function makePopupHarness({ paired = true, confirmCopy = false, clipboardReject = null, validServerProof = true } = {}) {
const elements = Object.fromEntries(
['tabInfo', 'pairingInfo', 'sendBtn', 'copyBtn', 'statusMsg', 'copyWarning', 'confirmCopy', 'openOptionsBtn']
['tabInfo', 'pairingInfo', 'sendBtn', 'copyBtn', 'statusMsg', 'openOptionsBtn']
.map((id) => [id, makeElement()])
);
const clipboardWrites = [];
const confirmationMessages = [];
const fetchCalls = [];
let cookieReads = 0;
const chromeApi = {
@@ -65,7 +66,7 @@ function makePopupHarness({ paired = true, confirmCopy = false, clipboardReject
chromeApi,
documentApi,
navigatorApi,
confirmCopy: () => confirmCopy,
confirmCopy: (message) => { confirmationMessages.push(message); return confirmCopy; },
cryptoApi: crypto.webcrypto,
fetchImpl: async (...args) => {
fetchCalls.push(args);
@@ -81,7 +82,7 @@ function makePopupHarness({ paired = true, confirmCopy = false, clipboardReject
return { ok: true, json: async () => ({ success: true }) };
}
});
return { controller, elements, clipboardWrites, fetchCalls, get cookieReads() { return cookieReads; } };
return { controller, elements, clipboardWrites, confirmationMessages, fetchCalls, get cookieReads() { return cookieReads; } };
}
test('manifest commits only a public key, stable ID, local storage, and exact loopback host access', () => {
@@ -174,11 +175,11 @@ test('copy cancellation occurs before cookie access and never writes the token',
assert.match(harness.elements.statusMsg.textContent, /cancelled/i);
});
test('confirmed copy warns about clipboard history and reports success without exposing token', async () => {
test('confirmed copy uses one concise prompt and reports success without exposing token', async () => {
const harness = makePopupHarness({ paired: true, confirmCopy: true });
await harness.controller.init();
assert.match(harness.elements.copyWarning.textContent, /clipboard (?:history|sync)/i);
await harness.controller.copyToken();
assert.deepEqual(harness.confirmationMessages, ['Copy VA token?']);
assert.deepEqual(harness.clipboardWrites, ['sensitive-va-token']);
assert.match(harness.elements.statusMsg.textContent, /copied/i);
assert.equal(harness.elements.statusMsg.textContent.includes('sensitive-va-token'), false);
@@ -201,6 +202,7 @@ test('options UI stores a validated pairing secret locally and can forget pairin
const js = read('options.js');
assert.match(html, /pairingSecret/);
assert.match(html, /type="password"/);
assert.doesNotMatch(html, /treat it like a password|privacy-note/i);
assert.match(js, /chrome\.storage\.local\.set/);
assert.match(js, /chrome\.storage\.local\.remove/);
assert.match(js, /aptPairingSecret/);
+22 -22
View File
@@ -11,7 +11,7 @@ const APPROVED_DIRECTORY = 'C:\\Program Files\\Alta Proxy Tool';
const APPROVED_HELPER = 'C:\\Program Files\\Alta Proxy Tool\\aware-cam-proxy.exe';
const VALID_HOST = 'tenant.avasecurity.com';
const VALID_DEVICE_ID = '123e4567-e89b-42d3-a456-426614174000';
const VALID_USERNAME = 'proxy.operator+apt@example.com';
const SYNTHETIC_COOKIE = 'HERMES_SENTINEL_SESSION_COOKIE';
function loadModule() {
return require(MODULE_PATH);
@@ -67,7 +67,7 @@ function validRequest(overrides = {}) {
return {
deploymentHost: VALID_HOST,
deviceId: VALID_DEVICE_ID,
username: VALID_USERNAME,
cookie: SYNTHETIC_COOKIE,
...overrides
};
}
@@ -76,18 +76,18 @@ test('exports the proxy manager module', () => {
assert.doesNotThrow(() => loadModule());
});
test('launches the approved helper directly in a visible interactive console', () => {
test('launches the approved helper directly with the paired Alta session', () => {
const { manager, calls } = createHarness();
const result = manager.launchProxy(validRequest());
assert.deepEqual(calls, [[
APPROVED_HELPER,
['-a', VALID_HOST, '-u', VALID_USERNAME, '-d', VALID_DEVICE_ID],
['-a', VALID_HOST, '-d', VALID_DEVICE_ID, '-k', SYNTHETIC_COOKIE],
{
shell: false,
detached: true,
stdio: 'inherit',
stdio: 'ignore',
windowsHide: false
}
]]);
@@ -100,16 +100,15 @@ test('launches the approved helper directly in a visible interactive console', (
});
});
test('passes username punctuation literally in argv without invoking a shell', () => {
const username = 'proxy+apt&literal|name@example.com';
test('passes the paired cookie literally without invoking a shell or console prompt', () => {
const cookie = 'abc_DEF-123.456==';
const { manager, calls } = createHarness();
manager.launchProxy(validRequest({ username }));
manager.launchProxy(validRequest({ cookie }));
assert.equal(calls[0][1][3], username);
assert.equal(calls[0][1][5], cookie);
assert.equal(calls[0][2].shell, false);
assert.equal(calls[0][2].stdio, 'inherit');
assert.notEqual(calls[0][2].stdio, 'ignore');
assert.equal(calls[0][2].stdio, 'ignore');
});
test('rejects the CRLF calc.exe reproducer in every structured input', () => {
@@ -117,8 +116,8 @@ test('rejects the CRLF calc.exe reproducer in every structured input', () => {
const attacks = [
{ deploymentHost: `${VALID_HOST}\r\ncalc.exe` },
{ deviceId: `${VALID_DEVICE_ID}\r\ncalc.exe` },
{ username: `${VALID_USERNAME}\r\ncalc.exe` },
{ username: `${VALID_USERNAME}\0calc.exe` }
{ cookie: `${SYNTHETIC_COOKIE}\r\ncalc.exe` },
{ cookie: `${SYNTHETIC_COOKIE}\0calc.exe` }
];
for (const attack of attacks) {
@@ -191,10 +190,10 @@ test('normalizes a valid Alta hostname to lowercase', () => {
assert.equal(calls[0][1][1], 'tenant.avigilon.com');
});
test('rejects empty, oversized, non-string, and control-character usernames', () => {
for (const username of ['', 'x'.repeat(255), 42, 'user\nname', 'user\rname', 'user\0name', 'user\u007fname']) {
test('rejects empty, oversized, non-string, and control-character cookies', () => {
for (const cookie of ['', 'x'.repeat(4097), 42, 'abc\nxyz', 'abc\rxyz', 'abc\0xyz', 'abc;xyz']) {
const { manager } = createHarness();
assert.throws(() => manager.launchProxy(validRequest({ username })), /username/i);
assert.throws(() => manager.launchProxy(validRequest({ cookie })), /cookie/i);
}
});
@@ -218,25 +217,27 @@ test('requires an absolute approved application directory and Windows platform',
);
});
test('reports a bounded spawn failure without credential redaction machinery', () => {
test('redacts the cookie if spawn throws an error containing it', () => {
const { createProxyManager } = loadModule();
const manager = createProxyManager({
appDirectory: APPROVED_DIRECTORY,
fs: { existsSync: () => true },
platform: 'win32',
spawn: () => { throw new Error('spawn failed'); }
spawn: () => { throw new Error(`spawn failed for ${SYNTHETIC_COOKIE}`); }
});
assert.throws(
() => manager.launchProxy(validRequest()),
(error) => {
assert.match(error.message, /spawn failed/);
assert.match(error.message, /\[REDACTED\]/);
assert.doesNotMatch(error.message, /HERMES_SENTINEL_SESSION_COOKIE/);
return true;
}
);
});
test('tracks only safe process metadata and never exposes usernames', () => {
test('tracks only safe process metadata and never exposes cookies', () => {
const { manager } = createHarness();
manager.launchProxy(validRequest());
@@ -247,7 +248,7 @@ test('tracks only safe process metadata and never exposes usernames', () => {
startedAt: 1_777_777_777_777,
status: 'running'
}]);
assert.doesNotMatch(JSON.stringify(tracked), /proxy\.operator/);
assert.doesNotMatch(JSON.stringify(tracked), /HERMES_SENTINEL_SESSION_COOKIE/);
});
test('stopping one tracked process waits for confirmed exit and leaves the other alive', async () => {
@@ -381,9 +382,8 @@ test('exit events remove only the matching owned child', () => {
assert.deepEqual(manager.listTrackedProxies().map((item) => item.processId), [4101]);
});
test('source contains no bearer argv, shell launchers, broad process killers, or persistence APIs', () => {
test('source contains no shell launchers, broad process killers, or credential persistence APIs', () => {
const source = fs.readFileSync(path.join(__dirname, '..', 'src', 'proxy-launch.js'), 'utf8');
assert.doesNotMatch(source, /cookie|bearer|token|-k/i);
assert.doesNotMatch(source, /\b(?:cmd(?:\.exe)?|powershell|taskkill|pkill|wmic)\b/i);
assert.doesNotMatch(source, /\.(?:bat|command)\b/i);
assert.doesNotMatch(source, /(?:writeFile|appendFile|mkdtemp|tmpdir)/);
+9 -1
View File
@@ -2,7 +2,7 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const { createRendererController } = require('../renderer-controller');
const { createRendererController, shouldShowPairingOnboarding } = require('../renderer-controller');
function harness(result) {
const calls = [];
@@ -39,3 +39,11 @@ test('successful disconnect renders disconnected state and clears device state',
['status', 'Disconnected from Alta.', 'info'],
]);
});
test('pairing onboarding is temporary but remains recoverable', () => {
assert.equal(shouldShowPairingOnboarding({ paired: false }), true);
assert.equal(shouldShowPairingOnboarding({ paired: true, secretVisible: true }), true);
assert.equal(shouldShowPairingOnboarding({ paired: true }), false);
assert.equal(shouldShowPairingOnboarding({ paired: true, secretVisible: true, connected: true }), false);
assert.equal(shouldShowPairingOnboarding({ paired: true, connected: true, manuallyOpen: true }), true);
});
+1
View File
@@ -40,6 +40,7 @@ test('renderer uses one hierarchy request and no parallel flat discovery', () =>
assert.match(renderer, /electronAPI\.getDeviceHierarchy\(\)/);
assert.doesNotMatch(renderer, /electronAPI\.getDevices\(|electronAPI\.getDeviceSites\(|Promise\.all\(\s*\[\s*window\.electronAPI\.get/);
assert.doesNotMatch(renderer, /\ballDevices\b|\ballSites\b|\bcollapsedSites\b|groupDevicesBySite/);
assert.doesNotMatch(renderer, /Selected camera is hidden|hidden by the current view/i);
assert.match(renderer, /loadGeneration/);
assert.match(renderer, /generation\s*!==\s*loadGeneration/);
});
+14 -36
View File
@@ -92,14 +92,11 @@ test('runtime keeps Alta credentials in main-owned modules and exposes only non-
localStorage: null,
}],
});
const launched = await runtime.launchProxy(
'550e8400-e29b-41d4-a716-446655440000',
'proxy.operator@example.com'
);
const launched = await runtime.launchProxy('550e8400-e29b-41d4-a716-446655440000');
assert.equal(launched.success, true);
assert.deepEqual(calls[0], {
deploymentHost: 'customer.avasecurity.com',
username: 'proxy.operator@example.com',
cookie: 'top-secret-cookie',
deviceId: '550e8400-e29b-41d4-a716-446655440000',
});
assert.deepEqual(runtime.getConnectionState(), {
@@ -189,7 +186,7 @@ test('newest hierarchy discovery owns the launch allowlist when completions arri
const freshB = runtime.getDeviceHierarchy();
pending[1]([{ guid: deviceB }]);
assert.equal((await freshB).success, true);
assert.equal((await runtime.launchProxy(deviceB, 'operator@example.com')).success, true);
assert.equal((await runtime.launchProxy(deviceB)).success, true);
pending[0]([{ guid: deviceA }]);
assert.deepEqual(await staleA, {
success: false,
@@ -197,7 +194,7 @@ test('newest hierarchy discovery owns the launch allowlist when completions arri
hierarchy: { devices: [], sites: [], groups: [] },
message: 'Alta device discovery result is stale',
});
assert.equal((await runtime.launchProxy(deviceA, 'operator@example.com')).success, false);
assert.equal((await runtime.launchProxy(deviceA)).success, false);
assert.deepEqual(launches, [deviceB]);
});
@@ -248,7 +245,7 @@ test('launch allowlist remains bound to the session origin that produced it', as
});
assert.equal((await runtime.getDeviceHierarchy()).success, true);
sessionStore.establish('https://second.avasecurity.com', 'replacement-cookie');
assert.equal((await runtime.launchProxy(deviceId, 'operator@example.com')).success, false);
assert.equal((await runtime.launchProxy(deviceId)).success, false);
assert.equal(launches, 0);
});
@@ -256,28 +253,6 @@ test('runtime rejects discovery deadlines above 60 seconds', () => {
assert.throws(() => new AppRuntime({ discoveryTimeoutMs: 60_001 }), /discovery timeout/i);
});
test('runtime rejects invalid usernames before calling the proxy manager', async () => {
const sessionStore = createSessionStore();
sessionStore.establish('https://customer.avasecurity.com', 'top-secret-cookie');
const deviceId = '550e8400-e29b-41d4-a716-446655440000';
let launches = 0;
const runtime = new AppRuntime({
sessionStore,
altaClient: { getDevices: async () => [{ guid: deviceId }] },
proxyManager: {
launchProxy() { launches += 1; },
listTrackedProxies() { return []; },
},
});
await runtime.getDevices();
for (const username of ['', 'x'.repeat(255), 'operator@example.com\r\n-k secret', null]) {
const result = await runtime.launchProxy(deviceId, username);
assert.equal(result.success, false);
assert.match(result.message, /username/i);
}
assert.equal(launches, 0);
});
test('bridge authenticates itself before accepting a one-time HMAC cookie request', async () => {
const protect = (value) => Buffer.from(`protected:${value}`);
@@ -403,10 +378,10 @@ test('runtime reconciles exited children and permits relaunch for the same devic
},
});
await runtime.getDevices();
assert.equal((await runtime.launchProxy(deviceId, 'operator@example.com')).processId, 4101);
assert.equal((await runtime.launchProxy(deviceId)).processId, 4101);
tracked.length = 0;
assert.deepEqual(runtime.getConnectionState().activeProxies, []);
assert.equal((await runtime.launchProxy(deviceId, 'operator@example.com')).processId, 4102);
assert.equal((await runtime.launchProxy(deviceId)).processId, 4102);
});
test('disconnect stops every owned proxy before clearing the Alta session', async () => {
@@ -474,7 +449,7 @@ test('kill request without exit keeps proxy tracked and session connected until
setTimeout(callback) { timeoutCallback = callback; return 1; },
clearTimeout() {},
});
proxyManager.launchProxy({ deploymentHost: 'customer.avasecurity.com', username: 'operator@example.com', deviceId });
proxyManager.launchProxy({ deploymentHost: 'customer.avasecurity.com', cookie: 'synthetic-cookie', deviceId });
const runtime = new AppRuntime({ sessionStore, altaClient: {}, proxyManager });
const firstDisconnect = runtime.disconnect();
@@ -525,13 +500,16 @@ test('preload and renderer expose only narrow, credential-free contracts', () =>
];
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, username\)/);
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"|updateProgress|Install Update/);
assert.match(html, /id="altaUsername"/);
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/);
});