GitHub Advisoryadvisory · Go

GHSA-cqxr-jxr2-85pq Dasel: Unbounded recursion in JSON and XML readers causes unrecoverable stack-overflow DoS

MEDIUM Published 2026-09-22 Modified 2026-09-22 Official page ↗
Aliases
CVE-2026-59168
Ecosystem
Go
Packages
github.com/tomwright/dasel/v3
Severity
Moderate
CVSS vector
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weaknesses
CWE-674
Reviewed
2026-09-22

Affected packages

• Go github.com/tomwright/dasel/v3: >= 3.0.0, < 3.11.1 → fixed in 3.11.1

Description

Summary `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). Affected Versions `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. Description JSON reader @ `parsing/json/json_reader.go` `decodeValue` (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. XML reader @`parsing/xml/reader.go` `parseElement` (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 `` is 3 bytes, so the 10 MB cap still permits ~3.3 M nesting levels, exhausting the stack long before the size limit fires. Reachability (both) Both are on the primary public read path: `parsing.Format().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. Precedent in this codebase 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. Proof of Concept Single runnable program, public API only (`poc/main.go`, module wired to a local clone via `replace`): ```go 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("", 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 -> 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`. • The deferred `recover()` never fires in either case — the process exits. • CLI equivalents: `printf '%.0s' {1..3200000} | dasel -r xml`. Impact 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. Suggested Fix Add a recursion-depth guard to both readers, mirroring the YAML reader's existing `maxExpansionDepth` / `ErrYamlExpansionDepthExceeded` pattern: • JSON — thread a `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. • XML — add a `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.

Related

Alias · CVE (1)

  • CVE-2026-59168 Dasel is a command-line tool and library for querying, modifying, and transforming data structures. vulnerability

Weakness · CWE (1)

  • CWE-674 Uncontrolled Recursion weakness

References