fix: integrate hardened APT security boundary

This commit is contained in:
2026-08-19 21:36:24 +00:00
parent b6df6f1a66
commit 2f492b662d
14 changed files with 1113 additions and 1730 deletions
+1 -1
View File
@@ -133,7 +133,7 @@ class BridgeAuth {
authenticateRequest(request) {
if (!request || typeof request !== 'object') return false;
const headers = request.headers || {};
const secret = headers['x-apt-pairing-secret'];
const secret = headers['x-apt-pairing'];
return this.authenticate({ origin: headers.origin, secret });
}
}
+300
View File
@@ -0,0 +1,300 @@
'use strict';
const fs = require('node:fs');
const path = require('node:path');
const {
APT_EXTENSION_ORIGIN,
BridgeAuth,
BridgeAuthError,
createRequestLimiter,
readJsonBody,
} = require('./bridge-auth');
const { RELEASES_PAGE_URL } = require('./update-policy');
const { validateDeviceId } = require('./proxy-launch');
const PAIRING_ENVELOPE_FILENAME = 'bridge-pairing.json';
function safeErrorMessage(error, fallback) {
const allowed = new Set([
'NO_ALTA_SESSION', 'INVALID_ALTA_ARGUMENTS', 'ALTA_TIMEOUT', 'ALTA_HTTP_ERROR',
'INVALID_ALTA_RESPONSE', 'INVALID_ALTA_RESPONSE_DATA', 'ALTA_RESPONSE_TOO_LARGE',
'UNSAFE_ALTA_REDIRECT', 'TOO_MANY_ALTA_REDIRECTS', 'HELPER_NOT_FOUND',
'UNSUPPORTED_PLATFORM', 'INVALID_DEVICE_ID', 'SPAWN_FAILED',
]);
return error && allowed.has(error.code) && typeof error.message === 'string'
? error.message
: fallback;
}
function loadPairingEnvelope(envelopePath) {
if (!fs.existsSync(envelopePath)) return null;
const bytes = fs.readFileSync(envelopePath);
if (bytes.length === 0 || bytes.length > 4096) throw new Error('Invalid pairing envelope file');
const parsed = JSON.parse(bytes.toString('utf8'));
// BridgeAuth performs the authoritative envelope schema validation.
return new BridgeAuth({ envelope: parsed }).envelope;
}
function atomicWritePairingEnvelope(envelopePath, envelope) {
const directory = path.dirname(envelopePath);
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
const temporaryPath = `${envelopePath}.${process.pid}.${Date.now()}.tmp`;
const payload = `${JSON.stringify(envelope)}\n`;
try {
fs.writeFileSync(temporaryPath, payload, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
fs.renameSync(temporaryPath, envelopePath);
fs.chmodSync(envelopePath, 0o600);
} catch (error) {
try { fs.unlinkSync(temporaryPath); } catch {}
throw error;
}
}
class PairingController {
constructor({ envelopePath, bridgeAuth } = {}) {
if (typeof envelopePath !== 'string' || envelopePath.length === 0) {
throw new TypeError('PairingController requires an envelope path');
}
this.envelopePath = envelopePath;
if (bridgeAuth) {
this.bridgeAuth = bridgeAuth;
} else {
let envelope = null;
try {
envelope = loadPairingEnvelope(envelopePath);
} catch {
try { fs.unlinkSync(envelopePath); } catch {}
}
this.bridgeAuth = new BridgeAuth({ envelope });
}
}
initialize() {
if (this.bridgeAuth.envelope) return { paired: true };
return this.rotate();
}
rotate() {
const { secret, envelope } = this.bridgeAuth.rotate();
try {
atomicWritePairingEnvelope(this.envelopePath, envelope);
} catch (error) {
this.bridgeAuth.revoke();
throw error;
}
return { paired: true, secret };
}
revoke() {
this.bridgeAuth.revoke();
try {
fs.unlinkSync(this.envelopePath);
} catch (error) {
if (error.code !== 'ENOENT') throw error;
}
return { paired: false };
}
getStatus() {
return { paired: Boolean(this.bridgeAuth.envelope) };
}
}
function writeJson(response, statusCode, payload) {
if (response.writableEnded) return;
response.writeHead(statusCode, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
'X-Content-Type-Options': 'nosniff',
});
response.end(JSON.stringify(payload));
}
function createBridgeHandler({
bridgeAuth,
sessionStore,
onConnectionStateChanged = () => {},
limiter = createRequestLimiter({ maxConcurrent: 4 }),
maxBodyBytes,
deadlineMs,
} = {}) {
if (!bridgeAuth || typeof bridgeAuth.authenticateRequest !== 'function') {
throw new TypeError('Bridge handler requires bridge authentication');
}
if (!sessionStore || typeof sessionStore.establish !== 'function') {
throw new TypeError('Bridge handler requires a session store');
}
return async function handleBridgeRequest(request, response) {
const origin = request && request.headers && request.headers.origin;
if (origin !== APT_EXTENSION_ORIGIN) {
writeJson(response, 403, { success: false, message: 'Forbidden' });
return;
}
response.setHeader('Access-Control-Allow-Origin', APT_EXTENSION_ORIGIN);
response.setHeader('Vary', 'Origin');
response.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
response.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-APT-Pairing');
response.setHeader('Access-Control-Max-Age', '600');
if (request.method === 'OPTIONS') {
response.writeHead(204, { 'Cache-Control': 'no-store' });
response.end();
return;
}
if (request.method !== 'POST' || request.url !== '/cookie') {
writeJson(response, 404, { success: false, message: 'Not found' });
return;
}
if (!bridgeAuth.authenticateRequest(request)) {
writeJson(response, 403, { success: false, message: 'Forbidden' });
return;
}
try {
await limiter.run(async () => {
const data = await readJsonBody(request, { maxBytes: maxBodyBytes, deadlineMs });
const state = sessionStore.establish(data.deploymentUrl, data.cookieValue);
onConnectionStateChanged({ connected: state.connected, origin: state.origin });
});
writeJson(response, 200, { success: true, message: 'Session received' });
} catch (error) {
const statusCode = error instanceof BridgeAuthError
? error.statusCode
: error && error.code === 'INVALID_SESSION_COOKIE'
? 400
: 400;
writeJson(response, statusCode, { success: false, message: statusCode === 429 ? 'Too many requests' : 'Invalid request' });
}
};
}
class AppRuntime {
constructor({
sessionStore,
altaClient,
proxyManager,
checkForUpdate,
currentVersion,
openExternal,
} = {}) {
this.sessionStore = sessionStore;
this.altaClient = altaClient;
this.proxyManager = proxyManager;
this.checkForUpdatePolicy = checkForUpdate;
this.currentVersion = currentVersion;
this.openExternal = openExternal;
this.allowedDeviceIds = new Set();
this.proxyByDevice = new Map();
}
async getDevices() {
try {
const devices = await this.altaClient.getDevices();
this.allowedDeviceIds = new Set();
for (const device of devices) {
const candidate = device && (device.guid || device.id);
try { this.allowedDeviceIds.add(validateDeviceId(candidate)); } catch {}
}
return { success: true, devices };
} catch (error) {
return { success: false, devices: [], message: safeErrorMessage(error, 'Failed to get devices') };
}
}
async getDeviceSites() {
try {
return { success: true, sites: await this.altaClient.getDeviceSites() };
} catch (error) {
return { success: false, sites: [], message: safeErrorMessage(error, 'Failed to get device sites') };
}
}
async getAuthInfo() {
try {
return { success: true, authInfo: await this.altaClient.getAuthInfo() };
} catch (error) {
return { success: false, message: safeErrorMessage(error, 'Failed to get authentication information') };
}
}
async launchProxy(deviceId) {
try {
const validatedId = validateDeviceId(deviceId);
if (!this.allowedDeviceIds.has(validatedId)) {
return { success: false, message: 'Select a device from the current Alta device list.' };
}
if (this.proxyByDevice.has(validatedId)) {
return { success: false, message: 'A proxy is already running for this device.' };
}
const session = this.sessionStore.requireSession();
const result = this.proxyManager.launchProxy({
deploymentHost: new URL(session.origin).hostname,
cookie: session.cookie,
deviceId: validatedId,
});
this.proxyByDevice.set(validatedId, result.processId);
return { success: true, processId: result.processId, deviceId: validatedId, status: result.status };
} catch (error) {
return { success: false, message: safeErrorMessage(error, 'Failed to launch proxy') };
}
}
async stopProxy(key) {
let deviceId = null;
let processId = null;
if (typeof key === 'string') {
try { deviceId = validateDeviceId(key); } catch { return { success: false, message: 'Invalid proxy key.' }; }
processId = this.proxyByDevice.get(deviceId);
} else if (Number.isSafeInteger(key) && key > 0) {
processId = key;
for (const [candidateDeviceId, candidateProcessId] of this.proxyByDevice) {
if (candidateProcessId === processId) deviceId = candidateDeviceId;
}
}
if (!processId || !deviceId) return { success: false, message: 'Proxy process is not owned by this app.' };
const result = this.proxyManager.stopProxy(processId);
if (result.success) this.proxyByDevice.delete(deviceId);
return { ...result, deviceId };
}
disconnect() {
this.sessionStore.clear();
this.allowedDeviceIds.clear();
return this.getConnectionState();
}
getConnectionState() {
const state = this.sessionStore.describe();
return {
connected: state.connected,
origin: state.origin,
activeProxies: Array.from(this.proxyByDevice, ([deviceId, processId]) => ({ deviceId, processId })),
};
}
async checkForUpdates() {
try {
const result = await this.checkForUpdatePolicy({ currentVersion: this.currentVersion });
return { success: true, ...result };
} catch {
return { success: false, message: 'Could not securely check for updates.' };
}
}
async openFixedReleasesPage() {
await this.openExternal(RELEASES_PAGE_URL);
return { success: true };
}
}
module.exports = {
AppRuntime,
PAIRING_ENVELOPE_FILENAME,
PairingController,
atomicWritePairingEnvelope,
createBridgeHandler,
loadPairingEnvelope,
};