InfraGuard

Security Diagnostics

Run rootkit and hardening scans across many servers at once, score each host A–F, and auto-promote critical failures to Problem Resolution cases.

Overview — what it does

Security Diagnostics runs a rootkit-trace scan plus a system-hardening scan across your registered servers, scores each result as a 0–100 score with an A–F grade, and shows your whole fleet's security posture on one screen. Every check carries a what/why/how-to-fix explanation, and any finding that is a failure whose severity is critical (fail + crit) is auto-promoted to a Problem Resolution case (deduplicated, and auto-resolved once the check later passes).

Core pieces:

Running the probe as root enables privileged checks such as /etc/shadow, sudoers, and sshd -T. When not root, privileged checks are downgraded to skip and excluded from the score denominator (no penalty).

When to use

Layout

Area Contents
KPI strip Fleet average score · scanned host count · open critical cases · total failing checks · last-scan time
Grade distribution Histogram of A/B/C/D/F host counts
Category scores Rollup of the 9 categories (fleet average, catalogue order, tone color)
Score trend Last 60 scans in chronological order
Hosts by grade Host card grid — sorted lowest (riskiest) score first. Each card shows grade, pass/warn/fail/crit counts, intake source, scan time. Click → report modal
Report modal Overall score/grade + summary (OS/kernel) + per-check detail (what/why/fix/evidence) + promoted-case link

Top toolbar: [Run scan] (multi-server remote run) · [Paste result] · Refresh · Auto-refresh toggle · Fullscreen.

How to use

Four intake tracks

infr_sec_scan.source classifies how a scan was received:

source Entry point Method
emulate [Run scan] modal Select instances → deploy & run the probe remotely. A paste / remote-emulate apply that carries an instance_id also records as emulate
paste [Paste result] modal Paste probe JSON produced directly on the target, with no instance binding
api POST /api/v1/security/scan Each host's cron runs the probe → pushes via an API key (the reporter track)
manual Fallback Default when the source value is not valid

1) Multi-server remote run — async polling

A synchronous full run would exceed PHP's execution time across many hosts, so the flow is register → fire-and-forget worker → poll.

  1. [Run scan] → tick the target instances (each shows reachable / unreachable).
  2. POST /security/diagnostics/run → creates a progress record and returns run_id immediately (Cache, TTL 30 min).
  3. The front end calls POST /security/diagnostics/run-worker/{runId} without awaiting → the worker runs all hosts sequentially under set_time_limit(0) and ignore_user_abort (one host failing does not stop the rest).
  4. The front end polls GET /security/diagnostics/run-status/{runId} to update progress (done/total/ok).

An instance marked unreachable does not match a run_report collector node, so it cannot be run remotely. Run the probe directly on the host and submit it via [Paste result]. See Remote Emulate for the remote-run wiring.

2) Paste / JSON ingest

Run the probe on the target host and paste its JSON.

# On the target host (recommended: root)
node infr_seccheck_probe.cjs --min

--min emits single-line JSON. Output shape:

{
  "host": "web-01",
  "generated_at": "2026-08-13T03:30:00Z",
  "os": "Rocky Linux 9.3",
  "kernel": "5.14.0-362.el9",
  "root": true,
  "checks": [
    { "key": "ssh_root_login", "category": "ssh", "status": "fail", "severity": "crit", "detail": "PermitRootLogin yes", "evidence": "/etc/ssh/sshd_config:32" },
    { "key": "svc_firewall",   "category": "services", "status": "pass", "severity": "crit", "detail": "firewalld active" }
  ],
  "summary": {}
}

It is received by POST /security/diagnostics/ingest. Binding an instance records source=emulate; leaving it unbound records source=paste.

3) API reporter (automation)

Each host's cron runs the probe and pushes the result into InfraGuard. Issue the API key in Administration ▸ API Keys; the key must carry the scan.write scope.

30 3 * * *  node /opt/infr/infr_seccheck_probe.cjs --min | curl -sS -X POST https://infraguard.example.com/api/v1/security/scan -H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" --data @-

Response (201):

{ "ok": true, "scan_id": 812, "host": "web-01", "score": 74, "grade": "C" }

Scoring — SecurityCheckbook

The score is a weighted ratio of each check's weight and status.

Status factor (STATUS_FACTOR) — only these count; skip/info are excluded from the denominator:

Status Factor
pass 1.0
warn 0.6
fail 0.0
skip · info excluded

Severity multiplier (SEV_MUL) — the heavier the implication of a failure, the more it moves the score:

fail_severity Multiplier
crit 1.6
warn 1.0
info 0.5

Per-check weight w = check_weight(1–3) × severity_multiplier; overall score = round(100 × Σ(w × status_factor) / Σw). Category scores use the same formula per category.

Grade & tone:

Score Grade Tone
≥ 90 A good
≥ 80 B good
≥ 70 C warn
≥ 60 D warn
< 60 F crit

(Grades are A/B/C/D/F; tone is good ≥ 80 · warn ≥ 60 · crit < 60.)

9 categories · 36 checks

Each category has a display weight and icon; actual scoring uses the per-check weight × severity multiplier above.

Category Label Checks Notable checks (implication on failure)
rootkit Rootkit / malware 6 rk_known_files·rk_ld_preload·rk_hidden_proc (crit)
patch Patch / updates 3 pt_security_updates·pt_eol_os (crit)
ssh SSH configuration 5 ssh_root_login·ssh_empty_pass (crit)
accounts Accounts / privileges 5 acc_uid0·acc_empty_pass (crit)
network Network exposure 3 net_legacy_services·net_db_exposed (crit)
filesystem Filesystem permissions 5 fs_shadow_perm (crit)·fs_world_writable·fs_suid (warn)
hardening System hardening 5 hd_selinux·hd_aslr·hd_syncookies (warn)
services Services / firewall 2 svc_firewall (crit)·svc_unnecessary (info)
logging Audit / logging 2 log_auditd·log_syslog (warn)

Unknown keys are absorbed into the other category. Each check's "what / why / fix" text renders in the report modal from seccheck.check.*.

Critical findings → Problem Resolution promotion

On every ingest, reconcileCases() reconciles the host's cases.

Promoted checks appear in the report modal with a "promoted to resolution" badge and a [View case] link. Cases are worked on the Security ▸ Problem Resolution board.

Deleting a scan (POST /security/diagnostics/delete) does not delete its promoted cases — they are independent.

Data sources

Source Role
infr_sec_scan (000766) Scan results (one per host). score·grade·pass/warn/fail/crit/total_count·category_scores·summary·findings JSON. Indexes (host,scanned_at)·(instance_id,scanned_at)·grade·score
infr_instance Target instances (resolves host → instance_id by alias/hostname)
infr_incident / infr_incident_event Promoted critical cases and their timeline
infr_api_key API reporter auth (scope scan.write)
Cache Dashboard aggregate cached 20 s (infr:sec:diag:build) · async run progress record (TTL 1800 s)
infr_seccheck_probe.cjs The source probe (local, read-only)

Access

For the level model in general (infr.level:N middleware), see RBAC levels.

Troubleshooting & FAQ

Q. An instance shows "unreachable" in [Run scan]. It doesn't match a run_report collector node, so there is no remote-run wiring. Run the probe on the host directly and submit via [Paste result].

Q. "No checks found" error. The pasted JSON has no checks (or items) array, or entries lack a key. Paste the output of infr_seccheck_probe.cjs --min verbatim.

Q. The score is low even though the fail count is small. That's the severity multiplier. One crit failure weighs 1.6× a warn. Use the category scores and report modal to see which finding is pulling the score down.

Q. All privileged checks are skip. The probe ran as a non-root account. Checks like /etc/shadow, sudoers, sshd -T need root. skip is excluded from the denominator (no penalty), but run as root (or via the gateway) for a full diagnosis.

Q. A run takes a while — can I close the browser? Yes. The worker runs fire-and-forget and continues under ignore_user_abort even if the request aborts. Reopen and poll run-status, or just read the grades once they appear on the dashboard. The progress record is kept for 30 minutes.

Q. A case disappeared on its own. If a later scan no longer reports that finding as fail+crit (fixed or not detected), the engine auto-resolves the case. That is expected.

Related

Security diagnostics — check results and grade
Security diagnostics — check results and grade