Zum Hauptinhalt springen
Die meisten statischen Security-Tools finden Bugs isoliert. Sie scannen eine Datei, listen Probleme auf und sind fertig. Das Problem: Die schädlichsten Schwachstellen in modernen Codebasen sind selten ein einzelner Bug. Sie sind eine Kette: ein hardcodierter Signing-Key plus eine fehlende Autorisierungsprüfung plus eine SQL-Injection, die einzeln alle handhabbar wirken. Zusammen sind sie ein Account-Takeover-Pfad. Genau diese Art von übergreifender Argumentation können LLMs gut – wenn man ihnen die richtige Struktur gibt. In diesem Artikel bauen wir einen Zwei-Agenten-Security-Code-Reviewer mit Python und der Venice-API. Am Ende hast du ein CLI, das du auf jede Python-Codebase richten kannst, um einen Markdown-Report mit atomaren Findings und Exploit-Chains zu erzeugen. Interesse an der vollständigen Implementierung? Schau in das GitHub-Repo. Bevor wir loslegen, brauchst du einen Venice-API-Schlüssel. Exportiere ihn als Umgebungsvariable:

Was wir bauen

Der Reviewer ist ein kleines Python-Projekt mit einigen klaren Teilen: Der Flow sieht so aus:
  1. Den Zielordner nach .py-Dateien durchwandern.
  2. Eine deterministische Repo-Map bauen (Imports, öffentliche Symbole, Signaturen).
  3. Für jede Datei dem Scanner ihren Quelltext plus einen pro Datei zugeschnittenen Nachbarschafts-Slice der Map senden und atomare Findings sammeln.
  4. Die Vereinigung der Findings plus die kondensierte Repo-Map dem Chainer senden und Exploit-Chains sammeln.
  5. Jede Chain verwerfen, die eine Finding-ID referenziert, die der Scanner nicht erzeugt hat, oder eine Datei nennt, aus der keines ihrer Findings stammt.
  6. Einen Markdown-Report schreiben.
Zwei Designentscheidungen lohnen sich vor dem Code zu nennen. Erstens: Warum zwei Agenten statt einem? Ein Einzelagent-Scanner, der in einem Prompt alles zu erledigen versucht, muss zwischen Gründlichkeit bei Per-Datei-Bugs und Cleverness bei kombinatorischer Argumentation balancieren. Die Aufteilung erlaubt dem Scanner, hartnäckig und laut zu sein, und dem Chainer, selektiv und ruhig. Ein zusätzlicher LLM-Aufruf, dediziert zum Kombinieren von Findings, schaltet für sehr wenig Extra-Code eine ganze Klasse von Bugs frei. Zweitens: Warum eine Repo-Map? Echte Codebasen verteilen sich über viele Dateien. Ein Bug, der lautet „der Validator läuft, wird aber nicht pro Iteration im Fetcher angewendet, und die Antwort des Fetchers landet im Renderer”, ist für einen reinen Per-Datei-Scanner unsichtbar. Vor jedem LLM-Aufruf laufen wir mit Pythons ast durch den Zielbaum und bauen eine strukturelle Map. Der Scanner sieht eine pro Datei zugeschnittene Nachbarschaft (wer von dieser Datei importiert, was diese Datei importiert, Signaturen dieser externen Symbole). Der Chainer sieht eine kondensierte vollständige Map (jedes Modul, jedes öffentliche Symbol, jede Import-Kante, kein Quelltext). Das ist das kleinste Context-Engineering, das wir gefunden haben, das es dem Chainer erlaubt, Chains zu bauen, deren Datenfluss Modulgrenzen überschreitet, ohne die Token-Kosten zu zahlen, die ganze Codebase in jeden Prompt zu stopfen.

Voraussetzungen

  • Python 3.12+
  • Ein Venice-API-Schlüssel von venice.ai
  • Grundverständnis von Pydantic, Pythons ast-Modul und dem OpenAI-Python-SDK
Die Referenz nutzt uv für Dependency-Management; eine reguläre Virtual Environment funktioniert genauso.

Projekt einrichten

Neues Projekt anlegen und Abhängigkeiten installieren:
Wer lieber pip nutzt, legt stattdessen eine Virtual Environment an:
Lege eine .env-Datei für die lokale Entwicklung an:
Wir legen den Quellcode unter src/venice_security_reviewer/ ab, damit er als Paket importierbar ist, und die Prompts unter prompts/ im Repo-Root, damit sie wie jede andere Quelldatei reviewbar und diff-bar sind:

Venice-Client aufsetzen

Venice ist OpenAI-kompatibel, also können wir das offizielle OpenAI-Python-SDK nutzen und nur die base_url auf Venice zeigen lassen. Die Client-Erstellung in einer Datei zu zentralisieren bedeutet, dass der Rest des Codes nie wissen muss, mit welchem Provider er spricht: Ein Backend-Wechsel betrifft nur dieses Modul. Erstelle src/venice_security_reviewer/client.py:
Ein paar Hinweise:
  • Wir nehmen standardmäßig zai-org-glm-5, weil es ein starkes generisches Venice-Modell ist; per Umgebungsvariable VENICE_MODEL kannst du das überschreiben. Für größere oder feinere Codebasen kann ein stärkeres Modell den Chainer in der erzählerischen Qualität spürbar besser machen.
  • build_client gibt Client und Modell-ID zurück, sodass Caller keine Env-Vars selbst lesen müssen und Tests eine Fake-Config ohne Monkey-Patching injizieren können.

Datenmodelle definieren

Der ganze Sinn, hier Pydantic statt rohe Dicts zu nutzen, ist eine harte Validierungsgrenze zwischen LLM und dem Rest des Programms. Liefert das Modell fehlerhaftes JSON oder erfindet eine Finding-ID, schlägt das Parsen laut fehl und wir propagieren die Halluzination nie in den Report. Erstelle src/venice_security_reviewer/models.py:
Die Constraints leisten hier echte Arbeit:
  • Finding.id und Chain.id sind auf eine Regex wie F-001, C-001 festgelegt. Wird das Modell kreativ, scheitert die Validierung.
  • Chain.findings verlangt mindestens zwei Einträge: Eine „Chain” mit einem Finding ist nur ein Finding.
  • Chain.severity ist auf high oder critical beschränkt. Eine Kombination, die den Impact nicht über die höchste Einzel-Severity hebt, ist keine berichtenswerte Chain.
  • Evidence erzwingt end_line >= start_line, sodass das Modell keine unsinnigen Zeilenbereiche liefern kann.
Das ist die Form-Validierung. Wir brauchen außerdem Cross-Reference-Validierung: Eine Chain, die eine Finding-ID referenziert, die der Scanner nie erzeugt hat, ist sinnlos. Ergänze in models.py:
Das ist die deterministische Leitplanke, die den Chainer ehrlich hält. Er darf nur Findings referenzieren, die der Scanner tatsächlich erzeugt hat, und er darf nur Dateien als beteiligt nennen, aus denen eines dieser Findings stammt. Die verworfenen Chains zurückzugeben statt sie still zu filtern, erlaubt es dem CLI, eine Warnung anzuzeigen, wenn das Modell versucht, etwas zu erfinden.

Die AST-Repo-Map bauen

