InfraGuard

Detections + JA3 Sequence Rulesets

The flagship detection screen — narrates the detection engines (Kitsune, srct, nginx, JA3) and derives anomaly rulesets from JA3 sequences with a deterministic heuristic (infr_sec_ruleset).

Overview — what it does

Security ▸ Detections (/security/detections) is InfraGuard's detection flagship. It brings three things together on one screen:

  1. Detection-algorithm narrative — educational popovers explaining how each engine catches threats (Kitsune ML · srct heuristics · nginx is_suspicious · JA3).
  2. Post-detection narrative — surfaces the anomaly IPs · suspicious web hits · run history each engine caught in the recent window, from real data.
  3. ★ JA3 sequence rulesets — a deterministic heuristic reads the chronological JA3 fingerprint sequence of a single source IP and enumerates its "continuous anomalous behavior" as professional rulesets stored in infr_sec_ruleset.

Collection and auto-blocking already run in the backend (the old stats-design UI only showed firewall/blacklist tables) — this screen's purpose is to surface that hidden telemetry as a trustworthy narrative.

This screen is read-mostly + on-demand analysis. The detectors themselves (kts_tx · srct_tx · eve_ingest) are Node collectors running under pm2 on target hosts; the app only reads the tables they fill. The app never sniffs traffic itself.

The LLM narration path was removed on 2026-09-04. Analysis used to send the sequence summary to an AI adapter and store its freeform answer. Three measured problems ended it: the LLM's conclusion ("JA3 rotation") was what a three-line ratio already produced, at roughly 9,100 tokens per run; freeform prose froze the writer's language, so English sessions read Korean cards; and the writer stamped provider/model from the adapter even on the fallback path, so 10 of 13 stored rows claimed model=kimi-k2.6 while being template output. Analysis is now heuristic-only — no external call, no token cost — and provider/model record what actually ran.


When to use


Layout

KPI strip

KPI Meaning Source
AI anomalies Kitsune anomaly count in the selected window kitsune_detail_log count
AI blocked Of those, is_blocked=1 (threshold + veto met) kitsune_detail_log where is_blocked
Suspicious web nginx is_suspicious=1 requests in window nginx_meta_ct count
Rulesets Currently active (status=active) rulesets infr_sec_ruleset

The first two KPIs say "AI" because Kitsune really is a machine-learning autoencoder — that label is accurate and stays.

Detection-algorithm panel (edu popovers)

Each engine card opens an educational popover with principle · what it watches · tips. The copy lives in the static security.det.edu.* lang pack.

pm2 deployment guide

Provides the command + source code to deploy the 5 detection collectors as pm2 crons on a target host (see Data sources · pm2 table).

Detection log & run history

JA3 offenders & rulesets


How to use

1) Scan the detection state

The top range toggle (1h / 6h / 24h) changes the observation window (default 24h). KPIs and detection logs recompute for that window. The page polls (~30s) and the service caches for 30s.

The offender list alone is always the last 6 hours, regardless of range. JA3 rotation accumulates over several hours, so a short window won't reveal the pattern (aligned with Auto-block's 360-minute JA3 window).

2) Run JA3 sequence analysis (★)

  1. Pick an IP from the offender list (or a deep-link from Overview/Threats) and click [Analyze].
  2. The server pulls that IP's TLS JA3 sequence from suricata_tls_ct in chronological order (window = ops-policy sec_ai_window_min, default 60 min · up to 200 rows).
  3. If the sequence length is below the minimum (sec_ai_min_seq, default 5), it is skipped with insufficient_sequence.
  4. The heuristic classifies the sequence into findings (see the rule table below).
  5. The result is saved as rulesets and the list + Overview funnel refresh immediately.

Example response:

{
  "ok": true,
  "provider": "heuristic",
  "model": null,
  "saved": 1,
  "seq_len": 37,
  "findings": [
    { "key": "ja3_rotation",
      "params": { "conns": 37, "variety": 34 },
      "category": "tls_anomaly",
      "severity": "crit", "action": "block", "confidence": 91 }
  ]
}

Analysis is deterministic and free — the same sequence always yields the same rulesets, it never contacts an external service, and it works with no network access. Running it costs nothing, so re-analyze freely whenever an offender's numbers change.

Heuristic rules

Findings are built from sequence statistics (rotation = ja3_variety / seq_len).

