Security Findings Reference
Reference guide to every security finding Netwarden produces, how it is detected, what it means, and how to clear it.
Security Findings Reference
Netwarden continuously evaluates each monitored host for posture issues and emits a discrete security finding every time it observes something worth your attention. This page is the reference for what those findings are, how they are produced, how they route to alerts, and how they auto-resolve.
If you want the marketing-level overview of why this exists, see /features/security. If you want a hands-on tour of building alerts on top of these findings, see /blog/alerts-that-actually-page-you. This page is the precise reference: every finding type by its actual identifier, its trigger condition, and its default severity.
What security findings are
A finding is a discrete, deduplicated observation about a host. It has:
- A finding type — a stable string identifier, e.g.
ssh_root_login_enabledorcve_match. - A severity —
critical,high,medium,low, orinfo. - A title and description describing what was observed.
- A details payload with the supporting evidence (the offending sshd setting, the CVE ID, the listening port, etc).
- A fingerprint — a stable hash of the host plus the finding type plus the parts of the observation that make it "the same finding next time". Two snapshots with the same fingerprint update one row; bursts of identical observations do not duplicate.
- A status — open, acknowledged, or resolved.
Because every finding is tied to a stable fingerprint, your inbox does not fill with the same alert every minute. The same misconfigured sshd_config produces one finding that stays open until the configuration changes; the same vulnerable package produces one finding that stays open until the package is upgraded.
How findings are produced
Findings come from two places.
┌─────────────────────────────────────────────────────────────────┐
│ Agent on host │
│ snapshot collectors: │
│ ssh_config sshd_config + advertised crypto │
│ listening_ports ss/netstat output │
│ installed_packages dpkg / rpm │
│ failed_login_geo auth log + GeoIP enrichment │
└────────────────────────────┬────────────────────────────────────┘
│
▼ (agent uploads snapshots)
┌─────────────────────────────────────────────────────────────────┐
│ Platform: snapshot evaluators │
│ pure functions: snapshot in → findings out │
│ upsert by fingerprint │
│ auto-resolve fingerprints that stop appearing │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Platform: server-side scrapers (no agent involvement) │
│ CVE matcher (daily): join packages × Ubuntu/Debian/Red Hat │
│ TLS cert scraper (daily): pull certs for tracked endpoints │
│ both upsert into the same findings table │
└─────────────────────────────────────────────────────────────────┘
The agent owns the data closest to the host (config files, sockets, package versions, auth log). The platform owns the data closest to the world (CVE feeds, certificates). Both write to the same findings table using the same fingerprint convention.
Severity levels
Severity drives alert routing and dashboard prominence. The five levels are deliberately blunt — anything more granular would not change the action you take.
| Severity | Routing | What it means in practice | |---|---|---| | critical | Email + push + webhooks; surfaces at the top of the dashboard | Active exploitable issue. Treat as paging-worthy. | | high | Email + push + webhooks; prominent on the dashboard | Serious posture problem. Fix this week. | | medium | Email; appears in normal lists | Worth attention. Bundle into a maintenance window. | | low | Weekly digest only | Hygiene item. Bundle into the next quarterly review. | | info | Dashboard only, no notification | Diagnostic context, not actionable on its own. |
Per-tenant routing rules can downgrade or upgrade these defaults; see How alerts route below.
The 14 finding types
Fourteen finding types today, grouped into six families. Each entry uses the exact identifier the platform writes (so you can match on it in webhooks and queries) and lists the trigger, default severity, and resolution behavior.
SSH posture (5)
These come from the ssh_config snapshot. The agent parses sshd_config, applies Match blocks, and reports the effective settings.
ssh_root_login_enabled
- Trigger:
PermitRootLogin yesis in effect. - Default severity: high.
- Why it matters: Direct root login over SSH is a high-value target for credential stuffing and brute force, and it eliminates per-user attribution in audit logs. Disable in favour of a non-root user with sudo.
- Auto-resolves when: the next
ssh_configsnapshot reportsPermitRootLogin no(orprohibit-passwordif you only allow root with keys).
ssh_password_auth_enabled
- Trigger:
PasswordAuthentication yesis in effect. - Default severity: medium.
- Why it matters: Password-based SSH is the dominant vector for credential-stuffing attacks. Public-key authentication eliminates the entire class.
- Auto-resolves when: the next snapshot reports
PasswordAuthentication no.
ssh_protocol_v1_enabled
- Trigger: the
Protocoldirective includes1. - Default severity: critical.
- Why it matters: SSHv1 has known cryptographic weaknesses and was deprecated decades ago. Modern OpenSSH builds drop the option entirely; if you see this finding, you are running a legacy fork.
- Auto-resolves when: the
Protocoldirective is removed or set to2only.
ssh_empty_passwords_allowed
- Trigger:
PermitEmptyPasswords yesis in effect. - Default severity: critical.
- Why it matters: Any account on the host with a blank password is reachable directly over SSH. This combines catastrophically with
ssh_password_auth_enabled. - Auto-resolves when: the next snapshot reports
PermitEmptyPasswords no.
ssh_x11_forwarding_enabled
- Trigger:
X11Forwarding yesis in effect. - Default severity: low.
- Why it matters: X11 forwarding is rarely needed on servers and broadens the SSH attack surface. Disable unless you actively run GUI applications over SSH.
- Auto-resolves when: the next snapshot reports
X11Forwarding no.
SSH crypto (3)
These come from the same ssh_config snapshot but evaluate the algorithms sshd advertises. At most one finding fires per category — the platform aggregates the offenders into a single row whose fingerprint includes the sorted offender list, so the finding mutates if you remove one bad algorithm and add another.
ssh_weak_kex
- Trigger: any algorithm in
KexAlgorithmsmatches the hardcoded weak list (diffie-hellman-group1-sha1,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1) or ends in-sha1. - Default severity: high.
- Why it matters: SHA-1 KEX variants and small Diffie-Hellman primes are broken in the cryptographic sense — exposed to downgrade and offline collision attacks.
ssh_weak_cipher
- Trigger: any algorithm in
Ciphersmatches the weak list (CBC-mode AES, 3DES, Blowfish, CAST, all RC4 variants). - Default severity: high.
- Why it matters: CBC mode is exposed to BEAST/POODLE-class issues; RC4 is broken outright.
ssh_weak_mac
- Trigger: any algorithm in
MACsmatches the weak list (hmac-md5,hmac-md5-96,hmac-sha1-96,[email protected], anything containingmd5, orhmac-sha1). - Default severity: medium when an MD5 or 64-bit-truncated MAC is present; low when only
hmac-sha1is present (a deprecated-but-still-widely-deployed legacy nudge). - Why it matters: MD5 forgery is computationally cheap; 64-bit MACs have insufficient collision resistance.
Network exposure (2)
These come from the listening_ports snapshot. The agent enumerates listening sockets, their bind addresses, and the owning process.
public_management_port
- Trigger: a socket is bound to a non-loopback address (i.e.
0.0.0.0,::, or a specific public interface) on a port from the management list (SSH 22, Postgres 5432, MySQL 3306, MongoDB 27017, Redis 6379, Memcached 11211, Elasticsearch 9200, Docker 2375/2376, Kubernetes API 6443, Telnet 23, RDP 3389, VNC 5900, etc). - Default severity: high.
- Why it matters: Management surfaces should never be reachable from the public internet. Bind to localhost or restrict by firewall.
- Note: one finding fires per
(port, proto)pair so you can resolve them independently. PID and process name are intentionally excluded from the fingerprint, so a service restart does not duplicate the finding.
many_public_bindings
- Trigger: the host has more than five more public-bind sockets than loopback-bind sockets.
- Default severity: info.
- Why it matters: A high public-to-loopback ratio is unusual for a server and worth reviewing. The default is informational because legitimate cases exist (an edge host with a dozen exposed services); use it as a prompt to audit, not a page.
Vulnerabilities (1)
cve_match
- Trigger: the daily CVE matcher joins each host's
installed_packagessnapshot againstcve_affected_packagesand emits a finding when the installed version is older than the fixed version listed in an advisory. - Default severity: routed by CVSS v3, NVD's published rating ranges:
- 9.0 – 10.0 → critical
- 7.0 – 8.9 → high
- 4.0 – 6.9 → medium
- 0.1 – 3.9 → low
- When CVSS is unavailable, fall back to the upstream feed's published severity (Red Hat usually has one). When neither is available, fall back to medium rather than guessing.
- Sources:
- Ubuntu USN feed (
ubuntu.com/security/notices.json) - Debian DSA tracker
- Red Hat OVAL (RHEL 7/8/9 — also covers Rocky, Alma, CentOS Stream)
- NVD for CVSS enrichment
- Ubuntu USN feed (
- Auto-resolves when: the next package snapshot reports a fixed version and the matcher's re-run no longer emits the fingerprint.
- Note: one finding per
(advisory_id, package_name)per host. A single Ubuntu USN that fixes three packages produces three findings on a host that has all three installed.
Identity (2)
These come from the failed_login_geo snapshot. The agent tails the auth log for failed SSH attempts every 60 seconds, groups by source IP, and the platform enriches with country data via MaxMind GeoLite2.
failed_login_high_risk_country
- Trigger: at least 20 failed login attempts in the 60-second window from a country on the high-risk list (RU, CN, KP, IR, BY).
- Default severity: high.
- Why it matters: These countries disproportionately appear as the source of SSH brute-force and credential-stuffing campaigns in real-world honeypot data. The list is deliberately conservative — hosts with legitimate users in those regions will see false positives, which auto-resolve as soon as the burst ends.
- Fingerprint: one finding per
(host, country)— bursts from the same country dedupe.
failed_login_country_anomaly
- Trigger: at least 5 failed login attempts from a country the host has not seen in failed-login traffic in the prior 30 days, AND the country is not already covered by a high-risk-country finding.
- Default severity: medium.
- Why it matters: Any sustained traffic from a brand-new origin is worth a second look. New contractor, relocated employee, VPN egress change — or genuine adversarial reconnaissance. Confirm with your team before assuming the worst.
- Auto-resolves when: the next 60-second window does not reproduce the fingerprint.
Both identity findings require GEOLITE2_CITY_PATH to be set on the platform (see the Self-Hosting guide for setup). Without GeoIP enrichment, country fields are null and these findings cannot fire.
Certificates (1)
ssl_cert_expiring_soon
- Trigger: the daily TLS scraper observes a tracked endpoint whose certificate expires within a configurable window (default 30 days).
- Default severity:
mediumoutside 14 days,highinside 14,criticalinside 7. - Why it matters: Expired certificates break clients and trigger browser warnings.
- Auto-resolves when: the next scrape sees a renewed certificate whose
notAfteris back outside the window.
How alerts route
A finding being created and an alert being delivered are separate things. The notification gate considers severity, per-channel preferences (Settings → Notifications → Security alerts), maintenance / quiet-hour suppression, and recent acknowledgement state. A finding that was acknowledged in the last hour does not re-page; the dashboard still surfaces it.
For the high-volume operator's pattern — webhook everything to your own router and build escalation policy in PagerDuty / Opsgenie / a homegrown tool — see /blog/alerts-that-actually-page-you.
Auto-resolve behavior
Every snapshot type carries a coverage list of the finding types it produces. When an ingest cycle completes, the platform compares the fingerprints it just emitted to the open findings of those types for the same host; anything no longer present is closed with a system resolution flag.
For example: a host with PermitRootLogin yes for weeks gets fixed. The next ssh_config snapshot lands without that fingerprint, and the open ssh_root_login_enabled finding flips to resolved with the timestamp attached.
cve_match is intentionally excluded from this snapshot-driven path — CVE findings are produced by a separate cron and are resolved by the matcher's own re-emission pass.
Acknowledging vs resolving
- Acknowledge — "I see this, I'm working on it." Stays in the active list with an acknowledged badge; stops re-notifications. Use during incident triage.
- Resolve (manual) — "I have fixed this." Moves out of the active list immediately. The next snapshot re-opens it if the underlying condition is still present.
Most of the time, let the auto-resolver close findings — the close timestamp then matches the moment the underlying condition actually changed.
Configuring per-tenant alert routing
Routing lives in Settings → Notifications → Security alerts, which renders a severity-by-channel matrix:
| | Email | Push | Webhook | |---|---|---|---| | Critical | on | on | on | | High | on | on | optional | | Medium | digest | off | optional | | Low | digest | off | off | | Info | off | off | off |
Common adjustments: turn webhook on for medium and low if you want everything in your own incident system; turn email on for low if you want a real-time stream rather than a digest. Per-host overrides are not yet available — preferences are tenant-wide. Per-host routing is on the roadmap.
Time-to-resolution
The Security overview dashboard shows a TTR widget — the median hours between a finding being opened and resolved, computed over the last 30 days and faceted by severity. It is intentionally a single number rather than a chart: a posture-improvement metric for postmortems and quarterly reviews, not a real-time gauge.
Caveats: only resolved findings count (open ones do not pull the median down); auto-resolved and manually-resolved are pooled; findings resolved within 60 seconds of opening are excluded so transient bursts do not bias the median toward zero. Use TTR as a directional signal — a trend up over weeks usually means a fleet-wide issue is going unaddressed.
What's NOT covered
Be honest about scope:
- Not a full vulnerability scanner. We match installed packages against published advisories. We do not run authenticated config scans, do not parse application configs (nginx, Apache, web frameworks), and do not crawl your runtime for misconfigured permissions.
- Not an EDR. No process behaviour analytics, no kernel hooks, no malware detection, no file integrity monitoring beyond what you can build yourself with custom alerts on top of the metrics pipeline.
- Not a SIEM. No log search, no correlation across thousands of events, no compliance dashboards. Failed-login enrichment is the only "log signal" we ingest, and it is bounded to a narrow purpose.
- No GeoIP without your MaxMind file. The two identity findings depend on customer-supplied GeoLite2 data. Without it, country fields are null and those findings do not fire. See Self-Hosting → GeoIP setup for how to wire it up.
- No SUSE / Arch / Gentoo / Alpine CVE feeds. Ubuntu, Debian, and the Red Hat family are the only distributions covered today. The other thirteen finding types still work on those distributions; only
cve_matchis unavailable.
If something on this list is a hard requirement for your environment and you would like our roadmap perspective on when it might land, get in touch.