Die Repo-Map ist das strukturelle Skelett einer Python-Codebase: jede öffentliche Oberfläche eines Moduls, jede Import-Kante und ein Reverse-Index von „Modul M” zu „Modulen, die von M importieren”. Sie wird einmal pro Scan-Lauf mit Pythons ast gebaut, nie per Ausführung – sicher auch bei adversärem Code, weil der Parser nichts aus dem gescannten Baum importiert oder ausführt. Wir konsumieren die Map in zwei Formen. Der Scanner bekommt einen pro Datei zugeschnittenen Nachbarschafts-Slice, damit die Prompts in der Größe begrenzt bleiben. Der Chainer bekommt eine kondensierte vollständige Map, um Chains über Dateien hinweg zu konstruieren. Erstelle src/venice_security_reviewer/repo_map.py und beginne mit den Pydantic-Modellen für die Map:
Jetzt der Helper, der durch den Baum läuft und Verzeichnisse überspringt, die wir nicht indizieren sollten:
Pro Datei wollen wir drei Dinge aus dem AST: die top-level definierten Symbole, die Import-Kanten und – falls vorhanden – eine explizite __all__-Liste. Funktions-Signaturen und Class-Header werden zu kompakten Strings gerendert, die das LLM direkt lesen kann:
Das _SIGNATURE_CHAR_CAP von 200 erhält typische echte Signaturen (inkl. Type-Hints) und verhindert pathologische Fälle wie eine 200-zeilige typisierte Union, die den Prompt sprengen würde. Als Nächstes der Extraktor, der die strukturellen Daten aus einem geparsten Modul herauszieht. Wir behandeln ast.FunctionDef, ast.ClassDef, top-level ast.Assign und ast.AnnAssign für Konstanten und sowohl ast.Import als auch ast.ImportFrom für die Import-Kanten. Relative Imports werden in ihre absolute, punktierte Form aufgelöst, damit der Chainer sie später gegen Modulnamen matchen kann:
Die vollständige Extraktionslogik läuft durch tree.body und gibt pro top-level Knoten SymbolDef- und ImportEdge-Einträge aus. Die _extract-Funktion in repo_map.py des Referenz-Repos zeigt die vollständige Implementierung. Heraus kommt eine Liste von ModuleEntry-Objekten, eines pro Datei. Der interessante Teil ist, was wir mit diesen Einträgen tun. Wickeln wir sie in eine RepoMap mit zwei Consumer-Methoden:
neighborhood(path) ruft der Scanner pro Datei auf. Es liefert ein ModuleNeighborhood-Objekt mit dem Modul selbst, jedem anderen Modul, das von ihm importiert, und jedem in-repo-Symbol, das es anderswo importiert (mit aufgelösten Signaturen). Damit hat der Scanner genug Kontext, um Findings zu flaggen, die nur im Cross-File-Kontext offensichtlich sind, ohne die ganze Codebase in den Prompt zu ziehen. condensed_dict() bekommt der Chainer. Snippets und Signaturen entfallen; nur Pfade, Modulnamen, öffentliche Exports und Import-Kanten bleiben. Das ist die kleinste Darstellung, mit der der Chainer trotzdem über modulübergreifenden Datenfluss räsonieren kann. Schließlich der Entry Point, der das Ganze baut:
Dateien, die wir nicht lesen können oder die nicht parsen, werden geloggt und übersprungen. Wir geben eine partielle Map zurück, statt den ganzen Lauf scheitern zu lassen; im schlimmsten Fall sieht ein Scanner-Aufruf für eine Datei keine Nachbarschaft, was immer noch ein funktionierender Scan ist.

Den Scanner-Agenten schreiben

Der Scanner läuft einen Zielpfad ab, sammelt Python-Quelldateien und bittet Venice, atomare Schwachstellen Datei für Datei zu identifizieren. Per-Datei-Scanning hält den Prompt klein und macht Fehler lokal: Eine kaputte Datei kippt nicht den ganzen Lauf. Wir halten den Prompt in einer separaten Datei, damit er wie jede andere Quelldatei reviewbar und diff-bar ist. Erstelle prompts/scanner.md:
Der vollständige Prompt im Referenz-Repo enthält außerdem einen Abschnitt „What to look for” mit gängigen Schwachstellenklassen (hardcodierte Secrets, SQL-Injection, Command-Injection, SSRF, unsichere Deserialisierung usw.) und einen Abschnitt „How to use the neighborhood”, der erklärt, wie das Modell den Cross-File-Kontext konsumieren soll. Ein paar Prompt-Design-Hinweise:
  • Wir weisen das Modell an, ausschließlich JSON ohne Prosa oder Fences zu liefern. Das OpenAI-SDK unterstützt response_format={"type": "json_object"}, das das API-seitig erzwingt – die Verstärkung im Prompt reduziert aber Edge-Cases.
  • Wir verbieten dem Scanner ausdrücklich, Cross-File-Chains zu produzieren. Chains sind die Aufgabe des Chainers; beides verlangen würde die Verantwortung verwischen.
  • Das Snippet muss verbatim kopiert sein. So kann der Report die genauen Bytes zitieren, die das Modell gesehen haben will, und ein Reviewer kann ein Finding stichprobenartig prüfen.