Condition Ruleset Category Severity Action Confidence
variety ≥ 3 AND rotation ≥ 0.5 JA3 rotation (automated scanner/botnet) tls_anomaly rotation≥0.9 → crit, else warn block 50 + round(rotation×45), max 95
Sensitive URLs seen (.env·.git·wp-config…) Credential/config file probing credential_access crit block 88
variety = 1 AND conns ≥ 10 AND sni_variety ≥ 4 Single-JA3 multi-target scan scan warn observe 72
None of the above TLS baseline activity other info observe 40

Sensitive URLs are matched by the regex .env .git wp-config .aws /actuator phpunit /.. shell.php wp-login xmlrpc (from nginx is_suspicious + suricata_http context).

Categories and severities are validated against the column enums before insert (recon · credential_access · exfil · bruteforce · c2 · tls_anomaly · scan · other / info · warn · crit / observe · block · isolate · report).

Ruleset wording & languages

A finding is stored as an identity, not a sentence. heuristicFindings() emits {key, params}; the row keeps rule_key + params (migration 000783) and the wording is applied when the row is read, in the reader's language.

Rows created before 000783 keep their original text. The migration recovers rule_key only where a row's own evidence reproduces the old template exactly (no guessing); freeform rows written by the old LLM path stay frozen until that IP is analyzed again, which replaces them.

3) Manage rulesets

Action Effect
Activate status=active (reflected in the Overview funnel / block narrative)
Dismiss status=dismissed
Delete Remove the row

Re-analyzing replaces only that IP's existing machine-derived rulesets (deletes entity_type=ip, entity_key=ip, source=ai then recreates) — manually authored rulesets are preserved.

A ruleset's action field is a recommendation, not an instruction. Nothing acts on it: Auto-block computes JA3 rotation directly from suricata_tls_ct and never reads infr_sec_ruleset. A ruleset saying action=block does not block anything — blocking is done from Blocklist or by the auto-block scheduler.

4) Scheduled accumulation (opt-in)

The infr:sec-analyze command iterates top JA3 offenders and accumulates rulesets. It is a single-shot command that exits after running, so it can be scheduled safely (not a daemon).

# Bypass the gate and run once (debug)
php artisan infr:sec-analyze --force

# Analyze a single IP (ignores the gate)
php artisan infr:sec-analyze --ip=203.0.113.10

5) Copy pm2 deployment commands

The pm2 card offers [Copy command] (pm2 run line), [Copy source] (snippet body), and [Fill with instance values & copy] (HOSTNAME/IP token substitution). Source comes from the seeded Inventory ▸ Tools snippet (infr_snippet).


Data sources

Legacy tables read (do not modify)

Table Use Feeding collector
kitsune_detail_log anomaly IP · anomaly_score · is_blocked kts_tx
kitsune_process_log Kitsune run history kts_tx
srct_process_log srct run history srct_tx
nginx_meta_ct suspicious web requests (is_suspicious) nginx_shipper/aggregator
suricata_tls_ct JA3 sequence (ja3_hash · sni · version) eve_ingest
suricata_http_ct HTTP context for analysis eve_ingest
blacklist_info auto-block results (reference) kts_tx · srct_tx
ip_info_loop geo/ASN enrichment —

App-owned new tables

pm2 deployment collectors

key Feeds Deployment command (cron-restart)
eve_ingest suricata_*_ct pm2 start infr_eve_ingest.cjs --name infr-eve --cron-restart="0 0 * * 6"
kts_tx kitsune_*_log · blacklist_info pm2 start infr_kts_tx.cjs --name infr-kitsune --cron-restart="0 0 * * 2"
srct_tx srct_process_log · blacklist_info pm2 start infr_srct_tx.cjs --name infr-srct --cron-restart="0 0 * * 4"
sync_black ipset · iptables pm2 start infr_sync_black.mjs --name infr-syncblack --cron-restart="*/5 * * * *"
sync_firewall firewall_status pm2 start infr_sync_firewall.cjs --name infr-firewall --cron-restart="0 0 * * 0"

Services & routes

Method Route
GET /security/detections (index)
GET /security/detections/live (poll)
POST /security/detections/analyze (analyze)
POST /security/detections/ruleset/save
POST /security/detections/ruleset/delete

Access

Operation Gate
View screen Security role-area access (min_req 2) — demo (1) doesn't see the security area at all; staff (5) · admin (10) can view
Analyze / ruleset save·delete user_level ≥ 2 (canWrite) + infr_audit_log audit

Troubleshooting & FAQ

Analysis skipped with insufficient_sequence — that IP's suricata_tls_ct sequence is below sec_ai_min_seq (default 5). Widen the window (sec_ai_window_min, default 60 min) or pick a more active IP from the offender list's 6-hour window.


Related

Detections and AI JA3 sequence analysis
Detections and AI JA3 sequence analysis