Dev

Claude Code Skills als Lieferkette: 2,2 Millionen Übernahmen, keine Registry und ein Scanner, der den Quelltext liest, während Python den Cache ausführt

Einen Skill zu lesen, bevor du ihn ausführst, ist der Standardrat, und er hat zwei Lücken. Ein kopierter Skill hat keinen Update-Kanal, die Kopie, die du gelesen hast, kann sich also unbemerkt von ihrer Quelle entfernen. Und bei einem Skill, der Python mitbringt, ist die Datei, die du liest, nicht immer die Datei, die der Interpreter lädt.

Zwei Paper sind diese Woche im Abstand von einem Tag auf arXiv erschienen, und sie beschreiben die beiden Enden derselben Leitung. Skill Constellations untersucht, wie SKILL.md-Dateien auf GitHub von Repository zu Repository wandern. PyCache Trap untersucht, was passiert, wenn eine dieser Dateien mit Python daneben ankommt und ein Scanner beurteilen soll, ob sie sicher ist.

Der Standardrat lautet: Lies einen Skill, bevor du ihn ausführst. Zusammen zeigen die beiden Paper die zwei Lücken in diesem Rat. Was du liest, ist eine Kopie, und nichts sagt dir, wenn sich ihre Quelle ändert. Und wenn der Skill Python mitbringt, lädt der Interpreter womöglich eine andere Datei als die, die du geöffnet hast.

Keines der beiden Paper handelt nur von Claude Code. Das Skill-Format ist offen, Codex, Cursor und andere laden es ebenfalls. Aber Anthropic hat das Format eingeführt, ich nutze es in Claude Code, und die Fragen unten sind die, die ich mir zu meinem eigenen Skills-Ordner gestellt habe. Die Zahlen aus den Papern gebe ich wieder, nachgerechnet habe ich sie nicht. Das Python-Verhalten in der Mitte dieses Beitrags habe ich dagegen selbst geprüft, auf meiner eigenen Maschine.

Wie verbreiten sich Claude Code Skills auf GitHub ohne Registry?

Durch Kopieren, meist gleich im Paket. Skill Constellations arbeitet mit dem GitSkills-Datensatz: jede Datei, die exakt SKILL.md heißt, gültiges Front Matter hat und ab dem 1. Oktober 2025 erstmals in einem Repository committet wurde, das kein Fork ist, verfolgt durch ihre Git-Historie. Das Paper zählt 2.193.119 Übernahmen. Eine Übernahme heißt: Ein Repository erwirbt einen Skill, der erste Commit des ursprünglichen Autors zählt mit. Gezählt werden also Paare aus Repository und Skill. Wie viele Menschen diese Skills ausführen, sagt die Zahl nicht.

Die interessante Einheit ist das Kopierereignis: ein Commit, der zehn oder mehr Skills auf einmal hinzufügt, wobei ein einzelnes früheres Repository bereits mindestens die Hälfte der Skill-Linien hielt, die das committende Repository hat. Ein Skill kommt also typischerweise als Teil eines ganzen Ordners an, der auf einen früheren Halter zurückgeht, und nicht, weil ein Entwickler ein einzelnes Werkzeug ausgesucht hat.

Die Verteilung sieht aus, wie man es danach erwartet. Die meisten Übernahmen geben den Skill an niemanden weiter, und eine kleine Zahl von Repositories steht für fast alle Weitergaben.

Zeigen GitHub-Sterne, welche Skill-Repositories ein Audit lohnen?

Nein, und zwar deutlich nicht. Laut Paper sagen Sterne fast nichts darüber aus, wie viele Repositories von einer Quelle kopieren. Sortiert man danach, wie oft ein Repository bereits kopiert wurde, findet man 4,0-mal so viele spätere Weitergaben wie mit einer Sortierung nach Sternen.

Auch naives Zählen von Kopien führt in die Irre. Von den 20 Repositories mit den höchsten Kopierzahlen bleiben nur 7 in den Top 20, sobald jedem nur die Skills angerechnet werden, die es als Erstes hatte. Der Rest sind späte Übernehmer, die wie Quellen aussehen.

Die Kernzahl stammt aus einer Wiederholung der Daten. Man passt auf den Daten vor dem 1. April 2026 ein Modell an, das beschreibt, welchen früheren Halter ein Kopierereignis wählt, und auditiert dann die 100 Repositories, die dieses Modell am höchsten einstuft. Auditieren heißt hier: ihre markierten Skills entfernen. Das verhindert 14,9 % der späteren Hochrisiko-Übernahmen. Ein Audit der 100 Repositories mit den meisten Sternen verhindert 0,5 %. Die Obergrenze ist so oder so niedrig: Bei denselben 100 Audits verhindert laut Paper selbst eine im Nachhinein gewählte Rangfolge nur etwa ein Viertel, weil rund die Hälfte der Hochrisiko-Übernahmen als neue Skills ankommt, die kein Audit bestehender Quellen gesehen hätte.