Jetzt der Agent-Code. Erstelle src/venice_security_reviewer/scanner.py und beginne mit File-Walker und Prompt-Loader:
MAX_FILE_BYTES ist eine Sicherheitsobergrenze. Jenseits ~200 KB überspringen wir die Datei, statt einen riesigen, wahrscheinlich teuren und qualitativ schwachen Prompt zu senden. Als Nächstes der Prompt-Builder. Das Template nutzt {filename}, {source} und {neighborhood} als Platzhalter; wir verwenden str.replace statt .format(), weil das Template JSON-Beispiele mit literalen geschweiften Klammern enthält:
Jetzt der Parser. Wir deserialisieren das JSON, validieren jedes Finding über Pydantic und verwerfen einzelne fehlerhafte Findings, statt die Datei zu scheitern. Ein schlechtes Finding soll nicht die guten kosten:
Der Scanner vergibt IDs wie F-001 pro Datei, aber der Chainer muss Findings über das ganze Repo hinweg referenzieren. Wir vergeben die IDs gegen einen monotonen Zähler neu, damit sie global eindeutig sind:
Der Single-File-Scan kombiniert all das. Wir lesen die Datei, bauen den Prompt, schicken ihn mit response_format={"type": "json_object"} und niedriger Temperatur an Venice und parsen das Ergebnis:
Zwei Details lohnen sich:
  • Wir setzen den Evidence-Dateipfad nach dem Parsen relativ zu repo_root, weil das Modell den übergebenen Dateinamen zurückgibt, wir aber im ganzen Report eine kanonische Form wollen.
  • temperature=0.1 ist bewusst niedrig. Der Scanner soll konservativ und über Läufe konsistent sein; Kreativität ist die Aufgabe des Chainers.
Zuletzt der Orchestrator, der jede passende Datei unter dem Root scannt:
Die Repo-Map wird vom Caller einmal gebaut und für jede Datei wiederverwendet, sodass der Scanner eine konsistente globale Struktur sieht, auch wenn einzelne Dateien beim Parsen scheitern oder übersprungen werden.

Den Chainer-Agenten schreiben

Der Chainer nimmt die Vereinigung der Scanner-Findings plus die kondensierte Repo-Map und fragt Venice, ob eine Teilmenge der Findings zu einer realen Exploit-Chain kombiniert werden kann. Zwei deterministische Leitplanken stehen zwischen LLM und Report:
  1. Jede Chain darf nur Finding-IDs referenzieren, die der Scanner erzeugt hat.
  2. Jede Chain darf nur Dateien benennen, die mindestens ein referenziertes Finding tatsächlich berührt.
