Harden loopback HTTP and document AMO unlisted signing.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
"""Native messaging host: 127.0.0.1 HTTP <-> Firefox extension stdio."""
|
||||
from __future__ import annotations
|
||||
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
@@ -21,6 +22,8 @@ if sys.platform == "win32":
|
||||
|
||||
HOST = "127.0.0.1"
|
||||
PORT = int(os.environ.get("FAB_PORT", "17634"))
|
||||
MAX_BODY = 256 * 1024
|
||||
ALLOWED_HOSTS = {f"127.0.0.1:{PORT}", f"localhost:{PORT}"}
|
||||
STATE_DIR = Path(os.environ.get("LOCALAPPDATA", str(Path.home()))) / "firefox-agent-bridge"
|
||||
TOKEN_PATH = STATE_DIR / "token"
|
||||
STDIN_LOCK = threading.Lock()
|
||||
@@ -91,20 +94,36 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def _gate_ok(self) -> bool:
|
||||
"""Block browser-origin calls and non-loopback Host headers."""
|
||||
origin = self.headers.get("Origin")
|
||||
if origin:
|
||||
self._send(403, {"ok": False, "error": "browser origin not allowed"})
|
||||
return False
|
||||
host = (self.headers.get("Host") or "").split("%")[0].lower()
|
||||
if host not in ALLOWED_HOSTS:
|
||||
self._send(403, {"ok": False, "error": "host not allowed"})
|
||||
return False
|
||||
return True
|
||||
|
||||
def _auth_ok(self) -> bool:
|
||||
if not self._gate_ok():
|
||||
return False
|
||||
token = read_token()
|
||||
if not token:
|
||||
self._send(500, {"ok": False, "error": "bridge token missing; run install-native-host.ps1"})
|
||||
return False
|
||||
header = self.headers.get("Authorization", "")
|
||||
got = header[7:].strip() if header.lower().startswith("bearer ") else ""
|
||||
if got != token:
|
||||
if not hmac.compare_digest(got, token):
|
||||
self._send(401, {"ok": False, "error": "missing or invalid bearer token"})
|
||||
return False
|
||||
return True
|
||||
|
||||
def _json_body(self) -> dict[str, Any]:
|
||||
length = int(self.headers.get("Content-Length") or "0")
|
||||
if length > MAX_BODY:
|
||||
raise ValueError("request body too large")
|
||||
if length <= 0:
|
||||
return {}
|
||||
raw = self.rfile.read(length)
|
||||
@@ -118,6 +137,8 @@ class Handler(BaseHTTPRequestHandler):
|
||||
def do_GET(self) -> None:
|
||||
parsed = urlparse(self.path)
|
||||
if parsed.path == "/health":
|
||||
if not self._gate_ok():
|
||||
return
|
||||
self._send(200, {"ok": True, "service": "firefox-agent-bridge", "port": PORT})
|
||||
return
|
||||
if not self._auth_ok():
|
||||
|
||||
Reference in New Issue
Block a user