Address remaining safe security hardening

This commit is contained in:
BigBodyCobain
2026-06-02 13:34:11 -06:00
parent 10a8c7b5be
commit c3dd95f6a9
14 changed files with 94 additions and 47 deletions
+6
View File
@@ -128,8 +128,14 @@ ADMIN_KEY=
# MESH_DM_ROOT_TRANSPARENCY_LEDGER_READBACK_URI=backend/../ops/root_transparency_ledger.json
# ── Self Update ────────────────────────────────────────────────
# Optional ZIP updater digest pin. The updater checks this first, then
# backend/data/release_digests.json, then the release SHA256SUMS.txt asset.
# MESH_UPDATE_SHA256=
# Optional strict nonce-only frontend CSP. Leave unset unless the exact build
# has been verified to hydrate cleanly in your deployment.
# SHADOWBROKER_STRICT_CSP=1
# ── Wormhole (Local Agent) ─────────────────────────────────────
# WORMHOLE_URL=http://127.0.0.1:8787
# WORMHOLE_TRANSPORT=direct
+17 -3
View File
@@ -19,7 +19,7 @@
**ShadowBroker** is a decentralized intelligence platform that aggregates real-time, multi-domain OSINT telemetry from 60+ live intelligence feeds into a single dark-ops map interface. Aircraft, ships, satellites, conflict zones, CCTV networks, GPS jamming, internet-connected devices, police scanners, mesh radio nodes, and breaking geopolitical events — all updating in real time on one screen as well as an obfuscated communications protocol and information exchange infrastructure.
Built with **Next.js**, **MapLibre GL**, **FastAPI**, and **Python**. 35+ toggleable data layers, including SAR ground-change detection. Multiple visual modes (DEFAULT / SATELLITE / FLIR / NVG / CRT). Right-click any point on Earth for a country dossier, head-of-state lookup, and the latest Sentinel-2 satellite photo. No user data is collected or transmitted — the dashboard runs entirely in your browser against a self-hosted backend.
Built with **Next.js**, **MapLibre GL**, **FastAPI**, and **Python**. 35+ toggleable data layers, including SAR ground-change detection. Multiple visual modes (DEFAULT / SATELLITE / FLIR / NVG / CRT). Right-click any point on Earth for a country dossier, head-of-state lookup, and the latest Sentinel-2 satellite photo. ShadowBroker has no accounts, product telemetry, or analytics; the dashboard talks to your self-hosted backend, while optional live OSINT panels may contact their configured public data providers when you use them.
Designed for analysts, researchers, radio operators, and anyone who wants to see what the world looks like when every public signal is on the same map.
@@ -28,7 +28,7 @@ Designed for analysts, researchers, radio operators, and anyone who wants to see
A surprising amount of global telemetry is already public — aircraft ADS-B broadcasts, maritime AIS signals, satellite orbital data, earthquake sensors, mesh radio networks, police scanner feeds, environmental monitoring stations, internet infrastructure telemetry, and more. This data is scattered across dozens of tools and APIs. ShadowBroker combines all of it into a single interface.
The project does not introduce new surveillance capabilities — it aggregates and visualizes existing public datasets. It is fully open-source so anyone can audit exactly what data is accessed and how. No user data is collected or transmitted — everything runs locally against a self-hosted backend. No telemetry, no analytics, no accounts.
The project does not introduce new surveillance capabilities — it aggregates and visualizes existing public datasets. It is fully open-source so anyone can audit exactly what data is accessed and how. ShadowBroker does not include product telemetry, analytics, or accounts. Operator-supplied keys stay in your local deployment, but live OSINT features necessarily make outbound requests to the public data providers you enable or query.
### Shodan Connector
@@ -113,6 +113,20 @@ That's it. `pull` grabs the latest images, `up -d` restarts the containers.
>
> Podman users should run the equivalent provider command, for example `podman-compose pull` and `podman-compose up -d`, or use `./compose.sh --engine podman pull` and `./compose.sh --engine podman up -d` from a bash-compatible shell.
### Update Integrity
Docker updates are delivered through signed container registries. The legacy ZIP self-updater verifies release archives through this chain, in order:
* `MESH_UPDATE_SHA256` when an operator pins a digest explicitly.
* `backend/data/release_digests.json` for bundled release pins.
* The release `SHA256SUMS.txt` asset on GitHub when a bundled pin is not present.
Release maintainers should run `python backend/scripts/release_helper.py hash <ShadowBroker_vX.Y.Z.zip>` before publishing, then publish `SHA256SUMS.txt` and update `backend/data/release_digests.json` when shipping a ZIP updater target. The updater keeps the operator override path intact instead of failing closed on missing bundled digests, so existing installs do not get stranded by a release-process mistake.
### CSP Hardening
The production frontend ships with a hydration-compatible CSP and a strict nonce-only CSP in `Content-Security-Policy-Report-Only`. Set `SHADOWBROKER_STRICT_CSP=1` only after verifying the exact build hydrates correctly in your deployment. Runtime Google Fonts are not required; the bundled Next font pipeline serves the dashboard font from the app build.
### ⚠️ **Stuck on the old version?**
**If `git pull` fails or `docker compose up` keeps building from source instead of pulling images**, your clone predates a March 2026 repository migration that rewrote commit history. A normal `git pull` cannot fix this. Run:
@@ -219,7 +233,7 @@ The first decentralized intelligence communication and governance layer built di
**Privacy primitive runway (NEW in v0.9.7):**
* **Function Keys — Anonymous Citizenship Proof** — A citizen proves "I am an Infonet citizen" without revealing their Infonet identity. 5 of 6 pieces shipped: nullifiers, challenge-response, two-phase commit receipts, enumerated denial codes, batched settlement. Issuance via blind signatures waits on a primitive decision (RSA blind sigs vs BBS+ vs U-Prove vs Idemix).
* **Function Keys — Anonymous Credential Scaffolding** — The plumbing is in place for nullifiers, challenge-response, two-phase commit receipts, enumerated denial codes, and batched settlement. Today's challenge-response is an HMAC-based placeholder for integration testing, not a production anonymous or zero-knowledge citizenship proof. True unlinkable issuance still waits on a primitive decision (RSA blind sigs vs BBS+ vs U-Prove vs Idemix).
* **Locked Protocol Contracts** — Stable interfaces in `services/infonet/privacy/contracts.py` for ring signatures, stealth addresses, Pedersen commitments, range proofs, and DEX matching. The `privacy-core` Rust crate is the integration target — no caller of the privacy module needs to know which scheme is active.
* **Sprint 11+ Path** — When the cryptographic scheme is chosen, primitives wire into the locked Protocols without API churn.
+2 -2
View File
@@ -7,7 +7,7 @@
},
{
"name": "BBC",
"url": "http://feeds.bbci.co.uk/news/world/rss.xml",
"url": "https://feeds.bbci.co.uk/news/world/rss.xml",
"weight": 3
},
{
@@ -47,7 +47,7 @@
},
{
"name": "Xinhua",
"url": "http://www.news.cn/english/rss/worldrss.xml",
"url": "https://www.news.cn/english/rss/worldrss.xml",
"weight": 2
},
{
+5
View File
@@ -167,6 +167,11 @@ def cmd_hash(args: argparse.Namespace) -> int:
print("")
print("Updater pin:")
print(f"MESH_UPDATE_SHA256={digest}")
print("")
print("Release checklist:")
print(" - add this digest to SHA256SUMS.txt for the GitHub release")
print(" - add/update backend/data/release_digests.json for bundled updater verification")
print(" - keep MESH_UPDATE_SHA256 available as the operator override path")
return 0 if asset_matches else 2
@@ -1,14 +1,20 @@
"""Function Keys — anonymous citizenship proof.
"""Function Keys — anonymous credential scaffolding.
Source of truth: ``infonet-economy/IMPLEMENTATION_PLAN.md`` §4.4,
``infonet-economy/BRAINDUMP.md`` §11 item 9.
A citizen should be able to prove "I am a UBI-eligible Infonet
citizen" to a real-world operator (food bank, community service)
**without revealing their Infonet identity**. The naive approach
(scramble a public key, record each redemption on chain) leaks
identity through metadata correlation (time, location, operator,
frequency).
A citizen should eventually be able to prove "I am a UBI-eligible
Infonet citizen" to a real-world operator (food bank, community
service) **without revealing their Infonet identity**. The current
Python implementation wires the accounting, nullifier, receipt, and
operator flows, but its HMAC challenge-response is a placeholder for
integration tests. It is not a production anonymous or zero-knowledge
citizenship proof until blind signatures or anonymous credentials are
selected and wired.
The naive approach (scramble a public key, record each redemption on
chain) leaks identity through metadata correlation (time, location,
operator, frequency).
The full design has six pieces; five are implemented in pure Python
here. The remaining piece — issuance via blind signatures or
@@ -27,7 +33,8 @@ Pieces:
operator: tracked via ``NullifierTracker``.
3. **Challenge-response** (`challenge_response.py`) — operator
issues a fresh nonce, key-holder signs with the Function Key's
secret. Prevents screenshot attacks, key sharing, replay.
secret. This is HMAC placeholder plumbing for screenshot/replay
resistance, not the final anonymous credential proof.
4. **Two-phase commit receipts** (`receipt.py`) — Phase 1
verification receipt (operator-signed, day-level date NOT
timestamp, no node_id). Phase 2 fulfillment receipt (citizen
+4 -2
View File
@@ -12,6 +12,8 @@ logger = logging.getLogger(__name__)
CONFIG_PATH = Path(__file__).parent.parent / "config" / "news_feeds.json"
MAX_FEEDS = 50
_FEED_URL_REPLACEMENTS = {
"http://feeds.bbci.co.uk/news/world/rss.xml": "https://feeds.bbci.co.uk/news/world/rss.xml",
"http://www.news.cn/english/rss/worldrss.xml": "https://www.news.cn/english/rss/worldrss.xml",
"https://www.channelnewsasia.com/rssfeed/8395986": "https://www.channelnewsasia.com/api/v1/rss-outbound-feed?_format=xml",
}
_DEAD_FEED_URLS = {
@@ -27,7 +29,7 @@ _DEAD_FEED_URLS = {
DEFAULT_FEEDS = [
{"name": "NPR", "url": "https://feeds.npr.org/1004/rss.xml", "weight": 4},
{"name": "BBC", "url": "http://feeds.bbci.co.uk/news/world/rss.xml", "weight": 3},
{"name": "BBC", "url": "https://feeds.bbci.co.uk/news/world/rss.xml", "weight": 3},
{"name": "AlJazeera", "url": "https://www.aljazeera.com/xml/rss/all.xml", "weight": 2},
{"name": "NYT", "url": "https://rss.nytimes.com/services/xml/rss/nyt/World.xml", "weight": 1},
{"name": "GDACS", "url": "https://www.gdacs.org/xml/rss.xml", "weight": 5},
@@ -35,7 +37,7 @@ DEFAULT_FEEDS = [
{"name": "Bellingcat", "url": "https://www.bellingcat.com/feed/", "weight": 4},
{"name": "Guardian", "url": "https://www.theguardian.com/world/rss", "weight": 3},
{"name": "TASS", "url": "https://tass.com/rss/v2.xml", "weight": 2},
{"name": "Xinhua", "url": "http://www.news.cn/english/rss/worldrss.xml", "weight": 2},
{"name": "Xinhua", "url": "https://www.news.cn/english/rss/worldrss.xml", "weight": 2},
{"name": "CNA", "url": "https://www.channelnewsasia.com/api/v1/rss-outbound-feed?_format=xml", "weight": 3},
{"name": "Mercopress", "url": "https://en.mercopress.com/rss/", "weight": 3},
{"name": "SCMP", "url": "https://www.scmp.com/rss/91/feed", "weight": 4},
+12 -1
View File
@@ -5,7 +5,7 @@ from pathlib import Path
import pytest
from services.fetchers.news import _resolve_coords
from services.news_feed_config import DEFAULT_FEEDS
from services.news_feed_config import DEFAULT_FEEDS, _normalise_feeds
CONFIG_PATH = Path(__file__).parent.parent / "config" / "news_feeds.json"
@@ -152,3 +152,14 @@ class TestFeedConfig:
urls = {f["url"] for f in DEFAULT_FEEDS}
assert "https://www.reutersagency.com/feed/?best-topics=world" not in urls
assert "https://rsshub.app/apnews/topics/world-news" not in urls
def test_legacy_http_feeds_are_migrated_to_https(self):
feeds = _normalise_feeds(
[
{"name": "BBC", "url": "http://feeds.bbci.co.uk/news/world/rss.xml", "weight": 3},
{"name": "Xinhua", "url": "http://www.news.cn/english/rss/worldrss.xml", "weight": 2},
]
)
urls = {f["url"] for f in feeds}
assert "https://feeds.bbci.co.uk/news/world/rss.xml" in urls
assert "https://www.news.cn/english/rss/worldrss.xml" in urls
@@ -147,18 +147,18 @@ describe('middleware matcher exclusions', () => {
});
// ---------------------------------------------------------------------------
// 5. Google Fonts domains are preserved in CSP
// 5. Runtime Google Fonts domains are not required in CSP
// ---------------------------------------------------------------------------
describe('Google Fonts domains in CSP', () => {
it('style-src includes https://fonts.googleapis.com', () => {
describe('local font CSP', () => {
it('style-src does not allow https://fonts.googleapis.com', () => {
const csp = getCsp();
expect(csp).toContain('https://fonts.googleapis.com');
expect(csp).not.toContain('https://fonts.googleapis.com');
});
it('font-src includes https://fonts.gstatic.com', () => {
it('font-src does not allow https://fonts.gstatic.com', () => {
const csp = getCsp();
expect(csp).toContain('https://fonts.gstatic.com');
expect(csp).not.toContain('https://fonts.gstatic.com');
});
});
@@ -178,9 +178,9 @@ describe('production CSP directive completeness', () => {
expect(csp).not.toMatch(/script-src [^;]*'nonce-/);
});
it('has style-src with unsafe-inline and fonts.googleapis.com', () => {
it('has style-src with hydration-compatible inline styles only', () => {
expect(csp).toMatch(/style-src [^;]*'unsafe-inline'/);
expect(csp).toMatch(/style-src [^;]*https:\/\/fonts\.googleapis\.com/);
expect(csp).not.toMatch(/style-src [^;]*https:\/\/fonts\.googleapis\.com/);
});
it('has worker-src self blob:', () => {
@@ -130,16 +130,17 @@ describe('unchanged directives in production', () => {
vi.unstubAllEnvs();
});
it('style-src preserves unsafe-inline and Google Fonts', () => {
it('style-src preserves unsafe-inline without runtime Google Fonts', () => {
const styleSrc = getDirective('style-src');
expect(styleSrc).toContain("'unsafe-inline'");
expect(styleSrc).toContain('https://fonts.googleapis.com');
expect(styleSrc).not.toContain('https://fonts.googleapis.com');
});
it('font-src preserves data: and fonts.gstatic.com', () => {
it('font-src preserves self and data without runtime Google Fonts', () => {
const fontSrc = getDirective('font-src');
expect(fontSrc).toContain("'self'");
expect(fontSrc).toContain('data:');
expect(fontSrc).toContain('https://fonts.gstatic.com');
expect(fontSrc).not.toContain('https://fonts.gstatic.com');
});
it('worker-src self blob:', () => {
+3 -3
View File
@@ -55,7 +55,7 @@
body {
background: var(--background);
color: var(--foreground);
font-family: 'JetBrains Mono', var(--font-roboto-mono), 'Roboto Mono', monospace;
font-family: var(--font-jetbrains-mono), var(--font-roboto-mono), 'Roboto Mono', monospace;
}
/* Global interactive cursor hints */
@@ -139,7 +139,7 @@ textarea:disabled {
padding: 12px 16px;
color: #d1d5db;
font-family:
'JetBrains Mono', var(--font-roboto-mono), 'Roboto Mono', monospace, 'Microsoft YaHei', 'PingFang SC',
var(--font-jetbrains-mono), var(--font-roboto-mono), 'Roboto Mono', monospace, 'Microsoft YaHei', 'PingFang SC',
'Noto Sans SC', 'Noto Sans JP', 'Noto Sans KR', sans-serif;
font-size: 13px;
min-width: 240px;
@@ -377,7 +377,7 @@ textarea:disabled {
/* ── INFONET CRT TERMINAL EFFECTS ── */
.infonet-font {
font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
font-family: var(--font-jetbrains-mono), ui-monospace, SFMono-Regular, monospace;
}
/* CRT scanline overlay — scoped to .crt containers only */
+9 -6
View File
@@ -1,9 +1,17 @@
import type { Metadata } from 'next';
import { JetBrains_Mono } from 'next/font/google';
import DesktopBridgeBootstrap from '@/components/DesktopBridgeBootstrap';
import { ThemeProvider } from '@/lib/ThemeContext';
import { I18nProvider } from '@/i18n';
import './globals.css';
const jetBrainsMono = JetBrains_Mono({
subsets: ['latin'],
weight: ['400', '700'],
display: 'swap',
variable: '--font-jetbrains-mono',
});
export const metadata: Metadata = {
title: 'WORLDVIEW // ORBITAL TRACKING',
description: 'Advanced Geopolitical Risk Dashboard',
@@ -22,12 +30,7 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet" />
</head>
<body className="antialiased bg-[var(--bg-primary)]" suppressHydrationWarning>
<body className={`${jetBrainsMono.variable} antialiased bg-[var(--bg-primary)]`} suppressHydrationWarning>
<I18nProvider>
<ThemeProvider>
<DesktopBridgeBootstrap />
@@ -38,19 +38,17 @@ export default function FunctionKeyView({ onBack }: FunctionKeyViewProps) {
<ChevronLeft size={14} className="mr-1" /> BACK
</button>
<div className="text-sm text-purple-400 font-bold uppercase tracking-widest flex items-center gap-2">
<KeyRound size={16} /> FUNCTION KEYS Anonymous Citizenship Proof
<KeyRound size={16} /> FUNCTION KEYS Credential Scaffolding
</div>
<div />
</div>
<div className="flex-1 overflow-y-auto pr-3 space-y-4">
<div className="text-xs text-gray-400 leading-relaxed">
A citizen proves &quot;I am an Infonet citizen&quot; to a real-world
operator <span className="text-purple-400">without revealing their Infonet identity</span>.
The naive approach (scramble a public key, record each redemption on chain) leaks
identity through metadata correlation. The Function Keys design is six pieces;
five are implemented; one (issuance via blind signatures / anonymous credentials)
waits on a cryptographic primitive decision.
Function Keys wire the nullifier, receipt, and settlement plumbing for future
anonymous credential proofs. The current challenge-response is an HMAC placeholder,
not production zero-knowledge citizenship. True unlinkable issuance still waits on
blind signatures or anonymous credentials.
</div>
{status && (
+1 -1
View File
@@ -387,7 +387,7 @@ export function ThreatMarkers({
borderRadius: '4px',
padding: '8px 20px 8px 12px',
color: riskColor,
fontFamily: "'JetBrains Mono', monospace",
fontFamily: 'var(--font-jetbrains-mono), monospace',
fontSize: '12px',
fontWeight: 'bold',
textAlign: 'center',
+2 -2
View File
@@ -18,12 +18,12 @@ function buildCsp(nonce: string, strictScripts = false): string {
const directives = [
"default-src 'self'",
scriptSrc,
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob: https:",
isDev
? "connect-src 'self' ws: wss: http://127.0.0.1:8000 http://127.0.0.1:8787 https:"
: "connect-src 'self' ws: wss: https:",
"font-src 'self' data: https://fonts.gstatic.com",
"font-src 'self' data:",
"object-src 'none'",
"worker-src 'self' blob:",
"child-src 'self' blob:",