Skip to content

Pfadschranke loest Symlinks auf - #8

Merged
GodModeAI2025 merged 1 commit into
mainfrom
fix/symlink-schranke
Sep 5, 2026
Merged

GodModeAI2025 merged 1 commit into
mainfrom
fix/symlink-schranke

Conversation

@GodModeAI2025

Copy link
Copy Markdown
Owner

Ein Link im Projekt, der nach draußen zeigte, kam durch. Gemessen: ein Link raus auf /etc ließ einen Write nach .../raus/hosts mit Exit 0 passieren. Dasselbe galt für einen harmlos benannten Link auf .claude/settings.json, die von innen eigentlich tabu ist. Die Schranke verglich Schreibweisen, nicht Orte.

Wie sie jetzt vergleicht

Beide Seiten werden physisch aufgelöst, bevor verglichen wird. Das Ziel eines Write existiert meist noch nicht, deshalb genügt cd && pwd -P allein nicht: der Auflöser spaltet ab, was es nicht gibt, lässt die Shell den tiefsten vorhandenen Vorfahren auflösen und hängt die abgespaltenen Teile wieder an. Die können keine Links sein, sie existieren ja nicht.

Ein Link als letzte Komponente wird eigens verfolgt. cd sieht ihn nicht, und genau darüber liefe sonst ein Write auf eine fremde Datei.

Weil auch die Wurzel aufgelöst wird, fällt /tmp gegen /private/tmp gleich mit weg. Das ging bisher zugunsten der Sperre aus und wies einen Write auf genau den erlaubten Ordner ab.

Nachgemessen

Vier neue Tests, jeder gegen einen echten Ordner mit echten Links:

  • Link auf einen Ordner draußen: <projekt>/raus/hosts → Exit 2
  • Link auf eine Datei draußen: <projekt>/fremde-datei → Exit 2
  • durch den Link in noch nicht existierendes Neuland: <projekt>/raus/neu/tief.txt → Exit 2
  • Link auf die Hook-Konfiguration → Exit 2, mit der Begründung "Hook-Konfiguration"

Dazu die Gegenprobe, dass Schreiben im Projekt weiter läuft, auch in noch nicht angelegte Unterordner, und dass eine Wurzel, die es noch gar nicht gibt, genauso behandelt wird wie eine vorhandene.

Zahnprobe: nimmt man dem Ziel die Auflösung, fällt die Tabelle. Nimmt man sie der Wurzel, fällt sie auch. 68 Tests, ein Skip (docker compose, hier läuft kein Docker).

Ein Nebenbefund, ehrlich eingeordnet

Bei einem relativen Link im Wurzelverzeichnis entstand //private/tmp/... statt /private/tmp/.... Ich hielt das zuerst für verhaltensrelevant und habe einen Test dagegen geschrieben. Der Test hat nicht angeschlagen, und zu Recht: beide Seiten gehen denselben Weg und bekommen den doppelten Schrägstrich gleichermaßen, das Urteil ändert sich also nie. In den Meldungen stand er trotzdem, und weg ist er jetzt. Der Test heißt jetzt nach dem, was er wirklich zusichert.

Was offen bleibt

Die Prüfung ist nicht atomar. Ein Link, der zwischen Prüfung und Schreibvorgang entsteht, wird vom Schreibvorgang verfolgt, und der Hook hat vorher geschaut. Dagegen hilft der Container, nicht der Hook. Das steht so in README, SECURITY.md, beiden SKILL.md und beiden erzeugten Setup-READMEs.

Ebenfalls unverändert offen: Schreiben über Bash geht weiter an den ersten Hook, der nur den Kommandotext liest.


🤖 Generated with Claude Code

https://claude.ai/code/session_01BL7UoPomBp2Luhg89R6fYk

…leichen

Ein Link im Projekt, der nach draussen zeigte, kam durch. Gemessen: ein Link
"raus" auf /etc liess einen Write nach .../raus/hosts mit Exit 0 passieren.
Dasselbe galt fuer einen harmlos benannten Link auf .claude/settings.json,
die von innen eigentlich tabu ist.

Der Hook loest jetzt beide Seiten physisch auf, bevor er vergleicht. Das Ziel
eines Write existiert meist noch nicht, deshalb genuegt "cd && pwd -P" nicht:
der Aufloeser spaltet ab, was es nicht gibt, laesst die Shell den tiefsten
vorhandenen Vorfahren aufloesen und haengt die abgespaltenen Teile wieder an.
Die koennen keine Links sein, sie existieren ja nicht. Ein Link als letzte
Komponente wird eigens verfolgt, denn "cd" sieht ihn nicht, und genau darueber
liefe sonst ein Write auf eine fremde Datei.

Weil beide Seiten aufgeloest werden, faellt /tmp gegen /private/tmp gleich mit
weg. Das ging bisher zugunsten der Sperre aus und wies einen Write auf genau
den erlaubten Ordner ab.

Vier neue Tests, jeder gegen einen echten Ordner mit echten Links: ein Link auf
einen Ordner draussen, ein Link auf eine Datei draussen, ein Weg durch den Link
in noch nicht existierendes Neuland, und ein Link auf die Hook-Konfiguration.
Dazu die Gegenprobe, dass Schreiben im Projekt weiter laeuft, auch in noch
nicht angelegte Unterordner. Zahnprobe: nimmt man dem Ziel die Aufloesung, oder
der Wurzel, faellt jeweils die Tabelle.

Mitgenommen, ohne Test: bei einem relativen Link im Wurzelverzeichnis entstand
"//private/tmp/..." statt "/private/tmp/...". Auf das Urteil wirkt sich das
nicht aus, weil beide Seiten denselben Weg gehen und den doppelten Schraegstrich
gleichermassen bekommen; in den Meldungen stand er trotzdem. Er ist weg.

Nicht geschlossen und an fuenf Stellen so dokumentiert: die Pruefung ist nicht
atomar. Ein Link, der zwischen Pruefung und Schreibvorgang entsteht, wird vom
Schreibvorgang verfolgt, und der Hook hat vorher geschaut. Dagegen hilft der
Container, nicht der Hook.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BL7UoPomBp2Luhg89R6fYk
@GodModeAI2025
GodModeAI2025 merged commit ab63bb4 into main Sep 5, 2026
1 check passed
@GodModeAI2025
GodModeAI2025 deleted the fix/symlink-schranke branch September 5, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant