Skip to content

About

Multi-server SSH attack intel: central SOC dashboard + lightweight log-shipper agents over Tailscale. React UI, stdlib Python API, SSO login, public abusers feed.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

82 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

SSH Sentinel logo SSH Sentinel β€” multi-server SSH attack intel (SOC dashboard)

Central React dashboard + Rust API + lightweight log-shipper agents. Modelled on Beszel (hub/agent) and Dozzle (distributed logs): agents push over your tailnet, central aggregates. SQLite holds derived state. Logs stay source of truth.

Docker GHCR License Stack Backend crates.io agent ci docker release

agent (any server) --tailscale+bearer--> central (main server) --> UI

πŸ€– AI install (fastest path)

curl -fsSL https://raw.githubusercontent.com/authoritydmc/ssh-sentinel/master/scripts/ai-install.sh | bash

Then paste the prompt from AI.md into your AI. The AI clones, configures, deploys, and verifies. It obeys AGENTS.md.

✨ Features

Area What you get
Overview Failed attempts, attacker IPs, successful logins, peak hour, top origin, most-wanted user
Trends 48h failed-vs-accepted timeline, auth-outcome donut
Map World attack map (lat/lon pins) + top attacking regions
Attackers Top 25 user@IP pairs, flag, city/org/ASN, recon badge, click for full intel
Intel modal Geo, org/ISP, ASN, rDNS, RDAP net, first/last seen, users tried, 48h activity bars, full log chain, optional SpiderFoot recon (cached 7d)
Live events Tailable /var/log/auth.log (or fleet-merged), filter, pause, newest/oldest first, 15s refresh
Fleet N agents push over HTTPS+bearer; host filter (All + per-host), online dot (120s), per-host timelines
Self-noise filter Container/node egress + SELF_PUBLIC_IPS excluded from attacker stats (count shown separately)
Ops Single static image, healthcheck on /healthz, no DB, JSON files only

πŸ“Έ Gallery

All shots use the header mask toggle. It hides IPs, usernames, host names, and session lines on every page. Only attacker data shows: attacker IPs, geo, and tried users. The choice persists per browser.

View Shot
Overview, masked fleet metrics overview
World map plus top regions map
Attackers table, masked attackers
Attacker intel plus recon intel
Admin bans bans
Attacker intel, full detail attacker
Attackers plus masked logins logins
Intel loading state loading

🎯 Use cases

  1. Single VPS watchtower β€” see who is hammering your SSH right now, which users they want, which countries they come from.
  2. Fleet SOC β€” 5–50 servers behind Tailscale, one dashboard. Agents push outbound only; members open zero inbound ports.
  3. Post-incident review β€” click an IP β†’ full log chain + RDAP/rDNS + recon findings, export the story.
  4. Demo / UI work β€” synthetic 48h, 10-country log (--profile demo) without touching real auth logs.
  5. Homelab gateway β€” runs next to Traefik/Authentik; keep 8079 on tailnet, put SSO in front for remote access.

πŸ—οΈ Architecture

