Hello, I was trying to make bat highlight files with uv run shebangs and an agent wrote a recursive syntax definition that crashes MacOS due to unbounded memory consumption. I thought you'd probably want to protect against this here or in syntect.
What steps will reproduce the bug?
The agent wrote this:
DON'T ACTUALLY USE THIS
%YAML 1.2
---
name: Python (uv script)
file_extensions: []
scope: source.python
first_line_match: '^#!.*\buv\s+run\b.*--script\b'
contexts:
main:
- include: scope:source.python
Here's an agent-written repro
A custom syntax whose scope: collides with the syntax it includes causes unbounded memory allocation (kernel panic)
⚠️ Do not run the repro below unsandboxed. On my machine bat allocated ~6 GB/s and twice took down the whole OS (macOS watchdog kernel panic, 48 GB RAM). The repro as written is bounded with timeout.
Summary
If a custom .sublime-syntax declares a scope: that is the same as the scope it pulls in via include: scope:..., the include resolves to the syntax itself. bat cache --build accepts this without complaint, and then highlighting any file that maps to that syntax allocates without bound until the machine dies. No output is ever produced.
I hit this accidentally — the offending 8-line syntax file was written by an LLM agent trying to add #!/usr/bin/env -S uv run --script shebang detection, and it took my laptop down twice before I found it. A malformed syntax definition shouldn't be able to do that.
Versions
bat 0.26.1 (Homebrew bottle), which vendors syntect 5.3.0
- macOS 26.6 (25G72), arm64 (M5 Pro, 48 GB)
Repro
R=$(mktemp -d); mkdir -p "$R/config/syntaxes" "$R/cache"
cat > "$R/config/syntaxes/loop.sublime-syntax" <<'YAML'
%YAML 1.2
---
name: Loopy Python
file_extensions: [loopy]
scope: source.python # <-- same scope as the bundled Python syntax
contexts:
main:
- include: scope:source.python # <-- therefore resolves to *this* syntax
YAML
bat cache --build --source "$R/config" --target "$R/cache" # succeeds, no warning
printf 'x = 1\n' > "$R/t.loopy"
# BOUNDED: kill after 2s. Without the timeout this eats all RAM.
BAT_CACHE_PATH="$R/cache" timeout 2 bat --color=always --paging=never "$R/t.loopy"
Sampling RSS of the bat process during those 2 seconds on a one-line input file:
t=250ms: 10 MB
t=500ms: 2388 MB
t=750ms: 3988 MB
t=1000ms: 5568 MB
t=1250ms: 7118 MB
It never emits the highlighted line. Left alone it reached 26.7 GB and 41.1 GB resident in two separate incidents before the kernel watchdog panicked (Compressor Info: 100% of compressed pages limit (BAD)), taking the machine down.
Control
Changing only the scope: line so the include is no longer self-referential makes it work correctly and instantly:
scope: source.python.uv
contexts:
main:
- include: scope:source.python
Expected
bat cache --build should reject (or at minimum warn about) a syntax whose context graph reaches itself through include: scope:, rather than producing a cache that makes bat unrunnable on the affected file types. Failing that, the highlighter should bail out with an error instead of allocating without bound.
Note that syntect 5.3.0 already contains a fix for #597 ("infinite loop when a syntax definition continually pushes contexts without consuming any input"), so this appears to be a distinct case that the existing guard doesn't cover.
What happens?
OOMs MacOS
What did you expect to happen instead?
Something less dramatic
How did you install bat?
homebrew
bat version and environment
bat 0.26.1
Software version
bat 0.26.1
Operating system
- OS: macOS 26.6
- Kernel: 25.6.0
Command-line
bat --style header,grid --theme GitHub --diagnostic
Environment variables
BAT_CACHE_PATH=<not set>
BAT_CONFIG_PATH=<not set>
BAT_OPTS=<not set>
BAT_PAGER=<not set>
BAT_PAGING=<not set>
BAT_STYLE=<not set>
BAT_TABS=<not set>
BAT_THEME=GitHub
COLORTERM=truecolor
LANG=C.UTF-8
LC_ALL=en_US.UTF-8
LESS=-IR
MANPAGER=<not set>
NO_COLOR=<not set>
PAGER=<not set>
SHELL=/opt/homebrew/bin/zsh
TERM=tmux-256color
XDG_CACHE_HOME=/Users/dan/.cache
XDG_CONFIG_HOME=/Users/dan/.config
System Config file
Could not read contents of '/etc/bat/config': No such file or directory (os error 2).
Config file
Could not read contents of '/Users/dan/.config/bat/config': No such file or directory (os error 2).
Custom assets metadata
Could not read contents of '/Users/dan/.cache/bat/metadata.yaml': No such file or directory (os error 2).
Custom assets
'/Users/dan/.cache/bat' is empty
Compile time information
- Profile: release
- Target triple: aarch64-apple-darwin
- Family: unix
- OS: macos
- Architecture: aarch64
- Pointer width: 64
- Endian: little
- CPU features: aes,crc,dit,dotprod,dpb,dpb2,fcma,fhm,flagm,fp16,frintts,jsconv,lor,lse,neon,paca,pacg,pan,pmuv3,ras,rcpc,rcpc2,rdm,sb,sha2,sha3,ssbs,vh
- Host: aarch64-apple-darwin
Less version
> less --version
less 668 (POSIX regular expressions)
Copyright (C) 1984-2024 Mark Nudelman
less comes with NO WARRANTY, to the extent permitted by law.
For information about the terms of redistribution,
see the file named README in the less distribution.
Home page: https://greenwoodsoftware.com/less
Hello, I was trying to make bat highlight files with
uv runshebangs and an agent wrote a recursive syntax definition that crashes MacOS due to unbounded memory consumption. I thought you'd probably want to protect against this here or in syntect.What steps will reproduce the bug?
The agent wrote this:
Here's an agent-written repro
A custom syntax whose
scope:collides with the syntax itincludes causes unbounded memory allocation (kernel panic)batallocated ~6 GB/s and twice took down the whole OS (macOS watchdog kernel panic, 48 GB RAM). The repro as written is bounded withtimeout.Summary
If a custom
.sublime-syntaxdeclares ascope:that is the same as the scope it pulls in viainclude: scope:..., the include resolves to the syntax itself.bat cache --buildaccepts this without complaint, and then highlighting any file that maps to that syntax allocates without bound until the machine dies. No output is ever produced.I hit this accidentally — the offending 8-line syntax file was written by an LLM agent trying to add
#!/usr/bin/env -S uv run --scriptshebang detection, and it took my laptop down twice before I found it. A malformed syntax definition shouldn't be able to do that.Versions
bat0.26.1 (Homebrew bottle), which vendorssyntect5.3.0Repro
Sampling RSS of the
batprocess during those 2 seconds on a one-line input file:It never emits the highlighted line. Left alone it reached 26.7 GB and 41.1 GB resident in two separate incidents before the kernel watchdog panicked (
Compressor Info: 100% of compressed pages limit (BAD)), taking the machine down.Control
Changing only the
scope:line so the include is no longer self-referential makes it work correctly and instantly:Expected
bat cache --buildshould reject (or at minimum warn about) a syntax whose context graph reaches itself throughinclude: scope:, rather than producing a cache that makesbatunrunnable on the affected file types. Failing that, the highlighter should bail out with an error instead of allocating without bound.Note that
syntect5.3.0 already contains a fix for #597 ("infinite loop when a syntax definition continually pushes contexts without consuming any input"), so this appears to be a distinct case that the existing guard doesn't cover.What happens?
OOMs MacOS
What did you expect to happen instead?
Something less dramatic
How did you install
bat?homebrew
bat version and environment
bat 0.26.1Software version
bat 0.26.1
Operating system
Command-line
Environment variables
System Config file
Could not read contents of '/etc/bat/config': No such file or directory (os error 2).
Config file
Could not read contents of '/Users/dan/.config/bat/config': No such file or directory (os error 2).
Custom assets metadata
Could not read contents of '/Users/dan/.cache/bat/metadata.yaml': No such file or directory (os error 2).
Custom assets
'/Users/dan/.cache/bat' is empty
Compile time information
Less version