Files
management/docs/issues/0055-npm-scope-aufloesung-threadnet-web.md
T
Thore Cimbal beeeaed145 #0055: Korrektur - "vollstaendig geschlossen" stimmte nicht
v0.6.0-rc.1 scheiterte am 2026-08-19 mit genau dem 404, den dieses Issue behoben
haben sollte. Die .npmrc stand nie in der COPY-Zeile von apps/web/Dockerfile und
kam im Docker-Build nie an.

Die Abnahme hat gemessen, ob die Konfiguration wirkt, wenn sie vorhanden ist -
nicht, ob sie ueberall ankommt, wo installiert wird. Gutgegangen ist es nur,
weil --frozen-lockfile unter pnpm 10 die gepinnte Tarball-URL nahm und gar nichts
aufloeste; der Schutz kam vom Lockfile, nicht von der .npmrc. pnpm 11 prueft mit
minimumReleaseAgeStrict jeden Eintrag, loest wieder auf und landet ohne .npmrc
bei npmjs. Genau diesen Ablauf sagt die .npmrc in ihrem eigenen Kommentar voraus.

Behoben in ThreadNet-Web:c90ac4d. Status bleibt done - korrigiert wurde die
Behauptung, nicht der Zustand.

Lehre: Fuer Konfiguration, die ueber ihre Position im Dateisystem wirkt, ist die
Liste der Orte die Pruefung, nicht das Verhalten an einem davon.
2026-08-20 12:00:00 +00:00

8.5 KiB

type, id, status, created, milestone, priority, area, related, gitlab_iid
type id status created milestone priority area related gitlab_iid
issue 0055 done 2026-08-16 M2 medium security
docs/issues/0054-ki-geraeuschunterdrueckung-element-call.md
docs/adr/0018-ki-geraeuschunterdrueckung-clientseitig-opt-in.md
docs/adr/0001-gitlab-kanonisch-push-mirror.md
39

Issue-0055: @sorb-Scope in ThreadNet-Web ist nirgends auf rohana festgelegt

Problem / Motivation

ThreadNet-Web bezieht @sorb/threadnet-call-embedded aus der Gitea-npm-Registry auf rohana, aber im Repo steht nirgends, dass der Scope @sorb dorthin zeigt. Es gibt weder eine .npmrc im Wurzelverzeichnis noch in apps/web, und die CI setzt auch keine.

Dass es trotzdem funktioniert, liegt allein am Lockfile: pnpm-lock.yaml pinnt die vollständige Tarball-URL

tarball: https://rohana.axion1337.de/api/packages/sorb/npm/%40sorb%2F…tgz

und die CI installiert mit --frozen-lockfile. Solange niemand die Version anhebt, wird der Scope nie aufgelöst — und der fehlende Eintrag fällt nicht auf.

