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
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:
@@ -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()
|
||||
Executable
+20
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+81
@@ -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"}
|
||||
]
|
||||
}
|
||||
Executable
+53
@@ -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
|
||||
Reference in New Issue
Block a user