Full design (diagrams + procedures, simplified English): docs/ARCHITECTURE.md.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  tail + push (10s)   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  member-01 β”‚ ── Bearer:TOKEN ───▢ β”‚      central        β”‚
β”‚ /var/log/  β”‚  outbound only       β”‚  ssh-sentinel (Rust) β”‚
β”‚ auth.log   β”‚                      β”‚  + React dist       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                      β”‚  data/hosts/*.jsonl β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                      β”‚  data/agents.json   β”‚
β”‚  member-02 β”‚ ───────────────────▢ β”‚  :8079  /healthz    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                              β”‚ :8079 (tailnet only)
                                              β–Ό
                                    React 19 SOC dashboard
  • Backend (central-rs/): Rust ssh-sentinel binary, serves dist/ + JSON API, parses syslog + ISO timestamps, enriches via ip-api/RDAP/rDNS (cached), optional SpiderFoot recon.
  • Agent (agent-rs/): static Rust tailer, inode+offset resume across rotation/restarts, exponential backoff, PUSH_EVERY=10 default.
  • Frontend (frontend/): Vite + React 19 + Tailwind v4, code-split charts/map/modal, 60s auto-refresh, base path /ssh/.

πŸš€ Quickstart

Option A β€” prebuilt image (recommended)

# Docker Hub
docker run -d --name ssh-sentinel --restart unless-stopped \
  -p 8079:8079 \
  -e HOST_ID=central \
  -v sentinel_data:/srv/data \
  -v /var/log/auth.log:/var/log/auth.log:ro \
  rajlabs/ssh-sentinel:latest

# or GHCR
docker run -d --name ssh-sentinel --restart unless-stopped \
  -p 8079:8079 \
  -e HOST_ID=central \
  -v sentinel_data:/srv/data \
  -v /var/log/auth.log:/var/log/auth.log:ro \
  ghcr.io/authoritydmc/ssh-sentinel:latest

# open http://localhost:8079  (or http://<tailnet-name>:8079 in fleet)
curl -s localhost:8079/healthz  # -> ok

Option B β€” compose (single server)

git clone https://github.com/authoritydmc/ssh-sentinel.git && cd ssh-sentinel
cp .env.example .env   # set HOST_ID + SELF_PUBLIC_IPS + AUTH_* login
docker compose up -d --build
# open http://localhost:8079 (image default AUTH_MODE=local is fail-closed:
# set AUTH_USER + AUTH_PASS_HASH in .env, or AUTH_MODE=none on a private net)

Mint a local-login hash (never commit the password or hash):

docker exec -it ssh-sentinel /srv/ssh-sentinel genhash
# -> AUTH_PASS_HASH=pbkdf2-sha256$200000$...   (paste into .env, compose up -d)

Option C β€” native binary (no Docker)

# From source (needs a Rust toolchain):
cargo install --git https://github.com/authoritydmc/ssh-sentinel ssh-sentinel
cargo install --git https://github.com/authoritydmc/ssh-sentinel ssh-sentinel-agent
# From crates.io once published: cargo install ssh-sentinel

# Or fetch a release binary (Linux, Windows, macOS assets on every tag):
# https://github.com/authoritydmc/ssh-sentinel/releases
AUTH_LOG=/var/log/auth.log DATA_DIR=./data ./ssh-sentinel

Behind Authentik + Traefik instead (same ForwardAuth pattern as Dozzle):

```yaml
# central labels (illustrative) β€” Authentik decides who gets in,
# central trusts the passed identity in AUTH_MODE=forward/oidc
labels:
  - "traefik.http.routers.ssh.middlewares=authentik@docker"
  - "traefik.http.middlewares.authentik.forwardauth.address=http://authentik:9000/outpost.goauthentik.io/auth/traefik"
  - "traefik.http.middlewares.authentik.forwardauth.trustForwardHeader=true"
  - "traefik.http.middlewares.authentik.forwardauth.authResponseHeaders=X-Forwarded-User,X-Forwarded-Email"
# central env for that setup
AUTH_MODE=forward
AUTH_ALLOWED_USERS=alice,bob@example.com   # optional allowlist
# headers are only honored from AUTH_TRUSTED_PROXIES (spoof-safe by default)

Built-in SSO: no proxy needed (AUTH_MODE=oidc)

Central speaks OIDC itself (authorization-code flow, RS256, server sessions). In Authentik, create a Provider (type OAuth2/OpenID, confidential client) plus an Application, and set:

  • Redirect URIs: https://<your-host>/oidc/callback
  • Signing key: any RSA key (RS256 is required)
  • Scopes: openid email profile
# central env
AUTH_MODE=oidc
OIDC_ISSUER=https://auth.example.com/application/o/ssh-sentinel/
OIDC_CLIENT_ID=<from authentik>
OIDC_CLIENT_SECRET=<from authentik>
OIDC_REDIRECT_URL=https://<your-host>/oidc/callback
AUTH_ALLOWED_USERS=alice@example.com   # optional allowlist

Open the UI β†’ it redirects to Authentik β†’ back with a session cookie (HttpOnly, 12h default via OIDC_SESSION_TTL). /oidc/logout ends it. OIDC_COOKIE_SECURE=1 when central serves HTTPS directly.

Or copy-paste ready: examples/sso-traefik-authentik.yml

SSO_HOST=ssh.example.com AUTH_ALLOWED_USERS=alice@example.com \
  docker compose -f docker-compose.yml -f examples/sso-traefik-authentik.yml up -d --build

Other SSO front doors that work with AUTH_MODE=forward (no code changes):

Provider How
Authelia + Traefik/Nginx ForwardAuth authResponseHeaders: Remote-User, Remote-Email β€” trusted Remote-User header is already accepted
Cloudflare Access + cloudflared Access policy on the hostname; central accepts Cf-Access-Authenticated-User-Email (cloudflared talks to loopback, inside default trusted proxies)
Tailscale Serve keep AUTH_MODE=none on tailnet-only :8079 (identity = tailnet), or put Authentik in front as above

πŸ” Recon providers (attacker enrichment)

Click any IP β†’ Recon auto-enriches via RECON_PROVIDER:

  • spiderfoot (default): needs a reachable SPIDERFOOT_URL; tune with RECON_MODULES. Unreachable backend β†’ clean error state, never blocks the UI. Start the bundled backend with docker compose --profile recon up -d --build. Central then uses http://spiderfoot:5001 automatically. The SpiderFoot UI binds loopback only (SPIDERFOOT_PORT). SpiderFoot has no login, so keep it private.
  • webhook: plug any probing service β€” POST {"ip": "1.2.3.4"} to RECON_WEBHOOK_URL (optional RECON_WEBHOOK_TOKEN bearer), return {"findings": [{"type": "ASN", "data": "AS…", "module": "my-source"}]}. Accepts eventType/finding/value/info and source/provider aliases. Results cached 7d like SpiderFoot scans.
  • none: recon section reports disabled (no external calls at all).

Option C β€” demo (any machine, 2 min, no real logs)

docker compose --profile demo up --build
# open http://localhost:8081  (sample fleet log, 10 countries, 48h)

The sample log is committed and frozen (seeded, deterministic).

🌐 Fleet: central + join N servers (Tailscale recommended)

On central: run the central service as above, then mint a join token:

docker exec ssh-sentinel /srv/ssh-sentinel gentoken web-01
# -> host=web-01, token=..., central=http://<this-host>:8079

On the joining server (needs tailscale status so it can reach central by tailnet name) β€” pick one role runner:

# Option 1 β€” container agent (same image, ROLE=agent, no systemd needed)
ROLE=agent AGENT_ID=web-01 CENTRAL_URL=https://<central-tailnet-name>:8079 \
AGENT_TOKEN=<token> docker compose --profile agent up -d --build

# Option 2 β€” host systemd agent (Linux, fetches the static binary)
git clone https://github.com/authoritydmc/ssh-sentinel.git && cd ssh-sentinel
CENTRAL_URL=http://<central-tailnet-name>:8079 AGENT_TOKEN=<token> sudo -E ./agent/install.sh
# or without clone: set AGENT_ID explicitly
CENTRAL_URL=http://central:8079 AGENT_TOKEN=<token> AGENT_ID=web-01 sudo -E ./agent/install.sh

# Option 3 β€” native binary (macOS, Windows, or any Linux)
# Download the matching asset from GitHub Releases and run it directly.
# Linux asset names use x86_64/aarch64, macOS uses the same, Windows adds .exe.

Verify on central:

```bash
curl -s localhost:8079/api/hosts | jq
curl -s 'localhost:8079/api/summary?host=web-01' | head -c 500

The UI gains a host filter (All hosts + per-host pills with online dot) once agents report.

Rotate a host: gentoken <host> again on central, update /etc/ssh-sentinel-agent/env on the member, systemctl restart ssh-sentinel-agent.

πŸ–₯️ UI tour

View Route state What to do
Overview sidebar β†’ Overview 6 metric cards, SSH activity trends (failed red / accepted green), auth donut, world map, top regions, top-8 attackers
Attackers sidebar β†’ Attackers full top-25 table, filter by IP/user/country, click row β†’ intel modal
Intel modal click any IP location, org/ASN, rDNS, RDAP, hits + first/last + users tried, 48h bars, Show full log chain, Recon (auto-start, cached 7d, re-run button)
Live events sidebar β†’ Live events filter (e.g. Accepted or an IP), pause/resume, order toggle, 15s refresh, severity badges (attack/auth/system)
Host pills header all hosts vs per-host, hover shows last-seen, green = online (<120s)

The UI is the React build in dist/ (ships in Docker). Without dist/, unknown routes return 404.

πŸ”Œ API reference

Same origin, no auth for reads (keep behind tailnet/SSO). Agent push requires bearer.

Method Path Params Notes
GET /healthz β€” ok (Docker healthcheck, always open)
GET /api/health β€” open liveness snapshot: status, uptime, version, mode, hosts, log/data checks (for Uptime Kuma etc.)
GET /oidc/login, /oidc/callback, /oidc/logout β€” built-in SSO flow in oidc mode (302 redirects + session cookie)
GET /api/auth β€” {mode, login, user, safe} β€” lock badge source, always open
GET /api/version β€” build info: version, short commit, full changelog text. Powers the footer About dialog. Always open, no secrets
GET /api/summary ?host=all|<id> total, ips, top[25], timeline[48h], logins[60], excluded_self, hosts (login required unless AUTH_MODE=none)
GET /api/hosts β€” [{id, local, last_seen, online, lines}] (login required unless none)
GET /api/tail ?q=&n=200&host= plain-text log slice, n clamped 10–2000, case-insensitive substring (login required unless none)
GET /api/ipinfo ?ip=&host= geo + rdap + rDNS + history{users,hits,first,last,timeline} (login required unless none)
GET /api/abusers ?host=&page=&per_page= public-safe attacker feed: ip, hits, first/last, attempted users, geo/org/ASN, risk + band + reasons. Repeat offenders only (β‰₯ABUSERS_MIN_HITS fails, score β‰₯ABUSERS_MIN_SCORE, no successful login, never whitelisted/private). Paginated (≀200/page), cached 60s, rate-limited (ABUSERS_RPM, 429 + Retry-After). Never exposes accepted logins, hostnames, internal/self IPs, or raw lines. Behind the login gate unless ABUSERS_PUBLIC=1 (or AUTH_MODE=none)
GET /abusers β€” public leaderboard page (same safe data, no login) β€” works only with ABUSERS_PUBLIC=1, else 404
GET /api/self β€” open self-check: your IP, list status, risk, whitelist state. The UI shows a red banner when your IP is listed ("ask the admin to whitelist you")
GET /api/admin/status β€” open setup status: setup_needed, ban/report flags. No secrets
GET /api/admin/config β€” enforcement values plus env lock plus source (login required)
POST /api/admin/config {ban_*, report_*} save enforcement overrides to DB. Env set locks a field. No restart (login required)
GET /api/admin/bans β€” active bans: ip, jail, reason, source, created, expiry, firewall state (login required)
GET /api/banlist β€” plain-text banned IPs, one per line (login required; same host can read banlist.txt directly)
GET /api/admin/activity ?limit= latest admin actions: bans, unbans, reports, setups (login required)
GET /api/admin/reports β€” abuse-report history per IP and provider (login required)
POST /api/admin/ban {"ip","reason"} ban an IP (fail2ban + DB + banlist). 400 for bad/private/whitelisted IPs
POST /api/admin/unban {"ip"} remove a ban
POST /api/admin/setup {"token","user","password"} first-setup only: creates the admin login. 410 after setup closes
POST /api/admin/password {"old","new"} change the file-based admin password (local mode only)
POST /api/admin/report {"ip","hits","risk","band"} report one IP now (throttled per provider)
POST /api/recon ?ip=&force= SpiderFoot scan orchestration; cached|started|running|done|error (poll). 400 for private IPs / unreachable SpiderFoot (login required unless none)
POST /api/agent/push Authorization: Bearer <token> + {"host","lines":[]} max 5000 lines/req, 2000 chars/line, capped at 60k lines/host

Examples:

curl -s 'http://localhost:8079/api/summary?host=all' | jq | head -n 40
curl -s 'http://localhost:8079/api/tail?q=Accepted&n=5'
curl -s 'http://localhost:8079/api/ipinfo?ip=77.91.71.90' | jq | head -n 40
curl -X POST 'http://localhost:8079/api/recon?ip=77.91.71.90'

βš™οΈ Configuration

Var Default Where Purpose
HOST_ID hostname central Local host id in fleet view
PORT / DEMO_PORT 8079 / 8081 compose Host port mapping
SELF_PUBLIC_IPS `` central Comma-separated own public IPs to exclude from attacker stats (NAT hairpin)
AUTH_LOG /var/log/auth.log central Live log path inside container (/srv/demo/auth.log.sample in demo)
DATA_DIR /srv/data central Hosts + agents.json volume
CENTRAL_URL http://central:8079 agent Central base URL (use tailnet name in fleet)
AGENT_TOKEN `` (required) agent Bearer from gentoken <host>
AGENT_ID hostname agent Must match the gentoken host id
PUSH_EVERY 10 agent Push interval seconds
SPIDERFOOT_URL http://spiderfoot:5001 central Optional recon backend; recon endpoints error gracefully if unreachable
RECON_MODULES sfp_dnsresolve,sfp_whois,sfp_ipapico,sfp_abusech central SpiderFoot module list
AUTH_MODE local central local (Basic login, fail-closed) | forward (Authentik+Traefik ForwardAuth via SSO headers) | oidc (built-in SSO code flow) | none (open β€” private tailnet/demo only)
AUTH_USER admin central Local-login username
AUTH_PASS_HASH `` central pbkdf2-sha256$… from docker exec ssh-sentinel /srv/ssh-sentinel genhash (preferred over AUTH_PASSWORD)
AUTH_PASSWORD `` central Plaintext fallback (never logged); prefer the hash
AUTH_ALLOWED_USERS `` central Optional allowlist for forward mode, e.g. alice,bob@example.com
AUTH_TRUSTED_PROXIES loopback + RFC1918 + Tailscale CGNAT central CIDRs allowed to present SSO identity headers (spoof-safe ForwardAuth)
RECON_PROVIDER spiderfoot central spiderfoot (needs SPIDERFOOT_URL) | webhook (POST {ip} to RECON_WEBHOOK_URL, returns {findings:[{type,data,module}]}) | none (recon disabled)
RECON_WEBHOOK_URL / RECON_WEBHOOK_TOKEN `` central Your intel hook (n8n, custom API…) + optional Bearer
ABUSERS_RPM 60 central /api/abusers per-client-IP requests/minute (429 past budget)
ALERT_WEBHOOK_URL / ALERT_WEBHOOK_TOKEN `` central Login + spike alerts (login.suspicious, spike.bruteforce); falls back to abuse webhook; test via POST /api/alerts/test
ALERT_ON_SUCCESS / ALERT_SPIKE_THRESHOLD / ALERT_SPIKE_WINDOW_S / ALERT_DEDUPE_S 1 / 20 / 300 / 3600 central Alert tuning: suspicious logins on, spike bar, window seconds, resend delay
WHITELIST_IPS / TRUSTED_IPS / TRUSTED_USERS `` central UI editable in Admin Settings. Whitelist never lists or bans. Trusted tunes suspicious logins.
BAN_ENABLED / BAN_AUTO / BAN_* off central UI editable in Admin Settings. Env set locks the field. See docs/FAIL2BAN.md.
REPORT_* / ABUSERS_MIN_* / ABUSERS_PUBLIC off central UI editable in Admin Settings. Env set locks the field.

n8n alert flow (2 min)

  1. n8n β†’ Import examples/n8n-ssh-alerts.json, activate, copy the test URL.
  2. Set ALERT_WEBHOOK_URL to that URL (Coolify env or .env).
  3. Replace the Notify (edit me) node with Slack/Telegram/email.
  4. curl -X POST <central>/api/alerts/test β†’ execution appears in n8n. | TLS_CERT / TLS_KEY | `` | central | Container paths to PEM cert/key β€” enables in-repo TLS 1.3-only listener (else terminate at Tailscale/Traefik) |

Files:

Path What
frontend/ Vite + React 19 + Tailwind v4 SOC dashboard (code-split)
central-rs/ Rust API + static dist serving + agent push + host store
agent-rs/ static Rust shipper (systemd via install.sh, or agent-rs/Dockerfile)
agent-rs/ Rust shipper pilot, same protocol, static image (see agent-rs/README.md)
demo/ sample log + generator for UI work without real attacks
Dockerfile node build + cargo build β†’ debian-slim runtime (no Python)
.github/workflows/ docker.yml (GHCR + Docker Hub publish), ci.yml (frontend lint/build + cargo test)

πŸ”’ Security

Read SECURITY.md before exposing anything.

  • Login is fail-closed by default (AUTH_MODE=local): UI + read APIs need HTTP Basic (AUTH_USER + AUTH_PASS_HASH/AUTH_PASSWORD); /healthz and /api/auth stay open. No credential configured β†’ deny-all with a setup hint.
  • AUTH_MODE=forward/oidc trusts SSO identity headers (X-Forwarded-User/Email, Authentik X-authentik-username/X-authentik-email, Authelia Remote-User, Cloudflare Access email) from Authentik-via-Traefik ForwardAuth (Dozzle-oidc pattern) only when the connection comes from AUTH_TRUSTED_PROXIES (spoof-safe); optional AUTH_ALLOWED_USERS allowlist; else 401/403.
  • AUTH_MODE=none is the explicit open flag for private tailnet/demo only.
  • Agents push outbound only (no inbound ports on members).
  • Bearer per-host tokens in data/agents.json (0600); Tailscale gives WireGuard identity + encryption. Optional in-repo TLS 1.3-only listener via TLS_CERT/TLS_KEY; agent CENTRAL_URL=https://… already verifies with system roots.
  • Never expose 8079 publicly without SSO in front (Tailscale Serve / Cloudflare Access / Authelia).
  • /api/abusers (+ /abusers page) is safe-fields-only by construction (no accepted logins, hostnames, internal/self IPs, or raw lines) and lists repeat offenders only: β‰₯5 fails, risk-scored (attempts, user breadth, recency, optional AbuseIPDB confidence), auto-excluded on any successful login. Stays behind the login gate unless ABUSERS_PUBLIC=1 β€” still rate-limited (ABUSERS_RPM, 429 + Retry-After).
  • WHITELIST_IPS removes owner/admin IPs from every attacker list (abusers, top table, map). /api/self tells each visitor their own list status.
  • .env and data/ are git-ignored; only .env.example ships. Tokens are runtime-minted via secrets.token_urlsafe(32).
  • Demo log is 100% synthetic (seeded sample) β€” safe to share screenshots.

πŸ› οΈ Development

Agent-land rules (docs style, checks, hooks): AGENTS.md.

scripts/install-hooks.sh                  # once per clone: commit-msg + pre-commit + pre-push gates
# frontend
cd frontend && npm ci && npm run dev      # vite dev
npm run build                              # tsc + vite -> dist/ (served by ssh-sentinel)
npm run lint                               # oxlint

# backend (no deps)
cargo run --manifest-path central-rs/Cargo.toml  # serves :8079
AUTH_LOG=demo/auth.log.sample cargo run --manifest-path central-rs/Cargo.toml

# agent
CENTRAL_URL=http://localhost:8079 AGENT_TOKEN=dummy cargo run --manifest-path agent-rs/Cargo.toml

# full stack
docker compose up --build
docker compose --profile demo up --build

When the API answers 401, the UI shows a sign-in panel. It explains the active mode. In oidc mode it shows a Sign in with SSO button.

Footprint is tracked in CI (image size plus RSS plus CPU per run). Rust plans live in docs/RUST_MIGRATION.md.

Images publish on every master push + tags via GitHub Actions:

  • ghcr.io/authoritydmc/ssh-sentinel:latest (+ :prod-<commit>, semver on tags)
  • rajlabs/ssh-sentinel:latest (same tags, requires DOCKER_HUB_USERNAME + DOCKER_HUB_ACCESS_TOKEN secrets)

Tags are always semver (vX.Y.Z via scripts/release.sh). No date tags. Native binaries (ssh-sentinel, ssh-sentinel-agent for Linux, Windows, macOS) attach to every GitHub Release automatically.

πŸ“ Changelog

See CHANGELOG.md. License: MIT.

About

Multi-server SSH attack intel: central SOC dashboard + lightweight log-shipper agents over Tailscale. React UI, stdlib Python API, SSO login, public abusers feed.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages