Agent, Git, and file execution-boundary batch¶
Source: GitHub Security Advisories, published/updated 2026-05-05.
This batch is durable because it repeats a modern tooling lesson: developer helpers, MCP tools, Git libraries, media processors, and package wrappers become execution boundaries whenever untrusted input can choose paths, command arguments, submodule metadata, or export destinations.
Advisories covered¶
- wireshark-mcp
export_objects— GHSA-3r68-x3xc-rxpg: arbitrary file write whenWIRESHARK_MCP_ALLOWED_DIRSis not configured. - simple-git — GHSA-hffm-xvc3-vprc: remote code execution through unsafe Git command construction.
- exiftool-vendored — GHSA-cw26-7653-2rp5: argument injection through newline characters in tag names.
- gix/gitoxide submodule state traversal — GHSA-fr8x-3vfx-f45h: unvalidated submodule names can redirect
.git/modulesstate/open operations to another repository. - gix/gitoxide symlinked
.gitmodules— GHSA-pg4w-g64p-qwhj: symlinked.gitmodulescan be followed and parsed outside the repository. - gix-pack — GHSA-x494-mj8g-cj27: crafted pack data can trigger unchecked indexing panics and uncapped memory allocations.
- gitoxide
.gitmodulescommand bypass — GHSA-f26g-jm89-4g65:CommandForbiddenInModulesConfigurationcan be bypassed ingix_submodule::File::update()to enable command execution. - gix submodule validation and credential disclosure — GHSA-p3hw-mv63-rf9w: submodule-name validation bypass plus trust inheritance can enable path traversal and credential disclosure.
- gix-transport curl backend — GHSA-9857-6mw7-fq2m: HTTP credentials can leak to redirected hosts.
Operator triage¶
- Inventory tools that parse attacker-controlled repositories, packets, media, EXIF metadata, or MCP requests.
- Pin/upgrade affected Git, simple-git, gix/gitoxide, ExifTool wrapper, and wireshark-mcp dependencies before processing untrusted inputs.
- Disable MCP file-export tools unless an explicit allowed-directory list is configured and enforced after canonical path resolution.
- Hunt build and analysis hosts for writes outside expected scratch directories, unexpected
.git/modulespaths, symlinked.gitmodules, suspicious submodule names, and Git config keys that spawn commands. - Treat credentials used during repository fetches as exposed if redirects to untrusted hosts occurred.
Durable controls¶
- Every helper that writes files needs a final canonical-path check under an operator-selected root, plus symlink refusal and extension/size limits.
- Never pass user-controlled metadata into command-line arguments without structural separation and rejection of control characters.
- Repository parsers must treat
.gitmodules, submodule names, pack files, and transport redirects as hostile input. - Credentials used for Git fetches should be scoped per host and stripped on cross-origin redirects.
- MCP tools should fail closed when capability configuration is absent; permissive defaults are unsafe.
October 6 follow-up: simple-git denylist family — five ways to miss the one spelling that matters (4 GHSAs, 23:47–23:48Z)¶
simple-git 3.36.0 shipped a four-advisory wave in one minute — another coordinated dump, and the cleanest public worked example of why every denylist-based guard is an enumeration that fails by omission. All legs share the precondition: the app flows attacker-influenced values into customArgs, instance config, or child env while relying on blockUnsafeOperationsPlugin defaults.
-c include.path=<file>absent from the config denylist (GHSA-g4wm-2vf7-vfgr / CVE-2026-102826, 8.1): loads any local file as gitconfig, which setscore.sshCommand(an otherwise-denied key), and the next remote op in the same clone executes it. The pending fix's regex/\s*include.path/still misses the conditional formincludeIf.<cond>.path— the advisory shows a fix that names one spelling of a multi-spelling feature. Denylist-differential rule: for every gated config key, enumerate the grammar variants Git actually accepts (include,includeIf.onbranch:.path, section-case, escaped quotes).- Long-option prefix abbreviation bypass (GHSA-858h-whjf-mvg5 / CVE-2026-102827, 8.1): the guard matches literal spellings (
/--(upload|receive)-pack/,'--exec') but the argv parser keeps the raw token asflag.namewhile git itself unambiguously abbreviates--receive-p→--receive-pack,--exe→--exec— executed on local/file remotes. The clone-side rule (--usubstring) covers abbreviations; the push-side rules don't. Durable axis: when a guard regexes an option name and the real binary accepts unambiguous prefixes, the guard must match the prefix set, not the full spelling — fuzz every blocked flag by truncating it one character at a time. trailer.<token>.cmdnot classified unsafe (GHSA-x6jw-m9v5-85vh / CVE-2026-102828): a documented shell-command config key forgit interpret-trailersmissing from the denylist entirely — same shape asinclude.path: the denylist enumerates known-exploited keys, not all command-executing keys. Audit rule for any git wrapper: enumerate every git config key whose docs say "command" or "shell" (core.editor,core.pager,core.sshCommand,sequence.editor,hooks.*,trailer.*.cmd,credential.helper,pager.*,alias.*) and test each against the wrapper's guard —alias.*is the classic never-listed one.VISUALenv var omitted from editor detection (GHSA-v5rq-49vh-5v5c / CVE-2026-102829):GitEnvKeysmapsEDITOR/GIT_EDITOR/GIT_SEQUENCE_EDITORtoallowUnsafeEditorbut dropsVISUALbefore inspection — while git falls back toVISUALfor editor resolution. Env-guard parity rule: enumerate the full fallback chain of the underlying binary (git's editor chain is GIT_EDITOR → core.editor → VISUAL → EDITOR → pager), not just the obvious names.
Operator summary: these four plus CVE-2026-28291/28292 form the longest public denylist-incomplete-fix chain in a developer library. For authorized assessments of CI bots, repo-parsing agents, or web apps shelling out via simple-git, the harness is: (1) inline -c key battery over the command-executing-key enumeration, (2) truncated-abbreviation battery over every blocked flag, (3) env-var fallback-chain battery, (4) proof with a touch-marker command against a local file remote only. Report the class (guard is enumeration-based) with the concrete surviving spelling — prior advisories in this chain each named their predecessor, so the fix-genealogy grep (detectVulnerableConfigWrites, preventUnsafeConfig) on the vendored source is the fastest fingerprint.
October 6 follow-up: Nx coordinated dump — four ways a build tool executes repository-controlled input (4 GHSAs, 23:28–23:29Z)¶
The same audit wave that produced the simple-git dump landed a four-leg coordinated Nx dump six minutes earlier, and it generalizes the same thesis to the whole developer-toolchain class: the repository is executable input to the tool that operates on it, across four distinct mechanisms.
- Shell-string interpolation of git revisions/refs (GHSA-w2vw-w76x-qr89, high, nx ≥14 <22.7.8 / <23.1.1): Nx core builds several
gitinvocations as shell command strings with untrusted values interpolated, so a value that should be a revision or ref is parsed by/bin/sh. Two reachable entry points:affectedcommands, whereaffected.defaultBasefromnx.json(orNX_BASE/NX_HEAD) reachesgit merge-base/git diff, andnx import, where a branch name advertised by a remote repository reachesgit fetch/git checkout/git config. Anyone controlling a repository — or opening a PR against one — gets command execution on the machine of anyone running an ordinary command against it, including CI runners. Theaffectedleg is the serious one:nx affected/nx show projects --affectedrun constantly in CI, so the repo config itself is the payload delivery. Fingerprint on any devtool source:execSync/exec/spawn(..., shell:true)call sites whose interpolated values originate from project config or remote-advertised refs. - Daemon/plugin-worker sockets world-connectable + module-load RPC (GHSA-w3vv-58gj-gw77 / CVE-2026-104854, high, nx ≥14.6 <22.7.9 / <23.1.2): Nx creates its daemon and plugin-worker Unix domain sockets in a shared temp directory with default permissions, and the daemon accepts a
PROCESS_IN_BACKGROUNDrequest that names a module to load and invokes its default export — socket access becomes code execution as the daemon's user. On shared build servers, shared dev boxes, or multi-account containers this is a standing cross-user LPE. Durable local-recon tactic beyond Nx: enumerate other users' AF_UNIX sockets in shared temp (find /tmp /var/tmp -type s \! -user $(id -un)), then grep the owning daemon's request grammar for module/plugin/path-named fields — a socket anyone-local can connect to, whose protocol loads a caller-named module, is a local-to-other-user RCE primitive regardless of product. nx migrateextraction before any user approval (GHSA-hrvq-x7jp-36xv / CVE-2026-104853, medium): each target package'snx-migrations.migrationsmanifest value is joined onto a temp directory unvalidated, so..segments or an absolute path steer the extraction to write/truncate outside the temp root — during migration planning, before the user reviews the migration list and before--run-migrations. Transitive packages pulled in through a trusted package'spackageGroupreach the same path. Durable rule: planning/preview phases of package managers routinely touch disk before the approval gate exists — treat install-time, migrate-plan, and dry-run code paths as untrusted-code territory in audits; the user's "I haven't run it yet" intuition is a false negative.- Release pipeline shell strings from project config (GHSA-6vc5-vf29-ffr2 / CVE-2026-104859, high, @nx/docker):
dockerinvocations built as/bin/sh -cstrings interpolatingrelease.docker.repositoryName/registryUrlfrom Nx config — a PR that changes the release config executes as the release job, which in CI holds registry credentials and cloud tokens. Same class as the repo-trust legs above, with the highest-value credential context.
Operator rules from the cluster: (1) for any tool with repo access, grep its source for shell-string construction and check whether interpolated values originate from config files, remote refs, or package manifests; (2) test planning-phase disk writes separately from execute-phase actions; (3) on shared hosts, socket enumeration in shared temp is a five-second primitive hunt; (4) proof boundary: inert touch-marker payloads in disposable repos and temp dirs only, never against release CI holding live credentials. The whole dump (simple-git four-pack + Nx four-pack + adjacent legs) landed in one 13-minute window — when one coordinated audit lands, re-sweep the same publisher/product family within the hour; sibling advisories publish out of order around the anchor.