External Security Posture Management (ESPM) — CVE monitoring for external assets.
ShadowRadar tracks your external infrastructure (web apps, firewalls, SaaS services, third-party APIs) against the NIST NVD CVE database and optionally enriches each finding with an AI-powered risk assessment via the Claude API. Results are stored locally in SQLite and surfaced through a clean web dashboard.
Dashboard — KPI summary, severity distribution, CVEs per asset, monthly trend, and assessment breakdown.
Assets — Full asset inventory with version tracking, scan status, and CVE count per asset.
CVE Results — Filterable CVE list with severity badges, CVSS scores, AI assessment column, and per-CVE detail panel.
| Dependency | Version |
|---|---|
| Node.js | 20+ |
No database server is needed — ShadowRadar uses an embedded SQLite file.
Optional: a NIST NVD API key (increases rate limits from 5 to 50 requests/30 s) and an Anthropic API key for AI-assisted CVE assessments.
Scan script: CVE scanning requires an external script. See CVE Scan Script below.
# 1. Create a docker-compose.yml with the snippet from the Docker section below
# and fill in JWT_SECRET
# 2. Pull and start
docker compose pull
docker compose up -d
# 3. Create your first user
docker exec -it shadowradar node scripts/create-user.jsOpen http://localhost:3500 in your browser. See the Docker section for the full docker-compose.yml and all configuration options.
# 1. Clone and install Node dependencies
git clone https://github.com/Gadotti/ShadowRadar
cd shadowradar
npm install
# 2. Configure environment
cp .env.example .env
# Edit .env — set a strong JWT_SECRET (≥32 random chars)
# 3. Create the database and seed development users
npm run db:migrate
npm run db:seed # creates admin/admin123 and viewer/viewer123
# 4. Start the server
npm start # productionOpen http://localhost:3500 in your browser.
ignore-scripts = truein your npm config?better-sqlite3ships a native binding that is only fetched/compiled by its install script. With install scripts disabled, the binding is missing and the server and tests fail withCould not locate the bindings file. After everynpm install/npm ci(e.g. after merging dependency updates), run:npm rebuild better-sqlite3 --ignore-scripts=falseThis enables install scripts for that single package only. Stop any running ShadowRadar process first — on Windows, loaded
.nodefiles are locked and the reinstall fails withEPERM.
| Variable | Default | Description |
|---|---|---|
PORT |
3500 |
HTTP port |
NODE_ENV |
development |
development or production |
DB_PATH |
./data/shadowradar.db |
Path to the SQLite database file |
JWT_SECRET |
— | Required. Random string ≥ 32 characters |
LOG_LEVEL |
info |
debug, info, warn, error |
ENCRYPTION_KEY |
— | 64-character hex string (AES-256-GCM key). Required only when using the Direct key mode in AI config. Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" |
CVE scanning is handled by an external script — not bundled in this repository. The project check-cve-assets is being adapted to integrate with ShadowRadar as its scan engine.
Once integrated, scans will be triggerable directly from the Run Scan page in the UI, which will spawn the external script as a child process pointed at the ShadowRadar SQLite database.
ShadowRadar uses two independent authentication mechanisms:
Login at /#/login with a username and password. A signed JWT is stored in an httpOnly, SameSite=Strict cookie (30-day expiry). The login endpoint is rate-limited to 10 attempts per 15 minutes per IP.
Editors can generate API keys from the API Keys page. Keys are stored hashed in the database and passed via the X-API-Key request header. They grant access to the machine-to-machine endpoints:
| Endpoint | Method | Description |
|---|---|---|
/api/v1/export |
GET | Full security report (assets + CVEs + risk level) |
/api/v1/assets/sync |
POST | Upsert assets from an external system |
| Role | Permissions |
|---|---|
reader |
Read-only access to all pages and data |
editor |
Full CRUD, scan execution, config changes, API key management |
The seed command creates one account of each role (admin / viewer). New users can be created interactively:
npm run create-user- Asset inventory — Register external assets with name, tag, URL, version, and a CVE scan start date. Assets can be activated or deactivated individually.
- CVE scanning — An external scan script (see check-cve-assets) queries the NIST NVD API for CVEs matching each asset's software name and version. Results are stored with severity, CVSS score, and publication date.
- AI assessment — When an Anthropic API key is configured and AI is enabled, the scan script sends each new CVE to Claude for an automated risk assessment. CVEs that already have an AI assessment are never reprocessed (cost optimisation).
- Dashboard — At-a-glance KPIs (monitored assets, total CVEs, unassessed findings, items in mitigation) and four charts: severity distribution, CVEs per asset (top 10), monthly trend, and assessment distribution. All charts support filtering by asset, time period, and severity.
- CVE results view — Sortable and filterable table with full-text search, severity chips, AI badge, per-row assessment editor, and an expandable detail panel. Toggle between list and macro (card) view.
- NIST config — Set your NVD API key and page size from the UI.
- AI config — Enable/disable AI enrichment, configure the Anthropic API key (either via environment variable name or stored directly in the database encrypted with AES-256-GCM using
ENCRYPTION_KEY), and choose the Claude model. - API documentation — Interactive Swagger UI at
/api/docs(public, no login required). OpenAPI 3.0 spec available as JSON at/api/docs/spec. - External sync — Push asset lists from CI/CD pipelines or asset management tools via
POST /api/v1/assets/sync(API key required). Thecve_start_datefield is never overwritten once set. - Security export — Pull a full JSON security report with computed risk levels via
GET /api/v1/export.
ShadowRadar uses Jest with an in-memory SQLite database for isolated, repeatable tests.
# Run all tests
npm test
# Run a single test file
node --experimental-vm-modules node_modules/jest/bin/jest.js tests/unit/services/authService.test.jsIf most suites fail with Could not locate the bindings file after updating node_modules, see the better-sqlite3 rebuild note in Quick Start.
Located in tests/helpers/:
| Helper | Purpose |
|---|---|
makeDb() |
Spins up an in-memory SQLite DB with all migrations applied |
seedUsers() |
Inserts an admin and a viewer account |
seedAsset() |
Inserts a test asset |
seedCve() |
Inserts a test CVE result |
seedConfig() |
Inserts NIST / AI configuration entries |
buildTestApp(db) |
Mounts the full Express app against the in-memory DB for HTTP integration tests |
tests/
helpers/ # Shared DB and app factory helpers
unit/ # Per-module tests (services, repositories, middleware)
integration/ # Full HTTP round-trip tests via supertest
Unit tests instantiate services and repositories directly. Integration tests make real HTTP requests through the Express app, exercising the full middleware stack including authentication and validation.
Frontend (Vanilla JS / ES Modules, hash-based routing)
└── HTTP/REST + fetch
Backend (Node.js + Express)
├── SQLite via better-sqlite3 (WAL mode)
└── child_process.spawn → check-cve-assets (external, coming soon)
├── NIST NVD API
└── Claude API (optional)
No build step — the frontend is plain HTML/CSS/ES modules served statically by Express. Chart.js is bundled locally under public/vendor/.
src/
api/ # Express route handlers (thin controllers)
services/ # Business logic
repositories/ # SQLite queries
integrations/ # NIST and Claude API clients
middleware/ # Auth, validation, error handler
db/ # Migration runner and connection singleton
public/
js/
app.js # Hash-router entry point
api.js # Centralised fetch wrapper
pages/ # One module per page
components/ # Reusable UI pieces (sidebar, custom-select)
css/ # Tokenised stylesheet split by concern
scripts/
# CVE scan script — provided by check-cve-assets (external repo, integration pending)
ShadowRadar is published as a container image on the GitHub Container Registry. The image bundles Node.js and Python 3 in a single runtime — no separate containers needed.
- Docker 24+
- Docker Compose v2
Copy the snippet below into a docker-compose.yml on your server, fill in the required values, and start the stack:
services:
shadowradar:
image: ghcr.io/gadotti/shadowradar:latest
restart: unless-stopped
ports:
- "3500:3500"
volumes:
- shadowradar_data:/app/data
environment:
- NODE_ENV=production
- PORT=3500
- DB_PATH=/app/data/shadowradar.db
- JWT_SECRET=TROQUE_POR_STRING_ALEATORIA_DE_ALTA_ENTROPIA
- LOG_LEVEL=info
# Required only when using Direct key mode in AI config
# - ENCRYPTION_KEY=TROQUE_POR_STRING_HEX_64_CHARS
volumes:
shadowradar_data:
name: shadowradar_datadocker compose pull
docker compose up -dThe application will be available at http://localhost:3500. Database migrations run automatically on startup.
| Variable | Required | Description |
|---|---|---|
JWT_SECRET |
Yes | Random string ≥ 32 characters. Generate with: openssl rand -base64 32 |
DB_PATH |
Yes | Must be /app/data/shadowradar.db when using the volume as configured above |
PORT |
No | Defaults to 3500 |
NODE_ENV |
No | Set to production in container deployments |
LOG_LEVEL |
No | debug, info, warn, or error. Defaults to info |
ENCRYPTION_KEY |
No | 64-character hex string. Required only when storing the AI API key directly in the database. Generate with: openssl rand -hex 32 |
After the container is running, create your first user:
docker exec -it shadowradar node scripts/create-user.jsThe SQLite database is stored in a named Docker volume (shadowradar_data), mapped to /app/data inside the container. The volume survives container restarts and image updates — docker compose pull && docker compose up -d updates the app without touching the data.
The scan script runs inside the container. Trigger it manually or via a host cron job using docker exec:
# Manual trigger
docker exec shadowradar python /app/scripts/scan.py --db /app/data/shadowradar.db
# Host crontab — run every 6 hours
0 */6 * * * docker exec shadowradar python /app/scripts/scan.py --db /app/data/shadowradar.dbBack up the database volume to a compressed archive in the current directory:
docker run --rm \
-v shadowradar_data:/data \
-v $(pwd):/backup \
busybox \
tar czf /backup/shadowradar_backup_$(date +%Y%m%d_%H%M%S).tar.gz -C /data .To restore from a backup:
docker run --rm \
-v shadowradar_data:/data \
-v $(pwd):/backup \
busybox \
tar xzf /backup/shadowradar_backup_20260603_143000.tar.gz -C /dataThis project is licensed under the MIT License.