Vulnerability Management Platform for Security Teams
Track. Prioritize. Remediate. — across every asset on your network, with the audit trail your auditors expect.
Stability disclaimer. SentinelCore is in beta. Core flows are stable on a single-host deployment with a dedicated PostgreSQL instance. Multi-tenant, HA, and containerized deployments are not yet supported. Use the issue tracker for production blockers.
The 1.0.1 release line ships a full Network Topology restyle plus a chain of operational improvements pulled from real deployment friction:
- Hex/orb device nodes with per-device-type colour and silhouette icons, link endpoint dots, subnet chips, and HUD overlays — built off the SentinelCore Design System in
docs/NetworkTopology_restyle/. - Slide-in device preview panel on icon click — the Overview content surfaces without a full navigation, with an "Apri dettaglio completo" button to go deeper.
- Inline pencil-per-card editing on the System Configuration tab (location, owner, criticality, model, OS, …) — no separate management page.
- Live scan progress banner with elapsed time, devices found, and current target — replacing the "did it start? did it die?" guesswork.
- Single source of truth for vuln counts via
assets.network_device_id— the network discovery pipeline and the scanner import pipeline now agree on the same device row. - Network Ports tab with aggregated open ports, detected services, and per-port vuln linkage.
- Configurable nmap discovery in Settings:
scan_type, timing template, port list, custom args — preview is a live nmap command string before save. - File logging system with retention worker (7d / 500MB rotation, gzip in place), level and destination configurable from Settings.
sudo NOPASSWDnmap wrapper so the discovery worker can do-sV -O -sU -p-scans without running the whole service as root.- CSRF middleware self-heal so restarts don't 403 every in-flight browser session.
SentinelCore is built around a single loop:
discover → import → score → assign → remediate → report
Each step has a real handler, a real table, and a real UI — not a dashboard tile masquerading as a feature.
- Built-in
nmap-based network discovery (configurable scan profile + scheduler). - Topology graph with device nodes, link endpoints, and subnet grouping.
- 10 third-party scanner importers: Qualys, Nessus, Burp Suite, OpenVAS/GVM, Nexpose / InsightVM, OWASP ZAP, Nmap, Nikto, Grype, Trivy.
- Composite risk score combining CVSS base, EPSS probability, business impact, asset exposure, and exploit availability.
- Severity tiers map directly onto SLA windows (Critical 1d, High 7d, Medium 30d, Low 90d) with 75% / 90% / 100% breach signals.
- Override channel for zero-days, ransomware-tied CVEs, and entries in CISA KEV.
- Comment threads on vulnerabilities and remediation plans, with
@mentionnotifications. - Multi-channel notification routing: email, Slack, Telegram, Microsoft Teams, generic webhook, PagerDuty, OpsGenie.
- Throttling and quiet-hours to keep the channel signal-to-noise sane.
- Bi-directional JIRA sync (status, priority, custom field mapping).
- NVD enrichment worker for CVE metadata.
- EPSS daily score refresh.
- JWT auth with
httpOnlycookies + CSRF token on state-changing requests. - RBAC:
admin,team_leader,user. - TOTP-based 2FA, session tracking with remote revocation.
- Full audit log for compliance reporting (PCI-DSS, ISO 27001, SOC2, HIPAA tags).
┌──────────────────────────┐ ┌──────────────────────────────────┐
│ React 18 + MUI 5 │ │ Axum 0.6 (Rust) │
│ │ HTTPS │ │
│ TanStack Query, axios │ ──────► │ sqlx 0.8 (compile-time queries) │
│ Cytoscape (topology) │ cookie │ Tower middleware (CSRF, RBAC) │
│ ECharts, Recharts │ + CSRF │ JWT (httpOnly cookie) │
└──────────────────────────┘ └────────────┬─────────────────────┘
│
▼
┌──────────────────────────┐
│ PostgreSQL 15+ │
│ 117 migrations │
│ JSONB, triggers, GIN │
└──────────────────────────┘
▲
│
┌────────────┴─────────────┐
│ Background workers │
│ ─────────────────────────│
│ auto_rescan │
│ device_metrics_snapshot │
│ epss_updater │
│ jira_sync │
│ log_retention │
│ notification_digest │
│ nvd_api │
│ report_generator │
│ sla_checker │
└──────────────────────────┘
Backend (vulnerability-manager/)
Rust 1.75+, Axum 0.6, sqlx 0.8 (offline cache committed in .sqlx/), tokio 1, jsonwebtoken 10, argon2, tracing + tracing-appender for file logs.
Frontend (vulnerability-manager-frontend/)
React 18, TypeScript 4.9, MUI 5, TanStack Query 5, Cytoscape (network topology), ECharts + Recharts (analytics), Framer Motion, three.js. Built with Create React App (react-scripts).
Database
PostgreSQL 15+, 117 migrations under vulnerability-manager/migrations/. Heavy use of JSONB, triggers (e.g. network_devices.assigned_at), and GIN indexes (e.g. team/user skills).
These steps assume a fresh Debian 12 host with sudo and a non-root deploy user.
sudo apt update && sudo apt install -y \
git curl build-essential pkg-config \
libssl-dev libpq-dev \
postgresql-15 nginx nmapcurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
. "$HOME/.cargo/env"
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejssudo -u postgres psql -c "CREATE USER sentinelcore WITH PASSWORD 'change-me';"
sudo -u postgres psql -c "CREATE DATABASE sentinelcore_db OWNER sentinelcore;"git clone https://github.com/Dognet-Technologies/sentinelcore.git
cd sentinelcore/vulnerability-manager
cat > .env <<'EOF'
DATABASE_URL=postgresql://sentinelcore:change-me@localhost/sentinelcore_db
SERVER_HOST=0.0.0.0
SERVER_PORT=8080
JWT_SECRET=replace-this-with-a-long-random-string
JWT_EXPIRATION=3600
CORS_ALLOWED_ORIGINS=http://localhost:3000
LOGGING_LEVEL=info
LOGGING_DESTINATION=/var/log/sentinelsuite/sentinelcore/app.log
EOF
cargo install sqlx-cli --no-default-features --features native-tls,postgres
sqlx migrate run
SQLX_OFFLINE=true cargo build --releasecd ../vulnerability-manager-frontend
npm install
npm run build # production bundle ends up in build/The discovery worker calls nmap with raw socket and OS-detection flags. Two options:
-
sudo NOPASSWD(default) — drop a sudoers fragment so the service can callnmapandarp-scanwithout a password:# /etc/sudoers.d/sentinelcore-discovery sentinelcore ALL=(root) NOPASSWD: /usr/bin/nmap, /usr/sbin/arp-scanReplace
sentinelcorewith whichever user runs the systemd service. -
File capabilities —
sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/nmap. This works on some kernels and silently fails on hardened ones; sudo is the boring reliable path.
server {
listen 80;
server_name your.domain;
root /opt/sentinelcore/vulnerability-manager-frontend/build;
index index.html;
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
try_files $uri /index.html;
}
}For TLS use certbot --nginx -d your.domain.
Open http://<host>/, log in with the seeded admin (printed once by the migration), and immediately:
- Change the admin password.
- Enable 2FA (Settings → Security).
- Tighten
CORS_ALLOWED_ORIGINS. - Configure notification channels.
- Set the discovery
subnetand schedule in Settings → Network Discovery.
All runtime configuration lives in vulnerability-manager/.env. The keys we touch most:
| Key | Default | Purpose |
|---|---|---|
DATABASE_URL |
— | PostgreSQL connection string. Required. |
SERVER_HOST / SERVER_PORT |
0.0.0.0 / 8080 |
Listen address. |
JWT_SECRET |
— | HMAC secret for tokens. Required, long random string. |
JWT_EXPIRATION |
3600 |
Token lifetime in seconds. |
CORS_ALLOWED_ORIGINS |
— | Comma-separated origins. |
RATE_LIMIT_ENABLED |
true |
Per-IP token bucket. |
LOGGING_LEVEL |
warn |
error / warn / info / debug. |
LOGGING_DESTINATION |
/var/log/sentinelsuite/sentinelcore/app.log |
Log file path; journal for systemd journal. |
LOG_RETENTION_DAYS |
90 |
Files older than this are pruned by the log_retention worker. |
Discovery and rescan parameters (subnet, schedule, scan type, timing, port list, custom args) are stored in the auto_rescan_settings table and edited from Settings → Network Discovery in the UI.
sentinelcore/
├── vulnerability-manager/ # Rust backend (Axum 0.6 + sqlx 0.8)
│ ├── src/
│ │ ├── api/ # Route definitions (186+ endpoints)
│ │ ├── handlers/ # Request handlers
│ │ ├── network/ # Discovery scanner + topology
│ │ ├── scanners/ # 10 third-party importers
│ │ ├── workers/ # 9 background tokio workers
│ │ ├── notifications/ # Email/Slack/Telegram/Teams/...
│ │ └── middleware/ # CSRF, RBAC, rate limit
│ ├── migrations/ # 117 SQL migrations
│ ├── .sqlx/ # Committed compile-time query cache
│ └── plugins/ # First-party plugin examples
│
├── vulnerability-manager-frontend/ # React 18 + MUI 5 + TanStack Query
│ ├── src/
│ │ ├── api/ # Shared axios client + per-domain APIs
│ │ ├── components/ # 43 components (incl. NetworkTopology)
│ │ ├── pages/ # 27 pages
│ │ ├── contexts/ # Auth + global state
│ │ └── hooks/
│ └── public/
│
├── docs/
│ └── NetworkTopology_restyle/ # Design system + UI kit for the topology page
│
├── scripts/ # Deployment + maintenance scripts
└── packer/ # VM image build (optional)
Pre-compiled binaries are distributed in the release package. No Rust toolchain is needed on the server.
Release package contents:
vulnerability-manager # backend binary (x86_64 Linux)
migrations/ # SQL migration files (56 files)
vulnerability-manager-frontend/build/ # React production build
- Debian 12/13 or Ubuntu 22.04+ (x86_64)
- 2+ CPU cores, 4 GB+ RAM
- PostgreSQL 15+, Nginx
nmap,arp-scanpackages
sudo apt update
sudo apt install -y postgresql nginx nmap arp-scan libssl3sudo systemctl start postgresql
sudo systemctl enable postgresql
sudo -u postgres psql -c "CREATE USER vlnman WITH PASSWORD 'your-strong-password';"
sudo -u postgres psql -c "CREATE DATABASE vulnerability_manager OWNER vlnman;"Important: The backend connects via TCP (
127.0.0.1:5432), not via Unix socket. Always use127.0.0.1(notlocalhost) in the connection URL — usinglocalhostcan cause libpq to fall back to the Unix socket, triggering peer authentication failure even though the pg_hba.conf allows scram-sha-256 for TCP connections.
Migrations are not applied automatically at startup. Run them manually before the first start, and again after any update that adds new migration files.
# Copy migrations to the server first, then:
for f in $(ls /opt/sentinelcore/migrations/*.sql | sort); do
PGPASSWORD=your-password psql -h 127.0.0.1 -U vlnman -d vulnerability_manager -f "$f"
doneThe migrations directory contains 56 SQL files numbered sequentially. They must be applied in alphabetical/numerical order.
sudo mkdir -p /opt/sentinelcore/{migrations,uploads}
sudo chown -R $USER:$USER /opt/sentinelcore
# Backend binary
cp vulnerability-manager /opt/sentinelcore/
chmod +x /opt/sentinelcore/vulnerability-manager
# Migrations (keep for future upgrades)
cp -r migrations/ /opt/sentinelcore/Create /opt/sentinelcore/.env:
# Database — use 127.0.0.1, not localhost (see section 2)
VULN_DATABASE_URL=postgresql://vlnman:your-password@127.0.0.1:5432/vulnerability_manager
# JWT secret — generate a strong random key (minimum 32 characters)
JWT_SECRET_KEY=replace-with-a-long-random-secret-key
# Logging
RUST_LOG=info,vulnerability_manager=infoConfig system note: The default configuration is embedded in the binary at compile time via
include_str!. Runtime overrides require theVULN_prefix. The separator is_, soVULN_DATABASE_URLmaps todatabase.url,VULN_SERVER_PORTmaps toserver.port, and so on.Alternatively, place a
config/development.yaml(orconfig/production.yamlwithAPP_ENV=production) in the working directory/opt/sentinelcore/. This file is loaded at runtime and merged on top of compiled-in defaults.
Create /etc/systemd/system/sentinelcore.service:
[Unit]
Description=SentinelCore Vulnerability Manager Backend
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=simple
User=your-user
WorkingDirectory=/opt/sentinelcore
EnvironmentFile=/opt/sentinelcore/.env
ExecStart=/opt/sentinelcore/vulnerability-manager
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=sentinelcore
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable sentinelcore
sudo systemctl start sentinelcore
# Verify
sudo systemctl status sentinelcore
sudo journalctl -u sentinelcore -fDeploy the React build and install the site config:
# Copy frontend static files
sudo cp -r build/* /usr/share/nginx/html/Create /etc/nginx/sites-available/sentinelcore:
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
index index.html;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript;
# Cache static assets (filenames are content-hashed by the build tool)
location ~* \.(js|css|woff2?|ttf|eot|svg|ico|png|jpg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Proxy API requests to the backend
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Proxy file uploads served by the backend
location /uploads/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# SPA fallback — all unmatched routes return index.html
location / {
try_files $uri $uri/ /index.html;
}
location ~ /\. {
deny all;
}
}sudo ln -sf /etc/nginx/sites-available/sentinelcore /etc/nginx/sites-enabled/sentinelcore
sudo rm -f /etc/nginx/sites-enabled/default # remove default nginx page
sudo nginx -t
sudo systemctl reload nginxWhy this nginx layout matters: The frontend must be served as static files by nginx, with only
/api/and/uploads/proxied to the backend. Proxying everything through the backend would bypass the React router (SPA fallback) and break client-side navigation.
git clone https://github.com/Dognet-Technologies/sentinelcore.git
cd sentinelcore
# Backend
cd vulnerability-manager
cp .env.example .env # then edit DATABASE_URL etc.
sqlx migrate run
cargo run
# Frontend (new terminal)
cd ../vulnerability-manager-frontend
npm install
npm start # dev server on :3000, proxies /api to :8080Quality gates:
# Rust
cargo fmt
cargo clippy --all-features -- -D warnings
cargo test
cargo audit
# TypeScript
cd vulnerability-manager-frontend
npm run format
npm test
npm auditsqlx offline cache
sqlx::query! and sqlx::query_as! macros are checked at compile time against the database. CI compiles with SQLX_OFFLINE=true against .sqlx/. After changing or adding a query:
cd vulnerability-manager
cargo sqlx prepare # connects to $DATABASE_URL, regenerates .sqlx/
git add .sqlxFile logging is on by default at /var/log/sentinelsuite/sentinelcore/app.log. The log_retention worker rotates by size (500 MB) or age (7 days) using copy-truncate so the open file descriptor stays valid, then gzips the previous file. Files older than LOG_RETENTION_DAYS are pruned.
Live tail:
sudo tail -f /var/log/sentinelsuite/sentinelcore/app.log
# or via journal
sudo journalctl -u sentinelcore -fsudo -u postgres pg_dump sentinelcore_db | gzip > "backup_$(date +%Y%m%d).sql.gz"Restore:
gunzip -c backup_YYYYMMDD.sql.gz | sudo -u postgres psql sentinelcore_dbcd /opt/sentinelcore
git pull origin main
cd vulnerability-manager
sqlx migrate run
SQLX_OFFLINE=true cargo build --release
cd ../vulnerability-manager-frontend
npm install
npm run build
sudo systemctl restart sentinelcore| Layer | What we do |
|---|---|
| Transport | TLS terminated at Nginx + HSTS header (max-age=31536000; includeSubDomains; preload). |
| Auth | JWT in httpOnly cookie + CSRF token on POST/PUT/DELETE/PATCH. |
| Passwords | Argon2id. |
| 2FA | TOTP (RFC 6238). |
| CSP | Strict default-src 'self'; configurable per deployment. |
| Rate limiting | Per-IP token bucket; per-user is on the roadmap. |
| SSRF / path injection | All file paths canonicalized and verified to stay under their canonical base. Outbound HTTP targets validated by host. |
| Audit log | Every state-changing action writes an audit entry tied to the user. |
If you spot something, please don't open a public issue. Email security@dognet.tech instead.
Shipped in 1.0.1
- Network Topology restyle on the SentinelCore Design System.
- Configurable nmap discovery with live scan progress.
- File logging + retention worker.
- Single source of truth for device ↔ vulnerability linkage.
- Inline pencil-per-card editing on the device detail page.
- CSRF middleware self-heal across restarts.
In flight on develop/v1.0.1
- Phase C: skill-based auto-assignment with "add skill" alert in the assignment dialog.
- Container scanning (Trivy / Grype) — importers already in tree, wiring up next.
Considered, not committed
- Per-user rate limiting.
- Multi-tenancy.
- Plugin SDK + first-class skill matching.
- Out-of-the-box k8s/Helm deployment.
| Role | Can | Cannot |
|---|---|---|
| admin | Everything below + user/team management, system settings, CORS, rate limits, JIRA, notifications, audit log, manual SLA override. | — |
| team_leader | Manage team members, assign vulns to team, create reports, comment, change status. | Modify system settings. |
| user | View assigned vulns, change their status, comment, view dashboard, export. | Assign vulns, manage teams. |
Pull requests and issues are welcome. Read CONTRIBUTING.md before sending a PR — it covers branch naming, commit format (<type>: <subject>), and the dev/release line policy.
Two things that will save us all time:
- Branch off
develop/v1.0.1, notmain.mainonly takes curated merges. - If you touch a sqlx query, commit the regenerated
.sqlx/cache in the same PR.
MIT — see LICENSE.
Built by Dognet Technologies · dognet.tech
If SentinelCore helps your team sleep at night, a GitHub star is the quietest way to say thanks.