Stable-Edge Filtering
7 July 2026 · UvA × KPMG Cyber
Keyword prompts, not a script. Glance, then talk in your own words. Swipe up through the cards in sync with the deck. [→] marks a press that reveals the next beat; the italic line at the bottom of each card is what to say as you move on.
- ceiling 0.490 SAGE / 0.512 RF, p 0.037
- maintenance 0.448 → 0.358, Δ −0.089, p 0.027, 8/10
- controls: random +0.051 · byte-volume −0.060 · phase-local 0.000
- cadence rep 1.000 every scenario (plain RF, steady-trained)
- re-filter cadence 1.000 → 0.793, Δ −0.207, p 0.031
- jitter 0.956–1.000, penalty −0.163, p 0.084, not significant
Checkpoints: S07 by ~4:05 · S11 by ~7:30
Hard cap 11:00 · target 10:32
Drop if behind: S09 leave-one-out · S11 clustering line
Title and welcome
- name · thesis title
- supervisors Papagianni & Hsu · KPMG Cyber
"Let me start with why this problem matters."
You cannot scan a running plant
- critical infra: grids, water, factories
- devices never stop talking
- can't safely scan (probing disturbs the process)
- so listen passively, ID each device from its traffic
"So we listen. But the traffic we hear is messy, and that is where the idea under test comes in."
Real traffic is messy, so clean it first?
- traffic = stable (HMI polls PLC, recurs) + transient (sessions, logins, scans)
- prior work deletes transient "to improve precision", assumes it helps, untested
- →HYPOTHESIS: removing non-persistent edges → better + more change-robust
- filter = keep edge only if it recurs in ≥ θ of windows · (W, θ) = 5 min, 0.8, fixed
"To test that hypothesis, I broke it into concrete questions."
One main question, three sub-questions
- MAIN Q: does deleting non-persistent edges improve classification + robustness under change?
- →0 baseline (not an RQ): filter removes nothing on steady → the reference point
- →RQ1 robustness under change
- →RQ2 location: own features or neighbours'?
- →RQ3 a better representation, without deleting
"Answering these needs a controlled environment, so I built one."
A virtual bottling factory
- 20 hosts, 5 classes × 4: controllers, supervisory, engineering, historians, IT gateways
- Modbus + S7 · one passive tap, no scanning
- 4 scenarios: maintenance, onboarding, drift, noise · edge-level ground truth
- train on steady, judge on unseen hosts · 10 × 10 seeds
"With the lab in place, let me set a baseline before I touch the filter."
Does the graph even help?
- 2 checks on steady: (1) filter removes nothing → reference point
- (2) does the graph help? same 6 features: GraphSAGE vs graph-free RF
- →RF matches GNN on unseen hosts: 0.512 vs 0.490, p 0.037
- graph = no measurable benefit → everything after holds for ANY classifier
"So the graph is settled. Now the real question: does the filter help under change?"
Helps nowhere, hurts the one outage
- filter helps nowhere
- →every bar ≤ 0, never improves (steady/onboard 0 · drift −0.020 n.s. · noise +0.004)
- →maintenance = the one real outage: 0.448 → 0.358, Δ −0.089, p 0.027, 8/10, significant
- helps nowhere, hurts exactly where the outage is
"That penalty under maintenance is the surprise, so let me show you exactly why it happens."
Delete the polls, it looks like a PC
- a controller = defined by its inbound polls (sup, eng, hist)
- →pause it → polls stop recurring, 22/30 ≈ 0.73 < 0.8 → filter deletes them
- →in-degree 20 → 0, bytes 2.1 M → 0 → all-zero = idle IT box, recall 0.92 → 0.70
- RQ2: harm is in the host's own features → every classifier shares it (RF −0.104)
"That is the mechanism. But is it real, or just an artefact of removing edges?"
Same count removed, four ways
- stable-edge (least-repeating) → hurts −0.089
- random → harmless +0.051 (so not "any removal hurts")
- →byte-volume (lowest-traffic) → also hurts −0.060 · phase-local (ideal) → removes nothing
- →DROPleave-one-controller-out: all on paused plc-1, −0.229 vs ~−0.037
- VERDICT: which edges you delete, not how many
"So deletion is the wrong tool. What is the right one?"
Three ways, only one works
- delete flaky edges (the filter) → ✗ erases what defines the device
- →temporal GNN, same windows → ✗ null 0.453 vs 0.413, p 0.44, windows flat (95% of cells)
- →rebuild windows from the protocol's messages → ✓ each class has its own rhythm
"Let me show you what that third approach actually buys."
Measure the rhythm, a simple model nails it
- W = 60 s from Modbus log, count messages/sec · rhythms: sup ~1/s · hist 0.5/s · eng 0.2/s + writes
- →old ceiling 0.490–0.512
- →plain RF, steady-trained → 1.000 every scenario (+0.51, ladder within 0.03)
- DROPclustering recovers classes, ARI 0.31 → 0.78
- →re-apply the SAME old filter → breaks again 1.000 → 0.793, Δ −0.207, p 0.031
- deleting is the problem, not the representation
"A perfect score deserves scrutiny, so let me be honest about what it means."
A mechanism, not a field number
- 1.000 = mechanism, not a field number
- lab rhythms exactly constant → perfect · field jittered/overlapping → a margin, not certainty
- stress test: jitter the cadences → still 0.956–1.000
- filter penalty weaker −0.163, p 0.084, no longer significant
- magnitude also lab-specific (single-hub topology)
"Let me pull the contributions together."
Four contributions
- 1 · reproducible lab + 4 scenarios + ground truth (released)
- 2 · controlled negative result: content-agnostic filtering is fragile
- 3 · plain explanation of why it fails, and when the inverse works
- 4 · the representation, not the model, is decisive
- future: harder cadence · learned filter · real NDA trace · W sweep · retrain-on-filtered
"And if you take one thing away, this is it."
The conclusion, in one sentence
- deleting messy traffic to clean the graph throws away the evidence that identifies a device → the fix is a better representation, not a cleaner graph
- practice: telemetry first, aggregation second, deletion not at all
- thank you · questions