Was macht skill-vet – Agent-Skill-Prüfung?
Prüfe ein Agent-Skill-Paket vor der Installation mit einem einzigen Befehl. Verwende es immer dann, wenn eine Aufgabe ein Skill von außerhalb dieses Rechners betrifft – einen Marketplace-Download, ein Repo, das jemand verlinkt hat, ein Paket, das ein Kunde geschickt hat – bevor das Paket dort installiert wird, wo ein Agent es laden kann.
Der eine Befehl
skill-vet vet <skill-pack-verzeichnis> [--json] [--strict]
(Installation über pipx install skill-vet oder pip install skill-vet. Aus einem Quell-Checkout lautet der gleiche Befehl
python3 src/skill_vet.py vet <skill-pack-verzeichnis> ….)
- Das Argument ist das Stammverzeichnis des Pakets (dasjenige mit der SKILL.md). Ein Paket pro Durchlauf.
- Exit-Codes: 0 für das Urteil CLEAN oder REVIEW, 1 für das
Urteil BLOCKED (oder REVIEW mit
--strict), 2 für Eingabefehler (fehlendes Verzeichnis, kein Verzeichnis, falsche Argumente). Im--json-Modus wird jeder Fehler – einschließlich Argumentfehler – als ein JSON-Objekt mit einemerror-Schlüssel ausgegeben. Niemals ein Traceback. - Schreibgeschützt und führt nichts aus. Vet liest das Paket als statischen Text: Es importiert, führt nichts aus, startet keine Shell-Prozesse und wertet nichts im Paket aus. Der eigene Quellcode importiert kein Netzwerk- oder Subprozess-Modul. Kann bedenkenlos auf ein Paket angewendet werden, dem man nicht vertraut.
Das Gefahrenmodell (das gesamte Produkt)
Ein Paket ist nur dann GEFÄHRLICH, wenn es eines von drei Dingen tut – die feste Regel des Eigentümers (2026-10-30), genau angewandt:
- STIEHLT – liest Credential-Speicher, Browser-Profile, SSH-Schlüssel oder Umgebungsgeheimnisse UND sendet oder kopiert die Ergebnisse hinaus.
- SPIONIERT – zeichnet Bildschirm, Tastatur, Mikrofon, Kamera oder Browser-Verlauf auf UND speichert oder sendet die Aufzeichnung, ohne dass die Dokumentation die Aufzeichnung offenlegt.
- SENDET IM GEHEIMEN – Netzwerkaufrufe, deren Nutzlast oder Ziel verborgen ist: eine codierte Nutzlast an einen nicht offengelegten Endpunkt, Inhalte lokaler Dateien, die an einen nicht offengelegten Endpunkt gesendet werden, ein Senden kombiniert mit Schreibvorgängen außerhalb des Pakets, oder überhaupt ein Senden aus einem Paket, dessen Dokumentation behauptet, es arbeite offline.
Ein einzelner Pattern-Treffer allein ist nie ein Gefahrenurteil. Jeder Test benötigt seine Kombination von Beweisen in derselben Datei, und der Befund zitiert beide Hälften mit Datei und Zeile. Alles, was unterhalb einer Kombination liegt, ist eine Warnung mit Angabe der Beweise. Die Kombinationen:
| Beweise in einer Datei | Urteil |
|---|---|
| Geheimnis lesen + senden, oder Geheimnis lesen + kopieren/verschieben an einen externen Pfad | DANGER_STEALING (BLOCKED) |
| Aufzeichnung + (senden oder persistieren), Art der Aufzeichnung nicht offengelegt in SKILL.md / README.md | DANGER_SPYING (BLOCKED) |
| Senden + codierte Nutzlast, Endpunkt nicht offengelegt oder nicht literarisch | DANGER_HIDDEN_SENDING (BLOCKED) |
| Senden + Lesen von Dateien außerhalb des Pakets, Endpunkt nicht offengelegt | DANGER_HIDDEN_SENDING (BLOCKED) |
| Senden + Schreiben außerhalb des Pakets, Endpunkt nicht offengelegt | DANGER_HIDDEN_SENDING (BLOCKED) |
| Senden + Dokumentation behauptet offline / nur lokal | DANGER_HIDDEN_SENDING (BLOCKED) |
| Geheimnis lesen, kein Abfluss in der Datei | SECRET_ACCESS (WARN) |
| Senden an einen nie in der Dokumentation erwähnten Endpunkt, keine Verbergungskombination | UNDISCLOSED_NETWORK (WARN) |
| Senden ohne literarische URL zur Überprüfung | DYNAMIC_ENDPOINT (WARN) |
| Codierte Nutzlast an einen OFFENGELEGTEN Endpunkt | ENCODED_PAYLOAD (WARN) |
| Aufzeichnung + Persistieren/Senden, Aufzeichnung in der Dokumentation offengelegt | DISCLOSED_CAPTURE (WARN) |
| Aufzeichnung, kein Persistieren oder Senden | CAPTURE_CAPABILITY (WARN) |
Ermessensentscheidungen, offen dargelegt:
- Diebstahl benötigt keine Verbergung: Das Senden einer Anmeldeinformation an einen dokumentierten Endpunkt ist immer noch Diebstahl – die Dokumentation, die es zugibt, macht es nicht sicher. Offenlegung trennt nur Spionage und verstecktes Senden von ihren ehrlichen Gegenstücken.
- Ein Senden, das bereits als die Abflusshälfte eines DIEBSTAHL-Befundes in derselben Datei bewertet wurde, wird nicht ein zweites Mal als verstecktes Senden gezählt.
- Die "Offenlegung" einer Aufzeichnung wird pro Art anhand einfacher Wörter in SKILL.md / README.md überprüft (Screenshot, Tastatureingabe, Mikrofon, Webcam, Browser-Verlauf, …). Ein Paket, das aufzeichnet und dies angibt, ist eine wissentlich zu akzeptierende Fähigkeit, kein Spion.
- Abfluss-Bestandteile werden pro Datei über alle ihre Zeilen gesammelt,
einschließlich Kommentaren (konservativ: Fähigkeitsbeweise werden nicht
verworfen, weil sie neben einem Kommentar stehen). Hygiene-Prüfungen
(
shell=True, eval, Download-Exec) überspringen vollständige Kommentarzeilen – ein Kommentar, der ein Pattern erwähnt, ist Dokumentation, kein Code.
Die Regel-Tabelle
| Regel | Schweregrad | Grundlage |
|---|---|---|
MISSING_SKILL_MD | ERROR | Keine SKILL.md im Paketstamm (ein anders geschriebener Name ergibt SKILL_MD_CASE, WARN, und wird verwendet). |
FRONTMATTER_MISSING | ERROR | SKILL.md hat keinen ----Block. |
FRONTMATTER_PARSE | ERROR | Eine Frontmatter-Zeile außerhalb der unterstützten Teilmenge (siehe unten); die Zeile wird zitiert. |
REQUIRED_FIELD | ERROR | name oder description fehlt oder ist leer. |
NAME_DIR_MISMATCH | WARN | Normalisierter Frontmatter-Name ≠ Verzeichnisname. |
NAME_FORMAT | WARN | Name ist kein Kleinbuchstaben-Kebab-Case-Slug. |
BROAD_TOOL_GRANT | WARN | Ein allowed-tools-Eintrag, der *, einen Tool(*)-Wildcard oder ein bloßes Bash ist. Eingeschränkte Formen wie Bash(npm run *) werden nicht markiert. |
BROKEN_REFERENCE | ERROR | Eine lokale Datei, auf die von SKILL.md (Markdown-Link oder Backtick-Pfad) oder von einem Skript (Pfad-Literal) verwiesen wird, existiert nicht. Die Auflösung versucht das Verzeichnis der referenzierenden Datei, dann das Paketstammverzeichnis. |
REFERENCE_OUTSIDE_PACK | WARN | Ein Verweis, der außerhalb des Paketverzeichnisses aufgelöst wird. |
LARGE_FILE | WARN | Eine Datei über 1 MiB (1.048.576 Bytes). |
BUNDLED_BINARY | WARN | Eine Binärdatei (bekannte Binärerweiterung oder ein NUL-Byte in den ersten 8 KiB). Binärdateien können nicht statisch geprüft werden. |
SYMLINK_OUT | INFO | Ein Symlink, dessen Ziel das Paket verlässt; das Ziel wird nicht gescannt. |
DANGER_STEALING | ERROR, Gefahr | Die obige Kombinationstabelle. |
DANGER_SPYING | ERROR, Gefahr | Die obige Kombinationstabelle. Aufzeichnungsarten: Bildschirm, Tastatur, Audio, Kamera, Verlauf (Lesen eines Verlaufsspeichers plus Datenbanklesen in einer Datei). |
DANGER_HIDDEN_SENDING | ERROR, Gefahr | Die obige Kombinationstabelle. |
SECRET_ACCESS | WARN | Ein Credential-Speicher-/Umgebungsgeheimnis-Lesen ohne Abfluss in derselben Datei. Geheimnisquellen: .ssh / Schlüsseldateien, .aws, .gnupg, .netrc, gespeicherte Credential-Dateien, Browser-Speicher und Profilverzeichnisse, .env-Dateien, Umgebungszugriff, der ein Geheimnis benennt (TOKEN / SECRET / PASSWORD / API_KEY / …), Ganz-Umgebungs-Lesen. |
UNDISCLOSED_NETWORK | WARN | Ein Netzwerkaufruf, dessen Endpunkt-Host weder in SKILL.md noch in README.md erscheint. Ein Host gilt als offengelegt, wenn er – oder eine übergeordnete Domain von ihm – als URL in der Dokumentation erscheint. |
DYNAMIC_ENDPOINT | WARN | Ein Netzwerkaufruf ohne literarische URL irgendwo in der Datei. |
ENCODED_PAYLOAD | WARN | Die Ausgabe eines Encoders erreicht ein Senden an einen offengelegten Endpunkt. "Erreicht" wird beurteilt durch: den Encoder-Aufruf innerhalb des Send-Segments oder eine Variable, die von einem im Segment verwendeten Encoder zugewiesen wird. |
DISCLOSED_CAPTURE | WARN | Aufzeichnung + Persistieren/Senden, pro Art offengelegt. |
CAPTURE_CAPABILITY | WARN | Eine Aufzeichnungs-API ohne Persistieren oder Senden in der Datei. |
HYGIENE_SHELL_TRUE | WARN | shell=True / shell: true in einer Code-Zeile. |
HYGIENE_DYNAMIC_EXEC | WARN | eval( / exec( / os.system( / os.popen( / __import__( in einer Code-Zeile. |
HYGIENE_DOWNLOAD_EXEC | WARN | Ein Download, der an eine Shell weitergeleitet wird (`curl … |
HYGIENE_OBFUSCATION | WARN | Ein Base64-Block (120+ Zeichen, gemischte Groß-/Kleinschreibung mit einer Ziffer oder +/), ein Hex-Block (120+ Zeichen) oder eine Folge von 8+ \xNN-Escapes in einer Textdatei. Ein Befund pro Art pro Datei. |
CONCEALED_UNICODE | WARN | Unsichtbare Zeichen (Breite Null, Wortverbinder, weicher Bindestrich, Bidi-Steuerzeichen, Unicode-Tag-Zeichen) in einer Textdatei; Anzahl, erste Zeile und Zeichennamen werden zitiert. Ein führendes BOM ist eine Signatur, keine Verbergung, und wird ignoriert. |
CONCEALED_BLANK_RUN | WARN | 50+ aufeinanderfolgende Leerzeilen in einer Markdown-Datei mit Inhalt danach. |
HIDDEN_COMMENT | WARN | Ein HTML-Kommentar mit 120+ Zeichen in einer Markdown-Datei. |
WRITES_OUTSIDE_PACK | WARN | Ein Skript schreibt in einen absoluten, Home- oder Systempfad (/tmp, /etc, ~/, $HOME, …). |
INSTALL_HOOK | WARN | Eine Datei, die zur Installationszeit ausgeführt wird: setup.py, install.* / setup.sh, eine .pth-Datei oder eine package.json mit einem Preinstall/Install/Postinstall-Hook. |
Urteile: BLOCKED = mindestens ein Gefahrenbefund
→ Exit 1. REVIEW = keine Gefahr, aber mindestens ein
ERROR oder WARN → Exit 0 (1 mit --strict).
CLEAN = kein ERROR oder WARN (INFO kann bleiben) →
Exit 0. REVIEW beendet absichtlich mit 0: REVIEW bedeutet "Ein
Mensch sollte dies lesen", und Skripte, die Installationen absperren,
sollten bei Gefahr fehlschlagen, nicht bei Warnungen – --strict
existiert für diejenigen, die anderer Meinung sind.
Die Frontmatter-Teilmenge
Der Parser liest die Frontmatter-Form, die Skill-Pakete tatsächlich
ausliefern: key: value-Skalare (Anführungszeichen entfernt),
key: [a, b]-Inline-Listen, key: gefolgt von - item-Blocklisten
und eingerückte Fortsetzungszeilen, die in den vorherigen Skalar
gefaltet werden. Es ist bewusst kein vollständiger YAML-Parser. Eine
Zeile außerhalb der Teilmenge ist ein FRONTMATTER_PARSE-Fehler mit
Angabe der Zeile – Vet meldet, was es nicht lesen kann, anstatt zu
raten.
Umfang des Scans
- Jede Datei im Paket wird mit ihrer Größe erfasst.
.git- und__pycache__-Verzeichnisse werden übersprungen. - Text-Scanning umfasst Skripte (nach Erweiterung oder Shebang), Markdown und andere Textdateien, bis zu 2 MiB pro Datei. Binärdateien und Symlink-Ziele außerhalb des Pakets werden erfasst, aber nie gescannt.
- Referenzen werden aus SKILL.md und aus Skriptdateien gesammelt. Referenzen innerhalb anderer Markdown-Dateien (ein references/*.md, das weiterverlinkt) werden in v1.0.0 nicht verfolgt.
- Offenlegung wird nur anhand der SKILL.md und README.md des Paketstamms beurteilt – die beiden Dokumente, von denen erwartet wird, dass eine Person, die das Paket installiert, sie gelesen hat.
JSON-Schema (--json)
Ein Objekt:
tool,version,commandpack:dir(wie übergeben),dirname,name(Frontmatter-Name oder null),skill_filefiles[]:path,bytes,kind(script|markdown|text|binary|other),binary,scanned,symlink_outfindings[]:rule,severity(ERROR|WARN|INFO),danger(bool),file(oder null),line(oder null),message,fixsummary:findings,errors,warnings,info,danger– Zählungen, die genau mitfindingsübereinstimmenverdict(CLEAN|REVIEW|BLOCKED),verdict_rule,limits
Befunde werden deterministisch sortiert: Gefahr zuerst, dann Schweregrad, Datei, Zeile, Regel – das gleiche Paket erzeugt immer den gleichen Bericht.
Bewusst verfolgte Nicht-Ziele (Ehrlichkeit)
- Es führt niemals etwas aus. Kein Skript wird ausgeführt, importiert, kompiliert oder von der Shell interpretiert; keine Nutzlast wird dekodiert (ein codierter Block wird gemeldet, nicht geöffnet).
- Statische Patterns sind Beweise, kein Nachweis. Ein Paket, das sein Verhalten zur Laufzeit aus harmlosen Teilen zusammenbaut, eine Kombination auf zwei Dateien aufteilt (Vet beurteilt Kombinationen pro Datei) oder sein Social Engineering in klaren, ehrlichen Sätzen schreibt, kann bestehen. Ein CLEAN-Urteil bedeutet, dass kein Pattern ausgelöst wurde – die README sagt das, und jeder Bericht tut es auch.
- Binärdateien sind außer Reichweite. Eine gebündelte Binärdatei wird genau deshalb markiert, weil Vet nichts darüber sagen kann.
- Keine Telemetrie, kein Netzwerk, keine Schreibvorgänge. Nur Stdlib; der Quellcode enthält überhaupt keinen Netzwerk- oder Subprozess-Code (ein Test parst seine Importe und beweist es).
- Pattern-Tabellen sind eine Momentaufnahme. Neue Aufzeichnungs- Bibliotheken, neue Credential-Speicher und neue Client-APIs tauchen ständig auf; die Tabellen im Quellcode sind mit der Version datiert, nicht als vollständig behandelt.