Composer-Token-Leak in CI-Logs (GHSA-f9f8-rm49-7jv2): Composer 2.9.8 / 2.2.28 / 1.10.28 sofort einspielen
Am 13. Mai 2026 hat das Composer-Team das Security Advisory GHSA-f9f8-rm49-7jv2 veröffentlicht. Composer leakt GITHUB_TOKEN-Werte und GitHub-App-Installation-Tokens in die CI-Logs, sobald die Tokens das neue ghs_<id>_<base64url-JWT>-Format mit Hyphen tragen. Severity: High, CVSS 7.5, CVE pending. Patches: Composer 2.9.8 (mainline), 2.2.28 (2.2-LTS), 1.10.28 (Legacy).
Hinweis für Moselwal-Kunden: Wir nutzen keine GitHub-hosted Runner. Unsere Build-Container laufen auf eigener Infrastruktur — Composer ist dort dennoch enthalten. Wir haben die Build-Umgebungen und alle Container heute beschleunigt auf Composer 2.9.8 aktualisiert. Falls Sie selbst GitHub Actions für eigene Repositories einsetzen, ist trotzdem Handlungsbedarf bei Ihnen — dieser Post liefert die Schritte.
Was hat sich geändert? GitHub rollt schrittweise ein neues strukturiertes Token-Format aus, das Hyphens enthält. Composer's Token-Validierungs-Regex ^[.A-Za-z0-9_]+$ akzeptiert keinen Hyphen und kopiert den abgelehnten Token ungeschwärzt in die Exception-Message — die Symfony Console dann auf stderr ausgibt. Der GitHub-Secret-Masker greift dort nicht zuverlässig. Wer ist betroffen? Jeder, der Composer in GitHub Actions fährt und dort ein GitHub-App-Installation-Token oder das auto-injected GITHUB_TOKEN in Composer's auth.json stehen hat. Was sollten Sie heute tun? Update auf 2.9.8 / 2.2.28, Job-Logs reviewen, ggf. Tokens rotieren, ggf. Logs löschen.

