Kai Ole Hartwig
4 Min. Lesezeit
Von

register_argc_argv=Off hat mich erwischt: php -r in einem gehärteten CI-Image debuggen

Ein php -r-Einzeiler, der auf jedem Laptop funktioniert und in CI stirbt. Die Ursache ist ein php.ini-Flag, das man vermutlich noch nie bewusst gesetzt hat: register_argc_argv. Dieser Beitrag ist Teil 1 von „The Ops Log“ — vier Vorfälle aus dem Betrieb einer selbst gehosteten GitLab-CI- + TYPO3/FrankenPHP-Plattform im Ein-Personen-Setup, jeder mit einer Lehre, die den Vorfall überlebt.

TL;DR

TL;DR: Gehärtete Container-Images liefern register_argc_argv=Off aus. Ist das Flag aus, ist $argv innerhalb von php -r null — php -r '…$argv…' file verliert sein Argument also lautlos. Daten stattdessen über stdin übergeben; das hängt nicht von der ini ab.

Was passiert ist

Ich war dabei, acht Lint-Jobs zu einem zusammenzulegen (das ist Teil 2 dieser Serie). Einer der zusammengefalteten Checks validiert JSON. Wenn das jsonlint-Binary im Repo nicht installiert ist, hatte ich einen Fallback ergänzt, der einmal pro Datei auf PHPs eigenen Parser umschaltet:

check_json — erster Versuch

 

# jede JSON-Datei finden, einzeln an php -r übergeben
find . -name '*.json' -exec php -r '
  foreach (array_slice($argv, 1) as $f) {
    json_decode(file_get_contents($f));
    if (json_last_error() !== JSON_ERROR_NONE) { exit(1); }
  }' {} +

 

Auf meinem Rechner grün. In der CI ging der Job rot, mit einer Meldung, die für gerade erst getesteten Code keinen Sinn ergab:

Job-Trace

 

PHP Fatal error:  Uncaught TypeError: array_slice():
  Argument #1 ($array) must be of type array, null given
  in Command line code:2

 

$argv war null. Nicht leer — null. Auf einem Entwicklungsrechner befüllt die CLI-SAPI $argv standardmäßig, weshalb man es nie bemerkt. Das goldene CI-Image basiert auf einem gehärteten Base-Image mit register_argc_argv=Off in der php.ini — eine verbreitete „wir brauchen kein argv in Produktion“-Härtung. Mit abgeschaltetem Flag existieren $argv und $argc schlicht nicht, und php -r kann den übergebenen Dateinamen nicht sehen.

Das Ganze lässt sich in drei Zeilen reproduzieren — ganz ohne Container:

Reproduktion

 

$ php -r 'var_dump($argv);' hello.json
array(2) { [0]=> "Standard input code" [1]=> "hello.json" }   ✓ funktioniert

$ php -d register_argc_argv=Off -r 'var_dump($argv);' hello.json
NULL                                                        ✗ null

 

Der Fix ist nicht, das Flag mit -d register_argc_argv=1 zurückzudrehen — das ist zerbrechlich, man bekämpft damit das Image. Der Fix ist, gar nicht erst von argv abhängig zu sein. Die Dateiliste NUL-getrennt über stdin einspeisen und mit stream_get_contents(STDIN) lesen:

check_json — der Fix

 

find . ! -path '*/vendor/*' -name '*.json' -print0 \
  | php -r '$bad=0;
      foreach (array_filter(explode("\x00", stream_get_contents(STDIN)), "strlen") as $f) {
        json_decode(file_get_contents($f));
        if (json_last_error() !== JSON_ERROR_NONE) {
          fwrite(STDERR, "✗ invalid JSON: $f — ".json_last_error_msg()."\n"); $bad=1;
        }
      } exit($bad);'

 

Verifiziert, indem ich die exakte CI-Bedingung lokal simuliert habe — -d register_argc_argv=Off —, damit es nicht auf demselben Weg regressieren kann. Ein gültiges Set beendet sich mit 0; eine fehlerhafte Datei mit 1 und einer lesbaren Zeile. Nebenbefund oben im ersten Versuch: Der erste Anlauf scannte auch vendor/, wo Abhängigkeiten absichtlich fehlerhafte JSON-Fixtures ausliefern — dessen Ausschluss war die halbe Miete.

Die Lehre

php -r plus $argv ist eine Portabilitäts-Falle: Es stützt sich auf einen php.ini-Default, den gehärtete Images abschalten. Alles, was in einem fremden Container laufen soll, sollte seine Eingabe über stdin bekommen, nicht über argv.

Häufige Fragen

Betrifft das nur den JSON-Lint-Fallback aus diesem Beispiel?+

Nein — jedes php -r-Snippet, das sich auf $argv/$argc verlässt, ist in einem Image mit register_argc_argv=Off betroffen. Der JSON-Lint-Fallback ist nur das konkrete Beispiel, an dem es sichtbar wurde.

Reicht es, register_argc_argv wieder einzuschalten?+

Technisch ja, mit -d register_argc_argv=1, aber das bekämpft das gehärtete Image statt mit ihm zu arbeiten — bei jedem Image-Update kann die Einstellung wieder verschwinden. Der robustere Fix ist, den Input über stdin statt argv zu beziehen.

Warum bemerkt man das Problem nicht auf dem eigenen Rechner?+

Weil die CLI-SAPI $argv auf Entwicklungsmaschinen standardmäßig befüllt — register_argc_argv=Off ist typischerweise nur in gehärteten Produktions-/CI-Images gesetzt, nicht lokal.

Fazit

Ein einzelnes php.ini-Flag, das kaum jemand bewusst setzt, hat aus einem funktionierenden Einzeiler einen stillen CI-Blocker gemacht. Die Lehre reicht über diesen Fall hinaus: Jeder Code, der in einem fremden, gehärteten Container laufen soll, sollte seine Eingaben so entgegennehmen, dass er von Defaults wie diesem unabhängig ist.

Ich härte und debugge Ihre CI-Pipelines auf gehärteten Base-Images — von PHP-Eigenheiten bis zu GitLab-Runner-Konfiguration.

Audit Ihrer CI-Jobs auf stille Abhängigkeiten von php.ini-Defaults, Umgebungsvariablen und Image-Annahmen, plus Migration auf robustere, image-unabhängige Muster.

Plattform-Betrieb statt Beratung auf Papier: Ich baue und härte Ihre CI/CD-Pipelines laufend.

Termin buchen →

Über den Autor

Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Freiberuflicher DevSecOps-Berater · OnlyOle Consulting

Programmiert seit 2002 – autodidaktisch gelernt, 2012 mit KO-Web selbständig gemacht. Über 100 Projekte, Fokus auf Security, Performance, Automatisierung und Qualität. Heute freiberuflich: DevSecOps-Beratung, Schulungen und Softwareentwicklung.