sec-scan

Anbieterneutraler Security-Scan eines eigenen Repos: Bedrohungsmodell, Aufteilung nach Vertrauensgrenzen, gezielte Prüfung je Paket (Injection, Authn/Authz, Secrets, Krypto, Deserialisierung, Pfade, S

SKILL.md

GitHub
<img src="banner.png" width="100%" alt="sec-scan banner">

sec-scan

Kern der ellmos-Security-Familie. Der gemeinsame Vertrag (Phasen, Dateien, Lebenszyklus, Severity, Regeln, Adapter) steht in references/contract.md, Schemas in schemas/, Validator in scripts/secfind.py + scripts/seclib.py (nur Standardbibliothek). Dieser Skill zitiert den Vertrag, wiederholt ihn nicht. Nachbarn: sec-threat-model, sec-diff-scan, sec-validate, sec-triage, sec-report.

Modi

  • standard: ein Durchlauf, ein Modell, eine Sitzung.
  • deep: mehrere unabhängige Durchläufe (andere Paketreihenfolge, Zuschnitt oder Modell), Vereinigung per id. Nur nach Kostenwarnung und Bestätigung des Nutzers (Vertrag 6.5).

Ablauf

  1. Ziel und Scope: Repo, Ref (Commit-SHA oder working-tree), ausgeschlossene Pfade. scanId vergeben. Das Scan-Verzeichnis heißt sec-scan-<scanId>/ (Vertrag 1), liegt unter einem Basisordner außerhalb des Repos und wird erst nach dem Preflight angelegt.
  2. Preflight:
    • Eigener oder autorisierter Code? Sonst Abbruch (Vertrag 6.1).
    • Lock-Prüfung für das Repo und den Basisordner des Scan-Verzeichnisses: python <lock_scan.py> --check-dir '<Pfad>' --json, Exitcode sofort sichern. 0 = frei, 1 = gesperrt, alles andere (Absturz, unlesbare Ausgabe) = unbekannt = stopp (Vertrag 6.2). Interpreter ausdrücklich aufrufen, nie das Skript "öffnen". Gibt es lock_scan.py auf dem Host nicht (kein ellmos-Lock-System), stattdessen LOCK*.txt im Repo und in den Elternordnern suchen: keine = frei (im Bericht vermerken), eine oder mehr = stopp.
    • Egress ansagen: geht Code an einen Anbieter (gehostete Review-Schicht, Fremdmodell)? Private Repos nur in der lokalen Sitzung (Vertrag 6.6). Kosten ansagen, bei deep Bestätigung abwarten (6.5).
  3. Bedrohungsmodell: threatmodel.md vorhanden und aktuell? Sonst sec-threat-model ausführen.
  4. Pakete bilden: das Repo nach Vertrauensgrenzen und Einstiegspunkten aus dem Modell teilen, je Paket Dateien und Untersuchungsfragen. Sehr große Pakete teilen, statt oberflächlich zu lesen.
  5. Je Paket prüfen (Source-to-Sink: Quelle -> Transformation -> Senke, Gegenmaßnahmen auf dem Pfad mitlesen): Injection (SQL, Shell, Template, LDAP/NoSQL), Authn/Authz und Mandantentrennung, Secrets im Code, Kryptografie (Algorithmen, Zufall, Schlüsselumgang), unsichere Deserialisierung, Pfad-/Dateizugriff (Traversal, Upload, Symlinks), SSRF und offene Weiterleitung, unsichere Konfiguration (Debug, CORS, Defaults, Rechte), Nutzung bekannt verwundbarer Abhängigkeiten (Muster im Code; Advisory-Abgleich ist security-tech-stack-check). Befunde, die der Pfad nicht trägt, nicht aufnehmen; Verdacht ohne Beleg gehört in coverage.limits, nicht in findings.
  6. Kandidat festhalten: rule dotted lowercase (py.sql-injection), cwe falls passend, severity und confidence nach Vertrag 3, status: candidate, location, evidence als zitierter Code/Trace. Geheimniswerte maskieren (Vertrag 4). id immer per python scripts/secfind.py id RULE PATH - (Snippet über stdin), nie von Hand.
  7. Coverage ehrlich führen: scanned mit Tiefe read / skimmed / pattern-only, notScanned mit Grund (generiert, vendored, zu groß, Binär, nicht erreichbar), limits für Methodengrenzen. complete: true nur bei leerem notScanned (Vertrag 5). unknown ist nicht "sicher".
  8. Validieren: python scripts/secfind.py validate findings.json coverage.json, Exitcode (0/1) und Ausgabe zeigen. Bei Fehlern korrigieren und erneut laufen lassen.
  9. Abschluss: Anzahl Kandidaten je Severity, Coverage-Stand, nächste Schritte (sec-validate, dann sec-triage, sec-report). Ein partieller Scan wird als partiell gemeldet (Vertrag 6.9).

deep

Mindestens zwei Durchläufe mit unterschiedlichem Zuschnitt, je eigene Kandidatenliste. Vereinigung per id: gleiche id zusammenführen (Evidenz ergänzen, bei abweichender Severity die höhere übernehmen und die Abweichung in description nennen), neue id aufnehmen. Coverage = Vereinigung der gelesenen Pfade; provider.model nennt alle beteiligten Modelle.

Adapter

Ein vorhandener Anbieter-Scanner kann den Scan-Teil übernehmen (Vertrag 7): Claude /claude-security, Codex codex-security. Gemini und Kimi haben keinen und folgen dem obigen Ablauf.

  1. Scanner nach Preflight (Schritt 2) laufen lassen; seine Ausgabedateien lesen und feldweise auf findings.json / coverage.json abbilden. Die Dateien und Felder des Scanners werden hier nicht vorweggenommen; das Mapping entsteht beim Lesen der tatsächlichen Ausgabe und wird in coverage.limits vermerkt.
  2. Alle Funde starten als candidate, auch wenn der Scanner sie als bestätigt führt. Ein Adapter überspringt die Validierung nie.
  3. provider.name / provider.model nach dem Scan, provider.engine = Name des Scanners.
  4. Coverage aus dem Scan-Bericht übernehmen. Gibt er keine Abdeckung her, complete: false und unter notScanned einen Eintrag "Abdeckung des Scanners unbekannt" anlegen (ohne notScanned-Eintrag ist complete: false laut Validator ungültig; limits allein genügt nicht).
  5. ids per secfind.py id neu bilden, damit Funde mit denen anderer Läufe zusammenfallen. Danach Schritt 8.

Grenzen

Kein Patchen, kein Commit, kein Netzscan (Vertrag 6.1, 6.3). Keine Laufzeit-Exploits; Nachstellen ist sec-validate.

Changelog

0.1.0 (2026-10-31)

  • Erstfassung: Modi standard/deep, Preflight, Pakete, Adapter.