TL;DR — die 90-Sekunden-Zusammenfassung
Composer leakt Tokens in CI-Logs. Ein Update auf 2.9.8 / 2.2.28 / 1.10.28 ist Pflicht. Wer GitHub Actions fährt, muss zusätzlich Logs prüfen und potenziell Tokens rotieren.
- Was ist passiert?
Composer akzeptiert in
Composer\IO\BaseIO::loadConfiguration()nur Tokens, die der Regex^[.A-Za-z0-9_]+$entsprechen. Das neue GitHub-Formatghs_<numeric-id>_<base64url-JWT>enthält Hyphens. Validation schlägt fehl, der Reject-Token wird unredacted in die Exception-Message interpoliert und via Symfony Console auf stderr ausgegeben — wo er in jedem CI-Log landet.- Wer ist betroffen?
Jede Composer-Installation der Versionen 2.3.0–2.9.7, 2.0.0–2.2.27 und 1.0–1.10.27 in einer GitHub-Actions-Umgebung, in der ein GITHUB_TOKEN oder GitHub-App-Token in
auth.jsonlandet. Viele Workflows tun das automatisch (z.B.shivammathur/setup-php, bereits gefixt).- Was ist zu tun?
Sofort: Composer auf 2.9.8 (mainline) / 2.2.28 (LTS) / 1.10.28 (Legacy) anheben. Bei verzögerter Update-Möglichkeit: betroffene Workflows oder GitHub Actions auf Organisations-/Repo-Ebene deaktivieren. Audit: Job-Logs der letzten 24 Stunden pro Repository auf
contains invalid characters-Stack-Traces durchsuchen.- Moselwal-Kunden
Wir nutzen keine GitHub-hosted Runner; unsere Builds laufen in eigenen Containern. Diese sind heute beschleunigt auf 2.9.8 aktualisiert. Sie müssen für unsere Pipelines nichts tun. Falls Sie selbst GitHub Actions in eigenen Repos einsetzen, gelten die Schritte oben — wir unterstützen Sie gerne.
Drei Sätze für Entscheider: Die Schwachstelle ist High Severity (CVSS 7.5), die Mitigation ist trivial (ein composer self-update), aber der Blast Radius im Token-Worst-Case reicht 24 Stunden auf Self-hosted Runnern und potenziell breiter scope bei App-Tokens. Wer GitHub Actions intensiv nutzt, sollte parallel zur Mitigation eine Log-Audit-Routine aufsetzen. Wer keine GitHub Actions nutzt, muss trotzdem den Composer-Stand im eigenen Build-Container aktualisieren.
Was ist das Problem?
Composer validiert seit 2021 alle konfigurierten GitHub-OAuth-Tokens — inklusive des in auth.json hinterlegten GITHUB_TOKEN — gegen einen Charakter-Set-Regex. Der Code in src/Composer/IO/BaseIO.php (Zeile 139 auf main, Zeile 143 auf 2.8.x) sieht so aus:
// allowed chars for GH tokens are from
// github.blog/changelog/2021-03-04-authentication-token-format-updates/
// plus dots which were at some point used for GH app integration tokens
if (!Preg::isMatch('{^[.A-Za-z0-9_]+$}', $token)) {
throw new \UnexpectedValueException(
'Your github oauth token for '.$domain.' contains invalid characters: "'.$token.'"'
);
}
Drei Probleme spielen zusammen:
1. Der Token landet wörtlich in der Exception-Message
Wenn die Regex fehlschlägt, wird der komplette Token-String in die UnexpectedValueException-Message interpoliert. Symfony Console schreibt diese Message anschließend auf stderr. Jede Umgebung, die stderr aufzeichnet (CI-Job-Logs, Log-Shipper, Monitoring, Support-Tickets), bekommt damit den Klartext-Token.
2. Die Regex erlaubt keine Hyphens
Das neue strukturierte Token-Format von GitHub für App-Installation-Tokens hat die Form ghs_<numeric-id>_<base64url-JWT>. Base64url-Encoding nach RFC 4648 §5 nutzt - und _ als URL-sichere Ersetzungen für + und /. Jede base64url-encodierte JWT-Signatur enthält damit fast immer mindestens ein -. Die Composer-Regex von 2021 wurde unter der Annahme gewählt, dass GitHub-Tokens nur [A-Za-z0-9_.] enthalten. Diese Annahme stimmt seit der GitHub-Token-Format-Umstellung nicht mehr.
3. Der GitHub-Actions-Secret-Masker greift nicht
GitHub Actions' built-in Secret-Masker erkennt registrierte Werte nur als exakte Substring-Matches. Wenn die Composer-Exception von Symfony Console gerendert wird, kann die Message umbrechen, in In BaseIO.php line N:-Framing eingebettet werden oder mit ANSI-Control-Sequenzen interleaved sein. Der Masker findet den Token-Substring nicht mehr und redactet nicht. Der Klartext-Token erreicht das Log.
Trigger-Bedingung im Standard-Pfad
Mehrere weit verbreitete GitHub Actions registrieren das Workflow-GITHUB_TOKEN automatisch in Composer's globaler auth.json — shivammathur/setup-php ist das prominenteste Beispiel (bereits gefixt). Wer eine solche Action verwendet und in der Workflow-Phase irgendwann composer install ruft, löst den Leak ohne weitere Konfiguration aus.
Wer ist betroffen?
Drei Profile:
- Profil A — GitHub-Actions-Nutzer mit Composer-Builds
Wer GitHub Actions für eigene Repositories einsetzt und in einem Workflow
composer installodercomposer updateruft, ist die primär betroffene Gruppe. Vor allem TYPO3-, Symfony-, Laravel-, Drupal- und Magento-Repositories mit CI-Pipelines. Dringlichkeit: hoch. Update sofort einspielen oder Workflows temporär deaktivieren.- Profil B — Self-hosted Runner-Betreiber
Wer GitHub Actions mit self-hosted Runners fährt, hat ein erweitertes Risiko-Fenster: Workflow-
GITHUB_TOKENs sind dort bis zu 24 Stunden gültig (gegenüber max. 6 Stunden auf GitHub-hosted Runners). Ein in einem Log geleakter Token bleibt entsprechend länger ausnutzbar.- Profil C — GitHub-App-Nutzer mit Composer
Wer
actions/create-github-app-tokenoder eigene GitHub-App-Installation-Tokens mit Composer-Auth kombiniert, hat ein zusätzliches Problem: diese Tokens haben Default-TTL von 1 Stunde, können aber breitere Installations-Permissions tragen als das Workflow-eigenepermissions:-Statement — d.h. ein leak liefert potenziell weitreichendere Zugriffsrechte als die Job-Decläration.
Wer ist nicht direkt betroffen?
- Composer-Installationen außerhalb von CI — Entwickler-Maschinen, lokale Builds, manuelle Deployments. Der Leak landet hier nur auf der lokalen Konsole, nicht in einem persistenten Log-Aggregator. Dennoch: Composer trotzdem anheben, weil die Validation auch lokale Tokens betrifft, sobald sie das neue Format haben.
- Build-Pipelines ohne GitHub Actions — GitLab CI, Jenkins, Bitbucket, Drone, Buildkite, Forgejo Actions etc. Wenn dort kein GitHub-App-Token in Composer's
auth.jsonhinterlegt ist, gibt es keinen leak-Vektor. Wer dort dennoch GitHub-Tokens nutzt: gleiche Mitigation. - Packagist.org selbst — nutzt keine GitHub-App, läuft Composer nie gegen App-Installation-Tokens.
Auswirkungen und Token-TTLs
Die praktische Tragweite eines Leaks hängt vom Token-Typ und der Runner-Umgebung ab. Der Packagist-Blog hat das präzise differenziert, das ist die operativ entscheidende Tabelle:
- GitHub-hosted Runner mit Workflow-GITHUB_TOKEN
Maximales Exposure-Fenster: 6 Stunden (Job-Maximum-Execution-Time). In der Regel terminiert die Composer-Exception den Job sofort, der Token expired damit umgehend. Praktischer Worst Case: 6 Stunden, realistisch deutlich kürzer.
- Self-hosted Runner mit Workflow-GITHUB_TOKEN
Max. Job-Execution-Zeit ist 5 Tage, aber der GITHUB_TOKEN ist ein Installation-Access-Token, der laut GitHub-Dokumentation maximal 24 Stunden refreshbar ist. Ein Self-hosted-Leak bleibt damit bis zu 24 Stunden nach Issuance gültig.
- GitHub-App-Installation-Tokens (z.B. via actions/create-github-app-token)
Default-TTL: 1 Stunde. Die Permissions können aber deutlich breiter sein als die Workflow-eigene
permissions:-Declaration. Ein leak grants damit potenziell mehr als der Job selbst rechtlich darf.
Was die Tokens können: typischerweise contents:read, häufig contents:write (z.B. für Conductor-Style Actions), bei App-Tokens nach Konfiguration auch actions:write, checks:write, issues:write, pull-requests:write. Wer einen leak übersieht und der Token noch lebt, riskiert: ungewollte Commits ins Default-Branch, Tag/Release-Manipulation, Workflow-Trigger via API, oder bei breiterem Scope Code-Modifikationen über Pull Requests hinweg.
Klartext: die Schwachstelle ist High Severity, aber das Real-World-Schadenspotenzial wird in den meisten Fällen durch kurze Token-Lebensdauern auf GitHub-hosted Runners gedeckelt. Auf Self-hosted Runners und bei App-Tokens ist die Gefahr substanziell höher.
Mitigation und Sofortmaßnahmen
Quick-Start: Composer anheben
# Composer phar self-update auf die gepatchte Version
composer.phar self-update
# alternativ explizit auf eine bestimmte Linie pinnen
composer.phar self-update 2.9.8 # mainline
composer.phar self-update 2.2.28 # 2.2 LTS
composer.phar self-update 1.10.28 # legacy (rather upgrade to 2.x)
# Composer-Version verifizieren
composer --version
Wer Composer aus dem Distributions-Paketmanager bezieht (z.B. Debian/Ubuntu): Distribution-Update abwarten oder über composer self-update bzw. getcomposer.org-Phar das Binary direkt austauschen.
Wer nicht sofort updaten kann: Workflows pausieren
Wenn der Composer-Update aus organisatorischen Gründen warten muss, ist die offizielle Packagist-Empfehlung eindeutig: Disable any GitHub Actions workflow that runs Composer commands until you have updated Composer. Konkret in der GitHub-UI:
- Auf Organisations-Ebene: Settings → Actions → General → Actions permissions → „Disable actions“.
- Auf Repository-Ebene: Settings → Actions → General → „Disable actions“.
- Pro Workflow: die Workflow-YAML kommentieren oder die
on:-Trigger temporal entfernen.
Container-/Image-Builds aktualisieren
Wer eigene Build-Container oder PHP-Base-Images mit gepinntem Composer verwendet: das Composer-Binary im Image neu bauen (z.B. COPY --from=composer:2.9.8) und Image-Tags neu publishen. Bei TYPO3-Hosting-Setups mit FrankenPHP/PHP-FPM-Containern ist Composer typischerweise zum Build-Zeitpunkt aktiv — die Production-Container brauchen ein Rebuild.
auth.json-Hygiene
Prüfen, welche Tokens in welchen auth.json-Lokationen liegen:
# Globale auth.json
cat ~/.composer/auth.json
cat ~/.config/composer/auth.json
# Projekt-lokal
cat ./auth.json
Tokens, die nicht mehr gebraucht werden, entfernen. Bei Service-Accounts: TTL prüfen, Rotation einplanen.
Detection und Log-Audit
Wo schauen?
Eine Composer-Exception mit Token-Leak hat ein charakteristisches Pattern. Im Job-Log sieht das so aus:
In BaseIO.php line 139:
Your github oauth token for github.com contains invalid characters: "ghs_..."
Quick-Check pro Repository
# GitHub-CLI: alle Job-Logs der letzten 7 Tage runterziehen und grep
gh run list --limit 100 --json databaseId --jq '.[].databaseId' | \
while read run_id; do
gh run view $run_id --log 2>/dev/null | \
grep -l 'contains invalid characters' && \
echo "LEAKED IN RUN $run_id"
done
Wer einen Treffer findet: den genauen Job-Log mit gh run view <id> --log abrufen, die Token-Werte extrahieren, jeden geleakten Token sofort revoken (GitHub-App-Token via App-Settings, klassische PATs via Personal Access Tokens) und das Log-File aus dem Repository-Storage löschen, falls der Token noch leben könnte (24h bei self-hosted, 6h bei GitHub-hosted, 1h bei App-Default).
Pro-aktive Suche nach Ungewolltem
Bei einem bestätigten Leak in einem noch lebenden Zeitfenster: GitHub-Audit-Log für Token-Aktivität einsehen, mindestens diese Endpoints:
- Repository-Push-Events seit Leak-Zeitpunkt — unerwartete Commits, Tags, Releases.
- Workflow-Run-API-Trigger — wurden Workflows extern via API gestartet?
- API-Audit-Log in GitHub-Enterprise: ungewöhnliche Token-User-Agents, fremde IP-Adressen.
Monitoring fortlaufend
Auch nach Patch und Rotation: ein einfacher CI-Step am Workflow-Anfang, der die Composer-Version prüft und den Job fail-t, wenn sie unter 2.9.8 / 2.2.28 liegt, ist eine Versicherung gegen Rückfall durch alte Container-Images.
Betreiberempfehlung
Operational Decision Block
- Wenn Sie GitHub Actions mit Composer-Builds und GitHub-hosted Runner führen — dann
updaten Sie Composer in allen Workflows auf 2.9.8 (oder 2.2.28 / 1.10.28) innerhalb der nächsten Stunden. Prüfen Sie parallel die letzten 6–8 Stunden Job-Logs auf das Leak-Pattern. Wenn Treffer: Token revoken, Log-Files entfernen.
- Wenn Sie GitHub Actions auf Self-hosted Runner führen — dann
ist Ihr Exposure-Fenster bis zu 24 Stunden. Update sofort, Log-Audit über die letzten 24–48 Stunden, und alle GITHUB_TOKEN-Aktivitäten im Audit-Log gegen den Leak-Zeitraum gegenprüfen.
- Wenn Sie GitHub-App-Installation-Tokens via Composer verwenden — dann
ist die Situation am ernstesten. Default-TTL 1 Stunde, aber breitere Permissions. Update einspielen, GitHub-App-Token rotieren, App-Audit-Log auf API-Aktivität gegenprüfen.
- Wenn Sie eigene Build-Container mit gepinnter Composer-Version laufen lassen — dann
Image-Tag-Rebuild mit Composer 2.9.8 (oder 2.2.28), Image im Registry pushen, alle Deployment-Pipelines auf neue Tag-Version umstellen, alte Tags markieren.
- Wenn Sie kein GitHub Actions verwenden — dann
ist die akute Leak-Gefahr deutlich reduziert. Trotzdem Composer auf 2.9.8 anheben, weil die Validation auch jede andere Build-Umgebung mit zukünftigen GitHub-Token-Formaten betrifft.
Was wir bewusst nicht tun
- Kein verzögertes Update mit „ist nur eine Annahme“. Die Pre-Conditions sind nicht hypothetisch — sie sind der Standard-Workflow vieler PHP-CI-Pipelines.
- Kein Verlass auf GitHub-Secret-Masking. Der Masker greift bei dieser Schwachstelle nicht zuverlässig. Annahme: jeder Token, der in der Composer-Exception erscheint, ist als geleakt zu betrachten.
- Kein Workaround via Regex-Patch am Composer-Quellcode. Das Upstream-Update redactet zusätzlich die Exception-Message; ein eigener Regex-Fix löst nur eines von drei Problemen.
Was wir bei Moselwal konkret getan haben
Bei uns gilt eine wichtige Vor-Klarstellung: Wir nutzen keine GitHub-hosted Actions Runner. Unsere CI- und Build-Pipelines laufen auf eigener Infrastruktur — GitLab-CI mit GitLab-Runner auf eigenen Hosts plus dedizierte Build-Container. GitHub-App-Installation-Tokens werden in unseren Pipelines nicht verwendet. Damit ist die GHSA-f9f8-rm49-7jv2-Leak-Bedingung in der primären Form bei uns nicht gegeben.
Trotzdem: Composer ist drin, also wird Composer gepatcht
Composer ist in praktisch jedem unserer PHP-Build-Container vorhanden — als Build-Tool zum Komponieren der TYPO3-/Symfony-/Sitepackage-Distributionen. Wir betreiben mehrere Container-Images:
- moselwal/build-base mit PHP + Composer als Basis-Image für alle Sitepackage-Builds
- moselwal/typo3-builder als spezialisiertes Build-Image für TYPO3-Distributionen
- moselwal/frankenphp-runtime mit FrankenPHP-Worker-Mode plus Composer für Build-Steps in Production-Pipelines
Heute (13.05.2026) haben wir alle drei Image-Linien mit beschleunigter Wartungs-Iteration auf Composer 2.9.8 gehoben. Die neuen Image-Tags sind in der Container-Registry, die Build-Pipelines sind auf die neuen Tags umgestellt. Smoketests sind gelaufen.
Bei Kunden auf eigener Infrastruktur
Wer von uns betreute Build-Container in eigenen CI-Pipelines (GitLab, Bitbucket, Drone) einsetzt, hat das Update bereits durch den Image-Pull der nächsten Build-Iteration. Wir empfehlen pro Kunde-Pipeline einen einmaligen Re-Run, damit der neue Container-Stand auch aktiv in den nächsten Deployment-Roll-up einfällt.
Für Kunden mit eigenem GitHub-Actions-Setup
Wenn Sie zusätzlich eigene GitHub-Repositories mit GitHub Actions führen, in denen Composer läuft: dort müssen Sie selber aktiv werden. Die Schritte aus dem Operational Decision Block oben gelten dann für Sie. Wir unterstützen bei Bedarf mit Log-Audit, Token-Rotation und Workflow-Hygiene.
Fazit
GHSA-f9f8-rm49-7jv2 ist ein klassischer Defense-in-Depth-Vorfall: kein klassischer Code-Execution-Bug, kein Speicher-Korruption, sondern eine Validation-Regex aus 2021, die mit einem evolvierten Token-Format nicht mehr funktioniert. Der eigentliche Schaden entsteht durch das Zusammenspiel aus rejected-Token-in-Exception-Message plus unzuverlässigem Secret-Masking. Composer-Team hat schnell reagiert — weniger als 11 Stunden zwischen privater Meldung und veröffentlichtem Patch.
Wer GitHub Actions führt, sollte heute patchen, Logs reviewen und Tokens bei Treffer rotieren. Wer keine GitHub Actions nutzt, sollte trotzdem Composer in allen Build-Containern anheben — die Validation läuft überall, wo Composer mit Tokens umgeht. Und wer einen Builds-Pipeline-Stack bauen will, der vor solchen Schwachstellen robuster ist: weniger Tokens in auth.json, schmalere Permissions auf App-Tokens, kürzere TTLs, Log-Scrubbing als Bestandteil der CI-Pipeline statt Verlass auf Plattform-Masker.
Für unsere Kunden ist die Lage entspannt, weil wir keine GitHub Actions als Build-Stack einsetzen — trotzdem ist Composer auf 2.9.8 angehoben, weil Build-Sauberkeit nicht von Annahmen über den Build-Verbraucher abhängen sollte.
Häufige Fragen zum Composer-Token-Leak
Warum hat es 5 Jahre gedauert, bis dieser Regex-Bug auffiel?+
Die Composer-Validation-Regex von 2021 war gegen das damalige GitHub-Token-Format korrekt. Erst die GitHub-Umstellung auf ghs_<id>_<base64url-JWT> mit Hyphens hat die Regex-Annahme gebrochen. Das ist ein typischer Fall von Protocol-Drift: korrekte Validation altert mit, wenn die Annahmen über den Validation-Input sich ändern. Die Lehre für eigene Build-Pipelines: Token-Format-Prüfungen periodisch gegen die aktuelle Spezifikation re-validieren, nicht 5 Jahre lang als stabil betrachten.
Wir sind Moselwal-Kunde — müssen wir etwas tun?+
Für die Build-Pipelines, die wir betreiben: nein. Wir nutzen keine GitHub-hosted Runner, und unsere Build-Container sind heute beschleunigt auf Composer 2.9.8 gehoben. Falls Sie zusätzlich eigene GitHub-Actions-Setups für Ihre Repositories führen, in denen Composer läuft, gilt die Update-Empfehlung für Sie eigenständig — wir unterstützen bei Bedarf mit Log-Audit und Workflow-Hygiene.
GitHub-Secret-Masker greift doch normalerweise, oder?+
Eben nicht zuverlässig in diesem Fall. Der Masker macht exakte Substring-Matches. Wenn Symfony Console die Exception rendert, kann der Token-String durch Zeilenumbrüche, In BaseIO.php line N:-Framing oder ANSI-Codes interleaved werden. Der Masker findet den Match nicht und redactet nicht. Das ist genau eines der drei Beitragsfaktoren zum Leak.
Wie finde ich heraus, ob bei mir ein Token geleakt ist?+
In Ihren GitHub-Actions-Job-Logs nach dem Pattern contains invalid characters oder BaseIO.php line suchen. Mit GitHub CLI: gh run list --limit 100 --json databaseId --jq '.[].databaseId' | xargs -I {} gh run view {} --log | grep -l 'contains invalid characters'. Wenn Treffer: den Job-Log einzeln prüfen und den Token-Wert extrahieren.
Was ist der konkrete Schaden, wenn ein Token leakt?+
Der geleakte Token gewährt die Permissions, mit denen er ausgestellt wurde. Bei Workflow-GITHUB_TOKEN: typischerweise contents:read, häufig contents:write. Damit sind unautorisierte Commits, Tag/Release-Manipulation, Workflow-Trigger möglich. Bei App-Tokens kann der Scope deutlich breiter sein (issues, PRs, checks, actions schreiben). Die Token-Lebensdauer begrenzt das Fenster: 6h GitHub-hosted, 24h self-hosted, 1h App-Default.
Müssen wir Composer wirklich sofort updaten?+
Wenn Sie Composer in GitHub Actions führen: ja, sofort. Die Schwachstelle ist High Severity (CVSS 7.5), die Mitigation ist trivial (composer.phar self-update), und das neue GitHub-Token-Format wird gerade gradually ausgerollt. Es ist nicht die Frage ob, sondern wann Ihre Repositories das neue Format bekommen.
Build-Pipelines, die nicht von Plattform-Maskern abhängen
Wir bauen TYPO3-, Symfony- und Laravel-Build-Stacks mit klaren Token-Hygiene-Regeln: schmale Permissions, kurze TTLs, Log-Scrubbing auf Pipeline-Ebene statt Verlass auf Plattform-Masker. Wenn Sie wissen wollen, wo Ihre CI-Pipeline Token-Hygiene-Schwachstellen hat, sprechen Sie uns an.




