From 72bb37ffe5dfa4a5a834ecc23b0fdaa9e5dc47e5 Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Sun, 1 Mar 2026 22:46:05 +0800 Subject: [PATCH] fix: correct sidebar LAN detection for public access Separate Tailscale IP detection from true LAN detection so public internet users routed through VPS no longer get non-routable port links in the sidebar. Co-Authored-By: Claude Opus 4.6 (1M context) --- dashboard/backend/config.py | 12 +++++++++++- dashboard/backend/main.py | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/dashboard/backend/config.py b/dashboard/backend/config.py index 16140c0..80aee94 100644 --- a/dashboard/backend/config.py +++ b/dashboard/backend/config.py @@ -48,9 +48,10 @@ CHAT_SUMMARY_DB = os.environ.get("CHAT_SUMMARY_DB", "/app/data/chat-summarizer/c OPENCLAW_GATEWAY_TOKEN = os.environ.get("OPENCLAW_GATEWAY_TOKEN", "") # Networking configs -LAN_IP_RANGES = os.environ.get("LAN_IP_RANGES", "192.168.31.0/24,100.64.0.0/10").split(",") +LAN_IP_RANGES = os.environ.get("LAN_IP_RANGES", "192.168.31.0/24").split(",") def is_lan_ip(ip_str: str) -> bool: + """Check if IP is in local LAN (not Tailscale)""" import ipaddress try: ip_obj = ipaddress.ip_address(ip_str) @@ -61,6 +62,15 @@ def is_lan_ip(ip_str: str) -> bool: except ValueError: return False +def is_tailscale_ip(ip_str: str) -> bool: + """Check if IP is in Tailscale CGNAT range (100.64.0.0/10)""" + import ipaddress + try: + ip_obj = ipaddress.ip_address(ip_str) + return ip_obj in ipaddress.ip_network("100.64.0.0/10") + except ValueError: + return False + TRUSTED_PROXIES = os.environ.get("TRUSTED_PROXIES", "127.0.0.1,::1,172.16.0.0/12").split(",") def is_trusted_proxy(ip_str: str) -> bool: diff --git a/dashboard/backend/main.py b/dashboard/backend/main.py index 8d22068..569f389 100644 --- a/dashboard/backend/main.py +++ b/dashboard/backend/main.py @@ -134,8 +134,19 @@ async def client_ip(request: Request): forwarded = request.headers.get("x-forwarded-for", "").split(",")[0].strip() if forwarded: ip = forwarded - import config - lan = config.is_lan_ip(ip) if not ip.startswith("100.") else config.is_lan_ip(ip) and _router_reachable() + + # LAN detection logic: + # - True LAN IP (192.168.31.x) → lan=True + # - Tailscale IP with router reachable → lan=True (user at home with Tailscale) + # - Tailscale IP without router reachable → lan=False (remote via VPS proxy) + # - Other IPs → lan=False (public internet) + if config.is_lan_ip(ip): + lan = True + elif config.is_tailscale_ip(ip): + lan = _router_reachable() + else: + lan = False + return {"lan": lan} def _router_reachable():