Übernehmen kopierte Claude Code Skills Korrekturen aus ihrer Quelle?

Meistens nicht. Das Paper berichtet, dass 20,9 % der Skill-Kopien einer späteren Änderung ihrer Quelle folgen. Zum Vergleich zitiert es frühere Arbeiten zu kopierten Code-Dateien, die Sicherheitskorrekturen von Upstream in 47 % bis 84 % der Fälle übernehmen. Innerhalb von Gruppen identischer Kopien sind 11,1 % der Änderungen über alle Mitglieder hinweg konsistent.

Der Grund liegt im Mechanismus. Ein Skill ist ein Ordner. Sobald er in dein Repository oder dein Home-Verzeichnis kopiert ist, verpflichtet ihn nichts, sich an seine Herkunft zu erinnern. Manche Installer halten eine Quelle fest, und das Paper nutzt diese Einträge, um seine Datierungen zu prüfen. Eine einfache Kopie trägt aber keine Version, keinen Lockfile-Eintrag und keine Ursprungs-URL. Eine Korrektur upstream erreicht dich nur, wenn du selbst nachsiehst.

Auf meiner eigenen Maschine liegen beide Sorten. Plugins aus einem Marketplace haben eine Quelle und eine Version, und Updates kommen darüber an. Die Ordner, die von Hand nach ~/.claude/skills kopiert wurden, haben keines von beiden, und nichts sagt mir, ob sich ihre Quelle seither bewegt hat.

Was bedeutet „Hochrisiko“ für einen Skill in Skill Constellations?

Fähigkeit, nicht böse Absicht. Das Paper markiert Skills, die ausführbare Dateien mitbringen, Tools vorab freigeben oder riskante Aktionen anweisen, und sagt ausdrücklich, dass die Markierung beschreibt, was ein Skill tun kann, und nicht, was sein Autor vorhat. Auf einer gelabelten Stichprobe von 200 Skills hat die strenge Markierung 98,3 % Precision und 80,9 % Recall, die Risikozahlen sind also Untergrenzen.

In Claude Code hat der zweite Punkt einen Namen. Das Feld allowed-tools im Front Matter eines Skills listet, in den Worten der Dokumentation:

Tools Claude can use without asking permission during the turn that invokes this skill.

Steht allowed-tools: Bash(python3 *) im Front Matter, kann Claude ein Skript, das neben dem Skill liegt, ausführen, ohne jedes Mal zu fragen, sofern keine verwaltete Richtlinie die Freigabe abschaltet. Das ist so gewollt, und es ist nützlich. Es heißt aber auch, dass ein mitgeliefertes Python-Skript laufen kann, ohne dass jemand auf den Befehl schaut. Genau diese Situation untersucht PyCache Trap.

Wie bringt PyCache Trap einen Skill an einem Scanner vorbei?

Der Scanner bekommt ein Programm, der Interpreter ein anderes. Das Paket enthält eine harmlose helper.py und in __pycache__ eine kompilierte Datei für dasselbe Modul mit anderem Verhalten. Scanner lesen den Quelltext. Python lädt beim Import des Moduls den Cache, wenn dessen Header die Prüfungen des Loaders besteht.

Das Paper testet sieben Skill-Scanner an 100 Skills. Es sind adaptive Ergebnisse: Der Angreifer hat bis zu fünf Kandidaten pro Skill und sieht die Rückmeldung des Scanners. Unter diesen Bedingungen kommt der Angriff je nach Scanner in 94 % bis 100 % der Fälle durch, und keiner der sieben hat erkannt, was der Cache tat. Bei vier der Scanner ließen die Autoren den zugelassenen Skill außerdem in einem Claude-Code-Harness laufen, gesteuert von MiniMax-M3: In 76 bis 83 von 100 Fällen kam der Skill am Scanner vorbei, und das versteckte Verhalten lief. Die Payloads waren synthetisch, die Secrets Attrappen, und nichts wurde auf einer echten Plattform veröffentlicht.

Warum es nicht reicht, den Header zu prüfen, fasst das Paper in einem Satz:

Header consistency alone cannot establish derivation because the publisher controls
both header and body.

Übersteht ein ausgetauschter Python-Cache einen git clone?

Das hängt davon ab, welche Art von Cache es ist, und die Auslieferung testet das Paper gar nicht. Also habe ich nachgesehen, mit Python 3.12.3 und git 2.43 unter Linux, mit einem Modul, das aus dem Quelltext den einen String zurückgibt und aus dem untergeschobenen Cache einen anderen. Was folgt, ist das, was ich auf diesem Setup beobachtet habe.

Python kennt drei Arten, eine .pyc zu validieren, festgelegt in ihrem Header (PEP 552). Der Standard speichert Änderungszeit und Größe der Quelldatei. Die beiden hashbasierten Modi speichern stattdessen einen Hash des Quelltexts: Der eine prüft ihn bei jedem Import, der andere vertraut ihm, solange der Interpreter nicht angewiesen wird zu prüfen.

Untergeschobener Cachegit clonezip, tar--check-hash-based-pycs always
Zeitstempel (Standard)Quelltext läuftCache läuftnicht anwendbar
Hash, ungeprüft, falscher HashCache läuftCache läuftQuelltext läuft
Hash, beide Modi, echter Quell-HashCache läuftCache läuftCache läuft

Die Zeitstempel-Variante hat den Clone nicht überstanden: git bewahrt keine Änderungszeiten, der Header passte also nicht mehr (ein Clone, der in derselben Sekunde landet wie das Original, würde weiterhin passen). zip und unzip, einen Tarball und cp -a hat sie überstanden, weil diese die Zeiten wiederherstellen. Ein Werkzeug, das sie nicht wiederherstellt, bricht sie genauso wie der Clone.

Den Hash-Varianten sind Dateizeiten völlig egal. Sieh dir die letzte Zeile an. Der Herausgeber berechnet den echten Hash des sauberen Quelltexts, schreibt ihn in den Header und hängt einen beliebigen Body an. Python hasht den Quelltext, findet eine Übereinstimmung und lädt den Body. Das strengste Interpreter-Flag hilft nicht. Das ist die Aussage des Papers über Header und Body, in ein paar Zeilen nachgestellt. All das setzt dieselbe Python-Minor-Version voraus, denn ein Cache, der für eine andere gebaut wurde, wird ignoriert.

Zwei Einschränkungen dazu. Ein Cache kommt per git nur an, wenn der Herausgeber ihn committet. Die meisten Python-Projekte ignorieren __pycache__, aber ein Skills-Repository ohne .gitignore nimmt ihn bei einem schlichten git add -A mit auf, so wie meines im Test. Und nur importierte Module werden aus dem Cache geladen. Ein Skript, das der Skill direkt ausführt, wird immer aus seinem Quelltext kompiliert.

Schützt python -B oder das Löschen von __pycache__ einen Claude Code Skill?

-B nicht, Löschen schon. python -B und PYTHONDONTWRITEBYTECODE hindern Python daran, Caches zu schreiben. Sie hindern es nicht daran, einen zu lesen, der schon da ist, und in meinem Test lief der untergeschobene Cache unter -B in jedem Fall, in dem er auch ohne lief.

Drei Dinge haben in meinem Test funktioniert. Die __pycache__-Verzeichnisse löschen, danach kompiliert Python aus dem Quelltext, den du lesen kannst. PYTHONPYCACHEPREFIX auf ein eigenes Verzeichnis setzen, dann schaut Python gar nicht erst neben den Quelltext. Und python -m compileall -f über den Ordner laufen lassen, worauf der Reparaturschritt des Papers hinausläuft (er entfernte die ausgetauschten Bytes in allen 99 Paketen mit Code, an denen die Autoren ihn probierten). Der letzte Weg ist der schwächste der drei: Er schreibt den Cache nur für den Interpreter und die Optimierungsstufe neu, mit denen du ihn ausführst, und lässt jede andere .pyc im Verzeichnis in Ruhe. Erst löschen.

Was würden versionierte Referenzen für Claude Code Skills ändern?

Sie gäben einer Kopie eine Stelle, an der sie nachsehen kann. Skill Constellations endet mit dieser Empfehlung: Plattformen sollten versionierte Referenzen statt Kopien verteilen. Eine Referenz hat eine Quelle, die man pinnen oder widerrufen kann. Ein kopierter Ordner hat am anderen Ende nichts.

