
Construye y consulta una representación de base de datos de grafos del código fuente.
# Trailmark
[](https://github.com/trailofbits/trailmark/actions/workflows/ci.yml)
[](https://github.com/trailofbits/trailmark/actions/workflows/mutation.yml)
Analiza el código fuente en grafos consultables de funciones, clases, llamadas y anotaciones semánticas para el análisis de seguridad.
Trailmark utiliza [tree-sitter](https://tree-sitter.github.io/) para el análisis de AST independiente del lenguaje y [rustworkx](https://www.rustworkx.org/) para el recorrido de grafos de alto rendimiento. La visión a largo plazo es combinar este grafo con pruebas de mutación y fuzzing guiado por cobertura para identificar brechas entre las suposiciones y la cobertura de pruebas que sean alcanzables desde la entrada del usuario.
## Cómo funciona
Trailmark opera en tres fases: **parse**, **index** y **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. Análisis
Un analizador específico del lenguaje recorre el directorio, analiza cada archivo en un AST de tree-sitter y extrae:
- **Nodos** — funciones, métodos, clases, structs, interfaces, traits, enums, módulos, espacios de nombres
- **Aristas** — llamadas, herencia, implementación, contención, importaciones
- **Metadatos** — anotaciones de tipo, complejidad ciclomática, ramas, docstrings, tipos de excepción
### Lenguajes compatibles
| Lenguaje | Extensiones | Construcciones clave |
| --- | --- | --- |
| Python | `.py` | funciones, clases, métodos |
| JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` | funciones, clases, funciones flecha |
| TypeScript | `.ts`, `.tsx` | funciones, clases, interfaces, enums |
| PHP | `.php` | funciones, clases, interfaces, traits |
| Ruby | `.rb` | métodos, clases, módulos |
| C | `.c`, `.h` | funciones, structs, enums |
| C++ | `.cpp`, `.hpp`, `.cc`, `.hh`, `.cxx`, `.hxx` | funciones, clases, structs, espacios de nombres |
| C# | `.cs` | métodos, clases, interfaces, structs, enums, espacios de nombres |
| Java | `.java` | métodos, clases, interfaces, enums |
| Go | `.go` | funciones, métodos, structs, interfaces |
| Rust | `.rs` | funciones, structs, traits, enums, bloques impl, bloques `verus!` |
| Solidity | `.sol` | contratos, interfaces, librerías, funciones, modificadores, structs, enums |
| Cairo | `.cairo` | funciones, traits, structs, enums, bloques impl, contratos StarkNet |
| Circom | `.circom` | plantillas, funciones, señales, componentes |
| Haskell | `.hs` | funciones, tipos de datos, clases de tipos, instancias |
| Erlang | `.erl` | funciones, registros, comportamientos, módulos |
| Miden Assembly | `.masm` | procedimientos, puntos de entrada, constantes, invocaciones |
| Swift | `.swift` | funciones, clases, structs, enums, protocolos, extensiones |
| Objective-C | `.m`, `.mm`, `.h` | funciones C, clases, métodos (nombres basados en selectores) |
| Kotlin | `.kt`, `.kts` | funciones, clases, interfaces, clases de datos, objetos, métodos |
| Dart | `.dart` | funciones, clases, clases abstractas, métodos, constructores |
| Move | `.move` | módulos, funciones, importaciones, llamadas directas |
| Tact | `.tact` | contratos, structs, receptores, funciones |
| Func | `.fc`, `.func` | funciones, inclusiones, llamadas directas |
| Sway | `.sw` | interfaces ABI, structs, métodos impl, funciones |
| Rego | `.rego` | paquetes, importaciones, reglas de política, llamadas a reglas |
| Proto | `.proto` | servicios, RPCs, mensajes, campos, enums |
| Thrift | `.thrift` | servicios, funciones, structs, campos, enums |
| GraphQL | `.graphql`, `.gql` | tipos de objeto, operaciones raíz, campos, enums |
| SQL | `.sql` | esquemas, tablas, vistas, funciones, procedimientos |
El analizador de Rust extrae funciones ordinarias, `spec` y `proof` con cuerpos
de bloques `verus! { ... }`. La gramática de Verus actualmente solo admite
bloques delimitados por llaves; `verus!(...)` y `verus![...]` se omiten con una advertencia.
Los módulos en línea conservan IDs cualificados (por ejemplo, `file.inner:function`), y
los métodos conservan su propietario impl o trait. Cada bloque se analiza de forma independiente;
los errores de sintaxis producen una advertencia con la ubicación del archivo y del bloque.
También se admiten invocaciones cualificadas como `vstd::prelude::verus!`.
Las funciones auxiliares locales conservan su función contenedora en sus IDs (por ejemplo,
`file:outer.inner`). Las llamadas a estas funciones auxiliares y las importaciones relativas como
`use super::*` se resuelven a declaraciones presentes en el archivo.
Los nombres locales repetidos en distintos bloques reciben un sufijo de desplazamiento de bytes para mantener
sus nodos distintos, y las llamadas respetan el ámbito del bloque.
Los structs, enums, traits y módulos en línea locales también conservan la función contenedora:
por ejemplo, `file:outer.Guard.drop` y `file:outer.local.helper`.
Sus cuerpos tienen llamadas y complejidad separadas. Las llamadas explícitas a tipos y módulos
locales conocidos se resuelven dentro del archivo. Las llamadas sin cualificar buscan en los ámbitos de funciones y módulos;
los elementos asociados usan rutas como `Type::method`. Las llamadas con punto se resuelven
solo cuando el receptor nombra un struct o enum conocido. Otros receptores de valor,
incluido `self.method()`, permanecen sin resolver. Las llamadas implícitas a destructores y la inferencia
de tipos del receptor no se modelan. Estas reglas se aplican tanto al Rust ordinario como a los
bloques de Verus. Editar código antes de una declaración con un sufijo de desplazamiento cambia
su ID, por lo que `trailmark diff` puede informar esa declaración como eliminada y añadida.
Trailmark no expande macros arbitrarias. Las invocaciones literales de Verus dentro de
macros opacas o cuerpos de funciones se omiten con una advertencia. Las definiciones de
`macro_rules!` no invocadas no se expanden. Las firmas de funciones sin cuerpos
y las llamadas en cláusulas `requires`/`ensures` no se extraen.
```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
```
Los ID de nodo siguen el esquema `module:function`, `module:Class` o `module:Class.method` para una búsqueda inequívoca. El análisis de directorios resuelve las llamadas entre archivos sin calificar cuando existe una definición única; las llamadas entre archivos ambiguas se dejan en su objetivo original de mejor esfuerzo y se marcan como `uncertain`. La confianza de las aristas se etiqueta como `certain` (llamadas directas, `self.method()`), `inferred` (acceso a atributos en objetos que no son self) o `uncertain` (despacho dinámico o resolución ambigua).
### 2. Index
El `GraphStore` carga el `CodeGraph` en un `PyDiGraph` de rustworkx y construye asignaciones bidireccionales de ID/índice para un recorrido rápido.
### 3. Query
El `QueryEngine` proporciona una API de alto nivel sobre el grafo indexado:
| Método | Descripción |
| --- | --- |
| `callers_of(name)` | Llamadores directos del objetivo nombrado |
| `callees_of(name)` | Llamados directos de la fuente nombrada |
| `ancestors_of(name)` | Toda función que puede alcanzar transitivamente el objetivo (rebanada ascendente) |
| `reachable_from(name)` | Toda función alcanzable transitivamente desde la fuente |
| `paths_between(src, dst)` | Todas las rutas de llamada simples entre dos nodos |
| `connect_subgraphs(source, target)` | Rutas que conectan dos subgrafos nombrados |
| `entrypoint_paths_to(name)` | Rutas desde cualquier punto de entrada detectado hasta el objetivo |
| `attack_surface()` | Puntos de entrada etiquetados con nivel de confianza, valor del activo y atributos del analizador cuando están presentes |
| `complexity_hotspots(n)` | Funciones con complejidad ciclomática ≥ n |
| `functions_that_raise(exc)` | Funciones cuya lista de excepciones detectada por el analizador incluye `exc` |
| `generic_parameters(name)` | Parámetros de tipo genérico declarados por un nodo |
| `type_references(name)` | Referencias de tipo de parámetro, retorno, excepción y límite genérico |
| `annotate(name, kind, description, source)` | Añadir una anotación semántica a un nodo |
| `annotations_of(name, kind=None)` | Obtener las anotaciones de un nodo, opcionalmente filtradas por tipo |
| `nodes_with_annotation(kind)` | Todo nodo etiquetado con el tipo de anotación dado |
| `clear_annotations(name, kind=None)` | Eliminar anotaciones de un nodo |
| `diff_against(other)` | Diff estructural del grafo de este motor frente a otro |
| `preanalysis()` | Ejecutar las pasadas de preanálisis integradas y almacenar anotaciones/subgrafos |
| `augment_sarif(path)` | Fusionar hallazgos SARIF en el grafo |
| `augment_weaudit(path)` | Fusionar hallazgos de weAudit en el grafo |
| `augment_binary(path)` | Fusionar un archivo JSON de grafo de análisis binario externo |
| `findings(kind=None)` | Devolver los nodos que llevan anotaciones de tipo hallazgo |
| `subgraph(name)` | Devolver los nodos de un subgrafo nombrado |
| `subgraph_edges(name)` | Devolver las aristas inducidas dentro de un subgrafo nombrado |
| `subgraph_names()` | Listar todos los subgrafos nombrados actualmente en el grafo |
| `summary()` | Recuentos de nodos, recuentos de aristas, dependencias |
| `to_json()` | Exportación completa del grafo |
### Modelo de datos
```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
```
**Tipos de nodo:** `function`, `method`, `class`, `module`, `struct`, `interface`, `trait`, `enum`, `namespace`, `contract`, `library`, `template`, `proxy`
**Orígenes de nodo:** `source`, `proxy`, `binary`, `synthetic`
**Tipos de arista:** `calls`, `inherits`, `implements`, `contains`, `imports`, `resolves_to`, `type_uses`, `specializes`, `corresponds_to`
**Confianza de arista:** `certain`, `inferred`, `uncertain`
Las llamadas no resueltas se materializan como nodos proxy tales como
`proxy.unresolved:<raw-symbol>` para que los resultados de recorrido puedan mostrar dónde el análisis de código fuente perdió la resolución en lugar de descartar silenciosamente esa arista. Los análisis de directorios, incluidos los análisis de un solo lenguaje, también aplican enlaces de repositorio y materializan aristas `TYPE_USES`. El soporte de análisis binario importa grafos de llamadas JSON externos; Trailmark no desensambla ejecutables por sí mismo.
### Grafo de ejemplo
Dado este código 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 produce un grafo como:
```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
```
## Instalación
Los ejemplos a continuación siguen la rama de desarrollo actual. Para el paquete
publicado más reciente, instale desde PyPI. Para el conjunto exacto de funciones descrito
aquí, instale desde una copia local y ejecute los comandos mediante `uv run`.
```bash
# Latest published release
uv pip install trailmark
# Current checkout / development branch
uv sync --all-groups
```
Requiere Python ≥ 3.12.
Trailmark requiere `tree-sitter-language-pack` 1.22.1 o posterior para la mayoría de las gramáticas.
Las cachés de gramáticas propiedad de root son compatibles cuando no son escribibles por grupo u otros.
Las versiones actuales utilizan el almacén de certificados de la plataforma para las descargas de gramáticas. En
entornos con inspección TLS o sin conexión, prellene la caché del paquete con
`python -c "import tree_sitter_language_pack as p; p.download_all()"` en una
plataforma compatible, y luego copie el directorio de caché resultante de `tree-sitter-language-pack` a la máquina de destino. También se respeta `HTTPS_PROXY`. La gramática SQL
se distribuye como la dependencia del wheel `tree-sitter-sql` y no utiliza esa caché.
Las gramáticas incluidas Circom, Miden Assembly y Verus se compilan en el primer uso.
Esto requiere un compilador de C (`cc`), los encabezados de desarrollo de Python y acceso de escritura
a sus directorios de gramáticas instalados. Los archivos Rust sin bloques Verus no
activan la compilación. Si la gramática Verus no se puede compilar o cargar, Trailmark
registra una advertencia, omite los bloques Verus y continúa analizando Rust ordinario.
La caché de enlace de Verus incluye los contenidos del código fuente compilado, la ABI de Python,
la plataforma y las opciones del compilador. Las entradas modificadas seleccionan un nuevo enlace; las compilaciones
completadas se publican de forma atómica. Los fallos del compilador incluyen el estado de salida y
stderr en la advertencia.
Las copias de trabajo de Git conservan la definición de la gramática Verus y generan su analizador C durante
las compilaciones con Tree-sitter CLI 0.27.1. Antes de `uv sync` o `uv build` en una copia de trabajo,
ejecute `npm ci` con los scripts de instalación de npm habilitados; se necesita Node.js para la generación.
El analizador y los encabezados generados están en gitignore. Los wheels y las distribuciones de código fuente
incluyen estos códigos fuente generados, por lo que instalar los paquetes publicados no requiere ni
Node.js ni Tree-sitter CLI.
Las instalaciones editables de `uv` vuelven a ejecutar el hook de compilación para restaurar los archivos generados faltantes;
las entradas sin cambios reutilizan la salida existente.
## Uso
```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
```
### Detección de puntos de entrada
Trailmark rellena automáticamente `graph.entrypoints` para que `attack_surface()`, la propagación de taint y el cruce de límites de privilegios tengan datos con los que trabajar. La detección se ejecuta en cuatro capas, cada una sobrescribiendo la anterior:
1. **Heurística genérica de `main`.** Cualquier función llamada `main` en cualquier lenguaje. Etiquetada como `user_input` / `trusted_internal` / `low`.
2. **Escaneo consciente del framework.** Patrones de decoradores, atributos y visibilidad por lenguaje — véase la tabla a continuación.
3. **`pyproject.toml [project.scripts]`.** Los objetivos CLI explícitos reciben una clasificación de confianza/activo mejorada.
4. **Archivo de anulación local del repositorio.** Los puntos de entrada seleccionados manualmente en `.trailmark/entrypoints.toml` siempre tienen prioridad.
Cobertura de frameworks:
| Lenguaje | Frameworks detectados |
| --- | --- |
| Python | Flask, FastAPI, aiohttp, Click, Typer, Celery |
| JavaScript / TypeScript | NestJS, Next.js (App Router + Pages API), AWS Lambda |
| Java | Spring MVC / WebFlux, JAX-RS, listeners de Kafka, servlets |
| C# | ASP.NET Core, Azure Functions |
| PHP | Atributos `#[Route]` de Symfony + anotaciones heredadas |
| Rust | actix-web, rocket, exportaciones FFI (`#[no_mangle]`, `pub extern "C"`), atributos async-main |
| Solidity | Visibilidad `external` / `public` |
| Cairo / StarkNet | `#[external]`, `#[view]`, `#[l1_handler]`, `#[constructor]` |
| Circom | Declaraciones `component main` |
| Miden Assembly | Directivas `export.<name>` |
| Haskell | `main ::` / `main =` de nivel superior |
| Erlang | Funciones listadas en `-export([...])` |
| Swift | Atributo de aplicación `@main` |
| Objective-C | Selectores del ciclo de vida de `UIApplicationDelegate` (p. ej. `application:openURL:options:`) |
| Kotlin | Anotaciones de Spring MVC / WebFlux (compartidas con Java), métodos del ciclo de vida de componentes Android (`onCreate`, `onReceive`, `onBind`, ...) |
| Dart | Marcadores nativos invocables `@pragma('vm:entry-point')` |
| Go | Registros de la stdlib `http.HandleFunc` / `http.Handle`, registros de manejadores estilo gin/chi/echo `<router>.GET/POST/...` |
| Ruby | Acciones de controladores Rails (clases que heredan de `ApplicationController` / `ActionController::*`), métodos `perform` de workers de Sidekiq |
| C / C++ | Enlace `extern "C"`, `__attribute__((visibility("default")))`, `__declspec(dllexport)` |
Para cualquier cosa que las heurísticas pasen por alto, declare los puntos de entrada explícitamente en `.trailmark/entrypoints.toml` en la raíz del proyecto. El archivo admite tanto entradas de nodo único como basadas en reglas:
```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"
```
Las entradas posteriores anulan a las anteriores cuando dos reglas etiquetan el mismo nodo, así que coloca las reglas generales primero y las correcciones específicas después.
Consulta [docs/entrypoint-patterns.md](https://github.com/trailofbits/trailmark/blob/main/docs/entrypoint-patterns.md) para la referencia completa, incluidos frameworks aún no implementados (Express / Koa / Fastify, Laravel, Cobra, axum, warp, clap y otros) con patrones listos para grep que los contribuidores pueden usar para añadir nuevos detectores.
La detección de Solidity utiliza metadatos del parser en lugar de regex de firmas. Las declaraciones de interfaz se excluyen, y las sobrescrituras derivadas anotan las implementaciones base coincidentes con `solidity_overridden_by` en lugar de eliminar el entrypoint base. Las funciones concretas `public` y `external` siguen siendo entrypoints, incluidas las funciones `view` y `pure`; sus atributos `solidity_visibility`, `solidity_mutability` y de sobrescritura son devueltos por `attack_surface()` para que los llamadores puedan distinguir la exposición de solo lectura y filtrar las implementaciones base sobrescritas. `attack_surface()` incluye atributos de entrypoint específicos del parser cuando están adjuntos al nodo del grafo subyacente.
### Enlaces entre lenguajes y externos
El análisis políglota fusiona los grafos de lenguajes, pero muchas relaciones RPC, FFI, de subprocesos y de host/contrato no son visibles en la sintaxis del código fuente. Declara estas de forma determinista en `.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
```
Las referencias pueden ser IDs de nodo exactos o nombres/sufijos únicos. Las referencias ambiguas, los endpoints internos desconocidos, los valores de enumeración no válidos y el TOML mal formado generan `ValueError`. Establecer `source_external = true` o `target_external = true` permite explícitamente ese endpoint sin resolver y crea un nodo proxy. Este archivo es una interfaz de configuración pública estable.
### Limitaciones del análisis
- `entrypoint_paths_to()` informa la alcanzabilidad del grafo de llamadas, no el flujo de datos controlado por el atacante. Utilice los resultados de taint del preanálisis como una señal separada de baja precisión; Trailmark aún no realiza análisis de taint interprocedimental.
- TypeScript resuelve llamadas directas y receptores sencillos asignados con `new ConcreteClass()`. El despacho de interfaces a través de manifiestos, nombres de propiedades computadas, contenedores de inyección de dependencias y otros mecanismos dinámicos sigue siendo de mejor esfuerzo.
- El soporte de SQL está orientado a PostgreSQL y extrae esquemas, tablas, vistas, funciones, procedimientos y dependencias de rutinas/vistas. No es un validador completo de dialectos SQL ni un analizador semántico de consultas.
### API programática
```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` y `PROCEDURE` son aditivos en v0.5.0;
los consumidores que hacen coincidencia exhaustiva de los valores del enum deben añadir casos para ellos.
## Desarrollo
```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
```
Los informes de mutación se escriben en `coverage/gremlins/`. Las solicitudes de extracción mutan los archivos Rust/Verus nombrados en `tests/mutation_baseline.json` y aplican la compuerta a las funciones nuevas y modificadas allí. Las campañas completas del repositorio se ejecutan en los pushes a main y mediante el disparador manual del flujo de trabajo de mutación. Las funciones heredadas sin cambios se identifican mediante hashes de código fuente y entran en la compuerta automáticamente cuando se editan. La compuerta rechaza supervivientes no revisados, pruebas/resultados faltantes, errores y tiempos de espera agotados. Las excepciones requieren una razón y una huella digital de la función y la mutación; cambiar esa función requiere revisar sus excepciones nuevamente.
El ejecutor fija pytest-gremlins 1.9.0 y utiliza subprocesos reales de pytest para cada mutante, incluidos fixtures y pruebas parametrizadas. Cada mutante conserva la suite de pruebas seleccionada completa: las pruebas unitarias de componentes se ejecutan primero, seguidas por las pruebas restantes, deteniéndose en el primer fallo. Los mutantes de helpers compartidos conservan las pruebas de todos los lenguajes. Las pruebas permanecen en sus archivos existentes; `tests/mutation_order.py` define el orden, y los componentes desconocidos conservan el orden de recolección. El ejecutor omite el pre-escaneo de cobertura redundante, verifica la línea base limpia y cada orden de pruebas instrumentado distinto, y preserva los metadatos del paquete y los IDs de parámetros exactos al cargar el código mutado.
Estas medidas de compatibilidad abordan problemas del ejecutor y de selección upstream; sus pruebas de control deben pasar antes de actualizar el plugin.
Las actualizaciones de instantáneas (`TRAILMARK_UPDATE_SNAPSHOTS=1`) están deshabilitadas durante CI y las ejecuciones de mutación. Para enfocar una campaña local, pase `--targets` con rutas de código fuente separadas por comas, seguidas de los archivos de prueba relevantes. `--workers=4` ya habilita la ejecución paralela. Use `--test-order=collection` para comparar con el orden original de pytest.
## Licencia
Apache-2.0