mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-06-03 21:08:13 +02:00
Address remaining safe security hardening
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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:', () => {
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 "I am an Infonet citizen" 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 && (
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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:",
|
||||
|
||||
Reference in New Issue
Block a user