Hoch

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.

Handgesetzter Drucker-Setzkasten aus Walnuss mit Metalllettern, ein separates Hyphen-Type auf einem Kraftpaper-Etikett mit oxblutfarbenem REJECTED-Stempelabdruck. Daneben eine messingfarbene Karteikartenkassette mit halb herausgezogener Karte, deren Token-String von einer Stencil-Schablone teilweise verdeckt ist. Im Hintergrund die helle Glasfront eines modernen Moselhauses mit sonnigem Weinberg-Hang.

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-Format ghs_<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.json landet. 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.jsonshivammathur/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 install oder composer update ruft, 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-token oder 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-eigene permissions:-Statement — d.h. ein leak liefert potenziell weitreichendere Zugriffsrechte als die Job-Decläration.

 

Wer ist nicht direkt betroffen?

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:

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:

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

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:

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.

Sprechen Sie mit uns