Overview — what it does
Detection Rulesets (/security/rulesets) is the canonical library that normalizes every detection rule InfraGuard observes and judges into 44 rulesets across 11 categories. The screen does three things in one place:
- Overview — lays out ruleset cards inside category sections and shows, for the selected time window (1h/6h/24h), the actual event count and last-seen timestamp each ruleset matched.
- Explanation — narrates each ruleset in three steps (what it detects · why it fires · worst case).
- Replay — clicking a card opens a modal with the recent events actually matched by that ruleset, in an engine-specific table.
The key design is the separation of classification structure from explanatory text. The structure (categories, ruleset keys, per-engine match rules) lives in the locale-independent static SOT App\Support\SecurityRulebook (cache-safe), while the explanatory text (name, meaning, evidence, risk) lives in resources/lang/{ko,en}/secrules.php. The frontend merges the two by key+category to render.
This screen is strictly read-only. It does not enable, disable, edit, or delete rulesets. It is a reference catalog that answers "what is being observed in this org right now, and what does it mean?" The place that actually changes detection constants (thresholds/windows) is Rules & Thresholds (Admin ▸ Rules).
The 44 rulesets are wired to real telemetry
Rulesets are not abstract rules — each is bound to a real collection table by a match rule (match). Every source is on the legacy_glory connection (the legacy DB the collector fleet fills).
| Engine | Match rule | Source table | Rulesets |
|---|---|---|---|
| Suricata IDS | sig: lowercase substring match against an array of signature fragments |
suricata_alert_ct |
33 |
| Web (nginx) | classifyWeb: priority, disjoint classification of is_suspicious requests by url/ua/status |
nginx_meta_ct |
8 |
| Kitsune ML | source=kitsune: autoencoder anomaly IPs |
kitsune_detail_log |
1 |
| Heuristic (srct) | source=srct: scan/rate heuristic runs |
srct_process_log |
1 |
| JA3 fingerprint | source=ja3: JA3 variety (rotation) per source IP |
suricata_tls_ct |
1 |
When to use
- Get context before triage — when you hit an unfamiliar signature in Threats or Detections · JA3, confirm which ruleset family it belongs to and what it implies.
- Scan the observation landscape — see at a glance which categories are firing over the last 24h, and whether web attacks are at recon level or have escalated to RCE probing.
- Onboarding & reporting — an educational/reporting artifact explaining "what we detect" by category and engine.
- Replay events — inspect the actual request IPs, URLs, and status codes recently matched by a specific ruleset (e.g.
web_secret_scan), then pivot suspects to IP Reputation or Blocklist.
Layout
Top KPIs
| KPI | Label | Meaning |
|---|---|---|
total |
Rulesets | Total rulesets in the catalog (always 44) |
active |
Observed rulesets | Rulesets that fired at least one event in the window (firing) |
events |
Events in window | Sum of event counts across all rulesets |
cats |
Categories | Category count (11) |
Internally it also computes per-engine sums (kitsune and suricata event totals) so the numbers stay consistent with the "rulesets" stage of the Security defense funnel.
Controls
- Window toggle —
1h(60 min) ·6h(360 min) ·24h(1440 min). Default24h. - Auto-refresh (LIVE) — polls
/security/rulesets/liveto refresh counts (pausable). - Fullscreen — hides the top nav for an immersive dashboard mode (ESC to exit).
Category sections + ruleset cards
The 11 categories form sections in display order, and within each section rulesets are laid out in a minmax(600) card grid. Each card shows the ruleset name, an MDI icon, a severity badge (crit/warn/info), the in-window event count, the last-seen timestamp, and [Open]. With no events it shows "quiet / none observed in window".
| # | Category key | Label | Engine | Rulesets |
|---|---|---|---|---|
| 1 | tcp_stream |
TCP stream integrity | Suricata | 10 |
| 2 | packet_decode |
Packet decoding | Suricata | 4 |
| 3 | protocol_anomaly |
Protocol anomaly | Suricata | 5 |
| 4 | http_anomaly |
HTTP compliance | Suricata | 5 |
| 5 | tls_anomaly |
TLS anomaly | Suricata | 5 |
| 6 | dns_anomaly |
DNS anomaly | Suricata | 1 |
| 7 | mail_anomaly |
Mail (SMTP) anomaly | Suricata | 3 |
| 8 | web_attack |
Web attack patterns | nginx | 8 |
| 9 | ml_anomaly |
ML anomaly detection | Kitsune | 1 |
| 10 | heuristic_scan |
Heuristic scan | srct | 1 |
| 11 | tls_fingerprint |
JA3 fingerprint | JA3 | 1 |
How to use
1) Pick a window → read the landscape
First choose the window of interest (1h/6h/24h). Use active (observed rulesets) and each card's event count to see what is firing now. Note that the severity badge is an implied severity — most Suricata engine events are sev3 (informational), but the implication differs (noise / scan / exploit). The ruleset badge reflects this implication tone, not the raw sev.
2) Click a card → recent events modal
Clicking a card calls /security/rulesets/events?key=<rulesetKey>&range=<window> and opens a modal containing:
- 3-step explanation — what it detects · why it fires · worst case.
- Aggregates — event count · unique sources (distinct src IPs).
- Matching signature chips (Suricata only) — the actual signature strings caught by this ruleset's sig substring match, each with count and minimum sev (top 12).
- Engine-specific event table (recent 60 rows).
- Source IP → reputation — an AbuseIPDB popup button per src IP. Registered internal-network IPs get an
internalbadge and the reputation button is hidden (prevents mistaking our own servers for attackers).
Engine-specific event table columns:
| Engine | Columns |
|---|---|
| Suricata | time · src · dst · port · proto · signature · sev |
| nginx | time · src · host · method · URL (rawurldecode, 180 chars) · status |
| Kitsune | time · IP · anomaly score · sessions · blocked |
| srct | time · scanned · blocked · note |
| JA3 | time · IP · JA3 variety · TLS conns · SNI variety |
3) Web attack classification priority (disjoint)
The 8 web (nginx) rulesets are mutually disjoint. A single is_suspicious=1 request is scanned in catalog definition order and attributed to exactly one first-matching ruleset; if it matches none, it falls to the catchall (web_suspicious_other). So the same request is never double-counted across two web rulesets.
| Priority | Ruleset key | Match cue (url/ua) | Severity |
|---|---|---|---|
| 1 | web_secret_scan |
.env .git wp-config id_rsa .aws credentials backup.sql .bak |
warn |
| 2 | web_path_traversal |
../ ..%2f %2e%2e etc/passwd boot.ini proc/self |
crit |
| 3 | web_sqli |
union select information_schema sleep( or 1=1 %27or |
crit |
| 4 | web_xss |
<script onerror= javascript: document.cookie |
warn |
| 5 | web_rce_probe |
/bin/sh exec( system( jndi: ${ phpunit |
crit |
| 6 | web_admin_brute |
wp-login xmlrpc.php phpmyadmin /admin/login |
warn |
| 7 | web_bad_bot |
UA: sqlmap nikto masscan nuclei python-requests curl/ |
warn |
| 8 | web_suspicious_other |
(catchall — suspicious requests matching none of the above) | info |
For performance, web classification aggregates over the most-recent sample of 6,000 rows (repository cap 8,000). During heavy scans, events outside the sample may not be reflected — read the numbers as distribution/trend, not absolute counts.
4) What Suricata substring matching implies
Suricata rulesets fire on a lowercase substring match of the signature string. For example, stream_invalid_ts sums every event whose signature contains invalid timestamp. A single ruleset can bundle several actual signatures (visible as "matching signatures" chips in the modal), and the sig keywords of different rulesets are designed not to overlap.
5) Measured example (for reference)
An illustrative 24h snapshot: 37 of 44 rulesets firing, stream_invalid_ts 10,243, web_secret_scan 3,304 (mostly .env-family 404s), srct_heuristic 126,252, ja3_rotation 300 (variety 307/307). Values vary by org and time.
Data sources
All sources are the legacy_glory connection (read-only), reached only via config('database.connections.legacy_glory') — no hardcoded credentials.
| Ruleset family | Table | Repository methods (excerpt) |
|---|---|---|
| Suricata (33) | suricata_alert_ct |
signatureHistogram() · recentAlertsBySignatures() · distinctIpsBySignatures() |
| Web (8) | nginx_meta_ct (is_suspicious=1) |
suspiciousRows($since, 6000) |
| Kitsune ML (1) | kitsune_detail_log |
kitsuneCount() · kitsuneRecentInWindow() |
| Heuristic (1) | srct_process_log |
srctBlockedSum() · srctRunsInWindow() |
| JA3 (1) | suricata_tls_ct |
topJa3Ips($since, N, excludePrefixes) |
Performance & caching
- The card overview (
build) is cached for 30 seconds (infr:sec:rulebook:<range>). Suricata pulls the signature histogram once and sums substring matches across all 44 rulesets to avoid N+1. - JA3 excludes the self-network (registered instance IPs /24) via
SecurityScope::excludePrefixes()NOT LIKE, and only counts source IPs withja3_variety >= 2as offenders. - Modal events are capped at 60 rows per window. Every repository query uses a narrow time window + LIMIT + parameter binding.
Access
- Read-only. The controller gate is
level≥1for reads, and entering the Security role area itself is protected by the sitemapmin_req=2(security operations) — in practice staff (=5), admin (=10), or security-scoped users reach it. - Because there is no write path, there is no step-up re-confirmation and no audit log.
/rulesets,/rulesets/live, and/rulesets/eventsare all pure reads. - The
eventsendpoint'skeyis allowed only from the catalog whitelist. Keys outside the list return404 unknown_ruleset(arbitrary table lookups are not possible).
Level convention: demo=1 · staff=5 · admin=10. Some areas are additionally gated by
infr.level:Nmiddleware. See RBAC Levels for the full grading scheme.
Troubleshooting & FAQ
All cards show "quiet"
Either there are no matching events in the selected window (default 24h), or the legacy_glory connection is unreachable. The service gracefully swallows source errors and shows empty counts (0) — cards do not disappear. Widen the window or check the connection.
Web ruleset counts differ from [Threats] / [Detections]
Web classification is a distribution aggregate over a most-recent sample of 6,000 rows (not an absolute count). It is also disjoint, so a request is attributed to exactly one ruleset. The authoritative event source follows each screen's own query.
A specific signature is not caught by any ruleset
Suricata matching is keyword substring matching. New/custom signatures the catalog does not cover are not summed into any ruleset count. To classify/explain them, add the signature keyword to SecurityRulebook's sig array — a code change, not a runtime setting.
JA3 offenders are 0 but there is heavy TLS traffic
The JA3 ruleset counts only source IPs with ja3_variety >= 2 (fingerprint rotation) as offenders. Without rotation, a source is treated as normal and not counted. Self-network IPs are also excluded.
An IP shows an internal badge in the event modal
That is a registered internal-network IP judged by SecurityScope::internalMatchers(). It prevents mistaking our own servers — which Suricata directionality can label as the "source" — for attackers, and hides the reputation button. The internal-network definition is managed under internal_networks in Operations Policy.
Related
- Detections · JA3 sequence analysis — the parent screen (Detections); a deterministic heuristic derives new rulesets from JA3 sequences.
- Threats — IDS threat-entity triage plus signature lore.
- Security Overview — consistent with the "rulesets" stage of the defense funnel.
- IP Reputation — check a modal source IP against AbuseIPDB.
- Blocklist — pivot a suspicious source into a block.
- Rules & Thresholds (Admin ▸ Rules) — actually tune detection collector constants (thresholds/windows).
