fix: close APT adversarial runtime gaps

This commit is contained in:
2026-08-19 21:59:19 +00:00
parent 2f492b662d
commit cf75ea9bc2
9 changed files with 688 additions and 233 deletions
+68 -8
View File
@@ -1,8 +1,26 @@
'use strict';
const APT_URL = 'http://127.0.0.1:18247/cookie';
const CHALLENGE_URL = 'http://127.0.0.1:18247/challenge';
const PAIRING_STORAGE_KEY = 'aptPairingSecret';
const PAIRING_SECRET_PATTERN = /^[A-Za-z0-9_-]{43}$/;
const NONCE_PATTERN = PAIRING_SECRET_PATTERN;
const BRIDGE_DEADLINE_MS = 3000;
function base64Url(bytes) {
let binary = '';
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');
}
async function hmacBase64Url(cryptoApi, secret, value) {
const encoder = new TextEncoder();
const key = await cryptoApi.subtle.importKey(
'raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
);
const signature = await cryptoApi.subtle.sign('HMAC', key, encoder.encode(value));
return base64Url(new Uint8Array(signature));
}
function isSupportedDeploymentUrl(value) {
try {
@@ -20,7 +38,8 @@ function createPopupController({
documentApi,
navigatorApi,
fetchImpl,
confirmCopy
confirmCopy,
cryptoApi
}) {
const tabInfo = documentApi.getElementById('tabInfo');
const pairingInfo = documentApi.getElementById('pairingInfo');
@@ -34,6 +53,16 @@ function createPopupController({
let pairingSecret = null;
let busy = false;
async function bridgeFetch(url, options) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), BRIDGE_DEADLINE_MS);
try {
return await fetchImpl(url, { ...options, signal: controller.signal });
} finally {
clearTimeout(timer);
}
}
function showStatus(message, type) {
statusMsg.textContent = message;
statusMsg.className = `status-msg ${type}`;
@@ -77,15 +106,45 @@ function createPopupController({
setBusy(true);
let cookieValue = null;
try {
showStatus('Authenticating Alta Proxy Tool...', 'info');
if (!cryptoApi || !cryptoApi.subtle || typeof cryptoApi.getRandomValues !== 'function') {
throw new Error('CRYPTO_UNAVAILABLE');
}
const clientNonce = base64Url(cryptoApi.getRandomValues(new Uint8Array(32)));
const challengeResponse = await bridgeFetch(CHALLENGE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ clientNonce })
});
const challenge = await challengeResponse.json();
if (!challengeResponse.ok || !challenge || challenge.success !== true ||
!NONCE_PATTERN.test(challenge.serverNonce) || !NONCE_PATTERN.test(challenge.serverProof)) {
throw new Error('BRIDGE_REJECTED');
}
const expectedServerProof = await hmacBase64Url(
cryptoApi,
pairingSecret,
JSON.stringify(['apt-server-challenge-v1', clientNonce, challenge.serverNonce])
);
if (challenge.serverProof !== expectedServerProof) throw new Error('BRIDGE_AUTH_FAILED');
showStatus('Sending to Alta Proxy Tool...', 'info');
cookieValue = await getVaCookieValue();
const response = await fetchImpl(APT_URL, {
const cookieRequest = {
clientNonce,
serverNonce: challenge.serverNonce,
deploymentUrl: detectedOrigin,
cookieValue
};
cookieRequest.proof = await hmacBase64Url(
cryptoApi,
pairingSecret,
JSON.stringify(['apt-cookie-request-v1', clientNonce, challenge.serverNonce, detectedOrigin, cookieValue])
);
const response = await bridgeFetch(APT_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-APT-Pairing': pairingSecret
},
body: JSON.stringify({ deploymentUrl: detectedOrigin, cookieValue })
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(cookieRequest)
});
const data = await response.json();
if (!response.ok || !data || data.success !== true) throw new Error('BRIDGE_REJECTED');
@@ -173,7 +232,8 @@ if (typeof module !== 'undefined' && module.exports) {
documentApi: document,
navigatorApi: navigator,
fetchImpl: fetch,
confirmCopy: (message) => window.confirm(message)
confirmCopy: (message) => window.confirm(message),
cryptoApi: crypto
}).init().catch(() => {
const status = document.getElementById('statusMsg');
status.textContent = 'Extension initialization failed. Open pairing settings and try again.';