Chains, die eine Regel verletzen, werden beim Parsen verworfen. Das stoppt das Modell daran, Chains „nur sicherheitshalber” zu halluzinieren oder zu behaupten, eine Chain umfasse Dateien, für die es keine Evidenz hat. Der Chainer-Prompt liegt unter prompts/chainer.md. Der Kern davon:
Der vollständige Prompt im Referenz-Repo erklärt zudem, wie man die Repo-Map liest, wie man entscheidet, was in files_involved gehört, und vor allem, wann nicht zu chainen. Dem Modell zu sagen „it is correct and expected for many codebases to have findings that do not chain” hält es davon ab, Chains zu erfinden, um produktiv zu wirken. Jetzt der Agent-Code. Erstelle src/venice_security_reviewer/chainer.py:
MAX_REPO_MAP_CHARS = 8000 ist eine weiche Obergrenze für den als JSON gerenderten Repo-Map-Block im Chainer-Prompt. Bei rund 4 Zeichen pro Token entspricht das ~2000 Tokens und passt komfortabel in jedes Venice-Modellkontext, selbst mit Findings und Erzähl-Budget obendrauf. Wir serialisieren Findings in einen kompakten JSON-Block. Wir streichen das snippet aus Evidence bewusst: Der Chainer braucht keine rohen Bytes, um zu entscheiden, ob zwei Findings kombinieren, und ihre Aufnahme verdoppelt grob den Token-Kostenpunkt auf realen Codebasen:
Bei größeren Codebasen kann die vollständige kondensierte Repo-Map unser Zeichen-Budget sprengen. Dann beschneiden wir auf finding-haltige Module plus deren direkte Nachbarn. Das bewahrt genug Struktur, damit der Chainer über Chains mit Evidenz räsonieren kann, und verwirft den Rest:
Die Prune-Strategie ist bewusst einfach: Module behalten, in denen unsere Findings liegen, und deren direkte Nachbarn im Import-Graph. Alles weiter weg hat keine plausible Rolle in einer Chain, für die wir aktuell Evidenz haben, und kann verworfen werden, ohne Argumentationskraft zu verlieren. Wir versehen das Payload auch mit _pruned-, _kept- und _total-Markern, sodass der Chainer-Prompt das Modell warnen kann, wenn die Map beschnitten wurde. Das Response-Parsen ist wie beim Scanner: deserialisieren, jede Chain über Pydantic validieren, fehlerhafte verwerfen:
Dann der Agent selbst:
Ein paar Hinweise:
  • Wir brechen vor dem Modell-Aufruf ab, wenn es weniger als zwei Findings gibt. Eine Chain aus einem einzelnen Finding gibt es nicht, und so verbrennen wir keine Tokens auf garantiert leerem Ergebnis.
  • temperature=0.2 ist leicht höher als die 0.1 des Scanners. Der Chainer profitiert von etwas mehr Kreativität, um nicht-offensichtliche Kombinationen zu finden, soll aber in den gegebenen Findings und der Map verankert bleiben.
  • Nach dem Parsen läuft validate_chain_references durch – die deterministische Cross-Reference-Prüfung von oben. Was übrig bleibt, ist sicher zum Rendern; was nicht, wird geloggt, damit der Operator weiß, dass das Modell etwas erfinden wollte.
Diese Cross-Reference-Prüfung ist das wichtigste Stück des ganzen Projekts. Sie ist die Grenze zwischen „nützliches Security-Tool” und „gelegentlich selbstbewusst falscher AI-Report”. Mit ihr im Spiel landet selbst halluzinierter Inhalt nie im Report.

Den Markdown-Report rendern

Das Rendern von der Agent-Logik zu trennen heißt: Dieselben Finding- und Chain-Objekte können später in andere Formate (JSON, SARIF, HTML) fließen, ohne Scanner oder Chainer anzufassen. Wir nutzen Jinja2 mit einer kleinen Template-Datei. Erstelle src/venice_security_reviewer/templates/report.md.j2:
Dann der Renderer in src/venice_security_reviewer/report.py:
Autoescape bleibt für das Markdown-Template aus (Markdown ist kein HTML), per Extension aber für künftige .html-Templates aktiviert.

Das CLI verdrahten

Das CLI ist der Orchestrator: Repo-Map bauen, scannen, chainen, rendern. Wir nutzen Typer für Argumente und Rich für eine schöne Summary-Tabelle. Erstelle src/venice_security_reviewer/cli.py:
Den Skript-Entry-Point in pyproject.toml ergänzen:
Damit ist die gesamte Pipeline verdrahtet.

