function runBridgeCli(argv) { var cmd = argv[0]; if (!cmd || cmd.indexOf('--') !== 0) return false; bridgeEphemeral = false; // the command line always edits the trust file itself var st = trustState(); var arg = argv[1]; function say(s) { console.log(' ' + s); } function fail(s) { console.error(' ! ' + s); process.exitCode = 1; return true; } function findMind(id) { return st.minds.filter(function (m) { return m.id === String(id || ''); })[0] || null; } switch (cmd) { case '--list-minds': { say('Trusted minds (' + st.minds.length + '). Tokens are never shown; only their hashes are kept.'); st.minds.forEach(function (m) { say(m.id + ' ' + m.kind + ' ' + m.name + (m.origin ? ' ' + m.origin : '') + ' scopes=' + m.scopes.join(',') + ' paired=' + (m.createdAt || '?') + ' last=' + (m.lastUsedAt || 'never') + (m.kind === 'cli' && !st.localTools ? ' (off)' : '')); }); say('Local tools: ' + (st.localTools ? 'trusted (token file ' + TOKEN_FILE + ')' : 'off')); say('Extra trusted origins: ' + (st.trustedOrigins.length ? st.trustedOrigins.join(', ') : 'none')); say('Project folders: ' + rootList().map(function (r) { return r.name + '=' + r.path; }).join(', ')); return true; } case '--revoke': { var m = findMind(arg); if (!m) return fail('No trusted mind with id ' + String(arg || '(missing)') + '. Try --list-minds.'); st.minds = st.minds.filter(function (x) { return x.id !== m.id; }); if (m.kind === 'cli') { st.localTools = false; try { fs.unlinkSync(TOKEN_FILE); } catch (e) {} } if (!saveTrust()) return fail('Could not save.'); ledgerAppend({ kind: 'pair', route: 'cli:--revoke', device: m.id, result: 'revoked' }); say('Revoked ' + m.name + ' (' + m.id + '). Its next request will be asked to pair.'); return true; } case '--revoke-all': { var before = st.minds.length; st.minds = st.minds.filter(function (x) { return x.kind === 'cli'; }); if (!saveTrust()) return fail('Could not save.'); ledgerAppend({ kind: 'pair', route: 'cli:--revoke-all', result: 'revoked-all' }); say('Revoked ' + (before - st.minds.length) + ' paired device(s). Local tools stay trusted.'); return true; } case '--allow-secrets': case '--deny-secrets': { var sm = findMind(arg); if (!sm) return fail('No trusted mind with id ' + String(arg || '(missing)') + '.'); sm.scopes = sm.scopes.filter(function (s) { return s !== 'secrets'; }); if (cmd === '--allow-secrets') sm.scopes.push('secrets'); if (!saveTrust()) return fail('Could not save.'); ledgerAppend({ kind: 'pair', route: 'cli:' + cmd, device: sm.id, result: 'scopes' }); say(sm.name + ' scopes: ' + sm.scopes.join(',')); return true; } case '--trust-origin': case '--untrust-origin': { var o = exactOrigin(arg); if (!o) return fail('Exact origins only, like https://example.dev or http://192.168.1.20:8000. Never *.'); st.trustedOrigins = st.trustedOrigins.filter(function (x) { return x !== o; }); if (cmd === '--trust-origin') st.trustedOrigins.push(o); if (!saveTrust()) return fail('Could not save.'); ledgerAppend({ kind: 'trust', route: 'cli:' + cmd, origin: o, result: 'ok' }); say((cmd === '--trust-origin' ? 'Trusted ' : 'Removed ') + o + '. It still has to pair before it can change anything.'); return true; } case '--add-root': { var name = String(arg || ''); var dir = argv[2] ? path.resolve(argv[2]) : ''; if (!/^[a-z0-9][a-z0-9_-]{0,31}$/i.test(name) || name === 'project') return fail('Name: letters, digits, - or _, up to 32, not "project".'); var isDir = false; try { isDir = fs.statSync(dir).isDirectory(); } catch (e) {} if (!dir || !isDir) return fail('Folder not found: ' + String(argv[2] || '(missing)')); st.roots[name] = fs.realpathSync(dir); if (!saveTrust()) return fail('Could not save.'); ledgerAppend({ kind: 'trust', route: 'cli:--add-root', result: 'ok' }); say('Trusted project folder "' + name + '" = ' + st.roots[name]); return true; } case '--remove-root': { if (!Object.prototype.hasOwnProperty.call(st.roots, String(arg || ''))) return fail('No project folder named ' + String(arg || '(missing)') + '.'); delete st.roots[arg]; if (!saveTrust()) return fail('Could not save.'); ledgerAppend({ kind: 'trust', route: 'cli:--remove-root', result: 'ok' }); say('Removed project folder "' + arg + '".'); return true; } case '--trust-local-tools': { st.localTools = true; if (!saveTrust()) return fail('Could not save.'); ensureLocalToolsToken(); ledgerAppend({ kind: 'pair', route: 'cli:--trust-local-tools', result: 'local-tools-on' }); say('Local tools on this computer are trusted. Token file: ' + TOKEN_FILE); return true; } case '--untrust-local-tools': { st.localTools = false; st.minds = st.minds.filter(function (x) { return x.kind !== 'cli'; }); try { fs.unlinkSync(TOKEN_FILE); } catch (e) {} if (!saveTrust()) return fail('Could not save.'); ledgerAppend({ kind: 'pair', route: 'cli:--untrust-local-tools', result: 'local-tools-off' }); say('Local tools now need to pair like any other device.'); return true; } default: bridgeLockUsage(); return fail('Unknown option ' + cmd); } } ## Exact source excerpt, characters 48734–48859 function bridgeLockInit() { trustState(); ensureLocalToolsToken(); } // ── end LAYER bridge-lock-v0 · v0.1 helpers ── ``` ## Exact source excerpt, characters 48861–48909 ### Block B: `handleRequest` (replaces L111-129) ## Exact source excerpt, characters 48911–49277 ```js function handleRequest(req, res) { // LAYER bridge-lock-v0: the old `Access-Control-Allow-Origin: *` is superseded here. var origin = req.headers.origin || ''; var pathname = String(req.url || '/').split('?')[0]; res.setHeader('Content-Type', 'application/json'); res.setHeader('Vary', 'Origin'); res.setHeader('X-Content-Type-Options', 'nosniff'); ## Exact source excerpt, characters 49279–49498 // 1. Host guard (DNS rebinding): only 127.0.0.1 / localhost. if (!hostAllowed(req.headers.host)) { req.resume(); return respond(res, 403, { error: 'Host not allowed', bridgeLock: BRIDGE_LOCK_VERSION }); } ## Exact source excerpt, characters 49500–50465 // 2. Origin guard. Browsers always send Origin on cross-origin fetches and // preflights. No Origin = local CLI or same-machine tool (still needs // a trusted token for anything that changes state). if (origin && !originAllowed(origin)) { if (pathname === '/code/git/commit') { ledgerAppend({ route: pathname, origin: origin, result: 'refused:origin' }); } req.resume(); return respond(res, 403, { error: 'Origin not on the FreeLattice allowlist', bridgeLock: BRIDGE_LOCK_VERSION }); } if (origin) { res.setHeader('Access-Control-Allow-Origin', origin); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Agent-Id, X-FL-Bridge-Token'); res.setHeader('Access-Control-Max-Age', '600'); if (req.headers['access-control-request-private-network']) { res.setHeader('Access-Control-Allow-Private-Network', 'true'); } } ## Exact source excerpt, characters 50467–50555 if (req.method === 'OPTIONS') { req.resume(); res.writeHead(204); res.end(); return; } ## Exact source excerpt, characters 50557–51059 // 3. Trusted-mind guard: which paired device (or local tool) is this? var gated = needsToken(req.method, pathname); var mind = gated ? requestMind(req) : null; if (gated && !mind) { if (pathname === '/code/git/commit') { ledgerAppend({ route: pathname, origin: origin, result: 'refused:token' }); } req.resume(); return respond(res, 401, { error: 'Pair with the Agent Bridge first (token missing, stale or revoked).', pair: 'code', bridgeLock: BRIDGE_LOCK_VERSION }); } ## Exact source excerpt, characters 51061–51618 // 4. Scope guard. A new mind has every scope except 'secrets' (.env files). var need = mind ? scopeFor(req.method, pathname) : ''; if (need && mind.scopes.indexOf(need) === -1) { if (pathname === '/code/git/commit') { ledgerAppend({ route: pathname, origin: origin, device: mind.id, result: 'refused:scope' }); } req.resume(); return respond(res, 403, { error: 'This mind has not been given "' + need + '" here. Kirk can add it in Paired minds.', scope: need, bridgeLock: BRIDGE_LOCK_VERSION }); } if (mind) touchMind(mind); ## Exact source excerpt, characters 51620–51757 // Allow requests to act as a different agent via X-Agent-Id header var requestAgentId = req.headers['x-agent-id'] || agentId.meshId; ## Exact source excerpt, characters 51759–52650 // 5. Body cap. Drain the rest so the client sees the 413 cleanly. var chunks = []; var size = 0; var tooBig = false; req.on('data', function(chunk) { size += chunk.length; if (size > MAX_BODY_BYTES) { tooBig = true; chunks = []; return; } if (!tooBig) chunks.push(chunk); }); req.on('end', function() { if (tooBig) return respond(res, 413, { error: 'Body too large (max ' + MAX_BODY_BYTES + ' bytes)' }); var body = Buffer.concat(chunks).toString('utf8'); var data = {}; if (body) { try { data = JSON.parse(body); } catch (e) { return respond(res, 400, { error: 'Body is not valid JSON' }); } if (!data || typeof data !== 'object' || Array.isArray(data)) return respond(res, 400, { error: 'Body must be a JSON object' }); } route(req.url, req.method, data, res, requestAgentId, { origin: origin, mind: mind }); }); } ``` ## Exact source excerpt, characters 52652–52717 ### Block C: pairing and trusted-minds routes (insert after L133) ## Exact source excerpt, characters 52719–55008 ```js // ── LAYER bridge-lock-v0 · v0.1: pairing and trusted minds ── if (url === '/pair' && method === 'POST') { var now = Date.now(); if (!pairState.code || now > pairState.expires) newPairCode('expired'); if (now < pairState.coolUntil) return respond(res, 429, { error: 'Resting for a moment. Try again in 30 seconds.' }); var given = normalizePairCode(data.code); var good = given.length === 6 && crypto.timingSafeEqual(Buffer.from(given, 'utf8'), Buffer.from(pairState.code, 'utf8')); var pairOrigin = (meta && meta.origin) || ''; if (!good) { pairState.tries += 1; if (pairState.tries >= PAIR_MAX_TRIES) { ledgerAppend({ kind: 'pair', route: '/pair', origin: pairOrigin, result: 'refused:lockout' }); newPairCode('lockout'); return respond(res, 429, { error: 'Too many tries. The bridge window shows a new code.' }); } return respond(res, 403, { error: 'That code does not match. Check the bridge window.', triesLeft: PAIR_MAX_TRIES - pairState.tries }); } var st = trustState(); var tok = newToken(); var mind = { id: newMindId(), name: cleanName(data.name) || (pairOrigin ? 'A browser at ' + pairOrigin : 'A local tool'), origin: pairOrigin, kind: pairOrigin ? 'browser' : 'tool', scopes: DEFAULT_SCOPES.slice(), tokenSha256: sha256Hex(tok), createdAt: new Date().toISOString(), lastUsedAt: '', expiresAt: null }; st.minds.push(mind); if (!saveTrust()) { st.minds = st.minds.filter(function (m) { return m.id !== mind.id; }); return respond(res, 500, { error: 'The bridge could not save this pairing. Check ~/.freelattice.' }); } ledgerAppend({ kind: 'pair', route: '/pair', origin: pairOrigin, device: mind.id, result: 'ok' }); console.log(' \u2726 Paired "' + mind.name + '" (' + mind.id + '). ' + (bridgeEphemeral ? 'Ephemeral mode: forgotten when the bridge stops.' : 'It stays paired until you revoke it.')); newPairCode('used'); // single use: the next device gets a fresh code return respond(res, 200, { token: tok, id: mind.id, name: mind.name, scopes: mind.scopes, persistent: !bridgeEphemeral, bridgeLock: BRIDGE_LOCK_VERSION }); } ## Exact source excerpt, characters 55010–55274