
Construire et interroger une représentation en base de données graphe du code source.
# Trailmark
[](https://github.com/trailofbits/trailmark/actions/workflows/ci.yml)
[](https://github.com/trailofbits/trailmark/actions/workflows/mutation.yml)
Analyse le code source en graphes interrogeables de fonctions, de classes, d'appels et d'annotations sémantiques pour l'analyse de sécurité.
Trailmark utilise [tree-sitter](https://tree-sitter.github.io/) pour l'analyse AST indépendante du langage et [rustworkx](https://www.rustworkx.org/) pour un parcours de graphe hautes performances. La vision à long terme est de combiner ce graphe avec le mutation testing et le fuzzing guidé par la couverture afin d'identifier les écarts entre les hypothèses et la couverture de tests qui sont atteignables depuis les entrées utilisateur.
## Fonctionnement
Trailmark fonctionne en trois phases : **parse**, **index** et **query**.
```mermaid
flowchart TD
A["Source Files"] --> B["tree-sitter Parser"]
B --> C["CodeGraph (nodes + edges)"]
C --> D["rustworkx GraphStore"]
D --> E["QueryEngine"]
E --> F["JSON / Summary / Hotspots"]
classDef src fill:#007bff26,stroke:#007bff,color:#007bff
classDef parse fill:#28a74526,stroke:#28a745,color:#28a745
classDef data fill:#6f42c126,stroke:#6f42c1,color:#6f42c1
classDef query fill:#ffc10726,stroke:#e6a817,color:#e6a817
class A src
class B parse
class C,D data
class E,F query
```
### 1. Analyse
Un analyseur syntaxique spécifique à chaque langage parcourt le répertoire, analyse chaque fichier en un AST tree-sitter et extrait :
- **Nœuds** — fonctions, méthodes, classes, structures, interfaces, traits, énumérations, modules, espaces de noms
- **Arêtes** — appels, héritage, implémentation, contenance, imports
- **Métadonnées** — annotations de type, complexité cyclomatique, branches, docstrings, types d'exceptions
### Langages pris en charge
| Langage | Extensions | Constructions clés |
| --- | --- | --- |
| Python | `.py` | fonctions, classes, méthodes |
| JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` | fonctions, classes, fonctions fléchées |
| TypeScript | `.ts`, `.tsx` | fonctions, classes, interfaces, énumérations |
| PHP | `.php` | fonctions, classes, interfaces, traits |
| Ruby | `.rb` | méthodes, classes, modules |
| C | `.c`, `.h` | fonctions, structures, énumérations |
| C++ | `.cpp`, `.hpp`, `.cc`, `.hh`, `.cxx`, `.hxx` | fonctions, classes, structures, espaces de noms |
| C# | `.cs` | méthodes, classes, interfaces, structures, énumérations, espaces de noms |
| Java | `.java` | méthodes, classes, interfaces, énumérations |
| Go | `.go` | fonctions, méthodes, structures, interfaces |
| Rust | `.rs` | fonctions, structures, traits, énumérations, blocs impl, blocs `verus!` |
| Solidity | `.sol` | contrats, interfaces, bibliothèques, fonctions, modificateurs, structures, énumérations |
| Cairo | `.cairo` | fonctions, traits, structures, énumérations, blocs impl, contrats StarkNet |
| Circom | `.circom` | templates, fonctions, signaux, composants |
| Haskell | `.hs` | fonctions, types de données, classes de types, instances |
| Erlang | `.erl` | fonctions, enregistrements, comportements, modules |
| Miden Assembly | `.masm` | procédures, points d'entrée, constantes, invocations |
| Swift | `.swift` | fonctions, classes, structures, énumérations, protocoles, extensions |
| Objective-C | `.m`, `.mm`, `.h` | fonctions C, classes, méthodes (nommage basé sur les sélecteurs) |
| Kotlin | `.kt`, `.kts` | fonctions, classes, interfaces, classes de données, objets, méthodes |
| Dart | `.dart` | fonctions, classes, classes abstraites, méthodes, constructeurs |
| Move | `.move` | modules, fonctions, imports, appels directs |
| Tact | `.tact` | contrats, structures, récepteurs, fonctions |
| Func | `.fc`, `.func` | fonctions, includes, appels directs |
| Sway | `.sw` | interfaces ABI, structures, méthodes impl, fonctions |
| Rego | `.rego` | paquets, imports, règles de politique, appels de règles |
| Proto | `.proto` | services, RPC, messages, champs, énumérations |
| Thrift | `.thrift` | services, fonctions, structures, champs, énumérations |
| GraphQL | `.graphql`, `.gql` | types d'objets, opérations racines, champs, énumérations |
| SQL | `.sql` | schémas, tables, vues, fonctions, procédures |
Le parseur Rust extrait les fonctions ordinaires, `spec` et `proof` avec leurs corps depuis les blocs `verus! { ... }`. La grammaire Verus ne prend actuellement en charge que les blocs délimités par des accolades ; `verus!(...)` et `verus![...]` sont ignorés avec un avertissement. Les modules inline conservent des ID qualifiés (par exemple, `file.inner:function`), et les méthodes conservent leur propriétaire impl ou trait. Chaque bloc est analysé indépendamment ; les erreurs de syntaxe produisent un avertissement avec le fichier et l'emplacement du bloc. Les invocations qualifiées telles que `vstd::prelude::verus!` sont également prises en charge. Les fonctions d'assistance locales conservent leur fonction englobante dans leurs ID (par exemple, `file:outer.inner`). Les appels à ces fonctions d'assistance et les imports relatifs tels que `use super::*` sont résolus vers les déclarations présentes dans le fichier. Les noms locaux répétés dans différents blocs reçoivent un suffixe de décalage d'octet pour garder leurs nœuds distincts, et les appels respectent la portée du bloc. Les structs, enums, traits et modules inline locaux conservent également la fonction englobante : par exemple, `file:outer.Guard.drop` et `file:outer.local.helper`. Leurs corps ont des appels et une complexité distincts. Les appels explicites vers des types et modules locaux connus sont résolus au sein du fichier. Les appels nus recherchent dans les portées des fonctions et des modules ; les éléments associés utilisent des chemins tels que `Type::method`. Les appels pointés ne sont résolus que lorsque le récepteur nomme un struct ou un enum connu. Les autres récepteurs de valeur, y compris `self.method()`, restent non résolus. Les appels implicites aux destructeurs et l'inférence de type du récepteur ne sont pas modélisés. Ces règles s'appliquent au Rust ordinaire ainsi qu'aux blocs Verus. Modifier le code avant une déclaration avec un suffixe de décalage change son ID, de sorte que `trailmark diff` peut signaler cette déclaration comme supprimée et ajoutée.
Trailmark ne développe pas les macros arbitraires. Les invocations Verus littérales à l'intérieur de macros opaques ou de corps de fonctions sont ignorées avec un avertissement. Les définitions `macro_rules!` non invoquées ne sont pas développées. Les signatures de fonctions sans corps et les appels dans les clauses `requires`/`ensures` ne sont pas extraits.
```mermaid
flowchart TD
subgraph "Per-File Parsing"
F["Source file"] --> TS["tree-sitter AST"]
TS --> EX["Extract nodes"]
TS --> EC["Extract call edges"]
TS --> EB["Count branches"]
TS --> ET["Resolve types"]
end
EX --> CG["CodeGraph"]
EC --> CG
EB --> CG
ET --> CG
classDef src fill:#007bff26,stroke:#007bff,color:#007bff
classDef parse fill:#28a74526,stroke:#28a745,color:#28a745
classDef extract fill:#ffc10726,stroke:#e6a817,color:#e6a817
classDef data fill:#6f42c126,stroke:#6f42c1,color:#6f42c1
class F src
class TS parse
class EX,EC,EB,ET extract
class CG data
```
Les identifiants de nœuds suivent le schéma `module:function`, `module:Class` ou `module:Class.method` pour une recherche non ambiguë. L'analyse des répertoires résout les appels inter-fichiers simples lorsqu'une définition unique existe ; les appels inter-fichiers ambigus sont laissés à leur cible d'origine au mieux et marqués `uncertain`. La confiance des arêtes est étiquetée comme `certain` (appels directs, `self.method()`), `inferred` (accès à un attribut sur des objets non-self) ou `uncertain` (dispatch dynamique ou résolution ambiguë).
### 2. Index
Le `GraphStore` charge le `CodeGraph` dans un `PyDiGraph` rustworkx et construit des mappings bidirectionnels ID/index pour un parcours rapide.
### 3. Requête
Le `QueryEngine` fournit une API de haut niveau sur le graphe indexé :
| Méthode | Description |
| --- | --- |
| `callers_of(name)` | Appelants directs de la cible nommée |
| `callees_of(name)` | Appelés directs de la source nommée |
| `ancestors_of(name)` | Toutes les fonctions pouvant atteindre transitivement la cible (tranche ascendante) |
| `reachable_from(name)` | Toutes les fonctions atteignables transitivement depuis la source |
| `paths_between(src, dst)` | Tous les chemins d'appel simples entre deux nœuds |
| `connect_subgraphs(source, target)` | Chemins reliant deux sous-graphes nommés |
| `entrypoint_paths_to(name)` | Chemins depuis tout point d'entrée détecté vers la cible |
| `attack_surface()` | Points d'entrée étiquetés avec le niveau de confiance, la valeur de l'actif et les attributs du parseur lorsqu'ils sont présents |
| `complexity_hotspots(n)` | Fonctions dont la complexité cyclomatique est ≥ n |
| `functions_that_raise(exc)` | Fonctions dont la liste d'exceptions détectée par le parseur inclut `exc` |
| `generic_parameters(name)` | Paramètres de type génériques déclarés par un nœud |
| `type_references(name)` | Références de type des paramètres, retours, exceptions et bornes génériques |
| `annotate(name, kind, description, source)` | Ajouter une annotation sémantique à un nœud |
| `annotations_of(name, kind=None)` | Obtenir les annotations d'un nœud, éventuellement filtrées par type |
| `nodes_with_annotation(kind)` | Chaque nœud étiqueté avec le type d'annotation donné |
| `clear_annotations(name, kind=None)` | Supprimer les annotations d'un nœud |
| `diff_against(other)` | Diff structurel du graphe de ce moteur par rapport à un autre |
| `preanalysis()` | Exécuter les passes de pré-analyse intégrées et stocker les annotations/sous-graphes |
| `augment_sarif(path)` | Fusionner les résultats SARIF dans le graphe |
| `augment_weaudit(path)` | Fusionner les résultats weAudit dans le graphe |
| `augment_binary(path)` | Fusionner un fichier JSON de graphe d'analyse binaire externe |
| `findings(kind=None)` | Retourner les nœuds portant des annotations de type résultat |
| `subgraph(name)` | Retourner les nœuds d'un sous-graphe nommé |
| `subgraph_edges(name)` | Retourner les arêtes induites à l'intérieur d'un sous-graphe nommé |
| `subgraph_names()` | Lister chaque sous-graphe nommé actuellement sur le graphe |
| `summary()` | Nombre de nœuds, nombre d'arêtes, dépendances |
| `to_json()` | Export complet du graphe |
### Modèle de données
```mermaid
classDiagram
class CodeGraph {
language: str
root_path: str
nodes: dict[str, CodeUnit]
edges: list[CodeEdge]
annotations: dict[str, list[Annotation]]
entrypoints: dict[str, EntrypointTag]
dependencies: list[str]
add_annotation(node_id, annotation)
clear_annotations(node_id, kind=None)
merge(other)
}
class CodeUnit {
id: str
name: str
kind: NodeKind
location: SourceLocation
parameters: tuple[Parameter]
return_type: TypeRef
exception_types: tuple[TypeRef]
cyclomatic_complexity: int
branches: tuple[BranchInfo]
docstring: str
}
class CodeEdge {
source_id: str
target_id: str
kind: EdgeKind
confidence: EdgeConfidence
}
class Annotation {
kind: AnnotationKind
description: str
source: str
}
class EntrypointTag {
kind: EntrypointKind
trust_level: TrustLevel
description: str
asset_value: AssetValue
}
CodeGraph "1" *-- "*" CodeUnit
CodeGraph "1" *-- "*" CodeEdge
CodeGraph "1" *-- "*" Annotation
CodeGraph "1" *-- "*" EntrypointTag
```
**Types de nœuds :** `function`, `method`, `class`, `module`, `struct`, `interface`, `trait`, `enum`, `namespace`, `contract`, `library`, `template`, `proxy`
**Origines des nœuds :** `source`, `proxy`, `binary`, `synthetic`
**Types d'arêtes :** `calls`, `inherits`, `implements`, `contains`, `imports`, `resolves_to`, `type_uses`, `specializes`, `corresponds_to`
**Confiance des arêtes :** `certain`, `inferred`, `uncertain`
Les appels non résolus sont matérialisés sous forme de nœuds proxy tels que
`proxy.unresolved:<raw-symbol>` afin que les résultats de parcours puissent montrer où l'analyse
de source a perdu la résolution au lieu de supprimer silencieusement cette arête. Les analyses de
répertoire, y compris les analyses monolingues, appliquent également les liens de dépôt et
matérialisent les arêtes `TYPE_USES`. La prise en charge de l'analyse binaire importe des graphes
d'appels JSON externes ; Trailmark ne désassemble pas lui-même les exécutables.
### Exemple de graphe
Étant donné ce code Python :
```python
class Auth:
def verify(self, token: str) -> bool:
return self._check_sig(token)
def _check_sig(self, token: str) -> bool:
...
def handle_request(req: Request) -> Response:
auth = Auth()
if auth.verify(req.token):
return process(req)
return deny()
```
Trailmark produit un graphe comme celui-ci :
```mermaid
graph TD
HR["handle_request"] -->|calls| AV["Auth.verify"]
HR -->|calls| P["process"]
HR -->|calls| D["deny"]
AV -->|calls| CS["Auth._check_sig"]
A["Auth"] -->|contains| AV
A -->|contains| CS
classDef fn fill:#007bff26,stroke:#007bff,color:#007bff
classDef cls fill:#6f42c126,stroke:#6f42c1,color:#6f42c1
class HR,P,D fn
class A,AV,CS cls
```
## Installation
Les exemples ci-dessous suivent la branche de développement actuelle. Pour le dernier
paquet publié, installez depuis PyPI. Pour l'ensemble exact de fonctionnalités décrit
ici, installez depuis un checkout et exécutez les commandes via `uv run`.
```bash
# Latest published release
uv pip install trailmark
# Current checkout / development branch
uv sync --all-groups
```
Nécessite Python ≥ 3.12.
Trailmark nécessite `tree-sitter-language-pack` 1.22.1 ou une version plus récente pour la plupart des grammaires.
Les caches de grammaires appartenant à root sont pris en charge lorsqu'ils ne sont pas accessibles en écriture par le groupe ou par les autres utilisateurs.
Les versions actuelles utilisent le magasin de certificats de la plateforme pour le téléchargement des grammaires. Dans
les environnements soumis à une inspection TLS ou hors ligne, pré-remplissez le cache du paquet avec
`python -c "import tree_sitter_language_pack as p; p.download_all()"` sur une
plateforme correspondante, puis copiez le répertoire de cache `tree-sitter-language-pack` obtenu vers la machine cible. `HTTPS_PROXY` est également pris en charge. La grammaire SQL
est fournie comme dépendance du wheel `tree-sitter-sql` et n'utilise pas ce cache.
Les grammaires Circom, Miden Assembly et Verus intégrées sont compilées lors de la première utilisation.
Cela nécessite un compilateur C (`cc`), les en-têtes de développement Python et un accès en écriture
à leurs répertoires de grammaires installés. Les fichiers Rust sans blocs Verus ne
déclenchent pas de compilation. Si la grammaire Verus ne peut pas être construite ou chargée, Trailmark
journalise un avertissement, ignore les blocs Verus et poursuit l'analyse du Rust ordinaire.
Le cache de liaison Verus inclut le contenu source compilé, l'ABI Python,
la plateforme et les options du compilateur. Des entrées modifiées sélectionnent une nouvelle liaison ; les
constructions terminées sont publiées de manière atomique. Les échecs du compilateur incluent le code de sortie et
stderr dans l'avertissement.
Les checkouts Git conservent la définition de la grammaire Verus et génèrent son analyseur C lors des
constructions avec Tree-sitter CLI 0.27.1. Avant `uv sync` ou `uv build` dans un checkout,
exécutez `npm ci` avec les scripts d'installation npm activés ; Node.js est nécessaire pour la génération.
L'analyseur et les en-têtes générés sont ignorés par git. Les wheels et les distributions source
incluent ces sources générées, de sorte que l'installation des paquets publiés ne nécessite ni
Node.js ni le Tree-sitter CLI.
Les installations `uv` modifiables réexécutent le hook de construction pour restaurer les fichiers générés manquants ;
les entrées inchangées réutilisent la sortie existante.
## Utilisation
```bash
# Report the installed version
trailmark --version # or: trailmark -V
trailmark version # subcommand form
# Full JSON graph (Python, the default)
trailmark analyze path/to/project
# Analyze a different language
trailmark analyze --language rust path/to/project
trailmark analyze --language javascript path/to/project
# Polyglot: auto-detect and merge every supported language found in the
# tree, or pass an explicit comma-separated list.
trailmark analyze --language auto path/to/project
trailmark analyze --language python,rust,solidity path/to/project
# Summary statistics
trailmark analyze --summary path/to/project
# Complexity hotspots (threshold >= 10)
trailmark analyze --complexity 10 path/to/project
# Augment the graph with external findings (SARIF from static analyzers,
# weAudit findings from the VS Code extension). Each --sarif / --weaudit
# flag is repeatable. Add --json to print the augmented graph.
trailmark augment --sarif results.sarif path/to/project
trailmark augment --weaudit findings.json path/to/project
trailmark augment --sarif a.sarif --sarif b.sarif --json path/to/project
# List detected entrypoints (attack surface). Uses heuristic detection
# (main() functions, pyproject.toml [project.scripts]) plus an optional
# override file at .trailmark/entrypoints.toml (see below).
trailmark entrypoints path/to/project
trailmark entrypoints --json path/to/project
# Structural diff between two code graphs. Accepts directory paths or
# git refs (branches, tags, commits). Surfaces added/removed nodes,
# call-edge changes, and — most usefully — attack-surface changes.
trailmark diff before/ after/
trailmark diff --repo . main HEAD # compare git refs
trailmark diff --json before/ after/ # machine-readable output
# Generate a Mermaid diagram from the code graph. --type is required; the
# choices are call-graph, class-hierarchy, module-deps, containment,
# complexity, and data-flow. Use --focus to scope large graphs.
trailmark diagram --target path/to/project --type call-graph
trailmark diagram -t path/to/project -T call-graph -f parse_file --depth 3
trailmark diagram -t path/to/project -T complexity --threshold 5 --direction LR
```
### Détection des points d'entrée
Trailmark remplit automatiquement `graph.entrypoints` afin que `attack_surface()`, la propagation de teinte et le franchissement des frontières de privilèges disposent de données sur lesquelles travailler. La détection s'exécute en quatre couches, chacune écrasant la précédente :
1. **Heuristique générique `main`.** Toute fonction nommée `main` dans n'importe quel langage. Étiquetée `user_input` / `trusted_internal` / `low`.
2. **Analyse adaptée au framework.** Modèles de décorateurs, d'attributs et de visibilité par langage — voir le tableau ci-dessous.
3. **`pyproject.toml [project.scripts]`.** Les cibles CLI explicites bénéficient d'une classification de confiance/actif améliorée.
4. **Fichier de surcharge local au dépôt.** Les points d'entrée sélectionnés manuellement dans `.trailmark/entrypoints.toml` l'emportent toujours.
Couverture des frameworks :
| Langage | Frameworks détectés |
| --- | --- |
| Python | Flask, FastAPI, aiohttp, Click, Typer, Celery |
| JavaScript / TypeScript | NestJS, Next.js (App Router + Pages API), AWS Lambda |
| Java | Spring MVC / WebFlux, JAX-RS, écouteurs Kafka, servlets |
| C# | ASP.NET Core, Azure Functions |
| PHP | Attributs Symfony `#[Route]` + annotations héritées |
| Rust | actix-web, rocket, exports FFI (`#[no_mangle]`, `pub extern "C"`), attributs async-main |
| Solidity | visibilité `external` / `public` |
| Cairo / StarkNet | `#[external]`, `#[view]`, `#[l1_handler]`, `#[constructor]` |
| Circom | déclarations `component main` |
| Miden Assembly | directives `export.<name>` |
| Haskell | `main ::` / `main =` au niveau supérieur |
| Erlang | fonctions listées dans `-export([...])` |
| Swift | attribut d'application `@main` |
| Objective-C | sélecteurs de cycle de vie `UIApplicationDelegate` (par ex. `application:openURL:options:`) |
| Kotlin | annotations Spring MVC / WebFlux (partagées avec Java), méthodes de cycle de vie des composants Android (`onCreate`, `onReceive`, `onBind`, ...) |
| Dart | marqueurs d'appel natif `@pragma('vm:entry-point')` |
| Go | enregistrements stdlib `http.HandleFunc` / `http.Handle`, enregistrements de gestionnaires de style gin/chi/echo `<router>.GET/POST/...` |
| Ruby | actions de contrôleur Rails (classes héritant de `ApplicationController` / `ActionController::*`), méthodes `perform` des workers Sidekiq |
| C / C++ | liaison `extern "C"`, `__attribute__((visibility("default")))`, `__declspec(dllexport)` |
Pour tout ce que les heuristiques manquent, déclarez explicitement les points d'entrée dans `.trailmark/entrypoints.toml` à la racine du projet. Le fichier prend en charge à la fois les entrées à nœud unique et les entrées basées sur des règles :
```toml
# Single-node entry
[[entrypoint]]
node = "my_module:handle_request" # node id, or "module.path:function"
kind = "api" # user_input | api | database | file_system | third_party
trust = "untrusted_external" # untrusted_external | semi_trusted_external | trusted_internal
asset_value = "high" # high | medium | low
description = "HTTP POST /auth"
# Rule: every PHP script under public_html/ is a web-exposed entrypoint.
[[entrypoint]]
file_glob = "public_html/**/*.php"
kind = "user_input"
trust = "untrusted_external"
asset_value = "high"
description = "Web-exposed PHP script"
# Rule: any function that takes a PSR-7 request object.
[[entrypoint]]
param_type = "ServerRequestInterface"
kind = "api"
trust = "untrusted_external"
asset_value = "high"
description = "PSR-7 HTTP handler"
# Rule: functions named `handle_*`.
[[entrypoint]]
name_regex = "^handle_"
kind = "api"
trust = "untrusted_external"
# Rule: conditions compose with AND — web.py files AND name starts with handle_.
[[entrypoint]]
file_glob = "public/*.py"
name_regex = "^handle_"
kind = "api"
trust = "untrusted_external"
```
Les entrées suivantes remplacent les précédentes lorsque deux règles marquent le même nœud, donc placez les règles générales en premier et les corrections spécifiques ensuite.
Consultez [docs/entrypoint-patterns.md](https://github.com/trailofbits/trailmark/blob/main/docs/entrypoint-patterns.md) pour la référence complète, y compris les frameworks pas encore implémentés (Express / Koa / Fastify, Laravel, Cobra, axum, warp, clap, et autres) avec des motifs prêts pour grep que les contributeurs peuvent utiliser pour ajouter de nouveaux détecteurs.
La détection Solidity utilise les métadonnées du parseur plutôt que des regex de signature. Les déclarations d'interface sont exclues, et les surcharges dérivées annotent les implémentations de base correspondantes avec `solidity_overridden_by` au lieu de supprimer le point d'entrée de base. Les fonctions concrètes `public` et `external` restent des points d'entrée, y compris les fonctions `view` et `pure` ; leurs attributs `solidity_visibility`, `solidity_mutability` et de surcharge sont renvoyés par `attack_surface()` afin que les appelants puissent distinguer l'exposition en lecture seule et filtrer les implémentations de base surchargées. `attack_surface()` inclut les attributs de point d'entrée spécifiques au parseur lorsqu'ils sont attachés au nœud de graphe sous-jacent.
### Liens inter-langages et externes
L'analyse polyglotte fusionne les graphes de langages, mais de nombreuses relations RPC, FFI, sous-processus et hôte/contrat ne sont pas visibles dans la syntaxe source. Déclarez-les de manière déterministe dans `.trailmark/links.toml` :
```toml
[[link]]
source = "backend:submit"
target = "contract:Verifier.verify"
kind = "calls" # defaults to calls
confidence = "certain" # defaults to inferred
description = "JSON-RPC eth_call"
[[link]]
source = "backend:notify"
target = "payments-webhook"
target_external = true # required because target is unresolved
```
Les références peuvent être des ID de nœuds exacts ou des noms/suffixes uniques. Les références ambiguës, les points de terminaison internes inconnus, les valeurs d'énumération invalides et le TOML malformé lèvent une
`ValueError`. Définir `source_external = true` ou `target_external = true`
autorise explicitement ce point de terminaison non résolu et crée un nœud proxy. Ce fichier
est une interface de configuration publique stable.
### Limites de l'analyse
- `entrypoint_paths_to()` signale l'accessibilité dans le graphe d'appels, et non les flux de
données contrôlés par l'attaquant. Utilisez les résultats de teinte de la préanalyse comme un signal distinct grossier ; Trailmark
n'effectue pas encore d'analyse de teinte interprocédurale.
- TypeScript résout les appels directs et les récepteurs simples assignés avec
`new ConcreteClass()`. La répartition d'interface via des manifestes, des noms de propriétés
calculés, des conteneurs d'injection de dépendances et d'autres mécanismes dynamiques reste
au mieux.
- La prise en charge de SQL est orientée PostgreSQL et extrait les schémas, tables, vues,
fonctions, procédures et dépendances de routines/vues. Ce n'est pas un validateur complet
de dialecte SQL ni un analyseur sémantique de requêtes.
### API programmatique
```python
from trailmark.parse import parse_directory, parse_file
from trailmark.query.api import QueryEngine
# Parse-only API: get the raw CodeGraph without building GraphStore/QueryEngine.
graph = parse_file("path/to/file.py")
graph = parse_directory("path/to/project", language="auto")
# Single-language (default) or auto-detect + merge across all languages
engine = QueryEngine.from_directory("path/to/project")
engine = QueryEngine.from_directory("path/to/project", language="auto")
engine = QueryEngine.from_directory("path/to/project", language="python,rust")
# Direct neighbors
engine.callers_of("handle_request")
engine.callees_of("handle_request")
# Transitive slicing — who could reach this sink, or what could it reach?
engine.ancestors_of("Auth._check_sig")
engine.reachable_from("handle_request")
# Attack-surface paths from any detected entrypoint
engine.entrypoint_paths_to("Auth._check_sig")
# All call paths between two nodes
engine.paths_between("handle_request", "Auth._check_sig")
# Functions with cyclomatic complexity >= 10
engine.complexity_hotspots(10)
# What functions can raise a given exception? (uses parser-detected
# exception_types; no runtime tracing required)
engine.functions_that_raise("PermissionError")
# Add and query semantic annotations
from trailmark.models.annotations import AnnotationKind
engine.annotate(
"handle_request",
AnnotationKind.ASSUMPTION,
"Caller has already authenticated the session token",
source="llm",
)
engine.annotations_of("handle_request")
engine.nodes_with_annotation(AnnotationKind.FINDING)
# Diff against an earlier snapshot of the same codebase
before = QueryEngine.from_directory("before/")
diff = engine.diff_against(before)
# diff contains: summary_delta, nodes {added/removed/modified},
# edges {added/removed}, entrypoints {added/removed/modified}
# Run the built-in audit-oriented preanalysis passes
engine.preanalysis()
engine.findings()
engine.subgraph_names()
# Programmatic augmentation hooks for external tooling
engine.augment_sarif("results.sarif")
engine.augment_weaudit("findings.json")
```
`NodeKind.SCHEMA`, `TABLE`, `VIEW` et `PROCEDURE` sont additifs dans la v0.5.0 ;
les consommateurs qui font une correspondance exhaustive des valeurs d'énumération doivent ajouter des cas pour ceux-ci.
## Développement
```bash
# Install package and dev dependencies
npm ci
uv sync --all-groups
# Lint and format
uv run ruff check --fix
uv run ruff format
# Type check
uv run --locked ty check
# Tests
uv run pytest -q tests/
# Mutation testing with pytest-gremlins
uv run python -m tests.run_mutations --reviewed --workers=4
uv run python -m tests.mutation_gate
# Full repository mutation campaign
uv run python -m tests.run_mutations --workers=4
```
Les rapports de mutation sont écrits dans `coverage/gremlins/`. Les pull requests mutent les fichiers Rust/Verus nommés dans `tests/mutation_baseline.json` et contrôlent les nouvelles fonctions et les fonctions modifiées qui s'y trouvent. Les campagnes complètes sur le dépôt s'exécutent lors des pushes vers main et via le déclenchement manuel du workflow de mutation. Les fonctions héritées inchangées sont identifiées par des hachages de source et entrent automatiquement dans le contrôle lorsqu'elles sont modifiées. Le contrôle rejette les survivants non examinés, les tests/résultats manquants, les erreurs et les délais d'attente. Les exceptions nécessitent une raison et une empreinte de la fonction et de la mutation ; modifier cette fonction nécessite de réexaminer ses exceptions.
Le runner épingle pytest-gremlins 1.9.0 et utilise de véritables sous-processus pytest pour chaque mutant, y compris les fixtures et les tests paramétrés. Chaque mutant conserve la suite de tests complète sélectionnée : les tests unitaires de composants s'exécutent en premier, suivis des tests restants, en s'arrêtant à la première défaillance. Les mutants de helpers partagés conservent les tests de chaque langage. Les tests restent dans leurs fichiers existants ; `tests/mutation_order.py` définit l'ordre, et les composants inconnus conservent l'ordre de collecte. Le runner ignore la pré-analyse de couverture redondante, vérifie la baseline propre et chaque ordre de tests instrumentés distinct, et préserve les métadonnées de package et les ID de paramètres exacts lors du chargement du code muté.
Ces mesures de compatibilité répondent aux problèmes du runner et de sélection en amont ; leurs tests de contrôle doivent passer avant de mettre à jour le plugin.
Les mises à jour de snapshots (`TRAILMARK_UPDATE_SNAPSHOTS=1`) sont désactivées pendant la CI et les exécutions de mutation. Pour cibler une campagne locale, passez `--targets` avec des chemins de source séparés par des virgules, suivis des fichiers de test pertinents. `--workers=4` active déjà l'exécution parallèle. Utilisez `--test-order=collection` pour comparer avec l'ordre original de pytest.
## Licence
Apache-2.0