app
Adom Bridge
Public Made by Adomby adom
Adom Bridge unleashes your AI onto your full PC: full power, full safety. The AI breaks out of the container it runs in and onto your real machine, managing and running your entire laptop as you: launch apps, move files, drive any window, control KiCad, Fusion 360 or your real signed-in browser. Works with any AI, cloud or local (Claude Desktop, Claude Code, Codex), no Hydrogen required. Bridge and its bridges are an operating system for AI, with managed Node and Python runtimes, and a human-onl
← Commit history
Add live web-control demo harness (detect ab, read status, show the open-KiCad wall)
1 file changed
+157
docs/web-control.mdadded+157@@ -0,0 +1,157 @@+# Talking to Adom Bridge from a web page++This page is a **live demo**. If you are reading it on `https://wiki.adom.inc` in a+browser on a machine that is running Adom Bridge, the panel below actually reaches+into the copy of Adom Bridge on that machine and reports back. Nothing here is a+mock-up: the buttons make real `fetch()` calls to `http://127.0.0.1:24680`, Adom+Bridge's loopback API.++## What a web page can and cannot do (read this first)++Adom Bridge runs a small HTTP API on the loopback address `127.0.0.1:24680`. If any+website could reach it, a malicious page could drive your computer. So the surface a+web page is allowed to touch is deliberately tiny, and this demo shows exactly where+the line is.++- **Allowed from the web, today:** `GET /health` and `GET /status`. Both are+ read-only: liveness, version, which bridges are installed. That is enough for a+ page to light up an "Open in KiCad" button only when KiCad and Adom Bridge are+ actually present.+- **Refused from the web, today:** `POST /command` (the verb-running endpoint) is+ hard-refused for every web origin with HTTP 403 `web_forbidden`. So no web page+ can change your machine. The "Try to open KiCad" button below deliberately hits+ that wall so you can watch it get refused.+- **Only `https://wiki.adom.inc` gets in at all.** Adom Bridge returns the CORS+ headers that let the browser read the response *only* when the request's `Origin`+ is exactly `https://wiki.adom.inc`. Any other site gets nothing. This is why the+ demo only works when you are reading it on the real wiki.++Actually *running* a verb like `kicad_open_board` from a web button is a planned next+phase: it will require a native Adom Bridge consent dialog (you approve the specific+action in Adom Bridge's own window, which no web page can script) layered under a+per-user cloud-key handshake. Until that ships, the answer to "can the wiki open+KiCad for me?" is: it can *detect* that it could, and it is *walled off* from doing+it. That wall is the feature.++<div id="ab-demo" style="font-family:system-ui,-apple-system,Segoe UI,sans-serif;border:1px solid #30363d;border-radius:10px;background:#0d1117;color:#e6edf3;padding:18px;margin:18px 0;max-width:760px">+ <div style="display:flex;align-items:center;gap:10px;margin-bottom:6px">+ <span id="ab-dot" style="width:12px;height:12px;border-radius:50%;background:#484f58;display:inline-block"></span>+ <strong style="font-size:16px">Adom Bridge web control demo</strong>+ </div>+ <div style="font-size:12px;color:#8b949e;margin-bottom:14px">+ This iframe's origin: <code id="ab-origin" style="color:#00e6dc">checking…</code>+ · API: <code style="color:#8b949e">http://127.0.0.1:24680</code>+ </div>+ <div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px">+ <button id="ab-detect" style="background:#00b8b0;color:#04211f;border:0;border-radius:6px;padding:8px 14px;font-weight:600;cursor:pointer">Detect Adom Bridge</button>+ <button id="ab-status" style="background:#1c2128;color:#e6edf3;border:1px solid #30363d;border-radius:6px;padding:8px 14px;cursor:pointer">Read status</button>+ <button id="ab-kicad" style="background:#1c2128;color:#e6edf3;border:1px solid #30363d;border-radius:6px;padding:8px 14px;cursor:pointer">Try to open KiCad (should be blocked)</button>+ </div>+ <pre id="ab-log" style="background:#010409;border:1px solid #21262d;border-radius:6px;padding:12px;font-size:12px;line-height:1.5;max-height:300px;overflow:auto;white-space:pre-wrap;margin:0;color:#c9d1d9">Ready. Click "Detect Adom Bridge" to begin.</pre>+ <script>+ (function () {+ var AB = "http://127.0.0.1:24680";+ var log = document.getElementById("ab-log");+ var dot = document.getElementById("ab-dot");+ document.getElementById("ab-origin").textContent = location.origin || "(null — sandboxed iframe)";+ function line(msg, color) {+ var t = new Date().toLocaleTimeString();+ log.textContent += "\n[" + t + "] " + msg;+ if (color) { log.style.color = color; }+ log.scrollTop = log.scrollHeight;+ }+ function reset() { log.textContent = "—"; log.style.color = "#c9d1d9"; }++ document.getElementById("ab-detect").onclick = function () {+ reset();+ line("GET " + AB + "/health …");+ fetch(AB + "/health")+ .then(function (r) { return r.ok ? r.json() : Promise.reject("HTTP " + r.status); })+ .then(function (j) {+ if (j && j.service === "adom-bridge") {+ dot.style.background = "#00b8b0";+ line("Adom Bridge is RUNNING on this machine. service=" + j.service, "#7ee787");+ line(JSON.stringify(j, null, 2));+ } else {+ line("Reached something, but it is not Adom Bridge: " + JSON.stringify(j), "#e6c86c");+ }+ })+ .catch(function (e) {+ dot.style.background = "#484f58";+ line("No Adom Bridge reachable (" + e + ").", "#8b949e");+ line("Either it is not installed/running here, or you are not on https://wiki.adom.inc (the only origin it answers). Mixed-content note: browsers allow an HTTPS page to fetch http://127.0.0.1 as a trustworthy loopback, so that is not the blocker.");+ });+ };++ document.getElementById("ab-status").onclick = function () {+ reset();+ line("GET " + AB + "/status …");+ fetch(AB + "/status")+ .then(function (r) { return r.ok ? r.json() : Promise.reject("HTTP " + r.status); })+ .then(function (j) {+ dot.style.background = "#00b8b0";+ line("Status (read-only, allowed from the web):", "#7ee787");+ line(JSON.stringify(j, null, 2));+ })+ .catch(function (e) { line("status failed: " + e, "#f0883e"); });+ };++ document.getElementById("ab-kicad").onclick = function () {+ reset();+ line("POST " + AB + "/command {kicad_open_board} — expecting this to be REFUSED …");+ fetch(AB + "/command", {+ method: "POST",+ headers: { "Content-Type": "application/json" },+ body: JSON.stringify({ app: "kicad", command: "kicad_open_board", args: {} })+ })+ .then(function (r) { return r.json().then(function (j) { return { status: r.status, body: j }; }); })+ .then(function (res) {+ if (res.status === 403 || (res.body && res.body.errorCode === "web_forbidden")) {+ line("REFUSED, as designed. HTTP " + res.status + " errorCode=" + (res.body && res.body.errorCode), "#7ee787");+ line("This is the safety wall: a web page cannot run a machine-changing verb. " ++ "The consent-gated action phase will change this to 'ask the human in Adom Bridge first'.");+ line(JSON.stringify(res.body, null, 2));+ } else {+ line("UNEXPECTED — POST /command was NOT refused (HTTP " + res.status + "). " ++ "That would be a security regression; please report it.", "#f85149");+ line(JSON.stringify(res.body, null, 2));+ }+ })+ .catch(function (e) {+ line("The browser blocked the POST before Adom Bridge answered (" + e + ").", "#e6c86c");+ line("That is also a refusal: with no CORS grant for a non-allowlisted origin, the browser will not even let page JS read the response.");+ });+ };+ })();+ </script>+</div>++## How it works under the hood++The gate lives in `src-tauri/src/direct_api.rs` (`web_gate` middleware). Every layer+must pass:++1. **Exact origin allowlist.** CORS headers are returned only for+ `Origin: https://wiki.adom.inc` (never `*`). The browser sets `Origin` and page+ JS cannot forge it, so it reliably identifies the site.+2. **Never `*.adom.cloud`, never a wildcard.** Any Adom user can be handed an+ `*.adom.cloud` subdomain, so allowlisting that pattern would let a nefarious+ user's page drive machines. The allowlist is exactly one first-party origin.+3. **Loopback Host check** (anti-DNS-rebind): the `Host` header must be a loopback+ literal.+4. **Private Network Access preflight:** a public-to-loopback fetch triggers the+ PNA preflight, which Adom Bridge answers only for the allowlisted origin.+5. **Path scope:** a web origin may reach only `/health` and `/status`. Everything+ else, including `/command`, is `web_forbidden`.++The fixed loopback port is `24680` (reserved and stable across reboots). For the+full ab-side reference, see the `adom-bridge-web-integration` skill.++## What this page is testing++Because it renders inside the wiki's HTML iframe, this page is also a live test of+whether that iframe's origin actually satisfies the gate. Watch the "This iframe's+origin" line: if it reads `https://wiki.adom.inc`, the CORS grant applies and the+detect/status buttons work. If it reads `null` (a sandboxed iframe), the browser+strips the origin and even the read-only calls are refused, which tells us the+harness has to run from a first-party origin rather than a sandboxed frame.