Skip to content

Commit 925ed5a

Browse files
Ryanmello07claude
andcommitted
fix(routing): clamp negative StallEvents and bound penalty to prevent corruption rewards
Guard StallEvents against negative/underflowed counters (corrupt telemetry) which would subtract negative values and add a reward. Cap stallPenalty at 3.0 to bound scores: prevents hostile telemetry from pushing score arbitrarily negative and keeps normal stall-count ordering (0 < 1 < 5) monotonic within realistic ranges. Add comprehensive test assertions: negative stalls do not score higher than 0, cannot win at hysteresis, ordering is preserved, and extreme stalls are bounded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PT7KcWCPKfFwQUc7SM3oZY
1 parent 5b6324a commit 925ed5a

2 files changed

Lines changed: 62 additions & 1 deletion

File tree

routing_score.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,20 @@ func exitScore(m ExitMetrics, w ScoreWeights) float64 {
7373
}
7474
goodputGood := goodput / (goodput + 1e6) // 1MB/s → 0.5
7575

76-
stallPenalty := float64(m.StallEvents) * 0.1
76+
// Clamp StallEvents: negative values (corrupt/underflowed counter) must never
77+
// become a reward. Cap the penalty to prevent unboundedly negative scores:
78+
// max positive contribution is ~3.0 (perfect RTT + Goodput + Jitter), so
79+
// capping stallPenalty at 3.0 prevents hostile telemetry from pushing score
80+
// below -3.0. This caps StallEvents at 30; normal ranges (<10) remain
81+
// monotonically ordered.
82+
stallCount := m.StallEvents
83+
if stallCount < 0 {
84+
stallCount = 0
85+
}
86+
stallPenalty := float64(stallCount) * 0.1
87+
if stallPenalty > 3.0 {
88+
stallPenalty = 3.0
89+
}
7790
return w.Rtt*rttGood + w.Goodput*goodputGood + w.Jitter*jitterGood - w.Stall*stallPenalty
7891
}
7992

routing_score_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,51 @@ func TestExitScoreSanitizesHostileInputs(t *testing.T) {
133133
}
134134
}
135135
}
136+
137+
func TestExitScoreGuardsStallEvents(t *testing.T) {
138+
w := classWeights(ClassLatency)
139+
140+
// Baseline: healthy exit with 0 stalls.
141+
healthy := exitScore(ExitMetrics{RttMillis: 50, GoodputBytesPerSec: 1e6, Jitter: 20, StallEvents: 0}, w)
142+
143+
// Assertion 1: Negative StallEvents must not score higher than 0.
144+
// Corrupt counter claiming -50 stalls must not become a reward.
145+
negativeStalls := exitScore(ExitMetrics{RttMillis: 50, GoodputBytesPerSec: 1e6, Jitter: 20, StallEvents: -50}, w)
146+
if negativeStalls > healthy {
147+
t.Errorf("negative stalls: score=%f should not be > healthy=%f", negativeStalls, healthy)
148+
}
149+
150+
// Assertion 2: Negative StallEvents must not win at 10% hysteresis.
151+
if challengerWins(healthy, negativeStalls, 10) {
152+
t.Errorf("negative stalls should not win challengerWins against healthy at 10%%, score=%f vs incumbent=%f",
153+
negativeStalls, healthy)
154+
}
155+
156+
// Assertion 3: Ordering preserved across normal stall counts (0 < 1 < 5).
157+
stall1 := exitScore(ExitMetrics{RttMillis: 50, GoodputBytesPerSec: 1e6, Jitter: 20, StallEvents: 1}, w)
158+
stall5 := exitScore(ExitMetrics{RttMillis: 50, GoodputBytesPerSec: 1e6, Jitter: 20, StallEvents: 5}, w)
159+
160+
if !(healthy > stall1) {
161+
t.Errorf("ordering: 0 stalls=%f should be > 1 stall=%f", healthy, stall1)
162+
}
163+
if !(stall1 > stall5) {
164+
t.Errorf("ordering: 1 stall=%f should be > 5 stalls=%f", stall1, stall5)
165+
}
166+
167+
// Assertion 4: Extreme stall count produces bounded, finite score.
168+
// Cap is at 3.0 penalty, so stalls above 30 should all give the same score.
169+
stall30 := exitScore(ExitMetrics{RttMillis: 50, GoodputBytesPerSec: 1e6, Jitter: 20, StallEvents: 30}, w)
170+
stall100 := exitScore(ExitMetrics{RttMillis: 50, GoodputBytesPerSec: 1e6, Jitter: 20, StallEvents: 100}, w)
171+
172+
if math.IsNaN(stall30) || math.IsInf(stall30, 0) {
173+
t.Errorf("stall30 returned non-finite: %f", stall30)
174+
}
175+
if math.IsNaN(stall100) || math.IsInf(stall100, 0) {
176+
t.Errorf("stall100 returned non-finite: %f", stall100)
177+
}
178+
// Both should be identical (capped).
179+
if stall30 != stall100 {
180+
t.Errorf("both stall30=%f and stall100=%f should be capped identically, not %f",
181+
stall30, stall100, stall100-stall30)
182+
}
183+
}

0 commit comments

Comments
 (0)