Aufgefallen beim Anheben auf 0.19.2-threadnet.8 (#0054): pnpm install griff nach registry.npmjs.org und brach ab.

ERR_PNPM_FETCH_404  GET https://registry.npmjs.org/@sorb%2Fthreadnet-call-embedded: Not Found

Die Anhebung ging nur durch, weil der Scope für diesen einen Aufruf von Hand gesetzt wurde:

env 'npm_config_@sorb:registry=https://rohana.axion1337.de/api/packages/sorb/npm/' pnpm install

Das steht in keiner Doku. Der nächste, der die Abhängigkeit anhebt, läuft in dieselbe Wand.

Warum das mehr ist als eine Unbequemlichkeit

Der heutige Fehlschlag ist laut — 404, Abbruch, niemand baut versehentlich etwas Falsches. Laut ist gut. Der Punkt ist, dass die Lautstärke von einer Bedingung abhängt, die uns nicht gehört: @sorb/threadnet-call-embedded existiert auf registry.npmjs.org derzeit nicht.

Registriert dort jemand diesen Namen, löst genau derselbe Befehl nicht mehr mit 404 auf, sondern erfolgreich — gegen ein fremdes Paket. Das ist das Muster, das als dependency confusion bekannt ist, und die Bedingung dafür ist nicht „ein Angriff auf uns", sondern „jemand legt einen Scope-Namen an". Der Schutz besteht heute ausschließlich darin, dass ein Name auf einer fremden Registry noch frei ist.

Verschärfend: Der Fehler träfe genau den Moment, in dem jemand eine Version anhebt — also den Moment, in dem eine neue Abhängigkeit ohnehin erwartet wird und ein Download nicht auffällt.

Das ist dieselbe Klasse wie die Befunde aus #0054 und der Backup-Reihe: die Absicherung besteht nicht, sie ergibt sich nur zufällig aus dem aktuellen Zustand. Sie „meldet" auch nichts — sie funktioniert stillschweigend, bis sie es nicht mehr tut.

Acceptance

  • Eine eingecheckte .npmrc in ThreadNet-Web bindet den Scope fest: @sorb:registry=https://rohana.axion1337.de/api/packages/sorb/npm/
  • Ein Anheben der Version funktioniert im frischen Klon ohne Zusatzschritte — das ist der eigentliche Nachweis, nicht das Vorhandensein der Datei.
  • Gegenprobe: Ein Lauf mit absichtlich unerreichbarer rohana scheitert, statt auf registry.npmjs.org auszuweichen.
  • Die anderen Repos der Gruppe sind auf denselben Befund geprüft: Wer sonst noch @sorb-Pakete zieht, hat dieselbe Lücke (threadnet-call selbst publisht nur, konsumiert aber ggf. auch).
  • Der Auslieferungsweg in ThreadNet-Web:docs/axion1337-fork.md nennt den Befehl zum Anheben.

Notes

Kein Token nötig. Der Lesezugriff auf die Registry ist anonym möglich (nachgeprüft: der Paket-Index antwortet ohne Authentifizierung). Die .npmrc enthält damit nur eine URL und kein Geheimnis und kann bedenkenlos eingecheckt werden — das ist der Grund, warum hier kein Secrets-Handling dranhängt.

Warum nicht einfach dokumentieren: Ein Satz in der Fork-Doku würde den 404 erklären, aber die registry.npmjs.org-Auflösung nicht verhindern. Nach der Lehre aus #0053 und dem Bindmount-Fall ist Aufschreiben hier ausdrücklich nicht die Maßnahme: die Regel war beide Male vorhanden und hat den Fehler nicht verhindert. Wirksam ist die eingecheckte Datei, weil sie den falschen Weg gar nicht erst offen lässt.

Verwandt, aber getrennt: Bei der Gelegenheit ist aufgefallen, dass der Job publish_npm in threadnet-call allow_failure: true trägt — ein scheiternder Publish färbt die Pipeline nicht rot. Das ist ein eigener Befund und gehört nicht in dieses Issue; hier nur notiert, damit er nicht verloren geht.

Erledigt 2026-08-18 — .npmrc eingecheckt, Auflösung nachgewiesen

Umgesetzt in ThreadNet-Web (be323ed): .npmrc im Wurzelverzeichnis bindet @sorb an die Registry auf rohana.

Ein Fund beim Umsetzen, der den naiven Fix stillschweigend zunichtegemacht hätte: Upstreams .gitignore enthält /.npmrc (Zeile 7) — sinnvoll, wo die Datei Tokens trägt. Die Datei wäre also lokal geblieben, ohne dass irgendetwas gemeldet hätte; CI und frischer Klon hätten weiter gegen npmjs aufgelöst. Die Ausnahme steht jetzt ausdrücklich mit Begründung im .gitignore (!/.npmrc), damit der nächste Upstream-Merge die Lücke nicht wieder aufmacht. Dieselbe Klasse wie die Befunde aus #0054: gemeldeter Erfolg ohne Wirkung.

Abnahme — in einem isolierten Baum gemessen, nicht angenommen:

Prüfung Ergebnis
ohne .npmrc (heutiger Zustand) löst gegen registry.npmjs.org auf, bricht ab
mit .npmrc, frische Auflösung ohne Zusatzschritt Tarball von rohana.axion1337.de, Integrity identisch mit dem Repo-Lockfile
Gegenprobe rohana unerreichbar ERR_PNPM_META_FETCH_FAIL, kein Ausweichen auf npmjs, kein Lockfile
pnpm install --frozen-lockfile (CI-Weg) grün, Lockfile unverändert

Andere Repos geprüft: threadnet-call publisht das Paket nur und schreibt die Scope-Zeile in seiner CI bereits selbst (.gitlab-ci.yml); es konsumiert keine @sorb-Pakete. In gitops kommt der Scope nicht vor. ThreadNet-Web war der einzige Konsument — die Lücke ist damit vollständig geschlossen, nicht nur an einer Stelle.

Dokumentiert: ThreadNet-Web:docs/axion1337-fork.md hat jetzt einen Abschnitt „Element Call anheben" mit dem Weg ohne Umgebungsvariable, dem --dir-statt---filter Fallstrick und der Prüfung, dass die Tarball-URL auf rohana zeigt.

Nicht angefasst: allow_failure: true auf publish_npm — eigener Befund, liegt weiterhin bei sorb.

Korrektur 2026-08-20 — „vollständig geschlossen" stimmte nicht

Der Abschnitt oben schließt mit dem Satz, die Lücke sei „vollständig geschlossen, nicht nur an einer Stelle". Das war falsch, und es ist am 2026-08-19 in Produktion aufgefallen.

Was passiert ist: Der Release-Kandidat v0.6.0-rc.1 erzeugte kein Image. docker_web scheiterte mit

[ERR_PNPM_FETCH_404] GET https://registry.npmjs.org/@sorb%2Fthreadnet-call-embedded
No authorization header was set for the request.

— also exakt der Fehler, den dieses Issue behoben haben sollte.

Warum die Abnahme ihn nicht gefunden hat: Sie hat gemessen, ob die .npmrc wirkt, wenn sie vorhanden ist — in einem isolierten Baum, im frischen Klon, im CI-Job web. Überall dort liegt sie im Wurzelverzeichnis und wird gelesen. Nicht geprüft wurde, ob sie überall ankommt, wo installiert wird. Und im Docker-Build kam sie nie an: Die COPY-Zeile in apps/web/Dockerfile listet die Dateien einzeln auf und führte .npmrc nicht.

Warum es trotzdem monatelang gutging — und das ist die eigentliche Pointe, denn die .npmrc sagt es in ihrem eigenen Kommentar voraus: Unter pnpm 10 nahm --frozen-lockfile die im Lockfile gepinnte Tarball-URL und löste gar nichts auf. Der Schutz kam also nicht von der .npmrc, sondern vom Lockfile. Erst der Upstream-Merge brachte pnpm 11 mit minimumReleaseAgeStrict: true; das prüft für jeden Eintrag das Veröffentlichungsdatum, braucht dafür Registry-Metadaten — und landet ohne .npmrc bei npmjs.

Behoben in ThreadNet-Web:c90ac4d: .npmrc steht jetzt in der COPY-Zeile. Belegt durch den Bau von v0.6.0-rc.2 und alle folgenden.

Die Lehre ist nicht „eine Datei vergessen". Die Abnahme hat die richtige Frage falsch gestellt: „wirkt die Konfiguration?" statt „liegt sie an jeder Stelle, an der installiert wird?". Für Konfiguration, die per Dateisystem-Position wirkt, ist die Liste der Orte die Prüfung — nicht das Verhalten an einem davon. Dieselbe Klasse wie der Fallstrick weiter oben, dass der Desktop-Client seine eigene config.json lädt.

Status bleibt done: Die Lücke ist jetzt tatsächlich geschlossen. Korrigiert wurde die Behauptung, nicht der Zustand.