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
+1 -1
View File
@@ -27,7 +27,7 @@ npm run build
- IPC methods are narrow and sender-validated against the main local file frame.
- Bridge is exactly `127.0.0.1:18247`, exact committed extension origin, and `X-APT-Pairing` authenticated.
- Persist only the pairing hash envelope under Electron `userData`; plaintext is shown once after first run/rotation.
- Proxy helper launches directly with `shell: false`; stop only app-owned child processes.
- Proxy helper launches directly with `shell: false`; passwordless launch uses the main-owned paired session only through the helper's required `-k` argument. Never expose that cookie to renderer IPC, UI, logs, files, shell commands, or tracked process metadata; stop only app-owned child processes.
- Update behavior is check-only against the exact GitPeji API. The only follow-up action opens the fixed GitPeji releases page externally.
- Never add download/install/replace logic, arbitrary URLs, redirect following, shell scripts, broad process killing, hardcoded bridge secrets, or credential-bearing renderer state.
- Synthetic tests only: never use a real tenant or Alta session.
+2 -3
View File
@@ -8,7 +8,7 @@ APT is a Windows Electron desktop app that imports an existing Alta Video sessio
- The renderer receives only connection origin/state, device/site/auth responses, and owned proxy metadata. It never receives or supplies the Alta session value.
- The bridge listens only on `127.0.0.1:18247`, accepts only the committed extension origin, requires the `X-APT-Pairing` secret, limits concurrent/body/deadline work, and validates an exact canonical Alta HTTPS origin.
- The pairing envelope is a scrypt hash stored atomically under Electron `userData` with restrictive permissions. The plaintext secret is shown once on first run or rotation. Revoke invalidates it.
- Proxy launch is a direct, detached `spawn` of the fixed helper with `shell: false`. Its exact arguments are deployment host, non-secret Alta username/email, and selected device UUID; no session bearer, password, or 2FA value reaches the command line. Stop actions can target only children owned by this app.
- Proxy launch is a direct, detached `spawn` of the fixed helper with `shell: false`. APT passes the main-process-only paired Alta session through the helper's supported `-k` cookie mode so users are not prompted for Alta credentials again. The cookie never crosses renderer IPC, enters a shell, or reaches logs, files, tracked process metadata, or UI state; it is necessarily present in the helper process arguments because the external helper exposes no pipe/stdin credential interface. Stop actions can target only children owned by this app.
- Updates are **check-only**. APT checks the exact GitPeji release API and can open only `https://git.pejicorp.com/peji/Alta-Proxy-Tool/releases` in the system browser. It never downloads, replaces, or executes an update.
## Requirements
@@ -30,8 +30,7 @@ npm start
2. Start APT. Under **Bridge Pairing**, copy the one-time secret.
3. Open the extension pairing settings, paste the secret, and save it.
4. Visit your Alta deployment in Chrome and use **Send to APT**.
5. Select a local camera, enter your Alta username/email, and choose **Start Proxy**.
6. Complete the password and 2FA prompts in the helper's visible Windows console. APT does not collect or pass those secrets.
5. Select a local camera and choose **Start Proxy**. APT reuses the paired Chrome session; no Alta username, password, or 2FA prompt is required.
Use **Generate / Rotate** if a pairing may have been exposed, then update the extension. Use **Revoke** to immediately disable bridge authentication.
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Alta Proxy Tool Bridge",
"version": "1.2.0",
"version": "1.2.5",
"description": "Send Alta session cookies to a paired Alta Proxy Tool desktop app.",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt4EZdkSgOsyiy5DRe0JkX+BpK94FpMjBU59NVIqDPO8QBDwvqNDWT/UjqHK/0aqSxzed5KibX6MdAvc495+u1sCybFdjDdXyBewEvg+PDqGiJketlZKC9dcR1RXHuPgAoM3NaNbMb3TqYcS9J4iGq0UwadxubkQrEcPiuyR6oriOkop8q9/5DWGb15wOGmiCuVmlXfUjNJIvNBm9P/ZHtgFBYDI2PuSSI5GI4j04VFpEyfNlFCrpi8GQ7bYZzezigZWXRjhhNwkx39bNHlkAWYa8XGZseCpKKvi0EaeCoBPjoYSAt161SM1dqX+/UC61/sLOU/SpDB1SYGTm5DC+7wIDAQAB",
"permissions": ["cookies", "activeTab", "clipboardWrite", "storage"],
-6
View File
@@ -75,9 +75,3 @@ button:disabled { cursor: not-allowed; opacity: 0.45; }
padding-top: 14px;
border-top: 1px solid #3c3c3c;
}
.privacy-note {
margin-top: 24px;
color: #aaa;
font-size: 12px;
}
+2 -2
View File
@@ -10,7 +10,7 @@
<body>
<main class="options-card">
<h1>Pair with Alta Proxy Tool</h1>
<p>In the desktop app, create a one-time pairing secret. Paste it below on this computer. Treat it like a password.</p>
<p>Create a one-time pairing secret in APT, then paste it below.</p>
<form id="pairingForm">
<label for="pairingSecret">One-time pairing secret</label>
<input id="pairingSecret" name="pairingSecret" type="password" inputmode="text" autocomplete="off" spellcheck="false" required>
@@ -22,7 +22,7 @@
<p id="currentState">Checking...</p>
<button id="forgetBtn" type="button" class="danger-btn" disabled>Forget pairing</button>
</section>
<p class="privacy-note">The secret is stored only in Chrome extension local storage. It is never displayed again or copied automatically.</p>
</main>
<script src="options.js"></script>
</body>
-8
View File
@@ -88,14 +88,6 @@ h1 {
opacity: 0.45;
}
.copy-warning {
margin: 10px 0 0;
padding: 8px 10px;
border-left: 3px solid #f0a33a;
background: rgba(240, 163, 58, 0.08);
color: #d7c29f;
font-size: 12px;
}
.status-msg {
display: none;
-1
View File
@@ -17,7 +17,6 @@
<button id="sendBtn" class="primary-btn" type="button" disabled>Send to APT</button>
<button id="copyBtn" class="secondary-btn" type="button" disabled>Copy VA Token</button>
</div>
<p id="copyWarning" class="copy-warning">Copying exposes the full bearer token. Clipboard history or sync may retain it. A confirmation is required.</p>
<div id="statusMsg" class="status-msg" role="status" aria-live="polite"></div>
</main>
<script src="popup.js"></script>
+2 -6
View File
@@ -45,7 +45,6 @@ function createPopupController({
const pairingInfo = documentApi.getElementById('pairingInfo');
const sendBtn = documentApi.getElementById('sendBtn');
const copyBtn = documentApi.getElementById('copyBtn');
const copyWarning = documentApi.getElementById('copyWarning');
const statusMsg = documentApi.getElementById('statusMsg');
const openOptionsBtn = documentApi.getElementById('openOptionsBtn');
@@ -159,9 +158,7 @@ function createPopupController({
async function copyToken() {
if (!detectedOrigin || !pairingSecret || busy) return;
const confirmed = confirmCopy(
'Copy the full VA bearer token? Clipboard history or sync may retain it. Continue only if you will paste it into a trusted destination.'
);
const confirmed = confirmCopy('Copy VA token?');
if (!confirmed) {
showStatus('Copy cancelled. The VA token was not read.', 'info');
return;
@@ -172,7 +169,7 @@ function createPopupController({
try {
cookieValue = await getVaCookieValue();
await navigatorApi.clipboard.writeText(cookieValue);
showStatus('VA token copied. Clear your clipboard after use.', 'success');
showStatus('VA token copied.', 'success');
} catch (error) {
showCookieError(error, true);
} finally {
@@ -182,7 +179,6 @@ function createPopupController({
}
async function init() {
copyWarning.textContent = 'Copying exposes the full bearer token. Clipboard history or sync may retain it. A confirmation is required.';
sendBtn.addEventListener('click', sendToApt);
copyBtn.addEventListener('click', copyToken);
openOptionsBtn.addEventListener('click', () => chromeApi.runtime.openOptionsPage());
@@ -40,7 +40,7 @@ Feature discovery begins only after Phase A passes independent security review a
- Use a fresh worktree from GitPeji `master` at `a80074ac57b7a4517837b5d95754f5e6433df3ac` or newer.
- Preserve current Tool Hub downloads and production visibility until Zac approves replacement.
- Do not use real Alta cookies in automated tests; use conspicuous synthetic sentinels.
- Do not write cookies to logs, disk, command files, shell strings, crash reports, analytics, or test artifacts.
- Do not write cookies to logs, disk, command files, shell strings, crash reports, analytics, or test artifacts. The sole accepted exception is the fixed helper's required `-k` argv value for Zac's explicitly required paired-extension passwordless proxy flow; keep it main-process-only, direct-spawned with `shell: false`, redacted from errors, and absent from renderer IPC and tracked metadata.
- No production release until the exact candidate passes controller verification plus independent security and release reviews.
- Mac distribution remains approval-gated under Kanban task `t_42bad4d3`.
@@ -16,3 +16,7 @@
- Synthetic sentinel credentials only in tests.
- Work remains on the isolated hardening branch until review gates pass.
- No feature additions until Critical/Important security closure.
## Accepted passwordless proxy exception
Zac explicitly requires the paired-extension **Connect → select camera → Start Proxy** flow without re-entering Alta credentials. The external `aware-cam-proxy.exe` helper supports cookie authentication only through `-k`; it exposes no stdin, named-pipe, environment-variable, or credential-file interface. APT therefore passes the validated main-owned session directly to the fixed helper as one argv value. This exception remains bounded by direct `shell: false` spawn, trusted-device allowlisting, no renderer/IPC credential parameter, no logs/files/shells, redacted launch errors, safe tracked metadata, and synthetic regression tests.
+4 -6
View File
@@ -51,11 +51,12 @@
</div>
<div class="connection-controls">
<button type="button" id="disconnectBtn" class="btn-outline" disabled>Disconnect</button>
<button type="button" id="managePairingBtn" class="btn-outline" hidden>Manage Pairing</button>
</div>
<div id="connectionStatus" class="status-message"></div>
</section>
<section class="content-section pairing-section">
<section id="pairingSection" class="content-section pairing-section" hidden>
<h2>Bridge Pairing</h2>
<p class="section-help">Only the committed APT Chrome extension can send a session. Pair it with a one-time secret.</p>
<div class="status-row">
@@ -70,6 +71,7 @@
<div class="proxy-buttons">
<button type="button" id="rotatePairingBtn" class="btn-primary">Generate / Rotate</button>
<button type="button" id="revokePairingBtn" class="btn-outline">Revoke</button>
<button type="button" id="hidePairingBtn" class="btn-outline" hidden>Done</button>
</div>
</section>
@@ -80,11 +82,7 @@
<label for="selectedDeviceId">Device UUID:</label>
<input type="text" id="selectedDeviceId" placeholder="Select a device from the list" readonly>
</div>
<div class="input-row">
<label for="altaUsername">Alta username / email:</label>
<input type="text" id="altaUsername" maxlength="254" placeholder="name@example.com" autocomplete="username" spellcheck="false">
</div>
<p class="section-help">The proxy helper opens a Windows console and prompts for your password and 2FA. APT never sends those secrets on its command line.</p>
<p class="section-help">APT uses the paired Chrome extension session. Select a camera, then start the proxy—no Alta credentials need to be entered again.</p>
<div class="proxy-buttons">
<button type="button" id="startProxyBtn" class="btn-primary" disabled>Start Proxy</button>
<button type="button" id="stopProxyBtn" class="btn-outline" disabled>Stop Proxy</button>
+1 -1
View File
@@ -81,7 +81,7 @@ function registerIpcHandlers() {
registerIpc('get-device-groups', () => runtime.getDeviceGroups());
registerIpc('get-device-hierarchy', () => runtime.getDeviceHierarchy());
registerIpc('get-auth-info', () => runtime.getAuthInfo());
registerIpc('launch-proxy', (deviceId, username) => runtime.launchProxy(deviceId, username));
registerIpc('launch-proxy', (deviceId) => runtime.launchProxy(deviceId));
registerIpc('stop-proxy', async (key) => {
const result = await runtime.stopProxy(key);
sendConnectionState();
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "alta-api-client",
"version": "1.2.0",
"version": "1.2.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "alta-api-client",
"version": "1.2.0",
"version": "1.2.5",
"license": "MIT",
"dependencies": {
"axios": "1.19.0"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "alta-api-client",
"version": "1.2.0",
"version": "1.2.5",
"description": "Secure Windows Electron client for the Alta Camera Proxy",
"main": "main.js",
"scripts": {
+1 -1
View File
@@ -15,7 +15,7 @@ contextBridge.exposeInMainWorld('electronAPI', Object.freeze({
getDeviceGroups: () => ipcRenderer.invoke('get-device-groups'),
getDeviceHierarchy: () => ipcRenderer.invoke('get-device-hierarchy'),
getAuthInfo: () => ipcRenderer.invoke('get-auth-info'),
launchProxy: (deviceId, username) => ipcRenderer.invoke('launch-proxy', deviceId, username),
launchProxy: (deviceId) => ipcRenderer.invoke('launch-proxy', deviceId),
stopProxy: (key) => ipcRenderer.invoke('stop-proxy', key),
disconnect: () => ipcRenderer.invoke('disconnect'),
getConnectionState: () => ipcRenderer.invoke('get-connection-state'),
+10 -1
View File
@@ -1,6 +1,15 @@
'use strict';
(function exposeRendererController(globalScope) {
function shouldShowPairingOnboarding({
paired = false,
secretVisible = false,
connected = false,
manuallyOpen = false,
} = {}) {
return manuallyOpen || !paired || (secretVisible && !connected);
}
function createRendererController({
disconnect,
renderConnectionState,
@@ -35,7 +44,7 @@
});
}
const api = Object.freeze({ createRendererController });
const api = Object.freeze({ createRendererController, shouldShowPairingOnboarding });
if (typeof module !== 'undefined' && module.exports) module.exports = api;
if (globalScope) globalScope.AptRendererController = api;
}(typeof window !== 'undefined' ? window : undefined));
+42 -9
View File
@@ -10,7 +10,6 @@ const statusIndicator = document.getElementById('statusIndicator');
const deviceSearch = document.getElementById('deviceSearch');
const disconnectBtn = document.getElementById('disconnectBtn');
const selectedDeviceId = document.getElementById('selectedDeviceId');
const altaUsername = document.getElementById('altaUsername');
const startProxyBtn = document.getElementById('startProxyBtn');
const stopProxyBtn = document.getElementById('stopProxyBtn');
const checkUpdateBtn = document.getElementById('checkUpdateBtn');
@@ -18,11 +17,14 @@ const updateNotice = document.getElementById('updateNotice');
const updateMessage = document.getElementById('updateMessage');
const openReleasesBtn = document.getElementById('openReleasesBtn');
const dismissUpdateBtn = document.getElementById('dismissUpdateBtn');
const pairingSection = document.getElementById('pairingSection');
const managePairingBtn = document.getElementById('managePairingBtn');
const pairingState = document.getElementById('pairingState');
const pairingSecret = document.getElementById('pairingSecret');
const pairingSecretRow = document.getElementById('pairingSecretRow');
const rotatePairingBtn = document.getElementById('rotatePairingBtn');
const revokePairingBtn = document.getElementById('revokePairingBtn');
const hidePairingBtn = document.getElementById('hidePairingBtn');
const ROW_HEIGHT = window.AptSidebarView.ROW_HEIGHT;
let connection = { connected: false, origin: null, activeProxies: [] };
@@ -32,6 +34,17 @@ let sidebarController = null;
let loadGeneration = 0;
let searchTimer = null;
let scrollFrame = null;
let pairingView = { paired: false, secretVisible: false, manuallyOpen: false };
function renderPairingVisibility() {
const visible = window.AptRendererController.shouldShowPairingOnboarding({
...pairingView,
connected: connection.connected,
});
pairingSection.hidden = !visible;
managePairingBtn.hidden = visible || !pairingView.paired;
hidePairingBtn.hidden = !pairingView.paired;
}
function showStatus(element, message, type) {
element.textContent = message;
@@ -51,8 +64,7 @@ function deviceStatusFor(device) {
function updateProxyButtons() {
const id = selectedDevice && selectedDevice.id;
const active = id ? activeDeviceIds().has(id.toLowerCase()) : false;
const hasUsername = altaUsername.value.trim().length > 0;
startProxyBtn.disabled = !connection.connected || !id || !hasUsername || active;
startProxyBtn.disabled = !connection.connected || !id || active;
stopProxyBtn.disabled = !id || !active;
}
@@ -67,6 +79,13 @@ function renderConnectionState(state) {
dot.className = `status-dot ${connection.connected ? 'online' : 'offline'}`;
text.textContent = connection.connected ? 'Connected' : 'Disconnected';
disconnectBtn.disabled = !connection.connected;
if (connection.connected) {
pairingView.secretVisible = false;
pairingView.manuallyOpen = false;
pairingSecret.value = '';
pairingSecretRow.style.display = 'none';
}
renderPairingVisibility();
updateProxyButtons();
if (sidebarController) sidebarController.refresh();
}
@@ -145,10 +164,9 @@ function installHierarchy(hierarchy) {
const count = snapshot.state.searchQuery && snapshot.state.searchCameraKeys
? snapshot.state.searchCameraKeys.size
: hierarchyModel.cameraCount;
const hidden = snapshot.rows.hiddenSelected ? ' Selected camera is hidden by the current view.' : '';
deviceResults.textContent = snapshot.state.searchQuery
? `${count} exact search result${count === 1 ? '' : 's'}.${hidden}`
: `${count} camera${count === 1 ? '' : 's'}.${hidden}`;
? `${count} exact search result${count === 1 ? '' : 's'}.`
: `${count} camera${count === 1 ? '' : 's'}.`;
},
});
sidebarController = window.AptSidebarController.createSidebarController({
@@ -201,7 +219,6 @@ const rendererController = window.AptRendererController.createRendererController
disconnect: () => window.electronAPI.disconnect(),
renderConnectionState,
clearDisconnectedState: () => {
altaUsername.value = '';
clearHierarchy();
},
showConnectionStatus: (message, type) => showStatus(connectionStatus, message, type),
@@ -216,7 +233,7 @@ async function handleStartProxy() {
if (!id) return;
startProxyBtn.disabled = true;
showStatus(connectionStatus, `Starting proxy for ${selectedDevice.name || 'selected device'}...`, 'info');
const result = await window.electronAPI.launchProxy(id, altaUsername.value);
const result = await window.electronAPI.launchProxy(id);
if (result.success) {
renderConnectionState(await window.electronAPI.getConnectionState());
showStatus(connectionStatus, 'Camera proxy started.', 'success');
@@ -259,6 +276,11 @@ async function checkForUpdates(showCurrent = true) {
}
function renderPairingStatus(result) {
pairingView = {
paired: result.paired === true,
secretVisible: Boolean(result.secret),
manuallyOpen: Boolean(result.secret) || result.paired !== true,
};
pairingState.textContent = result.paired ? 'Paired' : 'Revoked / not paired';
pairingState.className = result.paired ? 'paired' : 'unpaired';
revokePairingBtn.disabled = !result.paired;
@@ -270,6 +292,7 @@ function renderPairingStatus(result) {
pairingSecret.value = '';
pairingSecretRow.style.display = 'none';
}
renderPairingVisibility();
}
async function runSearch(query) {
@@ -340,10 +363,20 @@ deviceSearch.addEventListener('keydown', (event) => {
disconnectBtn.addEventListener('click', handleDisconnect);
startProxyBtn.addEventListener('click', handleStartProxy);
stopProxyBtn.addEventListener('click', handleStopProxy);
altaUsername.addEventListener('input', updateProxyButtons);
checkUpdateBtn.addEventListener('click', () => checkForUpdates(true));
openReleasesBtn.addEventListener('click', () => window.electronAPI.openFixedReleasesPage());
dismissUpdateBtn.addEventListener('click', () => { updateNotice.style.display = 'none'; });
rotatePairingBtn.addEventListener('click', async () => renderPairingStatus(await window.electronAPI.rotatePairing()));
revokePairingBtn.addEventListener('click', async () => renderPairingStatus(await window.electronAPI.revokePairing()));
managePairingBtn.addEventListener('click', () => {
pairingView.manuallyOpen = true;
renderPairingVisibility();
});
hidePairingBtn.addEventListener('click', () => {
pairingView.manuallyOpen = false;
pairingView.secretVisible = false;
pairingSecret.value = '';
pairingSecretRow.style.display = 'none';
renderPairingVisibility();
});
document.addEventListener('DOMContentLoaded', () => { initialize().catch(() => showStatus(connectionStatus, 'Application initialization failed.', 'error')); });
+8 -3
View File
@@ -37,7 +37,9 @@ function projectDevice(raw) {
type: nullableString(raw.type),
model: nullableString(raw.model),
address: nullableString(raw.address),
siteId: nullableId(raw.server_group_id),
// server_group_id identifies the Alta server/cloud connector, not a device site.
// The tree infers the site through device_group_id -> deviceGroups.parent_id.
siteId: null,
deviceGroupId: nullableId(raw.device_group_id),
displayStatus: nullableString(raw.live && raw.live.display_status),
localStorage: nullableBoolean(raw.capabilities && raw.capabilities.localStorage),
@@ -89,17 +91,20 @@ function projectDeviceHierarchy({ devices = [], sites = [], groups = [], metadat
throw new TypeError('Hierarchy projection requires arrays');
}
const deviceProjection = projectArray(devices, projectDevice);
const eligibleDevices = deviceProjection.projected.filter((device) => device.localStorage !== true);
const cloudNativeExcluded = deviceProjection.projected.length - eligibleDevices.length;
const siteProjection = projectArray(sites, projectSite);
const groupProjection = projectArray(groups, projectGroup);
const payload = {
devices: deviceProjection.projected,
devices: eligibleDevices,
sites: siteProjection.projected,
groups: groupProjection.projected,
diagnostics: {
devices: {
received: devices.length,
eligible: deviceProjection.projected.length,
eligible: eligibleDevices.length,
invalid: deviceProjection.invalid,
cloudNativeExcluded,
},
sites: {
received: sites.length,
+4 -5
View File
@@ -11,7 +11,7 @@ const {
} = require('./bridge-auth');
const { RELEASES_PAGE_URL } = require('./update-policy');
const { projectDeviceHierarchy, projectGroup, projectSite } = require('./device-projection');
const { validateDeviceId, validateUsername } = require('./proxy-launch');
const { validateCookie, validateDeviceId } = require('./proxy-launch');
const DEFAULT_DISCOVERY_TIMEOUT_MS = 60_000;
@@ -23,7 +23,7 @@ function safeErrorMessage(error, fallback) {
'INVALID_ALTA_RESPONSE', 'INVALID_ALTA_RESPONSE_DATA', 'ALTA_RESPONSE_TOO_LARGE',
'ALTA_RESPONSE_TOO_MANY_OBJECTS', 'PROJECTED_PAYLOAD_TOO_LARGE',
'UNSAFE_ALTA_REDIRECT', 'TOO_MANY_ALTA_REDIRECTS', 'HELPER_NOT_FOUND',
'UNSUPPORTED_PLATFORM', 'INVALID_DEVICE_ID', 'INVALID_USERNAME', 'SPAWN_FAILED',
'UNSUPPORTED_PLATFORM', 'INVALID_DEVICE_ID', 'INVALID_SESSION_COOKIE', 'SPAWN_FAILED',
]);
return error && allowed.has(error.code) && typeof error.message === 'string'
? error.message
@@ -363,10 +363,9 @@ class AppRuntime {
}
}
async launchProxy(deviceId, username) {
async launchProxy(deviceId) {
try {
const validatedId = validateDeviceId(deviceId);
const validatedUsername = validateUsername(username);
this._reconcileProxies();
if (!this.allowedDeviceDiscovery || !this._isCurrentDiscovery(this.allowedDeviceDiscovery) ||
!this.allowedDeviceIds.has(validatedId)) {
@@ -378,7 +377,7 @@ class AppRuntime {
const session = this.sessionStore.requireSession();
const result = this.proxyManager.launchProxy({
deploymentHost: new URL(session.origin).hostname,
username: validatedUsername,
cookie: validateCookie(session.cookie),
deviceId: validatedId,
});
this.proxyByDevice.set(validatedId, result.processId);
+16 -15
View File
@@ -5,7 +5,7 @@ const nodePath = require('node:path');
const { spawn: nodeSpawn } = require('node:child_process');
const HELPER_FILENAME = 'aware-cam-proxy.exe';
const MAX_USERNAME_LENGTH = 254;
const MAX_COOKIE_LENGTH = 4096;
const MAX_HOST_LENGTH = 253;
const DEVICE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
@@ -42,19 +42,20 @@ function validateDeviceId(value) {
return value.toLowerCase();
}
function validateUsername(value) {
if (typeof value !== 'string' || value.length === 0 || value.length > MAX_USERNAME_LENGTH || value.trim().length === 0) {
throw new ProxyLaunchError('Alta username is invalid.', 'INVALID_USERNAME');
function validateCookie(value) {
if (typeof value !== 'string' || value.length === 0 || value.length > MAX_COOKIE_LENGTH) {
throw new ProxyLaunchError('Alta session cookie is invalid.', 'INVALID_SESSION_COOKIE');
}
if (/[\u0000-\u001f\u007f-\u009f]/.test(value)) {
throw new ProxyLaunchError('Alta username must not contain control characters.', 'INVALID_USERNAME');
if (/[;\u0000-\u001f\u007f-\u009f]/.test(value)) {
throw new ProxyLaunchError('Alta session cookie must not contain separators or control characters.', 'INVALID_SESSION_COOKIE');
}
return value;
}
function safeProcessError(error) {
function safeProcessError(error, secret) {
const source = error && typeof error.message === 'string' ? error.message : 'Unknown process error';
return source.replace(/[\u0000-\u001f\u007f-\u009f]/g, ' ').slice(0, 512);
const redacted = secret ? source.split(secret).join('[REDACTED]') : source;
return redacted.replace(/[\u0000-\u001f\u007f-\u009f]/g, ' ').slice(0, 512);
}
function safeMetadata(entry, status = entry.status) {
@@ -129,7 +130,7 @@ function createProxyManager({
const deploymentHost = validateDeploymentHost(request.deploymentHost);
const deviceId = validateDeviceId(request.deviceId);
const username = validateUsername(request.username);
const cookie = validateCookie(request.cookie);
if (!fs.existsSync(helperPath)) {
throw new ProxyLaunchError('Proxy helper was not found in the approved application directory.', 'HELPER_NOT_FOUND');
@@ -139,17 +140,17 @@ function createProxyManager({
try {
child = spawn(
helperPath,
['-a', deploymentHost, '-u', username, '-d', deviceId],
['-a', deploymentHost, '-d', deviceId, '-k', cookie],
{
shell: false,
detached: true,
stdio: 'inherit',
stdio: 'ignore',
windowsHide: false
}
);
} catch (error) {
throw new ProxyLaunchError(
`Failed to launch proxy helper: ${safeProcessError(error)}`,
`Failed to launch proxy helper: ${safeProcessError(error, cookie)}`,
'SPAWN_FAILED'
);
}
@@ -268,10 +269,10 @@ function createProxyManager({
module.exports = {
DEFAULT_STOP_TIMEOUT_MS,
HELPER_FILENAME,
MAX_USERNAME_LENGTH,
MAX_COOKIE_LENGTH,
ProxyLaunchError,
createProxyManager,
validateCookie,
validateDeploymentHost,
validateDeviceId,
validateUsername
validateDeviceId
};
+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/);
});