GHSA-cqxr-jxr2-85pq
Dasel: Unbounded recursion in JSON and XML readers causes unrecoverable stack-overflow DoS
Quick fix
GHSA-cqxr-jxr2-85pq — github.com/tomwright/dasel/v3: upgrade to the fixed version with the command below.
go get github.com/tomwright/dasel/v3@v3.11.1Details
## 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 `<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.
### Reachability (both)
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.
### 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("<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`. - The deferred `recover()` never fires in either case — the process exits. - CLI equivalents: `printf '<a>%.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.
Are you affected?
Enter the version of the package you're using.
Affected packages
3.0.0Fixed in: 3.11.1go get github.com/tomwright/dasel/v3@v3.11.1References
- https://github.com/TomWright/dasel/security/advisories/GHSA-cqxr-jxr2-85pq[WEB]
- https://nvd.nist.gov/vuln/detail/CVE-2026-59168[ADVISORY]
- https://github.com/TomWright/dasel/commit/4c91d0d02dc59ce8404709b1bfed7a6fabe62f68[WEB]
- https://github.com/TomWright/dasel[PACKAGE]
- https://github.com/TomWright/dasel/releases/tag/v3.11.1[WEB]