diff --git a/backend/main.py b/backend/main.py
index 179134e..09133a5 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -1073,10 +1073,20 @@ def require_admin(request: Request):
raise HTTPException(status_code=403, detail=detail)
+def _is_local_or_docker(host: str) -> bool:
+ """Return True if the IP is loopback or a Docker-internal private network."""
+ if host in {"127.0.0.1", "::1", "localhost"}:
+ return True
+ # Docker bridge networks use 172.x.x.x or 192.168.x.x ranges
+ if host.startswith("172.") or host.startswith("192.168.") or host.startswith("10."):
+ return True
+ return False
+
+
def require_local_operator(request: Request):
- """Allow local tooling on loopback, or a valid admin key from elsewhere."""
+ """Allow local tooling on loopback / Docker internal network, or a valid admin key."""
host = (request.client.host or "").lower() if request.client else ""
- if host in {"127.0.0.1", "::1", "localhost"} or (_debug_mode_enabled() and host == "test"):
+ if _is_local_or_docker(host) or (_debug_mode_enabled() and host == "test"):
return
admin_key = _current_admin_key()
presented = str(request.headers.get("X-Admin-Key", "") or "").strip()
diff --git a/frontend/src/components/ChangelogModal.tsx b/frontend/src/components/ChangelogModal.tsx
index 23bb71e..77eebea 100644
--- a/frontend/src/components/ChangelogModal.tsx
+++ b/frontend/src/components/ChangelogModal.tsx
@@ -20,7 +20,7 @@ const STORAGE_KEY = `shadowbroker_changelog_v${CURRENT_VERSION}`;
const RELEASE_TITLE = 'InfoNet Experimental Testnet — Decentralized Intelligence Experiment';
const HEADLINE_FEATURE = {
- icon: ,
+ icon: ,
title: 'InfoNet Experimental Testnet is Live',
subtitle: 'The first decentralized intelligence mesh built directly into an OSINT platform. This is an experimental testnet — NOT a privacy tool.',
details: [
@@ -34,43 +34,43 @@ const HEADLINE_FEATURE = {
const NEW_FEATURES = [
{
- icon: ,
+ icon: ,
title: 'Meshtastic + APRS Radio Integration',
desc: 'Live Meshtastic mesh radio nodes plotted worldwide via MQTT. APRS amateur radio positioning via APRS-IS TCP feed. Both integrated into Mesh Chat and the SIGINT grid. Note: Mesh radio is NOT private — RF transmissions are public by nature.',
color: 'amber',
},
{
- icon: ,
+ icon: ,
title: 'Mesh Terminal',
desc: 'Built-in command-line interface. Send messages, DMs, run market commands, inspect gate state. Draggable panel, minimizes to the top bar. Type "help" to see everything.',
color: 'cyan',
},
{
- icon: ,
+ icon: ,
title: 'Shodan Device Search',
desc: 'Query Shodan directly from ShadowBroker. Search internet-connected devices by keyword, CVE, or port — results plotted as a live overlay on the map with configurable marker style.',
color: 'green',
},
{
- icon: ,
+ icon: ,
title: 'CCTV Mesh Expanded — 12 Sources, 11,000+ Cameras',
desc: 'Massive expansion: added Spain (DGT national + Madrid city), California (12 Caltrans districts), Washington State, Georgia, Illinois, Michigan, and Windy Webcams. Now covers 6 countries. Enabled by default.',
color: 'emerald',
},
{
- icon: ,
+ icon: ,
title: 'Train Tracking (Amtrak + European Rail)',
desc: 'Real-time Amtrak train positions across the US and European rail via DigiTraffic. Speed, heading, route, and status for every train on the network.',
color: 'blue',
},
{
- icon: ,
+ icon: ,
title: '8 New Intelligence Layers',
desc: 'Volcanoes (Smithsonian), air quality PM2.5 (OpenAQ), severe weather alerts, fishing activity (Global Fishing Watch), military bases, 35K+ power plants, SatNOGS ground stations, TinyGS LoRa satellites, VIIRS nightlights.',
color: 'purple',
},
{
- icon: ,
+ icon: ,
title: 'Sentinel Hub Imagery + Desktop Shell Scaffold',
desc: 'Copernicus CDSE satellite imagery via Sentinel Hub Process API with OAuth2 token flow. Desktop-native control routing scaffold (pre-Tauri) with session profiles and audit trail.',
color: 'yellow',
@@ -206,7 +206,7 @@ const ChangelogModal = React.memo(function ChangelogModal({ onClose }: Changelog
className="fixed inset-0 z-[10001] flex items-center justify-center pointer-events-none"
>
e.stopPropagation()}
>
{/* Header */}
@@ -214,14 +214,14 @@ const ChangelogModal = React.memo(function ChangelogModal({ onClose }: Changelog
-
+
v{CURRENT_VERSION}
-
+
WHAT'S NEW
-
+
{RELEASE_TITLE.toUpperCase()}
@@ -239,14 +239,14 @@ const ChangelogModal = React.memo(function ChangelogModal({ onClose }: Changelog
{/* === HEADLINE: InfoNet Testnet === */}
-
+
{HEADLINE_FEATURE.icon}
-
+
{HEADLINE_FEATURE.title}
-
+
{HEADLINE_FEATURE.subtitle}
@@ -256,7 +256,7 @@ const ChangelogModal = React.memo(function ChangelogModal({ onClose }: Changelog
{HEADLINE_FEATURE.details.map((para, i) => (
{para}
@@ -265,12 +265,12 @@ const ChangelogModal = React.memo(function ChangelogModal({ onClose }: Changelog
{/* Testnet disclaimer */}
-
!!
+
!!
-
+
EXPERIMENTAL TESTNET — NO PRIVACY GUARANTEE
-
+
InfoNet messages are obfuscated but NOT encrypted end-to-end. The Mesh network
(Meshtastic/APRS) is NOT private — radio transmissions are inherently
public. Do not send anything sensitive on any channel. Privacy and E2E encryption
@@ -281,7 +281,7 @@ const ChangelogModal = React.memo(function ChangelogModal({ onClose }: Changelog
{/* CTA */}
-
+
{HEADLINE_FEATURE.callToAction}
@@ -289,8 +289,8 @@ const ChangelogModal = React.memo(function ChangelogModal({ onClose }: Changelog
{/* === Other New Features === */}
-
-
+
@@ -301,10 +301,10 @@ const ChangelogModal = React.memo(function ChangelogModal({ onClose }: Changelog
>
{f.icon}
-
+
{f.title}
-
@@ -315,15 +315,15 @@ const ChangelogModal = React.memo(function ChangelogModal({ onClose }: Changelog
{/* Bug Fixes */}
-
-
+
+
FIXES & IMPROVEMENTS
{BUG_FIXES.map((fix, i) => (
- +
-
+ +
+
{fix}
@@ -333,8 +333,8 @@ const ChangelogModal = React.memo(function ChangelogModal({ onClose }: Changelog
{/* Contributors */}
-
-
+
+
COMMUNITY CONTRIBUTORS
@@ -343,19 +343,19 @@ const ChangelogModal = React.memo(function ChangelogModal({ onClose }: Changelog
key={i}
className="flex items-start gap-2 px-3 py-2 border border-pink-500/20 bg-pink-500/5"
>
-
+
♥
-
+
{c.name}
-
+
{' '}
— {c.desc}
{c.pr && (
-
+
{' '}
(PR {c.pr})
@@ -371,7 +371,7 @@ const ChangelogModal = React.memo(function ChangelogModal({ onClose }: Changelog
ACKNOWLEDGED