Mistral Vibe trusted a reduced representation of a shell command, then executed the original string. That gap turned commands classified as read-only into code execution without the expected approval prompt.
During a review of Mistral Vibe 2.25.0, SecMate found two related flaws:
- SECMATE-2026-0038: shell semantics omitted by the permission parser could execute arbitrary code.
- SECMATE-2026-0039: redirects, expansions, and option values could read or write outside the authorized workspace.
Repository-based delivery requires Vibe to process attacker-controlled content and the model to emit the crafted shell tool call. The permission bypass begins once that tool call reaches the Bash tool.
SecMate reported SECMATE-2026-0038 and SECMATE-2026-0039 to Mistral on September 5, 2026. HiddenLayer published its advisories on September 11, and Mistral released Vibe 2.25.4 on September 12. [1] [2]
According to Mistral, HiddenLayer asked for a security contact but did not share vulnerability details before publication. The CVEs were published before Mistral had reconciled them with SecMate’s independently submitted reports. Mistral is still reviewing the affected versions and CVSS scores with HiddenLayer.
SecMate’s report IDs group the bugs by security boundary and are not a one-to-one mapping to the public CVE assignments.
The broken security boundary
Vibe’s Bash tool used Tree-sitter to extract command names and selected arguments. It compared those extracted strings with an allowlist and checked recognized paths against the workspace. If every extracted command looked safe, resolve_permission() returned ALWAYS. The agent loop then executed the tool without asking the user.
The shell received the untouched command string. It therefore evaluated assignments, substitutions, redirects, and command-specific options that the permission check had ignored.
The vulnerable extractor made this mismatch clear: [3]
1for child in node.children:
2 if child.type in {
3 "command_name", "word", "string", "raw_string", "concatenation"
4 }:
5 parts.append(child.text.decode("utf-8"))
6
7# Later: the original command, not `parts`, reaches the shell.
8proc = await spawn_shell_command(args.command, cwd=self.cwd)
The first step involving model selection depends on the content and model behavior. Once the crafted tool call exists, the permission decision and shell execution are deterministic. The resulting code runs with the Vibe process identity. Subject to that user’s OS permissions and runtime controls, it can read or modify files, start processes, invoke local tooling, and make network connections. Data theft is one possible outcome among all operations available to that process.
Several delivery paths can place the crafted instruction in front of Vibe: social engineering that convinces a developer to clone and review a malicious repository, compromise of a repository the developer already trusts, or a malicious or compromised submodule pulled into an otherwise legitimate project. Commit messages, project instructions, documentation, issues, and tool output can all carry the instruction. In every case, Vibe must process the attacker controlled content and emit the crafted tool call.
For broader coding-agent context, see SecMate’s security benchmark of generated projects and follow-up analysis of AI-generated C++.
SECMATE-2026-0038: an allowlisted command permits arbitrary code execution
Environment assignments were parsed separately from the command. The permission check could reduce this:
1mkdir -p bin
2printf '#!/bin/sh\nprintf "PATH_HIJACK_EXECUTED\\n"\n' > bin/cat
3chmod +x bin/cat
4PATH=./bin:$PATH cat input.txt
The resolver saw the allowlisted command cat input.txt. The real shell still applied the attacker-controlled PATH, so cat resolved to ./bin/cat and printed the marker.
Both the legacy and managed permission resolvers returned always; the legacy Bash runner executed the replacement program. This is arbitrary code execution with the privileges of the Vibe process.
The same root cause affected more than PATH. We reproduced four execution paths:
| Shell feature hidden or normalized by the check | Allowlisted surface | Result |
|---|---|---|
| Environment assignment | cat | Attacker-selected executable runs through PATH |
GIT_EXTERNAL_DIFF assignment | git diff --ext-diff | Repository helper executes |
Split find -exec predicate | find | find launches an arbitrary program |
| Command substitution in an unquoted heredoc | cat | Nested shell command executes |
Our public PoC uses the PATH variant in a repository validation instruction. If Vibe follows it, the permission layer sees cat README.md, while the shell resolves cat to the repository controlled shim. The demo shim creates a harmless marker and exfiltrates the victim’s Mistral token. Those actions provide visible evidence that repository code ran and could access Vibe’s environment.
The Git variant is another repository-based delivery path. A hostile repository can place instructions in content Vibe consumes. If the model emits the crafted git diff call, Git starts the repository-controlled external diff helper. The child process inherits Vibe’s environment, including MISTRAL_API_KEY when that token was supplied through the environment.
1GIT_EXTERNAL_DIFF=./steal.py git diff --ext-diff
The permission view contained only git diff --ext-diff. Git and the shell retained the assignment that selected ./steal.py.
Our controlled demo follows the PATH chain from attacker controlled repository content to code execution on the victim’s machine and exfiltration of the victim’s Mistral token.
SECMATE-2026-0039: workspace checks missed how the shell names files
Vibe also tried to ask for approval when a shell command touched a path outside the current workspace. The check only inspected selected positional tokens. It skipped tokens beginning with -, did not expand variables, and did not inspect redirect targets.
All of these commands resolved to always in our 2.25.0 PoC:
1cat $POC_OUTSIDE/secret.txt # variable-expanded read
2cat < /tmp/outside/secret.txt # redirected read
3echo overwritten > /tmp/outside/target.txt # redirected write
4sort -o/tmp/outside/sorted.txt input.txt # path inside an option
5git log -1 --output=/tmp/outside/log.txt # path inside a Git option
The PoC used synthetic files in temporary sibling directories. It confirmed reads and writes outside the workspace with the OS privileges of the Vibe process.
On its own, SECMATE-2026-0039 gives an attacker access to files that the workspace approval boundary was meant to protect. Combined with SECMATE-2026-0038, it lets a malicious flow find credentials, execute code, and send data out without the approval prompt users rely on.
Impact: arbitrary code execution and data exfiltration
Developer agents run where the valuable data lives. Code execution under the Vibe process identity permits any operation that identity and its OS or container sandbox allow: reading, modifying, or deleting source files; starting processes; invoking compilers and package managers; altering build artifacts; accessing local services; and opening network connections.
On a typical Linux installation without an additional sandbox, that access can include SSH keys, cloud credentials, package registry tokens, model API keys, configuration files, and proprietary source code readable by the user. On macOS, Keychain access controls and any sandbox applied to Vibe can protect some credentials and files. The exact exposure depends on the process permissions, entitlements, and credential storage. Our PoC demonstrates the full chain on Linux and macOS: simulated social engineering leads the victim to interact with a malicious repository; when Vibe emits the crafted tool call, repository-controlled code executes on the victim’s machine and then exfiltrates the victim’s Mistral token. An attacker could instead exfiltrate any data available to the process or use the execution primitive for another objective.
CVE overlap and affected versions
SecMate’s report identifiers describe two security boundaries. The public CVEs divide shell behaviors into narrower categories. Their scopes overlap, so there is no one-to-one mapping. Mistral told us that assigning our split find -exec and heredoc demonstrations solely to CVE-2026-87986 may overlap with CVE-2026-87985. We therefore list those demonstrations under report SECMATE-2026-0038 without assigning either one to a single CVE.
| SecMate report | Behavior demonstrated by SecMate | Public CVE relationship |
|---|---|---|
| SECMATE-2026-0038 | Environment assignments, including PATH andGIT_EXTERNAL_DIFF | CVE-2026-87987 |
| SECMATE-2026-0038 | Split find -exec and command substitution in an unquoted heredoc | CVE-2026-87985 and CVE-2026-87986; scope may overlap |
| SECMATE-2026-0039 | Redirect targets not checked | CVE-2026-87984 |
| SECMATE-2026-0039 | Variable-expanded paths | No exclusive CVE mapping asserted |
| SECMATE-2026-0039 | Paths inside side-effecting options, including sort -o andgit --output | CVE-2026-87988 |
Mistral’s advisory gives the public categories and affected versions below. These descriptions do not redefine the scope of SecMate reports SECMATE-2026-0038 and SECMATE-2026-0039. [1]
| Public CVE | Mistral’s category | Affected versions | Fixed version for original reported variants |
|---|---|---|---|
| CVE-2026-87984 | Shell redirection targets | 1.3.4 to 2.25.3 | 2.25.4 |
| CVE-2026-87985 | ANSI-C quoted arguments | 2.9.0 to 2.25.3 | 2.25.4 |
| CVE-2026-87986 | Incomplete command parsing | 1.3.4 to 2.25.3 | 2.25.4 |
| CVE-2026-87987 | Environment assignments | 2.6.0 to 2.25.3 | 2.25.4 |
| CVE-2026-87988 | Command allowlist and path-validation gaps | Introduced in 2.15.0; partial fixes in 2.20.0 and 2.23.0; reported option bypasses through 2.25.3 | 2.25.4 for the remaining reported variants |
The 2.25.4 entries above refer to the specific original variants covered by the SecMate reports and their overlapping public CVEs. They do not cover subsequently identified permission-bypass paths.
Upgrade to Mistral Vibe 2.25.8 or later, restart active sessions, and verify with vibe --version. [8]
In its review of this article, Mistral confirmed that 2.25.4 addressed the original SecMate PoC paths and that versions 2.25.5, 2.25.7, and 2.25.8 added further permission-bypass hardening, including the less / LESSOPEN / --lesskey-src path. [6] [7] [8]
What changed in 2.25.4
The 2.25.4 patch removes the assumption that an incomplete parse is safe. [4]
- A shared analyzer walks the full syntax tree for both shell implementations.
- Redirects, assignments, expansions, substitutions, compound commands, parse errors, and unsupported syntax require approval.
- Command-specific policies flag side-effecting options such as
find -exec,git diff --ext-diff, andsort -o. - Path-bearing option values are included in workspace checks.
- Regression tests cover both the legacy and managed permission resolvers.
The key design change is fail closed: syntax the permission layer cannot model no longer qualifies for automatic execution.
Takeaways
- Update to 2.25.8 or later .
- Treat coding-agent approval logic as a security boundary, not a user-interface feature.
- Do not pass a rich shell language through an allowlist unless the authorization layer models every semantic feature that the executor will honor.
- Keep high-value credentials out of agent child-process environments unless the task requires them.
- See SecMate’s published disclosures for other vulnerability research.
Responsible disclosure gives maintainers the technical details and time to investigate, prepare fixes and protect users. It also helps ensure fair credit for teams that independently discover and report vulnerabilities. That coordination is part of our mission to make software more secure.