fix(mqtt): SparkplugCodec's never-throw guard must span the projection too

The try/catch stopped at Payload.Parser.ParseFrom, leaving the metric-projection
loop outside it — so the "never throws for any input" contract had a hole in
exactly the part that walks an attacker-shaped object graph. Widened to cover
parse AND projection.

Self-review finding; no test reddened, because a projection escape needs a
generated-code shape the vectors do not produce. That is the point: the guard is
the contract, not the observed behaviour of today's inputs.

Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
Joseph Doherty
2026-07-24 21:10:20 -04:00
parent 2589774480
commit c164ec3da1
@@ -74,19 +74,9 @@ public static class SparkplugCodec
return false; return false;
} }
Payload proto;
try try
{ {
proto = Payload.Parser.ParseFrom(wire); var proto = Payload.Parser.ParseFrom(wire);
}
catch (Exception)
{
// Deliberately broad. InvalidProtocolBufferException covers malformed, truncated and
// over-nested input, but this is the dispatcher-thread boundary: the contract is a
// verdict for EVERY input, and narrowing the catch would trade that guarantee for a
// taxonomy nobody downstream can act on.
return false;
}
var metrics = proto.Metrics.Count == 0 var metrics = proto.Metrics.Count == 0
? [] ? []
@@ -105,6 +95,18 @@ public static class SparkplugCodec
return true; return true;
} }
catch (Exception)
{
// Deliberately broad, and deliberately spanning the projection as well as the parse.
// InvalidProtocolBufferException covers malformed, truncated and over-nested input, but
// this is the dispatcher-thread boundary and the projection walks an attacker-shaped
// object graph: the contract is a verdict for EVERY input, so a guard that stopped at
// the parse would be a contract with a hole in it. Narrowing the catch would trade the
// guarantee for a taxonomy nobody downstream can act on.
payload = SparkplugPayload.Invalid;
return false;
}
}
/// <summary> /// <summary>
/// Decodes Sparkplug-B wire bytes, returning <see cref="SparkplugPayload.Invalid"/> when they /// Decodes Sparkplug-B wire bytes, returning <see cref="SparkplugPayload.Invalid"/> when they