Plugins kommen dem in Claude Code heute am nächsten. Ein Skill, der über ein Plugin installiert wurde, hat eine Quelle, eine Version und einen Update-Mechanismus: bei manchen Marketplaces automatisch, bei anderen eine Einstellung, die du einschaltest. Das löst die eingefrorene Kopie und öffnet die andere Frage, die ich schon beim Plugin-Verzeichnis gestellt habe: Ein Eintrag ist eine Aussage über eine Prüfung, und Prüfungen finden zu festen Zeitpunkten statt. Das Verzeichnis scannt jede neue Bundle-Version. Ein Plugin, das über eine Marketplace-URL hinzugefügt wird, bekommt gar keine Prüfung. In beiden Fällen liefert ein Update-Kanal Korrekturen und alles andere, was der Herausgeber als Nächstes ausliefert.

Auch die Obergrenze aus dem Paper gehört hierher. Beim selben Budget von 100 Repositories verhindert selbst eine im Nachhinein gewählte Rangfolge nur etwa ein Viertel der späteren Hochrisiko-Übernahmen. Rangfolgen und Registries machen das Problem kleiner. Was ein Skill darf, sobald er auf der Maschine ist, wird weiterhin dort entschieden, genauso wie bei jedem Tool, das du installierst.

Wie prüfst du diese Woche einen Claude Code Skills-Ordner?

Vier Befehle, und die ersten drei drehen sich um Caches. Sie gehen von persönlichen Skills in ~/.claude/skills und Plugins in ~/.claude/plugins aus. Nimm das .claude/skills eines Projekts dazu, wenn du eines geklont hast.

bash
# 1. Bytecode caches sitting next to skill code
find ~/.claude/skills ~/.claude/plugins -name '__pycache__' -type d
 
# 2. Hash-based caches: the kind that survives a clone
find ~/.claude/skills ~/.claude/plugins -name '*.pyc' \
  -exec sh -c 'xxd -s 4 -l 4 -p "$1" | grep -qE "^0[13]000000" && echo "$1"' _ {} \;
 
# 3. Remove them all; Python recompiles from the source you can read
find ~/.claude/skills ~/.claude/plugins -name '__pycache__' -type d -prune -exec rm -rf {} +
 
# 4. Skills that pre-approve tools
grep -rlE '^allowed-tools:' --include=SKILL.md ~/.claude/skills ~/.claude/plugins

Auf meiner Maschine findet der erste Befehl ein Verzeichnis, unter einem Skill, den ich selbst geschrieben habe, mit Zeitstempel-Header. Mein eigener Interpreter hat es also angelegt. Der zweite findet nichts. Kein Plugin hat einen Cache mitgeliefert. Das ist das erwartete Ergebnis. Hashbasierte Caches haben legitime Verwendungen, reproduzierbare Builds zum Beispiel, ein Fund allein beweist also nichts. Aber ein Cache gleich welcher Art, der in einem Skill mitkommt, den du nicht geschrieben hast, ist einen Blick wert, und ihn zu löschen kostet nichts: Python baut ihn aus dem Quelltext neu.

Lies die vierte Liste langsam. An einem Skill, der ein Shell-Muster vorab freigibt und ein Skript mitbringt, ist nichts falsch. Aber dort sagt dir das Lesen der SKILL.md am wenigsten, weil nicht die Prosa ausgeführt wird. Dieselbe Lektion wie bei der Settings-Datei eines geklonten Repos: Die Datei, die du überfliegst, und das, was ausgeführt wird, sind zwei verschiedene Objekte, und die Freigabe gilt für das zweite.

Diskussion

Hier gibt es keine Kommentarspalte. Diskussionen laufen auf X.

Max Nardit

Max Nardit

@mnardit

Weitere Artikel

Claude Code vs Codex: sieben Arten, wie Entwickler die Arbeit zwischen beiden aufteilen, statt sich für eines zu entscheiden

Die Übergaben sind inzwischen von den Herstellern dokumentiert: ein Review-Befehl, ein Session-Transfer, ein Importer in jede Richtung. Welches Tool welche Aufgabe bekommen soll, steht nirgends. Sieben Aufteilungen, jede bis zu ihrem Ursprung zurückverfolgt und als dokumentiert oder berichtet markiert, dazu die Sandbox-Voreinstellungen, die sich unterscheiden, und die Integrationen, die dieses Jahr entfernt wurden.

WebMCP auf einer echten Website: Tools mit document.modelContext registrieren, und warum mein navigator.modelContext-Skript nichts registriert

Im Mai ist die API von navigator zu document umgezogen. Schon davor hatte sie drei Methoden verloren, und danach gibt ihr Registrierungsaufruf ein Promise zurück. Ein Skript, das im April geschrieben wurde, um einen Detektor zufriedenzustellen, kann einem aktuellen Browser null Tools anbieten. Hier stehen die funktionierende Registrierung, ein Test, der ohne Agenten auskommt, und die Fälle, in denen ein schlichter MCP-Endpunkt der bessere Zugang ist.