19 min read
Medium

Symfony Process CVE-2026-24739: when an equals sign under Git Bash empties the drive — what German teams should check in the Composer pipeline

17 May 2026 (update). Symfony has released the patch for CVE-2026-24739: under MSYS2 or Git Bash on Windows the equals sign in unquoted arguments is not treated as „special", which means the MSYS2 conversion layer can re-route the argument path — in the worst case, an rmdir or del lands somewhere other than intended. Fix in 5.4.51, 6.4.33, 7.3.11, 7.4.5 and 8.0.5; the bug hits developer machines and Windows CI runners, not productive Linux hosts. Structurally the bug sits in the running argument-consistency series of the May week alongside NGINX Rift, the Composer token leak and the node-ipc incident — the platform lesson is not „patch Symfony“ but „dynamically composed paths in external processes are their own risk class“.

Zwei kleine Messingplaketten mit Gleichheitszeichen nebeneinander auf Beton; eine feine Risslinie zieht zwischen ihnen durch, ein roter Faden tritt aus dem Riss; daneben ein geschlossener Messingschlüssel und eine Lederhülle im kühlen Nordlicht.

TL;DR — 90 seconds

A flaw in symfony/process lets MSYS2/Git Bash on Windows re-route arguments containing = through the conversion layer. The bug is not spectacular — but it sits in the standard PHP stack of the Sylius and TYPO3 world, and its worst case is file deletion at an unintended path. The fix is a simple Composer update; the structural lesson is a different one.

QuestionAnswer
Affected?All PHP applications using symfony/process before 5.4.51 / 6.4.33 / 7.3.11 / 7.4.5 / 8.0.5 — Composer scripts, Sylius shop builds, TYPO3 site package builds, Symfony console commands — when the runtime environment is MSYS2-based (Git Bash, Cygwin derivatives). Native Linux and macOS hosts are not affected.
Risk?Argument injection / unintended file operation (CWE-78 / CWE-88); worst case, deletion of the contents of a different directory or drive than intended, if the path to be deleted contains = and the calling script invokes rmdir/del via symfony/process.
Immediate action?Pin Composer lock to the fix versions (composer require symfony/process:^7.4.5 or analogous per major), switch Windows build runners from Git Bash to cmd.exe or PowerShell, audit all Composer scripts for argument constructions containing =.
Recommendation?German Mittelstand: Composer update in the next maintenance sweep, Windows CI runner inventory. Enterprise: additionally pipeline audit for Symfony-Process-based file operations, hardening rule NO_MSYS2_SHELL_IN_CI in the build standard template.
Criticality?See hero badge — medium.

What is the problem?

Symfony Process is the standard component in the PHP universe for starting and managing external processes. Composer uses it in its scripts, Sylius in its build and deploy hooks, TYPO3 in its console commands, Drupal in its maintenance routines, and every productive Symfony application in a dense layer of calls across the stack. It provides the argument escape layer between PHP and the respective host shell — POSIX escape on Unix, CMD escape on Windows.

The flaw sits exactly in the Windows escape logic. Symfony Process treats a set of characters as „special" and quotes the corresponding arguments. The character = was not on that list — on native cmd.exe that is also uncritical, because CMD does not know = as an argument separator. It is different under MSYS2 and Git Bash: their mintty and bash conversion layer interprets an = in an unquoted argument as a variable assignment or path separator hint and can re-route the entire argument.

The Symfony analysis itself shows the example precisely: if a Composer script calls rmdir via symfony/process with the path C:\projects\build=temp\artifacts, the MSYS2 conversion layer can reshape this argument so that the directory actually deleted is a different one — up to deletion of the contents of a parent directory or the entire drive, depending on the argument layout of the calling script.

The bug is small and deep. Small, because the repair is an extension of the „special characters" list by = and a few related characters — the patch in symfony/symfony#63164 is a few lines of code. Deep, because it sits at a layer most PHP developers treat as solved: argument escape has been stable standard infrastructure for years, and nobody actively checks whether a Composer script call with an = in the path shows the same behaviour under Git Bash as under cmd.exe. Snyk has classified the bug as arbitrary argument injection — the class is right, but the operational effect sits not in the argument alone but in the interaction with the MSYS2 conversion layer.

Structurally, CVE-2026-24739 belongs to a class we have seen several times in recent weeks: stable stack libraries that violate a single consistency assumption at one point — an assumption that holds in the standard environment and breaks in an adjacent one. With NGINX Rift (CVE-2026-42945, 13 May) it was an 18-year-old assumption about Rewrite module behaviour. With node-ipc (14 May) it was an assumption about the stability of a maintainer identity over years. With Symfony Process it is an assumption about the universality of the CMD escape logic.

Who is affected?

A compact overview of affected and unaffected constellations.

ConstellationAffectedNot affected / conditions
PHP build on Windows CI runner under Git Bash / MSYS2symfony/process before 5.4.51 / 6.4.33 / 7.3.11 / 7.4.5 / 8.0.5; any Composer script or Symfony console command passing arguments with = to external file toolsBuild pipelines on native Linux or macOS runners; Windows runners with cmd.exe or PowerShell as shell; applications without Symfony Process calls in file operations
Sylius shop builds in German Mittelstand setupsIf the build pipeline runs on Windows runners under Git Bash (rare, but present in some agencies' maker setups)Standard Sylius build on Linux containers (Wolfi / Alpine / Debian) is not affected
TYPO3 site package builds and Composer update sweepsLocal development environments under Windows with Git Bash, where Composer scripts trigger file operations with dynamic pathsContainer-based build environments (DDEV, Lando, ddev-podman) are not affected — the container runs Linux
Symfony console applications in customer maintenance scriptsIf the console command line runs under Windows + Git Bash and executes file ops with user-defined paths via symfony/processConsole commands on Linux cron or Windows Task Scheduler with cmd.exe as shell
GitHub Actions workflows with shell: bash on windows-latest runnersshell: bash on Windows runners implicitly routes through Git Bash (MSYS2) — directly affected if the workflow starts PHP toolingshell: cmd or shell: pwsh on the same runner image; or the workflow moved to ubuntu-latest
GitLab runners on Windows hostsconfig.toml with shell = "bash" or without explicit configuration (default sh often routes through Git Bash on Windows hosts) and PHP tooling in the CI stages[runners.shell] with shell = "powershell" or shell = "cmd"

We operate the PHP stack in our customer platforms — TYPO3 and Sylius in the German Mittelstand corridor — consistently in Linux containers (Wolfi OS basis as standard since February 2026). Direct productive exposure for us is therefore low. The operational line sits in a different place, though: individual customers have their own Windows build hosts for historical pipeline remnants, and our architecture reviews regularly hit Composer scripts that compose paths dynamically — that is the actual attack surface.

Impact

The spectacular reading of the bug — „Symfony Process deletes the wrong directory“ — is correct, but it overstates the typical effect. The more frequent effect is subtler: a build pipeline step does not delete an artefact that it should have deleted, so a later build stage accesses a stale state. Or vice versa: a cleanup script removes a directory adjacent to the actual target, so a productive artefact has gone missing after the build run. Neither is a data protection incident, neither is an RCE, but both produce pipeline hallucinations that are hard to reconstruct in day-to-day operations.

The worst-case variant — deletion of a productive directory or content volume — requires the calling script to compose the argument path dynamically from configuration values and the path to contain an =. In the German Mittelstand platform context this is rare, but not excluded: build tools that insert tenant identifiers or configuration keys into paths regularly generate paths with = (especially in Base64-encoded identifier schemes), and a cleanup script can pass these paths to symfony/process.

Structurally, CVE-2026-24739 sits in the running series of argument-consistency findings in the PHP stack. Three data points compress this line: Composer GHSA-f9f8-rm49-7jv2 of 12 May 2026 (token leak in error messages because Symfony Console injects ANSI sequences that bypass the masking logic), node-ipc of 14 May 2026 (maintainer e-mail domain hijack via an expired domain), and the Sylius findings of March (CVE-2026-31821 to -31825, stabilised at 2.0.16/2.1.12/2.2.3). The lesson is not a Symfony or Sylius weakness, but that the test matrices of the standard frameworks do not systematically cover the adjacent environments — and that customer platforms carry the difference.

Mitigation / immediate measures

The patch is trivial, the operations around it are not. Three paths, in increasing depth.

Path 1: Composer lock to fix version

 

# In any PHP project with symfony/process in the Composer lock:
composer why symfony/process
composer require symfony/process:^7.4.5 --update-with-dependencies
# Or analogous for the respective major corridor:
# 5.4.x → 5.4.51
# 6.4.x → 6.4.33
# 7.3.x → 7.3.11
# 7.4.x → 7.4.5
# 8.0.x → 8.0.5

 

The composer why prelude is not cosmetic: Symfony Process arrives in over 80 percent of productive PHP projects as a transitive dependency — via symfony/console, symfony/messenger, composer/composer itself, phpunit/phpunit, friendsofphp/php-cs-fixer and dozens of others. The direct pin is not enough if a transitive path holds an older version. composer why symfony/process shows the full dependency tree.

Path 2: Switch Windows CI runners from Git Bash to cmd.exe / PowerShell

For GitHub Actions:

 

# Before (vulnerable when PHP tooling runs):
- name: Composer install
  shell: bash
  run: composer install

# After:
- name: Composer install
  shell: pwsh  # or: shell: cmd
  run: composer install

 

For self-hosted GitLab runners on Windows: switch the pipeline shell configuration from the default sh (which on Windows hosts often means Git Bash) to powershell.

 

# config.toml of the GitLab runner:
[[runners]]
  name = "windows-build"
  executor = "shell"
  [runners.shell]
    shell = "powershell"

 

MSYS2 is a historical bridge between Unix shell expectations and Windows process models. It is useful for developer convenience; it is not production-ready for unobserved build pipelines.

Path 3: Audit of Composer scripts and maintenance paths

 

# In any PHP project:
grep -rn "Process(" --include="*.php" src/ vendor/ scripts/
# Search for dynamically composed paths:
grep -rn "rmdir\|unlink\|del " --include="*.php" src/ scripts/
# Specifically: path constructions with '='
grep -rEn "\\.'='\\." --include="*.php" src/ scripts/

 

Anyone finding a hit in a cleanup script or build hook documents the location and decides: rebuild the path (without =), or replace the Symfony Process call with Filesystem::remove() from symfony/filesystem — the component uses PHP-native file API internally, does not know the host shell conversion layers, and is therefore predictable across a broader configuration matrix.

Detection

Three complementary detection paths — SCA tool, pipeline lint, host telemetry.

Path 1: SCA tool on the Composer lock

 

# With composer audit (built in since Composer 2.4):
composer audit --format=json | jq '.advisories[] | select(.package == "symfony/process")'

# With Trivy:
trivy fs --scanners vuln --severity HIGH,CRITICAL --include-dev-deps .

# With Snyk:
snyk test --severity-threshold=medium

 

The SCA path reports the flaw reliably once the GHSA database has rolled out the entry (status 17 May 2026: available in the GitHub Advisory Database and the Snyk database).

Path 2: Pipeline lint for Git Bash shells in Windows jobs

 

# YAML lint for GitHub Actions:
find .github/workflows -name "*.yml" -exec grep -Hn "shell: bash" {} \; | \
  xargs -I {} sh -c 'echo "Hit: {}"; grep -B2 "shell: bash" {} | grep -E "runs-on.*windows"'

# For GitLab CI:
grep -rEn "shell.*bash" .gitlab-ci.yml | grep -A2 "windows"

 

Anyone finding hits here has a direct mitigation task — even if symfony/process is already patched, the MSYS2 shell in Windows CI jobs is a general risk class and should be swapped for pwsh or cmd.

Path 3: Host telemetry with Falco / Tetragon / Sysmon

For Windows build hosts with Sysmon, Tetragon or comparable eBPF layer:

 

<!-- Sysmon config snippet (Windows Event ID 1): -->
<RuleGroup name="Symfony-Process-Auditing" groupRelation="or">
  <ProcessCreate onmatch="include">
    <CommandLine condition="contains all">rmdir;=</CommandLine>
    <CommandLine condition="contains all">del;=</CommandLine>
  </ProcessCreate>
</RuleGroup>

 

For Linux hosts (if PHP build containers run in the customer cluster):

 

# Falco rule:
- rule: PHP_Process_Argument_Equals_Sign_File_Op
  desc: PHP process starts rm/rmdir with '=' in path argument
  condition: >
    spawned_process and proc.pname startswith "php" and
    (proc.name in ("rm", "rmdir", "unlink")) and
    proc.args contains "="
  output: "PHP file op with '=' in path (proc=%proc.name args=%proc.args parent=%proc.pname)"
  priority: warning
  tags: [process, symfony, cve-2026-24739]

 

Tetragon TracingPolicy as an alternative (for eBPF-native clusters):

 

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: symfony-process-cve-2026-24739
spec:
  kprobes:
  - call: "fd_install"
    syscall: false
    selectors:
    - matchPIDs:
      - operator: In
        followForks: true
        values:
        - <pid_php_worker>
      matchArgs:
      - index: 1
        operator: "Contains"
        values:
        - "="

 

The telemetry layer is optional, but it is the line that makes the gap between „patch installed“ and „patch actually effective“ verifiable in customer platforms.

Operator recommendation

The flaw is correctly classified as medium criticality — it needs no emergency escalation, but it does need a calm, non-deferrable response. Four if/then bullets as an operational decision block:

German Mittelstand

Plan the Composer update, ideally as part of the next Composer sweep of the running sprint week. In parallel: Windows build host inventory — are there still productive Windows build runners in the customer estate that run under Git Bash? If yes, change the shell configuration to cmd.exe or PowerShell, ideally accompanied by a move to Linux containers for the next pipeline refresh.

Enterprise

Additionally: pipeline audit for Symfony Process based file operations, with focus on cleanup and deploy hooks. Hardening rule NO_MSYS2_SHELL_IN_CI in the build standard template. SBOM inventory of the Composer lock files across the platform portfolio, with symfony/process as a specific audit item.

Kubernetes and declarative stacks

Containerised PHP builds (Wolfi OS, Debian Slim) are consistently unaffected — the container runs Linux, MSYS2 plays no role. Anyone building on Wolfi: the next regular image rebuild wave will pick up the symfony/process update once chainguard/php pulls the patch (status 17 May 2026: Composer lock in the official image not yet updated, tracking via chainguard-images issues to be expected).

What we did concretely

On the morning of 17 May we set three steps running in parallel. First a Composer lock inventory across the entire customer platform portfolio: 23 platform repositories have a direct or transitive symfony/process entry, of which twelve are on one of the vulnerable versions. The update runs as a standard pull request wave, each with its own CI run against the platform tests; the first merges are expected by Sunday evening, the rest in the course of the following week.

Second a pipeline inventory for MSYS2 shells. We have no Windows runners with Git Bash in any of our own CI paths, because our pipeline architecture has consistently run on Linux Wolfi containers via GitLab CI since February 2026. At two customers we did identify historical Windows build hosts that continue to run for specific legacy applications. We have already switched the shell configuration of these hosts — cmd.exe as default, PowerShell as escalation variant. The structural question of Linux container migration runs as a separate architecture review item.

Third an audit of Composer scripts in customer repositories. We searched composer.json and the build hook scripts for Process() calls with dynamically composed file paths. Three hits in three different customer platforms — all three were cleanup routines that remove temporary artefacts after a build. The paths contained Base64-encoded tenant identifiers in two cases (with = as padding character). We moved these three places to Filesystem::remove() from symfony/filesystem — the component uses PHP-native file API internally instead of starting an external rmdir process.

Structurally, the run produces a lesson for our platform template: dynamically composed paths in external processes are their own risk class, regardless of the specific CVE. We are adding this as an additional lint point to our build hook standard template — anyone needing a file operation on a user-generated path uses the Filesystem component, not a Process call.

Architecture note in passing: the bug shows once again that the standard test matrices of the large PHP frameworks do not systematically cover the MSYS2 adjacent environment. Symfony has issued an explicit warning in the advisory against MSYS2-based shells in productive pipelines — which is de facto an extension of the Symfony support definition. Anyone still running productive pipelines under Git Bash on Windows hosts is operating them, from today, outside the standard configuration addressed by the vendor.

Frequently asked questions on CVE-2026-24739 and Symfony Process under Windows

Symfony Process CVE-2026-24739: Do Linux production servers need to be patched — or is the developer-machine focus enough?+

On production servers alone: no, not an escalation. On developer machines and in the CI pipeline: yes, in the next regular Composer sprint. The reason is not primarily the CVE specifics (which do not bite on Linux) but Composer lockfile discipline — a lockfile that pins unpatched components will sooner or later be flagged in the next security pipeline review. Better to pull it in the regular sprint now than three weeks later under audit pressure.

How do I check whether my composer.lock pins symfony/process at a vulnerable version?+

One-liner: composer show symfony/process shows the installed version. If it sits below the fix for your major line (5.4.51, 6.4.33, 7.3.11, 7.4.5, 8.0.5), the lockfile is affected. For a multi-project portfolio: composer audit in each project directory. The tool now pulls the GitHub Advisory Database and reports CVE-2026-24739 reliably. Anyone running Packagist Private with their own mirror already has the advisory feed integrated into the audit run.

We develop under WSL2 — are we affected by CVE-2026-24739?+

For DACH SMEs with a classic PHP stack: WSL2 is the ergonomic option when the hardware stays Windows anyway (licensing, IT standardisation). Native Linux is the robust option when the hardware choice is open. Both eliminate the MSYS2 conversion path from the local workflow and therefore close not only CVE-2026-24739 but also the next Symfony Process edge-case class. A pure “Git Bash on Windows“ strategy is no longer state of the art in 2026 — a recommendation that carries beyond this CVE.

Which Composer scripts are typical risk spots?+

Three classes we see regularly in reviews: (1)post-install-cmd hooks that delete or create build directories with configurable paths (e.g. --cache-dir=...). (2) Custom console commands for asset resampling that accept an output path as a CLI argument and internally call Process on convert or ffmpeg with a file argument. (3) Migration scripts offering a --target-dir= write path that perform file management in the background. All three are harmless under Linux but, under MSYS2/Git-Bash with an unpatched Process version, may act on a different path than intended.

Should we pin symfony/process explicitly in composer.json?+

No. symfony/process is a transitive dependency of most Symfony/TYPO3/Sylius stacks. An explicit pin in the project composer.json would block later automatic updates — undesirable. Instead: regular composer update in the sprint cycle, composer audit as a CI step with fail-on-high, and branch protection on the composer.lock so that a lockfile refresh runs through a code-reviewed MR. That is the robust option — a pin on one component is the reaction that gets forgotten in Q3 and comes back as security debt in Q4.

Conclusion

CVE-2026-24739 is a small bug at a stable location. The operational effect — file deletion at an unintended location under a configuration constellation many customers do not even run — is limited. The structural effect — confirmation that even the standard PHP stack libraries can break consistency assumptions in adjacent environments — is the more honest reading.

The lesson for our customer platforms is not „patch Symfony“ (which is trivial), but „dynamically composed paths in external processes are their own risk class“. We have added the lint to our platform template, moved the three affected cleanup routines to Filesystem::remove(), and initiated the Composer lock wave. The question is not whether you patch today or tomorrow. The question is whether you know which pipelines in the customer estate trigger file operations via external processes with user-defined paths.

Bevor der nächste Edge-Case kommt — sprechen wir über Ihre Composer-Pin-Strategie.

We audit and harden your PHP build pipelines against Symfony Process class findings.

We start in customer setups with a Composer lock inventory, a pipeline shell inventory and an audit of the build hook scripts for dynamically composed paths in external processes. Three concrete steps: Composer lock to the fix version, Windows CI runners from Git Bash to cmd.exe or PowerShell, audit of the cleanup routines for Process() calls with user-generated paths.

If you operate a productive PHP platform — Sylius in the B2B ordering process, TYPO3 as the content backbone, your own Symfony console applications — this audit belongs in the running DevSecOps line, not in a one-off measure. We do this as part of our regular DevSecOps platform operations and as part of our CMS hardening line.

Termin direkt vereinbaren

About the author

[Translate to English:] Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Founder · Moselwal Digitalagentur · OnlyOle

Programming since 2002 – self-taught, set up my own business with KO-Web in 2012, now Moselwal. Over 100 projects, with a focus on security, performance, automation and quality.