First Commit
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s

This commit is contained in:
dela
2026-10-09 08:38:16 +08:00
commit 0335d572de
756 changed files with 201663 additions and 0 deletions
+416
View File
@@ -0,0 +1,416 @@
#!/usr/bin/env python3
#
# Live event-matching test for the ARTEX Sigma rules (../../sigma/). check.sh
# installs a pinned pySigma inside a container and runs this script with the rule
# tree mounted read-only at /sigma and this directory at /src.
#
# WHAT THIS PROVES, AND WHY IT IS DIFFERENT FROM THE sigma/ SUITE
# --------------------------------------------------------------
# The sigma/ suite proves each rule is structurally valid and COMPILES to a
# backend query, and that its indicator strings survive into that query. It does
# NOT prove the rule actually fires on a matching event, or stays quiet on a
# benign one: a field renamed to something the log never carries, a wildcard that
# silently dropped, or an over-broad token would all still compile cleanly. The
# README's own principle is that "a detection you cannot run is only a claim," and
# the Suricata suite already backs its network rule with a real pcap replay
# (fires on the probe UA, silent on a benign browser). This suite closes the same
# gap for the host/log-layer Sigma rules on two levels:
# - ATOMIC rules (../../sigma/*.yml): for each rule a representative malicious
# event MATCHES and a benign event DOES NOT.
# - CORRELATION rules (../../sigma/correlation/*.yml): for each rule a positive
# timeline (threshold met, inside the window, within one group) FIRES and
# negative timelines (below threshold, threshold met but spread beyond the
# window, split across groups, or missing a leg) stay QUIET.
#
# HOW IT MATCHES (trust model)
# ----------------------------
# It does not hand-parse the YAML or re-implement Sigma's modifier logic. pySigma
# parses each rule and compiles its modifiers and condition into a tree:
# `|contains` becomes a wildcard-wrapped value, `|all` becomes an AND over values,
# `1 of selection_*` becomes an OR over the selection groups. This script only
# walks that compiled tree (AND / OR / NOT / field-equals / keyword) and tests
# each leaf against the event, so the authoritative parsing stays in pySigma. A
# leaf value or condition node this script does not explicitly support raises
# rather than passing silently (fail-closed), so a future rule using an
# unsupported construct surfaces loudly here instead of being waved through.
#
# For a correlation rule, pySigma likewise parses the aggregation spec — type
# (event_count / value_count / temporal), group-by fields, timespan, the
# threshold condition, and the resolved references to the atomic base rules. This
# script walks that parsed spec and applies it to a timeline, deciding which
# events feed each referenced rule with the very same atomic matcher above, so the
# Sigma logic again stays in pySigma; only the windowed aggregation is applied
# here. The correlation rules reference their atomics by id, so each is parsed in a
# collection that also holds every atomic rule (pySigma resolves the reference).
#
# SCOPE AND HONESTY (read before trusting a green run)
# ----------------------------------------------------
# - The CORRELATION window is the standard sliding-window interpretation: a
# window of `timespan` seconds anchored at each matching event, with inclusive
# bounds. Each timeline event carries an integer `ts` in relative seconds. A
# real SIEM's windowing (tumbling vs sliding, bound inclusivity, late arrival)
# may differ; this is a regression test for the rule's group-by / timespan /
# threshold logic — that it fires when they are satisfied and not when they are
# not — rather than a bit-exact model of any one backend's correlation engine.
# - Matching is CASE-INSENSITIVE. This mirrors the default of the splunk backend
# the sigma/ suite targets, and the destructive rule's own false-positive note
# assumes it (it warns that lowercase coreutils `truncate` shares the uppercase
# `TRUNCATE ` token and must be allow-listed). Your SIEM's case handling and
# field normalisation may differ; this is a regression test for the rules'
# field/value/condition logic, not a substitute for validating in your stack.
# - Keyword matching (the audit-framing rule) is modelled as a full-text
# substring search across all event field values, the common interpretation of
# an unbound Sigma keyword.
#
# Exits non-zero on any failure. Standard library only beyond pySigma.
import glob
import json
import os
import re
import sys
from sigma.collection import SigmaCollection
from sigma.conditions import (
ConditionAND,
ConditionFieldEqualsValueExpression,
ConditionNOT,
ConditionOR,
ConditionValueExpression,
)
from sigma.types import (
SigmaNull,
SigmaNumber,
SigmaRegularExpression,
SigmaString,
SpecialChars,
)
SIGMA_DIR = os.environ.get("SIGMA_DIR", "/sigma")
EVENTS_DIR = os.environ.get("EVENTS_DIR", "/src/events")
CORR_DIR = os.path.join(SIGMA_DIR, "correlation")
CORR_EVENTS_DIR = os.path.join(EVENTS_DIR, "correlation")
fail = 0
def note(msg):
print(f" {msg}")
def passed(msg):
note(f"PASS {msg}")
def bad(msg):
global fail
note(f"FAIL {msg}")
fail = 1
# --- matcher -------------------------------------------------------------------
def sigmastring_to_regex(value):
"""Compile a pySigma SigmaString (literal text plus wildcards) to an anchored,
case-insensitive regex. `|contains` already wrapped the value in multi
wildcards upstream, so a plain string compiles to an exact match and a
contains-value compiles to a substring match — exactly the Sigma semantics."""
parts = []
for part in value.s:
if part == SpecialChars.WILDCARD_MULTI:
parts.append(".*")
elif part == SpecialChars.WILDCARD_SINGLE:
parts.append(".")
elif isinstance(part, str):
parts.append(re.escape(part))
else:
raise ValueError(f"unsupported SigmaString part: {part!r}")
return re.compile("^" + "".join(parts) + "$", re.DOTALL | re.IGNORECASE)
def field_match(field, value, event):
if field not in event:
return False
observed = str(event[field])
if isinstance(value, SigmaString):
return sigmastring_to_regex(value).search(observed) is not None
if isinstance(value, SigmaNumber):
return observed == str(value.number)
if isinstance(value, SigmaNull):
return event.get(field) is None
if isinstance(value, SigmaRegularExpression):
return re.search(value.regexp, observed) is not None
raise ValueError(f"unsupported field value type: {type(value).__name__}")
def keyword_match(value, event):
"""Unbound keyword: full-text substring search across all field values."""
if not isinstance(value, SigmaString):
raise ValueError("unsupported keyword value type")
if any(not isinstance(p, str) for p in value.s):
raise ValueError("wildcard in keyword is not supported by this matcher")
token = "".join(value.s)
haystack = " ".join(str(v) for v in event.values())
return token.lower() in haystack.lower()
def evaluate(node, event):
if isinstance(node, ConditionAND):
return all(evaluate(a, event) for a in node.args)
if isinstance(node, ConditionOR):
return any(evaluate(a, event) for a in node.args)
if isinstance(node, ConditionNOT):
return not evaluate(node.args[0], event)
if isinstance(node, ConditionFieldEqualsValueExpression):
return field_match(node.field, node.value, event)
if isinstance(node, ConditionValueExpression):
return keyword_match(node.value, event)
raise ValueError(f"unsupported condition node: {type(node).__name__}")
def rule_matches(rule, event):
return any(evaluate(c.parsed, event) for c in rule.detection.parsed_condition)
# --- correlation evaluator -----------------------------------------------------
def group_key(event, fields):
if any(f not in event for f in fields):
return None
return tuple(event[f] for f in fields)
def correlation_fires(corr, timeline):
"""Apply a parsed SigmaCorrelationRule's aggregation to a timeline of events
(each carrying an integer `ts` in seconds). pySigma has parsed the rule into a
type, group-by fields, a timespan, a threshold condition, and resolved rule
references; this walks that parsed structure. Membership in a referenced rule
is decided by the same rule_matches the atomic suite uses, so the Sigma
detection logic stays in pySigma. The window is the standard sliding window:
`timespan` seconds anchored at each matching event, inclusive bounds."""
ctype = str(corr.type)
span = corr.timespan.seconds
group_by = corr.group_by or []
refs = [ref.rule for ref in corr.rules]
if ctype in ("event_count", "value_count"):
# A count correlation may reference several base rules; an event feeds the
# count if it matches ANY of them — the same union the temporal branch
# applies below. Looking at refs[0] alone would silently drop events
# matching the other referenced rules, a fail-open this suite's header
# forbids. With a single reference this reduces to the one-rule case, so
# the existing rules (each referencing one base rule) are unchanged.
matched = [e for e in timeline if any(rule_matches(r, e) for r in refs)]
groups = {}
for e in matched:
key = group_key(e, group_by)
if key is None:
continue
groups.setdefault(key, []).append(e)
threshold = corr.condition.count
fieldref = corr.condition.fieldref
for members in groups.values():
members = sorted(members, key=lambda e: e["ts"])
for anchor in members:
window = [
e for e in members if anchor["ts"] <= e["ts"] <= anchor["ts"] + span
]
if ctype == "event_count":
if len(window) >= threshold:
return True
else:
distinct = {e[fieldref] for e in window if fieldref in e}
if len(distinct) >= threshold:
return True
return False
if ctype == "temporal":
groups = {}
for e in timeline:
key = group_key(e, group_by)
if key is None:
continue
groups.setdefault(key, []).append(e)
for members in groups.values():
members = sorted(members, key=lambda e: e["ts"])
for anchor in members:
window = [
e for e in members if anchor["ts"] <= e["ts"] <= anchor["ts"] + span
]
if all(any(rule_matches(r, e) for e in window) for r in refs):
return True
return False
raise ValueError(f"unsupported correlation type: {ctype}")
def require_ts(events, stem, label):
for e in events:
if not isinstance(e.get("ts"), int):
raise ValueError(
f"{stem} ({label}): every timeline event needs an integer 'ts' "
f"(seconds); got {e!r}"
)
# --- loaders -------------------------------------------------------------------
def load_atomic_rules():
rules = {}
for path in sorted(glob.glob(os.path.join(SIGMA_DIR, "*.yml"))):
stem = os.path.splitext(os.path.basename(path))[0]
collection = SigmaCollection.from_yaml(open(path, encoding="utf-8").read())
for rule in collection.rules:
# Only plain atomic rules; correlation rules carry a `.type` and are
# handled separately below.
if type(rule).__name__ != "SigmaRule":
continue
rules[stem] = rule
return rules
def load_correlation_rules():
"""A correlation rule references its atomic base rules by id, so it must be
parsed in a collection that also contains those atomics. For each correlation
file, merge every atomic YAML with that one correlation YAML, parse the
collection (pySigma resolves the reference), and key the resulting
SigmaCorrelationRule by filename stem so it pairs with
events/correlation/<stem>.json."""
atomic_docs = [
open(p, encoding="utf-8").read()
for p in sorted(glob.glob(os.path.join(SIGMA_DIR, "*.yml")))
]
corrs = {}
for path in sorted(glob.glob(os.path.join(CORR_DIR, "*.yml"))):
stem = os.path.splitext(os.path.basename(path))[0]
merged = "\n---\n".join(atomic_docs + [open(path, encoding="utf-8").read()])
collection = SigmaCollection.from_yaml(merged)
found = [r for r in collection.rules if type(r).__name__ == "SigmaCorrelationRule"]
if len(found) != 1:
raise ValueError(f"{stem}: expected exactly 1 correlation rule, got {len(found)}")
corrs[stem] = found[0]
return corrs
def load_events(directory):
events = {}
for path in sorted(glob.glob(os.path.join(directory, "*.json"))):
stem = os.path.splitext(os.path.basename(path))[0]
events[stem] = json.load(open(path, encoding="utf-8"))
return events
def main():
rules = load_atomic_rules()
events = load_events(EVENTS_DIR)
print("== atomic 1/3 every atomic rule is paired with a sample-event file ==")
rule_stems = set(rules)
event_stems = set(events)
orphan_rules = sorted(rule_stems - event_stems)
orphan_events = sorted(event_stems - rule_stems)
if orphan_rules:
bad(f"atomic rules with no events/<name>.json: {orphan_rules}")
if orphan_events:
bad(f"event files with no matching atomic rule: {orphan_events}")
if not orphan_rules and not orphan_events:
passed(
f"rule/sample pairing: {len(rules)} atomic rules, "
f"{len(events)} event files, no orphans"
)
print("== atomic 2/3 each rule matches its malicious sample events (true positives) ==")
for stem in sorted(rule_stems & event_stems):
rule = rules[stem]
positives = events[stem].get("positive", [])
if not positives:
bad(f"{stem}: no positive sample events")
continue
missed = [e for e in positives if not rule_matches(rule, e)]
if missed:
bad(f"{stem}: {len(missed)}/{len(positives)} positive events did NOT match")
for e in missed:
note(f" unmatched: {json.dumps(e, ensure_ascii=False)}")
else:
passed(f"{stem}: {len(positives)}/{len(positives)} positive events matched")
print("== atomic 3/3 each rule rejects its benign sample events (true negatives) ==")
for stem in sorted(rule_stems & event_stems):
rule = rules[stem]
negatives = events[stem].get("negative", [])
if not negatives:
bad(f"{stem}: no negative sample events")
continue
fired = [e for e in negatives if rule_matches(rule, e)]
if fired:
bad(f"{stem}: {len(fired)}/{len(negatives)} benign events WRONGLY matched")
for e in fired:
note(f" wrongly matched: {json.dumps(e, ensure_ascii=False)}")
else:
passed(
f"{stem}: {len(negatives)}/{len(negatives)} benign events correctly "
"not matched"
)
corr_rules = load_correlation_rules()
corr_events = load_events(CORR_EVENTS_DIR)
print("== correlation 1/3 every correlation rule is paired with a timeline file ==")
corr_stems = set(corr_rules)
ce_stems = set(corr_events)
orphan_corr = sorted(corr_stems - ce_stems)
orphan_tl = sorted(ce_stems - corr_stems)
if orphan_corr:
bad(f"correlation rules with no events/correlation/<name>.json: {orphan_corr}")
if orphan_tl:
bad(f"timeline files with no matching correlation rule: {orphan_tl}")
if not orphan_corr and not orphan_tl:
passed(
f"rule/timeline pairing: {len(corr_rules)} correlation rules, "
f"{len(corr_events)} timeline files, no orphans"
)
print("== correlation 2/3 each rule fires on its positive timelines (true positives) ==")
for stem in sorted(corr_stems & ce_stems):
corr = corr_rules[stem]
positives = corr_events[stem].get("positive", [])
if not positives:
bad(f"{stem}: no positive timelines")
continue
for tl in positives:
require_ts(tl["events"], stem, tl["label"])
if correlation_fires(corr, tl["events"]):
passed(f"{stem}: fired — {tl['label']}")
else:
bad(f"{stem}: did NOT fire on a positive timeline — {tl['label']}")
print("== correlation 3/3 each rule stays quiet on its negative timelines (true negatives) ==")
for stem in sorted(corr_stems & ce_stems):
corr = corr_rules[stem]
negatives = corr_events[stem].get("negative", [])
if not negatives:
bad(f"{stem}: no negative timelines")
continue
for tl in negatives:
require_ts(tl["events"], stem, tl["label"])
if correlation_fires(corr, tl["events"]):
bad(f"{stem}: WRONGLY fired on a benign timeline — {tl['label']}")
else:
passed(f"{stem}: quiet — {tl['label']}")
print()
try:
import importlib.metadata as md
print(f"reference: pySigma {md.version('pysigma')}, atomic + correlation rules")
except Exception:
pass
print("RESULT: PASS" if fail == 0 else "RESULT: FAIL")
sys.exit(fail)
if __name__ == "__main__":
main()
+20
View File
@@ -0,0 +1,20 @@
#!/bin/sh
#
# In-container half of the ARTEX Sigma live event-matching test. run.sh launches
# this inside a Python container with the Sigma rule tree (atomic rules and the
# correlation/ subtree) mounted read-only at /sigma and this directory at /src. It
# installs a pinned pySigma, then hands off to check.py, which asserts that every
# atomic rule matches its malicious sample events and stays quiet on its benign
# ones, and that every correlation rule fires on its positive timeline and stays
# quiet on its negative ones (see check.py's header for the trust model and
# scope). pySigma does the parsing; check.py walks the compiled condition tree and
# aggregation spec and tests each sample event or timeline against it.
#
# POSIX sh (the slim image ships dash). Exits non-zero if any assertion fails.
set -eu
VERSION="${PYSIGMA_VERSION:-2.0.0}"
pip install --quiet --disable-pip-version-check "pysigma==${VERSION}" >/dev/null 2>&1
exec python3 /src/check.py
@@ -0,0 +1,10 @@
{
"note": "webserver access log. The rule matches cs-user-agent EXACTLY equal to 'artex-enrich/1.0' (enrich/enrich.go:233). A browser UA, and the same UA with a trailing suffix, must not match.",
"positive": [
{"cs-method": "GET", "cs-uri-stem": "/", "cs-user-agent": "artex-enrich/1.0", "c-ip": "203.0.113.7"}
],
"negative": [
{"cs-method": "GET", "cs-uri-stem": "/", "cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0", "c-ip": "203.0.113.8"},
{"cs-method": "GET", "cs-uri-stem": "/robots.txt", "cs-user-agent": "artex-enrich/1.0 (proxied)", "c-ip": "203.0.113.9"}
]
}
@@ -0,0 +1,9 @@
{
"note": "application log line. The rule is an unbound keyword matching the guard's audit-control framing marker (guard/guard.go). It should match wherever the marker appears in the message, and stay quiet on an ordinary log line.",
"positive": [
{"message": "2026-10-07T03:11:09Z guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"}
],
"negative": [
{"message": "2026-10-07T03:11:09Z auth: user login ok uid=42 ip=203.0.113.8"}
]
}
@@ -0,0 +1,10 @@
{
"note": "file-creation (file_event) telemetry. The rule needs TargetFilename to contain BOTH '_ca' AND 'mitmproxy-ca-cert.pem' (|all), which narrows it to ARTEX's '<dir>/_ca/mitmproxy-ca-cert.pem' layout (traffic/traffic.go). A standalone mitmproxy cert under .mitmproxy/ has the filename but not the '_ca' directory, so it must NOT match — that is the specificity the |all modifier buys.",
"positive": [
{"TargetFilename": "/home/ubuntu/.local/share/artex/data/_ca/mitmproxy-ca-cert.pem", "Image": "/opt/artex/artex"}
],
"negative": [
{"TargetFilename": "/home/ubuntu/.mitmproxy/mitmproxy-ca-cert.pem", "Image": "/usr/bin/mitmproxy"},
{"TargetFilename": "/etc/ssl/certs/ca-certificates.crt", "Image": "/usr/sbin/update-ca-certificates"}
]
}
@@ -0,0 +1,9 @@
{
"note": "forward-proxy egress log. The rule matches c-useragent EXACTLY equal to 'artex-selfupdate' (selfupdate/github.go), the UA ARTEX sets when it fetches its own release from GitHub. A generic client UA must not match.",
"positive": [
{"c-useragent": "artex-selfupdate", "cs-host": "github.com", "cs-uri-stem": "/Autumn-27/ARTEX/releases/latest"}
],
"negative": [
{"c-useragent": "curl/8.5.0", "cs-host": "github.com", "cs-uri-stem": "/"}
]
}
@@ -0,0 +1,864 @@
{
"note": "webserver access log timeline for the ARTEX Enrichment Fan-Out correlation (value_count of DISTINCT cs-host >= 20, grouped by c-ip, within a 10-minute window). 'ts' is relative seconds. Breadth — distinct hosts touched, not request volume — is the signal, so a high-volume/low-breadth burst must stay quiet.",
"positive": [
{
"label": "20 distinct hosts from one source within the 10-minute window",
"events": [
{
"ts": 0,
"c-ip": "10.0.0.9",
"cs-host": "host00.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 25,
"c-ip": "10.0.0.9",
"cs-host": "host01.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 50,
"c-ip": "10.0.0.9",
"cs-host": "host02.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 75,
"c-ip": "10.0.0.9",
"cs-host": "host03.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 100,
"c-ip": "10.0.0.9",
"cs-host": "host04.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 125,
"c-ip": "10.0.0.9",
"cs-host": "host05.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 150,
"c-ip": "10.0.0.9",
"cs-host": "host06.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 175,
"c-ip": "10.0.0.9",
"cs-host": "host07.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 200,
"c-ip": "10.0.0.9",
"cs-host": "host08.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 225,
"c-ip": "10.0.0.9",
"cs-host": "host09.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 250,
"c-ip": "10.0.0.9",
"cs-host": "host10.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 275,
"c-ip": "10.0.0.9",
"cs-host": "host11.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 300,
"c-ip": "10.0.0.9",
"cs-host": "host12.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 325,
"c-ip": "10.0.0.9",
"cs-host": "host13.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 350,
"c-ip": "10.0.0.9",
"cs-host": "host14.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 375,
"c-ip": "10.0.0.9",
"cs-host": "host15.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 400,
"c-ip": "10.0.0.9",
"cs-host": "host16.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 425,
"c-ip": "10.0.0.9",
"cs-host": "host17.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 450,
"c-ip": "10.0.0.9",
"cs-host": "host18.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 475,
"c-ip": "10.0.0.9",
"cs-host": "host19.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
}
]
}
],
"negative": [
{
"label": "below the breadth threshold: only 19 distinct hosts",
"events": [
{
"ts": 0,
"c-ip": "10.0.0.9",
"cs-host": "host00.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 25,
"c-ip": "10.0.0.9",
"cs-host": "host01.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 50,
"c-ip": "10.0.0.9",
"cs-host": "host02.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 75,
"c-ip": "10.0.0.9",
"cs-host": "host03.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 100,
"c-ip": "10.0.0.9",
"cs-host": "host04.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 125,
"c-ip": "10.0.0.9",
"cs-host": "host05.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 150,
"c-ip": "10.0.0.9",
"cs-host": "host06.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 175,
"c-ip": "10.0.0.9",
"cs-host": "host07.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 200,
"c-ip": "10.0.0.9",
"cs-host": "host08.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 225,
"c-ip": "10.0.0.9",
"cs-host": "host09.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 250,
"c-ip": "10.0.0.9",
"cs-host": "host10.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 275,
"c-ip": "10.0.0.9",
"cs-host": "host11.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 300,
"c-ip": "10.0.0.9",
"cs-host": "host12.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 325,
"c-ip": "10.0.0.9",
"cs-host": "host13.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 350,
"c-ip": "10.0.0.9",
"cs-host": "host14.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 375,
"c-ip": "10.0.0.9",
"cs-host": "host15.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 400,
"c-ip": "10.0.0.9",
"cs-host": "host16.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 425,
"c-ip": "10.0.0.9",
"cs-host": "host17.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 450,
"c-ip": "10.0.0.9",
"cs-host": "host18.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
}
]
},
{
"label": "20 distinct hosts but spread over ~13 minutes, so no single 10-minute window sees 20",
"events": [
{
"ts": 0,
"c-ip": "10.0.0.9",
"cs-host": "host00.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 40,
"c-ip": "10.0.0.9",
"cs-host": "host01.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 80,
"c-ip": "10.0.0.9",
"cs-host": "host02.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 120,
"c-ip": "10.0.0.9",
"cs-host": "host03.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 160,
"c-ip": "10.0.0.9",
"cs-host": "host04.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 200,
"c-ip": "10.0.0.9",
"cs-host": "host05.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 240,
"c-ip": "10.0.0.9",
"cs-host": "host06.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 280,
"c-ip": "10.0.0.9",
"cs-host": "host07.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 320,
"c-ip": "10.0.0.9",
"cs-host": "host08.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 360,
"c-ip": "10.0.0.9",
"cs-host": "host09.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 400,
"c-ip": "10.0.0.9",
"cs-host": "host10.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 440,
"c-ip": "10.0.0.9",
"cs-host": "host11.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 480,
"c-ip": "10.0.0.9",
"cs-host": "host12.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 520,
"c-ip": "10.0.0.9",
"cs-host": "host13.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 560,
"c-ip": "10.0.0.9",
"cs-host": "host14.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 600,
"c-ip": "10.0.0.9",
"cs-host": "host15.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 640,
"c-ip": "10.0.0.9",
"cs-host": "host16.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 680,
"c-ip": "10.0.0.9",
"cs-host": "host17.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 720,
"c-ip": "10.0.0.9",
"cs-host": "host18.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 760,
"c-ip": "10.0.0.9",
"cs-host": "host19.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
}
]
},
{
"label": "high volume, low breadth: 25 requests from one source but only 4 distinct hosts",
"events": [
{
"ts": 0,
"c-ip": "10.0.0.9",
"cs-host": "host00.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 20,
"c-ip": "10.0.0.9",
"cs-host": "host01.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 40,
"c-ip": "10.0.0.9",
"cs-host": "host02.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 60,
"c-ip": "10.0.0.9",
"cs-host": "host03.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 80,
"c-ip": "10.0.0.9",
"cs-host": "host00.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 100,
"c-ip": "10.0.0.9",
"cs-host": "host01.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 120,
"c-ip": "10.0.0.9",
"cs-host": "host02.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 140,
"c-ip": "10.0.0.9",
"cs-host": "host03.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 160,
"c-ip": "10.0.0.9",
"cs-host": "host00.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 180,
"c-ip": "10.0.0.9",
"cs-host": "host01.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 200,
"c-ip": "10.0.0.9",
"cs-host": "host02.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 220,
"c-ip": "10.0.0.9",
"cs-host": "host03.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 240,
"c-ip": "10.0.0.9",
"cs-host": "host00.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 260,
"c-ip": "10.0.0.9",
"cs-host": "host01.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 280,
"c-ip": "10.0.0.9",
"cs-host": "host02.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 300,
"c-ip": "10.0.0.9",
"cs-host": "host03.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 320,
"c-ip": "10.0.0.9",
"cs-host": "host00.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 340,
"c-ip": "10.0.0.9",
"cs-host": "host01.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 360,
"c-ip": "10.0.0.9",
"cs-host": "host02.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 380,
"c-ip": "10.0.0.9",
"cs-host": "host03.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 400,
"c-ip": "10.0.0.9",
"cs-host": "host00.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 420,
"c-ip": "10.0.0.9",
"cs-host": "host01.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 440,
"c-ip": "10.0.0.9",
"cs-host": "host02.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 460,
"c-ip": "10.0.0.9",
"cs-host": "host03.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 480,
"c-ip": "10.0.0.9",
"cs-host": "host00.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
}
]
},
{
"label": "breadth split across two sources: 10 distinct hosts each, neither source reaches 20",
"events": [
{
"ts": 0,
"c-ip": "10.0.0.9",
"cs-host": "host00.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 25,
"c-ip": "10.0.0.9",
"cs-host": "host01.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 50,
"c-ip": "10.0.0.9",
"cs-host": "host02.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 75,
"c-ip": "10.0.0.9",
"cs-host": "host03.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 100,
"c-ip": "10.0.0.9",
"cs-host": "host04.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 125,
"c-ip": "10.0.0.9",
"cs-host": "host05.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 150,
"c-ip": "10.0.0.9",
"cs-host": "host06.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 175,
"c-ip": "10.0.0.9",
"cs-host": "host07.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 200,
"c-ip": "10.0.0.9",
"cs-host": "host08.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 225,
"c-ip": "10.0.0.9",
"cs-host": "host09.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 0,
"c-ip": "10.0.0.10",
"cs-host": "host10.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 25,
"c-ip": "10.0.0.10",
"cs-host": "host11.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 50,
"c-ip": "10.0.0.10",
"cs-host": "host12.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 75,
"c-ip": "10.0.0.10",
"cs-host": "host13.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 100,
"c-ip": "10.0.0.10",
"cs-host": "host14.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 125,
"c-ip": "10.0.0.10",
"cs-host": "host15.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 150,
"c-ip": "10.0.0.10",
"cs-host": "host16.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 175,
"c-ip": "10.0.0.10",
"cs-host": "host17.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 200,
"c-ip": "10.0.0.10",
"cs-host": "host18.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 225,
"c-ip": "10.0.0.10",
"cs-host": "host19.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
}
]
}
]
}
@@ -0,0 +1,979 @@
{
"note": "webserver access log timeline for the ARTEX Enrichment Scan Velocity correlation (event_count >= 30 probes from one c-ip within a 5-minute window). 'ts' is relative seconds. Velocity (density in time), not total count, is the signal.",
"positive": [
{
"label": "30 enrichment probes from one source inside the 5-minute window",
"events": [
{
"ts": 0,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 9,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 18,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 27,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 36,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 45,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 54,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 63,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 72,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 81,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 90,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 99,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 108,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 117,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 126,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 135,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 144,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 153,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 162,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 171,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 180,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 189,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 198,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 207,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 216,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 225,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 234,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 243,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 252,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 261,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
}
]
}
],
"negative": [
{
"label": "below the threshold: only 29 probes",
"events": [
{
"ts": 0,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 9,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 18,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 27,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 36,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 45,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 54,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 63,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 72,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 81,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 90,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 99,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 108,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 117,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 126,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 135,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 144,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 153,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 162,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 171,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 180,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 189,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 198,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 207,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 216,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 225,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 234,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 243,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 252,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
}
]
},
{
"label": "30 probes total but spread over ~10 minutes, so no 5-minute window reaches 30",
"events": [
{
"ts": 0,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 20,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 40,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 60,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 80,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 100,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 120,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 140,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 160,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 180,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 200,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 220,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 240,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 260,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 280,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 300,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 320,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 340,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 360,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 380,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 400,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 420,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 440,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 460,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 480,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 500,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 520,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 540,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 560,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
},
{
"ts": 580,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "artex-enrich/1.0"
}
]
},
{
"label": "30 requests in the window but the User-Agent is a normal browser (base rule does not match)",
"events": [
{
"ts": 0,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 9,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 18,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 27,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 36,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 45,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 54,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 63,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 72,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 81,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 90,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 99,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 108,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 117,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 126,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 135,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 144,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 153,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 162,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 171,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 180,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 189,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 198,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 207,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 216,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 225,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 234,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 243,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 252,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
},
{
"ts": 261,
"c-ip": "10.0.0.9",
"cs-host": "assets.example.test",
"cs-method": "GET",
"cs-uri-stem": "/",
"cs-user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
}
]
}
]
}
@@ -0,0 +1,152 @@
{
"note": "application/audit log timeline for the ARTEX Guard-Block Burst correlation (event_count >= 5 guard-control markers on one host within a 10-minute window). 'ts' is relative seconds. A single marker can be a quoted string; a burst on one host indicates an actively engaged ARTEX run.",
"positive": [
{
"label": "5 guard-control markers on one host inside the 10-minute window",
"events": [
{
"ts": 0,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 60,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 120,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 180,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 240,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
}
]
}
],
"negative": [
{
"label": "below the threshold: only 4 markers",
"events": [
{
"ts": 0,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 60,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 120,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 180,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
}
]
},
{
"label": "5 markers but spread over ~13 minutes, so no 10-minute window holds 5",
"events": [
{
"ts": 0,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 200,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 400,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 600,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 800,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
}
]
},
{
"label": "markers split across two hosts: 3 and 2, neither host reaches 5",
"events": [
{
"ts": 0,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 60,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 120,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 0,
"host": "web02",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 60,
"host": "web02",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
}
]
},
{
"label": "ordinary log lines on one host, no guard marker (base rule does not match)",
"events": [
{
"ts": 0,
"host": "web01",
"message": "auth: user login ok uid=42 ip=203.0.113.8"
},
{
"ts": 60,
"host": "web01",
"message": "auth: user login ok uid=42 ip=203.0.113.8"
},
{
"ts": 120,
"host": "web01",
"message": "auth: user login ok uid=42 ip=203.0.113.8"
},
{
"ts": 180,
"host": "web01",
"message": "auth: user login ok uid=42 ip=203.0.113.8"
},
{
"ts": 240,
"host": "web01",
"message": "auth: user login ok uid=42 ip=203.0.113.8"
}
]
}
]
}
@@ -0,0 +1,81 @@
{
"note": "host timeline joining application/audit logs and process-creation logs for the ARTEX Guard Marker With Destructive Command temporal correlation (both referenced rules must fire on the SAME host within a 30-minute window). 'ts' is relative seconds.",
"positive": [
{
"label": "guard marker then a destructive command on the same host within 30 minutes",
"events": [
{
"ts": 0,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 600,
"host": "web01",
"CommandLine": "rm -rf / --no-preserve-root",
"Image": "/usr/bin/rm"
}
]
}
],
"negative": [
{
"label": "only the guard marker, no destructive command",
"events": [
{
"ts": 0,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 120,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
}
]
},
{
"label": "only a destructive command, no guard marker",
"events": [
{
"ts": 0,
"host": "web01",
"CommandLine": "rm -rf / --no-preserve-root",
"Image": "/usr/bin/rm"
}
]
},
{
"label": "both present but ~60 minutes apart, outside the 30-minute window",
"events": [
{
"ts": 0,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 3600,
"host": "web01",
"CommandLine": "rm -rf / --no-preserve-root",
"Image": "/usr/bin/rm"
}
]
},
{
"label": "the two legs on different hosts",
"events": [
{
"ts": 0,
"host": "web01",
"message": "guard 【ARTEX 平台管控·非目标防御】此操作被禁止: rm -rf /"
},
{
"ts": 600,
"host": "db02",
"CommandLine": "rm -rf / --no-preserve-root",
"Image": "/usr/bin/rm"
}
]
}
]
}
@@ -0,0 +1,13 @@
{
"note": "process_creation telemetry (CommandLine). The rule hunts destructive shell/DB/availability commands via '1 of selection_*', so one representative command from each of the three selection groups must match. Benign commands — including a plain 'rm' without the recursive/force flags — must not. (The rule's own false-positive note documents that lowercase coreutils 'truncate' DOES share the TRUNCATE token and must be allow-listed, so it is intentionally not used here as a negative.)",
"positive": [
{"CommandLine": "rm -rf /var/www/html", "Image": "/usr/bin/rm"},
{"CommandLine": "mysql -u root -e 'DROP TABLE customers'", "Image": "/usr/bin/mysql"},
{"CommandLine": "iptables -F", "Image": "/usr/sbin/iptables"}
],
"negative": [
{"CommandLine": "ls -la /var/www/html", "Image": "/usr/bin/ls"},
{"CommandLine": "rm /tmp/scratch.txt", "Image": "/usr/bin/rm"},
{"CommandLine": "git status", "Image": "/usr/bin/git"}
]
}
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
#
# Reproducible live event-matching test for the ARTEX Sigma rules — both the
# atomic rules (../../sigma/*.yml) and the correlation rules
# (../../sigma/correlation/*.yml). The sibling sigma/ suite proves those rules are
# valid and COMPILE to a backend query; this suite proves they actually FIRE on a
# matching event (or timeline) and stay quiet on a benign one — the same "a
# detection you cannot run is only a claim" guarantee the suricata/ suite already
# gives the network rule with a pcap replay.
#
# It proves six properties with no host dependency beyond Docker (pySigma runs in
# a container, nothing is installed on the host and nothing is written to the repo
# tree):
#
# atomic 1 rule/sample pairing every atomic rule has an events/<name>.json and
# every events file maps to a rule (no orphans)
# atomic 2 true positives each rule matches all of its malicious events
# atomic 3 true negatives each rule matches none of its benign events
# corr 1 rule/timeline pairing every correlation rule has an
# events/correlation/<name>.json (no orphans)
# corr 2 true positives each rule FIRES on its positive timeline
# (threshold met, inside the window, one group)
# corr 3 true negatives each rule stays QUIET on its negative timelines
# (below threshold, window exceeded, split group,
# or a missing leg)
#
# pySigma parses each rule — for an atomic rule its condition tree, for a
# correlation rule its aggregation spec (type, group-by, timespan, threshold, and
# the resolved references to the atomic base rules) — and check.py only walks that
# parsed structure, so the authoritative Sigma logic stays in pySigma (see
# check.py's header). The correlation window is the standard sliding-window model
# and matching is case-insensitive; see check.py for the full scope and honesty
# notes.
#
# Usage: detections/tests/sigma_match/run.sh
# Env: PYTHON_IMAGE (default python:3.12-slim)
# PYSIGMA_VERSION (default 2.0.0 — the pinned reference version)
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
REPO="$(cd "$HERE/../../.." && pwd)"
SIGMA_DIR="$REPO/detections/sigma"
PYTHON_IMAGE="${PYTHON_IMAGE:-python:3.12-slim}"
PYSIGMA_VERSION="${PYSIGMA_VERSION:-2.0.0}"
# Everything runs inside the container: check.sh installs the pinned pySigma and
# runs check.py, which asserts the three properties and exits non-zero on any
# failure. The rule tree and this directory are mounted read-only.
docker run --rm \
-v "$SIGMA_DIR:/sigma:ro" \
-v "$HERE:/src:ro" \
-e PYSIGMA_VERSION="$PYSIGMA_VERSION" \
"$PYTHON_IMAGE" sh /src/check.sh