Die Guardrails testen

Wir haben uns durchgehend auf eine Idee gestützt: Die deterministischen Leitplanken trennen ein nützliches Security-Tool von einem selbstbewusst-falschen. Diese Behauptung ist nur wertvoll, wenn wir nachweisen können, dass die Leitplanken halten – deshalb rufen die wertvollsten Tests dieses Projekts gar nicht Venice auf. Sie zurren die Pydantic-Grenze und das Prompt-Assembly-Plumbing fest, laufen offline in Millisekunden, ohne API-Key und ohne Token-Kosten. Zuerst Dev-Abhängigkeiten:
Als Erstes lohnt es, die Modell-Grenze selbst zu testen. Diese Tests behaupten, dass fehlerhafte Findings und Chains zur Konstruktionszeit abgelehnt werden, bevor sie in einen Report gelangen können. Erstelle tests/test_models.py:
Jeder dieser Tests spiegelt ein Constraint, das wir den Modellen gegeben haben: ein invertierter Zeilenbereich, eine ID, die nicht zum F-###-Muster passt, und eine „Chain” aus einem einzelnen Finding. Falls einer jemals aufhört zu raisen, ist eine ganze Halluzinationsklasse stillschweigend wieder möglich. Der wichtigste Test deckt den Cross-Reference-Validator ab, weil das die Funktion ist, die erfundene Chains wirklich verwirft:
F-999 wurde vom Scanner nie erzeugt, daher landet die Chain, die das referenziert, in dropped und erreicht den Report nie. Der zugehörige Test im Referenz-Repo, test_validate_chain_references_drops_unknown_files, macht dasselbe für eine Chain, die eine Datei behauptet, aus der keines ihrer Findings stammt. Als Zweites lohnt es, das Plumbing zu testen, das den Chainer füttert. Es ist leicht, das Prompt-Assembly zu refaktorieren und still aufzuhören, Cross-File-Kontext mitzugeben – woraufhin der Chainer weiterläuft, aber leise schlechter wird. Dieser Test baut ein Zwei-Modul-Fixture, rendert den Prompt und behauptet, dass die Cross-File-Information tatsächlich vorhanden ist – ohne Venice-Round-Trip. Erstelle tests/test_cross_file_chain.py:
Geht dieser Test durch, bekommt der Chainer einen Prompt mit beiden Findings, beiden Dateipfaden und der Import-Kante dazwischen. Ob das Modell diese Information gut nutzt, ist eine separate, out-of-band-Auswertung; dieser Test schützt nur das Plumbing, das die Information in den Prompt bringt. Die ganze Suite plus Linter und Type-Checker:
Da keiner dieser Tests Netzwerk berührt, kannst du sie bei jedem Commit und in CI laufen lassen, ohne Tokens zu verbrennen oder einen Venice-Key zu brauchen. Das Referenz-Repo enthält zudem tests/test_scanner_parse.py, tests/test_chainer_parse.py und tests/test_repo_map.py, die JSON-Parsing-Edge-Cases (fehlerhafte Einträge verwerfen statt zu crashen) und den AST-Repo-Map-Builder abdecken.

Das Projekt ausführen

Auf einer echten Codebase: das CLI auf ein Verzeichnis mit Python-Quellcode richten:
Oder mit pip install -e . ins Virtualenv installieren und venice-security-reviewer scan path/to/your/code ausführen. Der Output sieht ungefähr so aus:
Der Markdown-Report zeigt oben jede Chain mit ihrem Narrativ, darunter alle Einzel-Findings mit Severity, CWE, Dateiposition, Beschreibung und dem verbatim Snippet, das das Modell gelesen haben will. Das Referenz-Repo liefert vier mitgelieferte Demo-Ziele, die je eine andere Argumentationsform testen:
  • examples/vulnerable_app — eine Multi-File-Flask-App mit drei „low”-Findings, von denen zwei zu einer kritischen Privilege-Escalation-Chain über Dateien hinweg kombinieren. Testet, ob der Chainer selektiv kombiniert.
  • examples/url_preview — ein Multi-File-URL-Fetcher mit einer defensiven Allowlist, die nicht pro Iteration angewendet wird. Testet Cross-File-Datenfluss kombiniert mit Deployment-Topologie (Link-Local-IPs sind Cloud-Credential-Gateways).
  • examples/csv_query — ein Single-File-CSV-Filter mit eval-Sandbox-Escape via __class__.__base__.__subclasses__(). Testet Sprach-Level-Argumentation statt HTTP-Flow.
  • examples/webhook_handler — ein Single-File-HMAC-Verifier mit einer JSON-Parser-Differential-Schwachstelle. Testet Argumentation über mehrere Spezifikationen hinweg.
