| name | deserialization-review |
| description | Use when reviewing JSON/YAML/protobuf/binary deserialization paths for panic-on-unknown, recursion depth, size limits, untrusted-input boundaries, and serde flatten footguns. |
Deserialization Safety Review
Decision notes and checklists for auditing deserialization paths in Edgion.
Threat Model
Untrusted or semi-trusted input can cross a deserialization boundary in four places:
- Admin API request bodies — JSON posted to Controller or Gateway admin endpoints. Attacker-controlled if the admin endpoint is exposed without proper auth.
- CRD
spec from Kubernetes — Any user with kubectl apply rights can craft arbitrary YAML field values. The K8s API server validates CRD schema, but serde deserialization happens again in the Controller.
- gRPC streams from Controller / Center — Config sync messages carrying serialized resource payloads. Attacker-controlled if the control plane is compromised or if a malicious controller is substituted.
- DSL bytecode load —
CompiledScript::deserialize_base64 reads base64-encoded serde-json bytecode delivered via K8s CR or gRPC. This is the highest-risk path: a crafted payload can trigger serde-json panics, OOM, or Constant::Regex recompilation (ReDoS). See dsl-bytecode-deserialize-safety.md.
General Principles
- Size limits before deserialization: reject oversized payloads at the framing layer (HTTP body length, gRPC message size, base64 string length) before calling
from_slice / from_str. Do not rely on serde to bound depth or size.
- Recursion depth: serde-json does not enforce stack depth by default. Deeply nested JSON objects can overflow the stack. For untrusted JSON, prefer
serde_json::from_reader with a custom depth-limited deserializer or pre-validate nesting depth.
#[serde(deny_unknown_fields)]: use on internal structs that should be stable and closed. Omit on user-facing extension points. Missing this on an internal struct causes silent field drops that may mask attacks.
#[serde(flatten)] footguns: flattening a HashMap<String, Value> absorbs unknown fields silently. Review every flatten to confirm the absorb-all behavior is intentional.
- Regex recompilation on deserialize: any struct that contains a compiled regex and derives
serde::Deserialize will recompile on every deserialization. Add size_limit / dfa_size_limit via regex::RegexBuilder to bound ReDoS risk.
- Error handling: deserialization errors must return a structured error to the caller, not panic. Verify call sites use
? or match the Result; bare .unwrap() on a deserialization result is a P0 finding.
Coding rule references: see skills/03-coding/02-rust-coding-rules.md Rule 2 (no bare unwrap) and Rule 5 (graceful degrade in long-running paths).
Known Rules in This Directory
Usage
When a code review surfaces a deserialization call:
- Identify which threat-model bucket the call belongs to (see above).
- Check that size limits exist at the framing layer.
- Check that the error path does not panic.
- Check for regex fields in the deserialized struct.
- Consult the rule file for the specific subsystem if one exists.