Documentation Index
Fetch the complete documentation index at: https://wiki.krkn.tech/llms.txt
Use this file to discover all available pages before exploring further.
Mnemos — Correlation Engine
Mnemos is Aegis’ stateful request correlation engine. It detects attacks that span multiple HTTP requests from the same client within a configurable time window — the kind of campaigns that single-request WAF rules cannot catch.
Mnemos is analogous to Snort’s flowbits + threshold + detection_filter, but operates at the HTTP application layer instead of the packet layer.
Why Correlation Matters
Traditional WAF rules evaluate each request in isolation. Many real-world attacks look like a sequence of individually benign or low-severity requests that become dangerous only in aggregate:
- OOB SQL Injection campaigns — a client probing multiple endpoints with
LOAD_FILE, xp_dirtree, and DNS exfil patterns
- Data exfiltration — systematically walking sensitive endpoints (
/api/users, /api/billing, /api/config/export) with bulk extraction parameters
- Credential stuffing — repeated login attempts with different usernames from the same IP
- Scanner reconnaissance — schema probing followed by targeted injection attempts
Mnemos links these individual request signals into a single correlated detection event and escalates severity automatically.
How It Works
Architecture
Mnemos maintains an in-memory circular buffer of recent request history per client (keyed by host:source_ip). Every proxied request is recorded as a snapshot containing path, query, body, headers, user-agent, matched WAF rules, and action taken.
Request arrives
|
v
WAF rule chain evaluates (regex + condition rules)
|
v
Request snapshot recorded into Mnemos correlation store
|
v
Correlation rules evaluate against client history
|
v
If threshold met within window ──► correlation event fires
Core Components
| Component | Description |
|---|
| CorrelationStore | sync.Map of per-client circular buffers (default 64 entries per client, 5-minute TTL) |
| RequestSnapshot | Captured request and response data: timestamp, request ID, method, path, query, body (first 512 bytes), headers, cookies, user-agent, content-type, source IP, matched rules, action — plus response fields: status, size, content-type, latency, headers, body sample |
| Correlation Rules | WAF rules with match_mode: correlated and a correlation_config block |
| Background GC | Periodic garbage collection removes stale client histories (2x TTL) |
Evaluation Flow
When a correlation rule evaluates:
- Window — filter the client’s history to only snapshots within
window_seconds of the current request
- Predicates — apply optional condition filters to narrow the snapshot set (e.g., only
GET/POST requests to API paths)
- Trigger rules — check that the required WAF rules have fired within the window (all must be present, or in sequence if
sequence_mode is enabled)
- Unique fields — if configured, count distinct values across the specified fields (e.g., unique paths) and compare against threshold
- Threshold — if the filtered snapshot count meets or exceeds the threshold, the correlation fires
Correlation Config Schema
Correlation rules use match_mode: correlated and require a correlation_config block:
| Field | Type | Required | Description |
|---|
window_seconds | integer | yes | Time window for correlation analysis (1–3600 seconds) |
threshold | integer | yes | Minimum number of matching requests required (minimum 2) |
group_by | string | no | Grouping key — currently only source_ip is supported |
trigger_rules | array of strings | no | WAF rule names that must have fired within the window |
sequence_mode | boolean | no | If true, trigger rules must occur in the specified order |
unique_fields | array of strings | no | Count unique values across these fields: path, query, body, user_agent |
predicates | array of objects | no | Condition filters applied to snapshots before evaluation |
Predicate Object Schema
Predicates use the same field/operator/value structure as condition-based WAF rules:
| Field | Type | Description |
|---|
field | string | Request field to inspect |
operator | string | Comparison operator |
value | string | Value to compare against |
case_sensitive | boolean | Enable case-sensitive matching |
negated | boolean | Invert the result |
Example: OOB SQL Injection Campaign
This is a real detection chain from the bundled mnemos-oob-sqli-and-exfil.yaml rule file. It uses two regex trigger rules that detect individual OOB SQLi techniques, then a Mnemos correlation rule that links them into a campaign:
Step 1 — Trigger rules (regex)
# Trigger 1: OOB SQLi function primitives
- name: OOB-SQLi-Payload
match_mode: regex
severity: high
action: block
targets: [query, body, path]
pattern: >-
(?i)(?:load_file\s*\(|into\s+(?:out|dump)file\b|xp_cmdshell|
xp_dirtree|utl_http\.request|dblink\s*\()
# Trigger 2: DNS/UNC exfil patterns
- name: OOB-SQLi-DNS-Exfil
match_mode: regex
severity: high
action: block
targets: [query, body]
pattern: >-
(?i)(?:\\\\\\\\[a-z0-9][-a-z0-9]*\.[a-z]{2,}\\\\|
\.(?:burpcollaborator|oastify|interact\.sh)\b)
Step 2 — Mnemos correlation rule
- name: Mnemos - OOB SQLi Campaign
match_mode: correlated
severity: critical
action: block
tags: [mnemos, correlation, oob-sqli]
correlation_config:
window_seconds: 180
threshold: 3
group_by: source_ip
trigger_rules:
- OOB-SQLi-Payload
- OOB-SQLi-DNS-Exfil
sequence_mode: false
unique_fields:
- path
predicates:
- field: request.method
operator: in_list
value: GET,POST,PUT
- field: request.path
operator: matches_regex
value: '(?i)(?:/api|/graphql|/query|/search|/admin|/v[0-9])'
This fires when the same client triggers both OOB-SQLi rules across 3+ unique paths within 3 minutes, targeting API-like endpoints. A single probing request is blocked by the trigger rules; the Mnemos rule escalates repeated probing to critical severity.
Example: Data Exfiltration Campaign
- name: Mnemos - Data Exfiltration Campaign
match_mode: correlated
severity: critical
action: block
tags: [mnemos, correlation, exfiltration]
correlation_config:
window_seconds: 300
threshold: 3
group_by: source_ip
trigger_rules:
- Exfil-Sensitive-Endpoint
- Exfil-Bulk-Extraction
- Exfil-Schema-Recon
sequence_mode: false
unique_fields:
- path
predicates:
- field: request.method
operator: in_list
value: GET,POST
This correlates a client systematically walking sensitive endpoints (/api/users, /api/config, /api/billing/export) with bulk extraction parameters or schema reconnaissance within 5 minutes. The unique_fields: [path] constraint ensures the rule only fires when the client is walking different resources, not refreshing the same page.
Bidirectional Correlation (Response-Aware Rules)
Standard Mnemos rules correlate across requests — they evaluate what the client sends. Bidirectional correlation extends this to also evaluate what the server responds, enabling detection of attack patterns that are only visible in the full request-response flow.
Why Response Correlation Matters
Some attacks can only be detected by looking at both sides of the conversation:
- Credential stuffing — the same client sends login requests, but only the response status codes (200 vs 401) reveal success/failure patterns
- Forced browsing / IDOR — a client enumerates IDs, but only the response sizes or status codes reveal which resources exist
- Data exfiltration — a client walks API endpoints, but the response
Content-Type and body size reveal whether data was actually returned
- Error-based reconnaissance — a client probes endpoints; 500 errors in the responses indicate the server is crashing on specific inputs
How It Works
Bidirectional correlation uses two separate correlation stores:
Request arrives
|
v
WAF rule chain evaluates (request-side)
|
v
Snapshot recorded to REQUEST correlation store
|
v
Request-side Mnemos rules evaluate
|
v
Request proxied to upstream
|
v
Response returns from upstream
|
v
Response fields captured (status, size, content-type, latency, headers, body sample)
|
v
Snapshot updated with response data, recorded to RESPONSE correlation store
|
v
Response-aware Mnemos rules evaluate
|
v
If match ──► retrospective block (auto-blacklist/timeout the source IP)
The WAF engine automatically routes correlation rules to the correct store based on their predicates:
- Rules with only request-side predicates go to the request correlation store (evaluated immediately)
- Rules with any
response.* predicate go to the response correlation store (evaluated after the upstream responds)
Response Fields Available in Predicates
| Field | Description |
|---|
response.status | HTTP status code (e.g., 200, 401, 500) |
response.size | Response body size in bytes |
response.content_type | Response Content-Type header value |
response.latency_ms | Request-to-response latency in milliseconds |
response.header.<name> | Specific response header value |
response.body | Response body sample (truncated) |
Response-Aware Unique Fields
In addition to request-side unique fields, correlation rules can count unique values across response fields:
| Field | Description |
|---|
response_status | Unique HTTP status codes |
response_size | Unique response body sizes |
response_content_type | Unique Content-Type values |
Example: Credential Stuffing Detection
- name: Mnemos - Credential Stuffing Campaign
match_mode: correlated
severity: critical
action: block
tags: [mnemos, correlation, credential-stuffing]
correlation_config:
window_seconds: 120
threshold: 5
group_by: source_ip
unique_fields:
- body
predicates:
- field: request.path
operator: matches_regex
value: '(?i)/(?:api/)?(?:auth|login|signin|token)'
- field: response.status
operator: equals
value: "401"
This fires when the same client makes 5+ login attempts with different request bodies (different credentials) within 2 minutes, where all responses are 401 Unauthorized.
Example: Forced Browsing / IDOR Detection
- name: Mnemos - IDOR Enumeration
match_mode: correlated
severity: high
action: block
tags: [mnemos, correlation, idor]
correlation_config:
window_seconds: 60
threshold: 10
group_by: source_ip
unique_fields:
- path
predicates:
- field: request.path
operator: matches_regex
value: '(?i)/api/(?:users|accounts|orders|invoices)/\d+'
- field: response.status
operator: equals
value: "200"
This detects a client walking sequential numeric IDs across 10+ unique paths within 1 minute where the server responds 200 — indicating the resources exist and were returned.
Retrospective Blocking
When a response-aware correlation rule fires, the response has already been sent to the client. Aegis cannot undo that response, but it can retrospectively block the source IP to prevent future requests:
- The response correlation match fires
- Aegis applies the same auto-blocking logic (blacklist or timeout, host or global scope)
- The source IP is blocked for all future requests
- The proxy runtime reloads immediately
This means the attacker may receive responses during the detection window, but is cut off as soon as the correlation threshold is met. The block reason is logged as a retrospective block with the correlation rule name and category.
Importing Mnemos Rules
Mnemos correlation rules can be imported alongside regular WAF rules via the same JSON/YAML import endpoint. The bundled mnemos-oob-sqli-and-exfil.yaml file is a complete example containing trigger rules and their corresponding Mnemos correlation rules in a single file.
POST /api/v1/rules/import
Content-Type: multipart/form-data
Imported correlation rules are validated through the same pipeline as UI-created rules — window_seconds, threshold, trigger_rules, unique_fields, and predicates are all checked server-side.
Correlation Events
When a Mnemos rule fires, a CorrelationEvent is persisted to SQLite with the matched snapshots. These events are queryable via the admin API and visible in the Mnemos page of the admin UI.
API
| Method | Path | Description |
|---|
GET | /api/v1/correlation-events | Query correlation events (filterable by host, source IP, rule, time range) |
Event Data
| Field | Description |
|---|
id | Event ID |
host | Proxy host where the correlation fired |
source_ip | Client IP that triggered the correlation |
rule_name | Name of the Mnemos correlation rule |
rule_id | WAF rule ID |
matched_snapshots | JSON array of the request snapshots that contributed to the match |
window_seconds | The correlation window that was configured |
threshold | The threshold that was configured |
created_at | When the correlation event fired |
Request Log Enrichment
When a request triggers a correlation match, the WAF detail in the request log is enriched with correlation context:
| Field | Description |
|---|
correlation.window_seconds | The correlation window |
correlation.threshold | The configured threshold |
correlation.matched_snapshots | Summary of contributing requests |
This allows traffic log queries to surface which requests were part of a correlated campaign, not just which individual rule matched.
Admin UI
Mnemos has its own page in the Aegis admin dashboard accessible from the sidebar. The Mnemos page provides:
- Live view of correlation events
- Stateful request correlation history per client
- Filtering by host, source IP, rule, and time range
In the rule editor, selecting match_mode: correlated switches to the correlation configuration interface where you define the window, threshold, trigger rules, predicates, and unique field constraints.
Tuning
| Parameter | Effect of increasing | Effect of decreasing |
|---|
window_seconds | Catches slower campaigns | Reduces false positives from unrelated requests |
threshold | Fewer false positives | Catches faster / shorter campaigns |
| Trigger rules | More specific detection | Broader detection with fewer prerequisites |
unique_fields | Requires diversity (different paths) | Fires on repeated access to same resource |
| Predicates | Narrows to specific traffic patterns | Broadens to all client traffic |