Probier sie aus:
Wenn das CLI jemals chainer referenced N unknown finding id(s) or file(s); chains dropped loggt, hat der Cross-Reference-Validator das Modell beim Erfinden einer Chain erwischt. Die verworfenen Chains schaffen es nie in den Report; du bekommst nur eine Warnung, mit der du den Prompt anpassen oder weitere Chainer-Läufe sampeln kannst.

Dieses Beispiel erweitern

Die Zwei-Agenten-Form lässt sich gut verallgemeinern. Ein paar lohnenswerte Richtungen:
  • Mehr Sprachen. Der Scanner ist auf Prompt-Ebene sprachagnostisch; nur der AST-Builder ist Python-spezifisch. Tausche ihn gegen tree-sitter aus und du bekommst dieselbe Nachbarschafts-/Kondensiert-Map-Form für TypeScript, Go oder Rust.
  • Ein dritter Agent für Fixes. Ist eine Chain einmal da, kann ein Patcher-Agent einen Unified Diff vorschlagen, der eines der konstituierenden Findings entschärft. Den Diff per Pydantic gegen denselben Evidence-File-Satz validieren und du bekommst denselben Halluzinationsschutz gratis.
  • Output-Formate. Nur render_report kennt Markdown. Ergänze einen SARIF-Renderer und dieselben Findings fließen in GitHub Code Scanning. Ein JSON-Renderer pipest Ergebnisse in ein nachgelagertes System.
  • Caching per Datei-Hash. Die Per-Datei-Aufrufe des Scanners sind unabhängig und idempotent. Caching nach (file_hash, prompt_hash, model) bedeutet, dass ein Rescan eines Repos, in dem nur eine Datei geändert wurde, nur den Scanner für diese eine Datei erneut ausführt.
  • Sampling für den Chainer. Bei kritischen Läufen den Chainer N-mal bei leicht höherer Temperatur aufrufen und die Ergebnisse schneiden. Chains, die das Modell konsistent findet, sind eher real; Chains, die einmal auftauchen und nie wieder, sind eher Rauschen.
  • Stärkere Modelle. zai-org-glm-5 ist der Default, weil es ein gutes Preis-/Qualitätsverhältnis bei kombinatorischer Argumentation bietet. Für härtere Codebasen kann ein stärkeres Venice-Modell (über VENICE_MODEL) die Narrative des Chainers merklich verdichten.

Abschluss

Danke fürs Lesen! Hoffentlich hilft das, ein KI-Security-Tool zu strukturieren, das tatsächlich vertrauenswürdig ist. Das Muster verallgemeinert auch über Security hinaus: Immer wenn ein LLM dateiübergreifend argumentieren soll und sich dabei auf reale Evidenz erden muss, ist das Rezept dasselbe. Eine deterministische strukturelle Map bauen, dem Modell einen kontextfähigen Slice davon geben, die Modell-Referenzen gegen die Struktur zurückvalidieren und alles verwerfen, was es nicht erden kann. Mit Python und der Venice-API können wir Agenten bauen, die LLM-Argumentation mit harten Validierungsgrenzen kombinieren, und liefern etwas, das eine nützliche Antwort gibt, statt nur selbstbewusst zu klingen.