| name | alert-judgment |
| description | Use when judging whether a NetFlow alert is credible, suspicious, or unsupported from alert metadata and matched flow evidence. |
| allowed-tools | Read, Grep, Glob, Edit, Write |
alert-judgment
Use when
- You need to judge whether a NetFlow alert is
credible, suspicious, or unsupported.
- The input includes alert metadata,
real-data/*.json, and paired CSV flow evidence.
Do not use when
- You need mitigation decisions.
- You need deployment validation.
- You need cross-alert prioritization.
Required input
Each alert should include:
target_ip
start_time or startTime
end_time or endTime
If the input is already pre-denoised into events, each event should include:
event_id
target_ip
event_start_time
event_end_time
attack_type
If judge_alerts.py is used, the event input should already carry structured evidence for scoring, for example:
pre_denoise
evidence.matched_flow_summary
evidence.bad_flow_summary
evidence.good_flow_summary
evidence.label_distribution
evidence.artifact_refs
If a paired NetFlow CSV exists, the final judgment must go through relabel_netflows.py first.
In that case, do not treat light_evidence alone as sufficient final evidence.
light_evidence is only a fallback for preliminary triage when relabeled evidence is not yet available or when no paired CSV is available.
Judgment-object requirements:
- use the merged event as the primary judgment object
- once multiple member alerts have been merged into one event, do not judge those member alerts separately
Never use Read to open a full CSV.
If you need CSV inspection, only do one of the following:
- read the header plus a very small sample only
- use
scripts/summarize_netflows.py first to get structured statistics
- use downstream helper scripts that already stream the CSV locally and return compact JSON
Do not read the raw CSV line by line when the helper scripts can be used directly; prefer the scripts’ structured output as the primary evidence source.
When resolving files, always treat user-provided paths as project-relative first.
Do not hardcode repository-absolute paths; if a real-data path does not exist from the current working directory, rely on the helper scripts' project-local fallback search by filename.
Helper script
Prefer the local helper scripts instead of re-reading raw evidence manually.
scripts/summarize_netflows.py
Use it to inspect one NetFlow CSV safely without returning the whole file content.
The script streams the CSV locally and returns:
- header fields
- bounded sample rows
- row counts and byte / packet totals
- protocol and label distributions
- top talkers, destination ports, and next hops
Example:
python .claude/skills/alert-judgment/scripts/summarize_netflows.py \
--json-path real-data/1.2.3.4.json \
--pretty
python .claude/skills/alert-judgment/scripts/summarize_netflows.py \
--csv-path real-data/1.2.3.4_ddos.csv \
--target-ip 1.2.3.4 \
--label udp_flood \
--sample-limit 5 \
--pretty
Use the script when:
- the CSV is large and direct
Read would exceed token limits
- you need quick context before invoking
judge_alerts.py
- you need safe structured evidence for manual reasoning or reporting
scripts/normalize_alerts.py
Use it to turn raw real-data/<target>.json anomaly windows into canonical alerts.
Example:
python .claude/skills/alert-judgment/scripts/normalize_alerts.py \
--json-path real-data/1.2.3.4.json \
--pretty
python .claude/skills/alert-judgment/scripts/normalize_alerts.py \
--json-path real-data/1.2.3.4.json \
--record-index 0 \
--csv-path real-data/1.2.3.4_ddos.csv \
--pretty
Use the script when:
- the input is a raw
real-data/<target>.json bundle
- you want one normalized alert per anomaly window
- you want
source_json and source_csv preserved automatically
scripts/pre_denoise_alerts.py
Use it to merge adjacent or overlapping anomaly windows into candidate events before final judgment.
The current implementation is intentionally conservative:
- merge by
target_ip + attack_type
- compress exact duplicate windows
- do a lightweight raw-flow presence check from the paired CSV
- mark each merged event as
keep or filtered
Example:
python .claude/skills/alert-judgment/scripts/pre_denoise_alerts.py \
--json-path real-data/1.2.3.4.json \
--output-json /tmp/events.json \
--pretty
python .claude/skills/alert-judgment/scripts/pre_denoise_alerts.py \
--alerts-json alerts.json \
--output-json events.json \
--merge-gap-seconds 120 \
--raw-match-threshold 1 \
--pretty
Use the script when:
- the input contains multiple anomaly windows for the same target
- you want to convert alert windows into candidate attack events first
- you want a lightweight prefilter before final per-alert or future per-event judgment
- you want to save one reusable
events.json and avoid rerunning pre-denoise just to feed downstream scripts
scripts/judge_alerts.py
Use it to score and judge events that have already been prepared by earlier helper scripts.
This script no longer normalizes raw alerts, merges windows, or scans raw CSV on its own.
Its responsibility is to:
- consume pre-built
events.json
- read existing event evidence and summaries
- use relabeled event evidence as the default and required final input when a paired CSV exists
- only use
pre_denoise_alerts.py light evidence as a fallback for preliminary triage when relabeled evidence is not yet available
- compute
auto_handle_score and importance_score
- combine evidence conclusion with the two scores into the final event-level judgment
Example:
python .claude/skills/alert-judgment/scripts/judge_alerts.py \
--events-json events.json \
--pretty
Use the script when:
- the input is already pre-denoised into candidate events
- event-level evidence has already been prepared by other helper scripts
- you only have
pre_denoise_alerts.py output and need one fallback triage judgment before richer evidence is attached
- you want one dedicated scoring / judgment step instead of one all-in-one pipeline
scripts/relabel_netflows.py
Use it to rewrite the row-level label column in NetFlow CSV data with fine-grained attack labels.
The script now focuses only on the attack types currently used by alert-judgment:
udp_flood, syn_flood, and icmp_flood.
It applies attack-window matching plus lightweight attack-specific row scoring.
Example:
python .claude/skills/alert-judgment/scripts/relabel_netflows.py \
--json-path real-data/1.2.3.4.json \
--csv-out real-data/1.2.3.4_fine_grained.csv \
--pretty
python .claude/skills/alert-judgment/scripts/relabel_netflows.py \
--alerts-json alerts.json \
--csv-in real-data/1.2.3.4_ddos.csv \
--csv-out real-data/1.2.3.4_fine_grained.csv \
--pretty
python .claude/skills/alert-judgment/scripts/relabel_netflows.py \
--events-json events.json \
--csv-in flows.csv \
--csv-out flows_fine_grained.csv
Use the script when:
- you need row-level labels in a NetFlow CSV for
udp_flood, syn_flood, or icmp_flood
- the input is raw
real-data/<target>.json, canonical alerts.json, or reference-style events.json
- you want the script to preserve all existing CSV columns while only rewriting
label
Steps
- Normalize raw anomaly windows with
normalize_alerts.py.
- Merge windows into candidate events with
pre_denoise_alerts.py.
- If a paired CSV exists, run
relabel_netflows.py before the final judgment. This is mandatory for the final judgment path.
- Use
summarize_netflows.py for compact context or reporting, but do not treat it as a substitute for relabel_netflows.py when a paired CSV exists.
- Assemble event-level evidence from relabeled outputs, including
matched_flow_summary, bad_flow_summary, good_flow_summary, label_distribution, and artifact_refs.
- Run
judge_alerts.py only after the event and its evidence are ready. If a paired CSV exists, the final judgment input must include relabeled evidence rather than only light_evidence.
- In
judge_alerts.py, calculate two separate scores:
auto_handle_score for whether the event can be handled automatically
importance_score for event priority and sorting
- Decide:
credible if matched evidence is strong and auto_handle_score is high enough
suspicious if evidence is partial or mixed, or evidence is strong but auto_handle_score is not high enough
unsupported if evidence is missing, inconsistent, or pre-denoise has already filtered the event
- Return structured output with event evidence, automation, importance, and an evidence pack.
Output
Return:
{
"stage": "alert-judgment",
"status": "completed",
"event_count": 1,
"events": [
{
"event_id": "",
"target_ip": "",
"member_alert_count": 0,
"member_alert_ids": [],
"judgment": "credible | suspicious | unsupported",
"confidence": 0.0,
"judged_attack_type": "",
"reason": "",
"scale_band": "small | medium | large | unknown",
"automation": {
"auto_handle_score": 0.0,
"auto_handle_level": "low | medium | high",
"evidence_strength": 0.0,
"attack_certainty": 0.0,
"completeness_score": 0.0,
"minimum_required_score": 0.6
},
"importance": {
"target_value_score": 0.0,
"scale_score": 0.0,
"completeness_score": 0.0,
"importance_score": 0.0,
"importance_level": "low | medium | high"
},
"analyst_action": {
"mode": "auto_handle | manual_review | drop",
"note": ""
},
"evidence": {
"source_json": "",
"source_csv": "",
"event_window": {
"start_time": "",
"end_time": ""
},
"member_alerts": [],
"bad_flow_summary": {},
"good_flow_summary": {},
"other_attack_summaries": [],
"label_distribution": {},
"artifact_refs": {},
"mixed_attack_hints": [],
"matched_flow_summary": {
"matched_flow_count": 0,
"total_packets": 0,
"total_bytes": 0,
"unique_source_count": 0
},
"data_gaps": []
}
}
]
}