/releases/ wurde bisher offen ausgeliefert, damit ausgelieferte Anwendungen
ohne Zugangsdaten nach Updates suchen koennen. Das bedeutete aber auch, dass
jeder im Internet die vollstaendigen Pakete herunterladen konnte - mitsamt
allem, was versehentlich darin liegt. Genau so lag ein echter API-Schluessel
in einer mitgelieferten appsettings.json oeffentlich abrufbar.
Zugang haengt jetzt am Lizenzschluessel: Wer eine gueltige Lizenz fuer ein
Produkt hat, kommt an dessen Updates. Die Anwendung kennt ihren Schluessel
ohnehin und versorgt sich damit selbst - es muss nichts verteilt werden.
Server
- ReleaseGuard erzeugt je Produktverzeichnis .htaccess und .htpasswd.
Bewusst getrennt: eine gemeinsame Datei wuerde bedeuten, dass eine Lizenz
fuer Produkt A auch Produkt B oeffnet. license_licenses.product_id bindet
jeden Schluessel ohnehin an genau ein Projekt.
- Eingetragen werden aktive, nicht abgelaufene Lizenzen (Benutzername =
Passwort = Schluessel; Basic Auth braucht zwei Felder, es gibt aber nur ein
Geheimnis) sowie alle Installationskonten - bei einer Erstinstallation gibt
es noch keinen Schluessel, mit dem sich das Paket holen liesse.
- Deren Hash wird unveraendert aus dc_users uebernommen: password_hash()
erzeugt bcrypt im Format $2y$, genau das versteht Apache. Ein
Klartextpasswort wird nirgends gebraucht. Argon2-Hashes werden erkannt und
uebersprungen statt eine unbrauchbare Datei zu erzeugen.
- Lizenzschluessel werden mit Kosten 8 gehasht statt 12: 29 Zeichen
maschineller Zufall sind kein Menschenpasswort, Apache prueft aber bei
*jeder* Anfrage neu.
- Geschrieben wird ueber eine temporaere Datei mit rename() - ein Abbruch
wuerde sonst eine halbe Zugangsdatei hinterlassen und in dem Moment die
halbe Kundschaft aussperren.
- Neu erzeugt bei jeder Lizenz- und Kontoaenderung. Abgelaufene Lizenzen
loesen anders als ein Widerruf nichts aus; dafuer gleicht cli/tick.php nach
und erzeugt spaetestens alle sechs Stunden neu.
- Statusanzeige und Schaltflaeche im WebUI unter UpdateService.
Client
- ReleaseCredentials: Lizenzschluessel oder Installationskonto als Basic Auth.
- UpdateClient und Agent senden sie fuer latest.json und package.tar.gz.
- UpdateCheckResult.Unauthorized trennt "Lizenz traegt nicht mehr" von einem
Netzwerkfehler. Ohne diese Unterscheidung sucht man an der falschen Stelle.
- LaunchUpdateAgent reicht licenseKey als --license-key durch.
- Der Installer benutzt die beim Anmelden eingegebenen Zugangsdaten auch fuer
den Paketabruf; das Setup-Token taugt dafuer nicht, weil Apache prueft und
nicht die Anwendung.
Sonstiges
- deploy.py klammert artifacts/ aus. Ohne das landeten die gebauten
Installer-Binaries zusaetzlich unter /artifacts/ im Webroot.
ACHTUNG Reihenfolge: Der Schutz sperrt jede Anwendung aus, die noch mit dem
alten SDK gebaut ist. Erst ausliefern, dann scharfschalten - siehe
UPGRADE.md §16.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ohne .gitattributes wandelt Git auf einem Windows-Arbeitsplatz beim Auschecken
in CRLF um. Fuer install.sh ist das kein Schoenheitsfehler, sondern ein
Ausfall: Das Skript wird auf Linux-Zielsystemen ausgefuehrt und scheitert dort
mit "bad interpreter: /bin/sh^M". Aufgefallen an der Umwandlungswarnung beim
vorigen Commit - die ausgelieferte Fassung auf dem Server hat noch LF, die
naechste aus dem Repository gebaute haette es nicht mehr gehabt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bisher gab es nur den Update-Weg: eine Anwendung musste bereits installiert und
eingerichtet sein, damit sich etwas aktualisieren liess. Die Erstinstallation
auf einem neuen System war Handarbeit - Paket kopieren, Konfiguration
abtippen, Token besorgen.
Setup-API (neu)
- POST /api/setup/v1/login tauscht Benutzername und Passwort gegen ein Token
mit 30 Minuten Gueltigkeit und ausschliesslich setup:install. Es wird nicht
mitgeschrieben und lebt im Installer nur im Speicher.
- GET /api/setup/v1/catalog zeigt nur, was zur Laufzeitkennung des anfragenden
Systems passt. Ein Projekt mit ausschliesslich Windows-Paket taucht auf einem
Linux-Rechner gar nicht erst auf.
- POST /api/setup/v1/token stellt das Dauertoken der Anwendung aus. Welche
Rechte vergeben werden, entscheidet der Server; die Anfrage kann nur
einschraenken. Sonst waere der Umweg ueber ein kurzlebiges Token wirkungslos.
Rollentrennung (Migration 012)
- dc_users bekommt role, disabled und last_login_at. Die Rolle "installer"
darf sich ueber den Setup-Weg anmelden und nicht am WebUI. Die Zugangsdaten
werden auf jedem Zielsystem eingetippt; mit einem Administratorkonto
verteilte man damit den Zugang zu Tokens, Lizenzen und Monitoren auf jeden
Rechner, auf dem je etwas installiert wurde.
- Auth::verifyCredentials() prueft sessionfrei, damit Setup- und WebUI-Login
nicht zwei verschiedene Haertungsgrade haben (Drosselung, Timing-Angleichung,
Rehash gelten fuer beide).
- Konten mit hinterlegtem TOTP-Geheimnis werden am Setup-Weg mit 501
abgewiesen. Eine TOTP-Pruefung gibt es im Deploymentcenter noch nicht; sie
stillschweigend zu uebergehen waere ein Rueckschritt.
- Benutzerverwaltung im WebUI - es gab bisher gar keine, nur den einen von
install_db.php angelegten Admin. Das letzte aktive Administratorkonto laesst
sich weder deaktivieren noch loeschen.
Installer
- update-agent --action install fuehrt durch Anmeldung, Auswahl,
Zielverzeichnis, Installation und Einrichtung. Die Dateien kommen ueber
denselben Pfad wie ein Update - mit Pruefsumme, Signatur, Staging und
Rollback. Ein zweiter Download-Weg waere ein zweiter Ort fuer dieselben
Fehler.
- --action configure holt die Einrichtung nachtraeglich.
- setup.json im Paket beschreibt die benoetigten Werte. Bewusst im Paket und
nicht zentral: so ist sie mit der Anwendung versioniert.
- Gefragt wird nur, was uebrig bleibt: bereits gesetzt -> detect:... ->
provision -> fragen. Platzhalter wie changeme oder <dein-wert> gelten dabei
nicht als eingerichtet, sonst liefe die Anwendung mit der Vorlage los.
- SetupWriter erhaelt vorhandene Inhalte. Eine appsettings.json fuehrt neben
den abgefragten Werten meist Logging und anderes; sie neu zu erzeugen waere
bequemer und verloere das - bei einer Neuinstallation ohne Backup.
int und bool landen als JSON-Typ, nicht als Zeichenkette.
Downloads
- scripts/build_installer.ps1 baut selbstenthaltende Einzeldateien fuer
win-x64, linux-x64 und linux-arm64 (rund 34 MB, .NET-Laufzeit inbegriffen).
Ohne NativeAOT und ohne Trimming: Spectre.Console loest ueber Reflexion auf
und braeche sonst erst beim Anwender.
- scripts/upload_installer.py laedt sie nach /installer/. Getrennt von
deploy.py, das client-dotnet bewusst ausklammert.
- Bereich "Installer" auf der UpdateService-Seite mit Groessen, Pruefsummen
und den wget-Befehlen; die Angaben stammen aus installer.json statt aus fest
eingetragenem Text.
- install.sh und install.ps1 laden, pruefen die Pruefsumme und legen ab -
sie richten bewusst nichts selbst ein. Das Manifest wird BOM-frei
geschrieben, sonst scheitert json_decode() daran.
Enthaelt ausserdem die bislang nicht committete Arbeit an den
RocketChat-Benachrichtigungen (Migrationen 010 und 011) sowie die Loesch- und
Editierfunktion des UpdateService; die betroffenen Dateien liessen sich nicht
getrennt stagen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Behebt eine Reihe zusammenhaengender Fehler im Update-Weg, die zusammen
verhindert haben, fuer mehr als eine Plattform auszuliefern - und die im
Fehlerfall halb aktualisierte Installationen hinterliessen.
Server
- Migration 009: Spalte platform samt neuem Unique-Key. Zuvor verdraengte das
zuletzt veroeffentlichte Paket alle anderen Plattformen derselben Version,
weil ON DUPLICATE KEY auf (slug, version, channel) griff. Ein Linux-System
zog sich damit das Windows-Paket.
- Aufloesungsregel: je Version das plattformgenaue Paket, sonst das
plattformunabhaengige. Ein Client ohne Plattformangabe sieht ausschliesslich
'any' - lieber kein Update als das falsche.
- manifest_json wird endlich befuellt; die Spalte blieb bisher immer leer,
wodurch die API nie der Rueckfall sein konnte, als der sie gedacht war.
- Releases werden serverseitig mit RSA-SHA256 signiert, neuer Endpunkt
/api/updateservice/v1/pubkey. Bewusst kein HMAC: der Pruefende laeuft auf
fremden Systemen und darf den Signierschluessel nicht besitzen.
Packager
- Bricht ab, statt die Versionshistorie zu verlieren. Schlug das Lesen der
bestehenden latest.json fehl, ersetzte ein leeres catch die komplette
Historie durch einen einzigen Eintrag - ohne jede Meldung.
- Echte Glob-Muster. Zuvor trafen "logs/**" und "scratch/**" aus der
mitgelieferten Beispielkonfiguration nie zu.
- preservePatterns: Konfigurationsvorlagen werden ausgeliefert, ersetzen am
Ziel aber keine vorhandene Datei. Eine settings.json mit Zugangsdaten
ueberschrieb bisher beim Update die Konfiguration jedes Zielsystems.
- Warnt vor Dateien, die nach Zugangsdaten aussehen und auf keiner Liste stehen.
- Prueft --version gegen die Hauptassembly. Eine Abweichung fuehrte zu einer
Endlosschleife: Clients aktualisieren, melden weiter die alte Version,
halten das Release erneut fuer neu.
- --platform mit Ableitung aus dem Publish-Pfad.
Agent
- Anwenden mit Plan, Backup und vollstaendigem Rollback. Die Stelle war als
"Atomic Replace with Backup" kommentiert und war eine Kopierschleife.
- Verwaiste Dateien werden entfernt, aber nur solche aus dem Manifest der
Vorversion. Was nicht aus einem Release stammt, bleibt liegen.
- Das laufende Agent-Binary wird zur Seite gelegt statt ueberschrieben.
- API-Rueckfall in FetchManifestAsync; bisher nur im SDK vorhanden, weshalb
die Anwendung "Update verfuegbar" und der Agent "kein Release" sagen konnte.
- Installierte Version aus --current-version oder manifest.json statt des
Textes "Unbekannt", der als 0 gelesen wurde und jede Version neuer erscheinen
liess. Reparatur funktioniert damit auch ohne manifest.json.
- Setzt das Ausfuehrungsbit fuer Linux-Pakete, die unter Windows gebaut wurden.
SDK
- ResolveAgentPath() liefert den plattformrichtigen Namen; ein fest verdrahtetes
"update-agent.exe" wird unter Linux nie gefunden.
- LaunchUpdateAgent uebergibt jetzt --restart (wurde nie uebergeben, die
Anwendung blieb nach dem Update zu), --wait-for-pid (kein Wettlauf mehr mit
dem Herunterfahren) und --platform.
Enthaelt ausserdem die bislang nicht committete Arbeit an Watchdog, Lizenz-
Client und cli/tick.php samt Migration 008; die betroffenen Dateien liessen
sich nicht getrennt stagen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Watchdog-Anleitung vollständig überarbeitet
- Alle drei Codebeispiele trugen das geseedete Demo-Token fest im Quelltext.
Es ist an die Source "srv-db-01" gebunden — wer es übernommen hätte, wäre für
jeden anderen Dienst abgewiesen worden. Jetzt Umgebungsvariable und eine
Anleitung, wie man ein eigenes Token erzeugt.
- Der Ratschlag "sende beim Beenden einen Ping mit Status stopped" beschrieb
etwas, das die API nicht konnte. Statt die Anleitung an die Lücke anzupassen,
ist die Lücke geschlossen: status akzeptiert jetzt "stopped" und
"maintenance". Der Evaluator lässt solche Monitore in Ruhe, statt wenige
Minuten nach jedem sauberen Shutdown einen Fehlalarm zu erzeugen.
- Neu dokumentiert: checks (Gesundheitszustand per Push, ohne offene Ports),
metrics samt Verlauf und Abweichungsvergleich, Alarmunterdrückung über die
Hierarchie, die Schwellen des Evaluators (2x warning, 4x down) und der
erforderliche Cron-Job.
Lizenz-Anleitung
- Neuer Abschnitt zum Antwortformat. Die Lizenz-Endpunkte antworten bewusst
ohne den status/error-Umschlag der übrigen API; das Feld status auf oberster
Ebene trägt den Lizenzzustand. Genau diese Besonderheit hatte ich beim Umbau
übersehen, weshalb sie jetzt ausdrücklich festgehalten ist — samt Tabelle
aller Zustände.
- Ergänzt: Deaktivierung braucht den shared_key, mit Beispiel für den .NET-
Client und curl. Verhalten bei Ratenbegrenzung.
UpdateService-Anleitung
- Prüf-Endpunkte dokumentiert (check, latest, releases) samt Antwortformat.
- Tabelle zum Versionsvergleich mit den Fällen, die vorher falsch liefen.
- Auto-Resolve beim Veröffentlichen beschrieben.
Agent-Prompt-Vorlage
- Fehler-Schnittstelle ergänzt, inklusive Hinweis auf "ignored": true, damit
ein Agent bekannte Fehler nicht untersucht.
Alle in der Dokumentation genannten API-Pfade und Scopes wurden maschinell
gegen Routing und TokenManager abgeglichen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rücksprung auf das Dashboard
Nach jedem Speichern landete man auf der Standard-Unteransicht des Moduls
statt dort, wo man gearbeitet hatte. Drei Ursachen:
- Die Handler setzten nur den Modul-Anker (#tab-watchdog). Damit blieb die
Unteransicht unbestimmt und es wurde die erste angezeigt.
- Die Wiederherstellung aus dem Anker leitete den Modulnamen aus dem Namen der
Unteransicht ab (sub-<modul>-<rest>). Für "sub-update-releases" ergab das
"update" statt "updateservice".
- Der zugehörige Navigationsknopf wurde über seinen Beschriftungstext gesucht,
der den Modulnamen nie enthält. Der Rückweg schlug also immer fehl.
Jetzt teilt das Formular beim Absenden mit, welche Unteransicht offen war
(_return), und der Server leitet dorthin zurück. Umgesetzt als zentraler
submit-Zuhörer, damit künftige Formulare das Verhalten erben. Die Zuordnung
Unteransicht -> Modul kommt aus derselben Liste, die auch die Navigation
aufbaut, und die Knöpfe tragen eine eindeutige Kennung statt Text.
Zusätzlich wird die Ansicht jetzt auch bei Vor- und Zurück-Navigation im
Browser wiederhergestellt.
Lizenzmodul
Vier Unteransichten für eine Aufgabe sind auf zwei sinnvolle reduziert:
- "Dashboard" zeigte drei Zahlen; sie stehen jetzt über der Liste.
- "Lizenz Bearbeiten & HW" ist entfallen. Zum Bearbeiten musste man die gerade
angeklickte Lizenz in einer Auswahlliste erneut suchen. Jetzt öffnet sich ein
Dialog direkt über der Tabelle, die Hardware-Liste steht daneben.
- "Settings" enthielt zwei fest verdrahtete URLs und ist in "Integration"
aufgegangen, zusammen mit einem Beispielaufruf.
- Der dort angezeigte "Ed25519 Public Key" war frei erfunden und wurde
entfernt; die Offline-Vorschau nennt jetzt das tatsächlich verwendete
HMAC-SHA256 und weist darauf hin, dass nur der Download signiert ist.
Nebenbei
Der Inhalt des Projekte-Moduls lag direkt im Modul-Container, ohne
subtab-content. Der Navigationseintrag "Projekte Übersicht" zeigte damit ins
Leere. Jetzt einheitlich wie alle anderen Module.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Seite brach beim Aufbau ab und lieferte im Browser
"switchMainTab is not defined": das Rendering endete vor dem Script-Block,
sodass keine einzige Funktion definiert wurde.
Ursache: e() war als ?string deklariert. PHP wandelt numerische Array-Schlüssel
automatisch in int um, sodass die Zeitraum-Auswahl im Fehler-Stream mit den
Schlüsseln 0/1/24/168/720 einen int übergab. Unter declare(strict_types=1) warf
das einen TypeError mitten in der Ausgabe.
Die enge Signatur war generell unpassend: mit ATTR_STRINGIFY_FETCHES = false
liefert PDO numerische Spalten als int, und genau diese Werte werden über e()
ausgegeben. Die Funktion nimmt jetzt jeden skalaren Typ entgegen.
Gefunden hat den Fehler die Selbstmeldung: die unbehandelte Exception landete
als Item im eigenen Bugtracker, inklusive Datei und Zeilennummer.
Die übrigen typisierten Helfer (dc_dt, dc_ago, getMonitorIconUrl, btUrl,
errUrl) wurden gegengeprüft; sie erhalten ausschließlich Strings bzw. Arrays.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fehler-Schnittstelle
- Neuer schlanker Eingang POST /api/errors/v1/report für den globalen
Exception-Handler einer Anwendung. Titel und Dringlichkeit leitet der Server
ab; gespeichert wird in derselben Tabelle wie der Bugtracker. Ein zweiter
Speicher wäre nur ein zweiter Ort, an dem man suchen müsste.
- error_level (fatal/error/warning) trennt die technische Art des Ereignisses
von der geschäftlichen Dringlichkeit. Ein Duplicate-Entry ist technisch ein
error, geschäftlich belanglos — beides zu vermischen war der Grund, warum
solche Meldungen als Bug im Dashboard landeten.
Ignore-Regeln gegen bekanntes Rauschen
- bugtracker_ignore_rules mit contains/regex/exception_class, Pflichtfeld für
die Begründung und optionaler Alarmschwelle.
- Ein Treffer bedeutet nicht "wegwerfen": Der Fehler wird weiterhin erfasst und
hochgezählt, bleibt aber aus der Übersicht heraus und löst keine
Benachrichtigung aus. Der Zähler ist der eigentliche Zweck — dass ein
bekannter Fehler auftritt, ist normal; dass er plötzlich hundertmal so oft
auftritt, ist ein Signal. Dafür das rollende Stundenfenster und
error.rate_exceeded.
- Neue Regeln lassen sich rückwirkend auf bestehende Einträge anwenden.
Gruppierung überarbeitet
- Der Schlüssel nahm bisher 300 Zeichen Stacktrace auf. Derselbe Fehler
zersplitterte dadurch, sobald ein Aufrufer den Stack einmal mitschickte und
einmal nicht. Jetzt zählt der Ursprungsort: bevorzugt die Dateiangabe, sonst
der erste Rahmen des Stacktrace.
- Die Normalisierung ersetzte nur Zahlen ab vier Stellen, wodurch
'AA-1' und 'BB-2' getrennt blieben. Werte in Anführungszeichen, die Ziffern
enthalten, gelten jetzt als veränderlich — der Schlüsselname bleibt erhalten,
sodass verschiedene Unique-Keys unterscheidbar sind. Mit 9 Testfällen belegt.
Metrik-Verlauf
- watchdog_metrics speichert numerische Heartbeat-Werte mit Zeitstempel.
Zuvor wurde metrics_json bei jedem Heartbeat überschrieben; damit ließ sich
"die Platte läuft seit drei Tagen voll" nicht erkennen, nur "sie ist voll".
- GET /api/watchdog/v1/metrics liefert den verdichteten Verlauf und die
Abweichung vom eigenen Sieben-Tage-Durchschnitt. Dieser relative Ansatz
braucht keine projektspezifischen Schwellwerte.
- Aufbewahrung 14 Tage, Bereinigung stündlich durch den Evaluator.
Health-Checks per Push statt Abruf
- Der Heartbeat nimmt ein checks-Objekt entgegen, das die Anwendung selbst
ermittelt. Das Deploymentcenter interpretiert die Namen nicht, es liest nur
ok und message — was "gesund" bedeutet, entscheidet jede Anwendung selbst.
Schlägt eine Prüfung fehl, wird ein als ok gemeldeter Heartbeat auf warning
herabgestuft.
- Bewusst ausgehend: auf den Zielmaschinen müssen keine Ports geöffnet werden.
Abhängigkeitsbewusste Alarmierung
- Fällt ein Monitor aus, dessen Parent selbst unten ist, wird der Alarm
unterdrückt. Der Zustand bleibt sichtbar. Vorher erzeugte ein ausgefallener
Hypervisor mit zwölf VMs dreizehn Meldungen für ein Problem.
- Mehrere Ebenen und fehlerhafte Hierarchien (Zyklen, gelöschte Parents) sind
abgesichert; mit 10 Testfällen belegt.
WebUI
- Neue Ansicht "Fehler-Stream" mit Filtern nach Projekt, Fehlerklasse,
Umgebung, Zeitraum und Sichtbarkeit sowie Volltextsuche und Pagination.
Stummgeschaltete Einträge sind standardmäßig ausgeblendet.
- Verwaltung der Ignore-Regeln inklusive Trefferzähler.
- Die Detailansicht zeigt Fehlerklasse, Stummschaltungsgrund und die Häufung
im laufenden Stundenfenster.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Regression aus dem vorigen Commit
- /api/license/v1/validate lieferte die Antwort im neuen status/error-Umschlag.
Der Vertrag dieses Endpunkts ist aber bereits ausgerollt: das Feld "status"
auf oberster Ebene trägt den Lizenzzustand (valid, revoked, expired ...).
LicenseClient las dadurch "success" statt "valid" — jeder ausgelieferte
Client hätte seine Lizenz für ungültig gehalten. Die Lizenz-Endpunkte
antworten jetzt wieder ohne Umschlag (Http::raw).
Gefunden durch Ausführen der projekteigenen Test-Suite gegen den Server.
Packager
- FTP-Zugangsdaten standen als Standardwerte im Quelltext und zusätzlich in
packager.config.json und in der Integrationsanleitung. Alle drei Fundstellen
bereinigt; die Konfigurationsdatei ist nicht mehr versioniert. Zugangsdaten
kommen aus Datei, Umgebungsvariablen oder CLI-Argument, sonst bricht das
Programm mit einer klaren Meldung ab.
- Das Veröffentlichen sendet jetzt ein Token (updateservice:publish) und nutzt
den Endpunkt /api/updateservice/v1/publish.
- Fehler wurden von einem leeren catch verschluckt, und ohne Erfolgsfall wurde
gar nichts ausgegeben. Das Werkzeug meldete am Ende immer Erfolg und lieferte
Rückgabewert 0, selbst wenn FTP-Upload und API-Aufruf fehlgeschlagen waren.
Jetzt ehrliche Meldungen und Rückgabewerte 0/1/2.
- packager.config.json wurde vom csproj nie ins Ausgabeverzeichnis kopiert,
weshalb sie dort nie gefunden wurde und stets die hartkodierten Werte griffen.
UpdateClient
- IsVersionNewer entfernte die Vorabkennung, aber kein führendes "v". Damit
scheiterte Version.TryParse bei "v1.4.2" und es wurde auf einen
alphabetischen Vergleich zurückgefallen, in dem "v1.9.0" als neuer gilt als
"v1.10.0" — derselbe Fehler wie zuvor serverseitig im SQL. Ersetzt durch
einen vollständigen semantischen Vergleich, verifiziert mit 16 Testfällen.
- Der Rückfall auf die API lag in einem catch-Block, aber GetAsync wirft bei
einem 404 keine Exception. Fehlte die statische latest.json, brach die
Prüfung ab, statt die API zu befragen.
Dokumentation
- BUGTRACKER_INTEGRATION_GUIDE.md beschrieb denselben Workflow ein zweites Mal
und war bereits auseinandergelaufen: Aufrufe ohne Token, alte Pfade, weder
Claim/Lease noch Idempotenz. Ersetzt durch einen Verweis auf das gepflegte
Agenten-Handbuch samt Übersicht der Änderungen.
- UPDATESERVICE_INTEGRATION_GUIDE.md um Token, Umgebungsvariablen und
Rückgabewerte ergänzt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sicherheit
- install_db.php war ohne Authentifizierung erreichbar und setzte bei jedem
Aufruf das Admin-Passwort auf einen fest im Code stehenden Wert zurück.
Jetzt Auth-Pflicht; ein Konto wird nur bei leerer Benutzertabelle angelegt.
- Stored XSS im Bugtracker-Detail-Modal: Titel, Beschreibung, Fehlermeldung,
Stacktrace und Kommentare gingen ungefiltert durch innerHTML.
- report.php, projects.php und das Veröffentlichen von Releases verlangen jetzt
zwingend ein Token. Publish war zuvor völlig ungeschützt.
- CSRF-Token in allen Formularen, Session-Regenerierung nach Login,
Drosselung fehlgeschlagener Anmeldeversuche.
- Zugangsdaten aus der Versionskontrolle entfernt (Serverdaten.txt,
config.php, .htpasswd, deploy_config.json). Historie enthält sie weiterhin,
Rotation erforderlich (siehe docs/UPGRADE.md).
- Token-Validierung nur noch über SHA-256-Hash; expires_at wird ausgewertet.
Behobene 500er
- Audit::log() war in index.php weder eingebunden noch importiert. Jeder
Klick auf "Aktivierung freigeben" endete in einem Fatal Error.
- Derselbe benannte PDO-Platzhalter mehrfach je Statement (:id in
revokeToken/deleteToken, :q siebenfach in der Volltextsuche). Bei
EMULATE_PREPARES=false ist das nicht zulässig und warf HY093.
- Migration 005 nutzte dynamisches SQL, dessen Semikolons in String-Literalen
vom alten explode(';')-Installer als Statement-Ende gelesen wurden. Sie
schlug still fehl, wodurch push_id/target_agent/tags dauerhaft fehlten.
- Monitor-Umbenennung ohne Transaktion, verschachtelte Transaktionen im
RateLimiter.
Funktionale Korrekturen
- Der Watchdog-Evaluator fehlte vollständig: Monitor-Zustände änderten sich nur
beim Eintreffen eines Heartbeats, ein ausgefallenes System blieb dauerhaft
"up". Erster Lauf auf dem Produktivsystem: 7 von 10 Monitoren waren
tatsächlich seit über einem Tag nicht erreichbar.
- Das Feld "os" fehlte im Monitor-Dialog, wurde aber gespeichert und löschte
damit bei jedem Speichern das Betriebssystem.
- Der Resolve-Dialog existierte im HTML nicht; der Button war funktionslos.
- Versionsvergleich erfolgte lexikografisch, wodurch 1.9.0 als neuer galt
als 1.10.0.
- Schreiboperationen meldeten Erfolg auch für nicht existierende IDs.
- Post/Redirect/Get gegen doppelte Einträge beim Neuladen.
Neue Struktur
- src/bootstrap.php mit PSR-4-Autoloader ersetzt die require-Ketten.
- Core: Config, Http, Csrf, ApiAuth, Logger, Migrator, ErrorReporter.
- Migrator mit zeichenweisem SQL-Parser, dc_migrations und Baseline-Verfahren,
damit bestehende Installationen keine Beispieldaten zurückbekommen.
Agenten-Workflow
- Claim/Lease: Items werden exklusiv übernommen, damit nicht zwei Agenten am
selben Problem arbeiten. action=next holt und reserviert in einem Zug.
- Idempotenz über client_ref, Deduplizierung auch für Feature Requests,
Erkennung von Regressionen, automatische Eskalation des Schweregrads.
- Strukturierter Code-Kontext (repo_url, commit_sha, file_path, line_no).
- Delta-Abfragen über updated_since, Pagination, Bulk-Update.
- Beim Veröffentlichen eines Releases schließen sich Items mit passendem
resolved_in_build selbst.
- Ausgehende Webhooks mit HMAC-Signatur, /api/health, /api/openapi.json.
- Unbehandelte Fehler meldet die Plattform in ihren eigenen Bugtracker.
WebUI
- Serverseitige Filterung mit Pagination statt Rendern aller Datensätze.
- Migrations-Schranke, Evaluator-Warnung, Übersicht aktiver Agenten.
Zeitstempel liegen in der Datenbank durchgängig in UTC und werden für die
Anzeige in die App-Zeitzone umgerechnet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>