CVE-2026-67987 Overview
CVE-2026-67987 is a polynomial-time regular expression denial-of-service (ReDoS) vulnerability in the crmne/ruby_llm Ruby library. The flaw affects commit fa6f279847d6d7027814539d9c0dfc3bbdfd2a83 when running on Ruby 3.1.x. A crafted or anomalous model response containing many unterminated <think> tags forces two consecutive regular expressions in the chat-completion parser to consume excessive CPU. The result delays chat-completion processing and can starve the host process of resources.
Critical Impact
A malicious or malformed LLM response can trigger CPU exhaustion in ruby_llm chat-completion parsing, degrading availability of any Ruby service that relays untrusted model output.
Affected Products
- crmne/ruby_llm at commit fa6f279847d6d7027814539d9c0dfc3bbdfd2a83
- Ruby runtime version 3.1.x
- Downstream Ruby applications performing chat-completion parsing through ruby_llm
Discovery Timeline
- 2026-09-29 - CVE-2026-67987 published to NVD
- 2026-09-29 - Last updated in NVD database
Technical Details for CVE-2026-67987
Vulnerability Analysis
The ruby_llm library parses assistant responses to separate visible content from reasoning traces wrapped in <think>...</think> tags. The parsing logic in lib/ruby_llm/protocols/chat_completions/chat.rb at lines 355–356 applies two consecutive regular expressions to the raw response string. When the response contains many unterminated <think> tags, the regex engine exhibits polynomial time complexity while attempting to match nested and repeated patterns.
The vulnerability is an algorithmic complexity issue rather than a memory-safety flaw. Any caller that forwards untrusted model output through extract_content_and_thinking can trigger the condition. Because Ruby processes are typically single-threaded per request, sustained CPU exhaustion blocks legitimate chat-completion traffic and can cascade into broader service degradation.
Root Cause
The root cause is unbounded backtracking in the two consecutive regular expressions used to extract <think> tag content from string responses. The parser assumed well-formed model output and did not cap iteration or reject malformed sequences of opening tags without closures. Streaming accumulation logic in lib/ruby_llm/stream_accumulator.rb compounded the issue by tracking @inside_think_tag and @pending_think_tag state that fed additional partial data into the same regex pipeline.
Attack Vector
An attacker who controls or influences model responses (for example, through prompt injection, a malicious model server, or a compromised upstream provider) can return a payload with numerous unterminated <think> tags. The Ruby host consuming that response invokes the vulnerable parser and burns CPU cycles proportional to the number of malformed tags. Repeated requests amplify the effect into a denial-of-service condition.
# Security patch in lib/ruby_llm/protocols/chat_completions/chat.rb
# Stop parsing <think> tags out of assistant content
end
def extract_content_and_thinking(content)
- return extract_think_tag_content(content) if content.is_a?(String)
return [content, nil] unless content.is_a?(Array)
text = extract_text_from_blocks(content)
# Source: https://github.com/crmne/ruby_llm/commit/5e88411f171721b381853fa77d254e266dcf6ad8
# Security patch in lib/ruby_llm/stream_accumulator.rb
# Removes think-tag state tracking from the streaming accumulator
@raw_content = nil
@raw_reasoning = nil
@finish_reason = nil
- @inside_think_tag = false
- @pending_think_tag = +''
@latest_tool_call_id = nil
@tool_call_ids_by_index = {}
end
# Source: https://github.com/crmne/ruby_llm/commit/5e88411f171721b381853fa77d254e266dcf6ad8
Detection Methods for CVE-2026-67987
Indicators of Compromise
- Sustained single-core CPU saturation in Ruby worker processes correlated with inbound LLM responses.
- Chat-completion request latency spikes without matching increases in downstream model provider latency.
- Assistant response payloads containing repeated <think> substrings with no matching </think> closures.
Detection Strategies
- Log the raw byte length and <think> tag count of each model response before invoking extract_content_and_thinking, and alert on outliers.
- Add per-request wall-clock timers around chat-completion parsing and flag calls that exceed a millisecond threshold.
- Inspect application performance monitoring traces for stack frames stuck inside regex evaluation from chat.rb:355-356.
Monitoring Recommendations
- Track CPU-per-request metrics on Ruby services that consume ruby_llm and baseline against typical response processing times.
- Enable rate-limit and timeout alerts for chat-completion endpoints to catch amplification patterns.
- Capture and retain anomalous model responses for offline analysis when parser latency alerts fire.
How to Mitigate CVE-2026-67987
Immediate Actions Required
- Upgrade ruby_llm past commit fa6f279847d6d7027814539d9c0dfc3bbdfd2a83 to a release containing commit 5e88411f171721b381853fa77d254e266dcf6ad8.
- Apply request-level timeouts around any code path that parses model responses.
- Audit application logs for prior chat-completion latency anomalies that may indicate exploitation attempts.
Patch Information
The upstream fix is GitHub Commit 5e88411, which removes <think> tag parsing from string assistant content in lib/ruby_llm/protocols/chat_completions/chat.rb and strips the @inside_think_tag and @pending_think_tag state from lib/ruby_llm/stream_accumulator.rb. Review the affected code snippet in Chat.rb and the ruby_llm repository for release details.
Workarounds
- Wrap chat-completion parsing in a Timeout.timeout block with a strict upper bound while the patch is deployed.
- Reject or truncate assistant responses whose length or <think> tag count exceeds a defined threshold before handing them to the parser.
- Route model responses through an intermediary sanitization step that strips unmatched <think> tags.
# Configuration example: pin ruby_llm to a patched revision in Gemfile
bundle update ruby_llm
bundle info ruby_llm | grep -i revision
# Verify the deployed commit hash is at or after 5e88411f171721b381853fa77d254e266dcf6ad8
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

