Volver a actualizaciones
Nuevo releaseJul 24, 2026

trailmark v0.5.0

Construye y consulta una representación de base de datos de grafos del código fuente.

Compartir

Trailmark

CI Mutation Testing

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 para el análisis AST independiente del lenguaje y rustworkx 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 son 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 para cada lenguaje recorre el directorio, analiza cada archivo en un AST de tree-sitter y extrae:

- **Nodos** — funciones, métodos, clases, estructuras, 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 admitidos

| Lenguaje | Extensiones | Constructos 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 |
| Solidity | `.sol` | contratos, interfaces, librerías, funciones, modificadores, structs, enums |
| Cairo | `.cairo` | funciones, traits, structs, enums, bloques impl, contratos StarkNet |
| Circom | `.circom` | templates, funciones, señales, componentes |
| Haskell | `.hs` | funciones, tipos de datos, clases de tipos, instancias |
| Erlang | `.erl` | funciones, registros, behaviours, 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 (nombramiento basado 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, includes, 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 |```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 IDs 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 llamadas entre archivos sin prefijo cuando existe una definición única; las llamadas ambiguas entre archivos se dejan en su destino original de mejor esfuerzo y se marcan como uncertain. La confianza de los bordes 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 una navegación rápida.

3. Query

El QueryEngine proporciona una API de alto nivel sobre el gráfico indexado:

MétodoDescripción
callers_of(name)Llamadores directos del objetivo nombrado
callees_of(name)Destinatarios directos de la fuente nombrada
ancestors_of(name)Todas las funciones que pueden alcanzar transitivamente el objetivo (corte ascendente)
reachable_from(name)Todas las funciones transitivamente alcanzables desde la fuente
paths_between(src, dst)Todos los caminos de llamada simples entre dos nodos
connect_subgraphs(source, target)Caminos que conectan dos subgrafos nombrados
entrypoint_paths_to(name)Caminos 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 detectadas 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ámetros, retorno, excepción y límite genérico
annotate(name, kind, description, source)Agregar una anotación semántica a un nodo
annotations_of(name, kind=None)Obtener anotaciones para un nodo, opcionalmente filtradas por tipo
nodes_with_annotation(kind)Todos los nodos etiquetados con el tipo de anotación dado
clear_annotations(name, kind=None)Eliminar anotaciones de un nodo
diff_against(other)Diferencia estructural del gráfico de este motor contra otro
preanalysis()Ejecutar los pases de preanálisis integrados y almacenar anotaciones/subgrafos
augment_sarif(path)Fusionar hallazgos SARIF en el gráfico
augment_weaudit(path)Fusionar hallazgos de weAudit en el gráfico
augment_binary(path)Fusionar un archivo JSON de gráfico de análisis binario externo
findings(kind=None)Devolver nodos que portan anotaciones de tipo hallazgo
subgraph(name)Devolver los nodos en un subgrafo nombrado
subgraph_edges(name)Devolver bordes inducidos dentro de un subgrafo nombrado
subgraph_names()Listar todos los subgrafos nombrados actualmente en el gráfico
summary()Recuentos de nodos, recuentos de bordes, dependencias
to_json()Exportación completa del gráfico

Data Model```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 nodos:** `function`, `method`, `class`, `module`, `struct`, `interface`, `trait`, `enum`, `namespace`, `contract`, `library`, `template`, `proxy`

**Orígenes de nodos:** `source`, `proxy`, `binary`, `synthetic`

**Tipos de aristas:** `calls`, `inherits`, `implements`, `contains`, `imports`, `resolves_to`, `type_uses`, `specializes`, `corresponds_to`

**Confianza de aristas:** `certain`, `inferred`, `uncertain`

Las llamadas no resueltas se materializan como nodos proxy como
`proxy.unresolved:<raw-symbol>` para que los resultados de recorrido puedan mostrar dónde el análisis de fuente perdió resolución en lugar de eliminar silenciosamente esa arista. El soporte de análisis binario importa gráficos de llamadas JSON externos; Trailmark no desensambla ejecutables por sí mismo.

### Ejemplo de Grafo

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 gráfico 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, instálelo desde PyPI. Para el conjunto exacto de características descrito aquí, instale desde un checkout y ejecute 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 usa tree-sitter-language-pack para la mayoría de las gramáticas. 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, prepare la caché de paquetes con python -c "import tree_sitter_language_pack as p; p.download_all()" en una plataforma coincidente, luego copie el directorio de caché de tree-sitter-language-pack resultante a la máquina destino. También se respeta HTTPS_PROXY. La gramática SQL viene como dependencia de wheel de tree-sitter-sql y no utiliza esa caché.

Usage```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 llena automáticamente `graph.entrypoints` para que `attack_surface()`, la propagación de datos contaminados y el cruce de límites de privilegios tengan datos con los que trabajar. La detección se ejecuta en cuatro capas, cada una anula la anterior:

1. **Heurística genérica `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 — vea la tabla a continuación.
3. **`pyproject.toml [project.scripts]`.** Destinos CLI explícitos obtienen una clasificación de confianza/activos mejorada.
4. **Archivo de anulación local del repositorio.** Los puntos de entrada seleccionados manualmente en `.trailmark/entrypoints.toml` siempre ganan.

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, Kafka listeners, servlets |
| C# | ASP.NET Core, Azure Functions |
| PHP | Symfony `#[Route]` attributes + legacy annotations |
| Rust | actix-web, rocket, FFI exports (`#[no_mangle]`, `pub extern "C"`), async-main attributes |
| Solidity | `external` / `public` visibility |
| Cairo / StarkNet | `#[external]`, `#[view]`, `#[l1_handler]`, `#[constructor]` |
| Circom | `component main` declarations |
| Miden Assembly | `export.<name>` directives |
| Haskell | top-level `main ::` / `main =` |
| Erlang | funciones listadas en `-export([...])` |
| Swift | atributo de aplicación `@main` |
| Objective-C | selectores de ciclo de vida de `UIApplicationDelegate` (ej. `application:openURL:options:`) |
| Kotlin | anotaciones de Spring MVC / WebFlux (compartidas con Java), métodos del ciclo de vida de componentes de Android (`onCreate`, `onReceive`, `onBind`, ...) |
| Dart | marcadores `@pragma('vm:entry-point')` invocables nativamente |
| Go | registros de `http.HandleFunc` / `http.Handle` de la stdlib, registros de manejadores `<router>.GET/POST/...` estilo gin/chi/echo |
| Ruby | acciones de controlador de Rails (clases que heredan de `ApplicationController` / `ActionController::*`), métodos `perform` de trabajadores Sidekiq |
| C / C++ | enlace `extern "C"`, `__attribute__((visibility("default")))`, `__declspec(dllexport)` |

Para cualquier cosa que las heurísticas no detecten, declare puntos de entrada explícitamente en `.trailmark/entrypoints.toml` en la raíz del proyecto. El archivo admite entradas tanto 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 las anteriores cuando dos reglas etiquetan el mismo nodo, así que coloque reglas amplias primero y correcciones específicas después.

Consulte docs/entrypoint-patterns.md para obtener la referencia completa, incluidos los frameworks aún no implementados (Express / Koa / Fastify, Laravel, Cobra, axum, warp, clap y otros) con patrones listos para grep que los contribuyentes pueden usar para agregar nuevos detectores.

La detección de Solidity utiliza metadatos del analizador en lugar de expresiones regulares de firma. Interfaz declaraciones son excluidas y una anulación derivada suprime la implementación base coincidente. Las funciones concretas public y external siguen siendo puntos de entrada, incluidas las funciones view y pure; sus atributos solidity_visibility y solidity_mutability son devueltos por attack_surface() para que los llamadores puedan distinguir la exposición de solo lectura. attack_surface() incluye atributos de punto de entrada específicos del analizador cuando están adjuntos al nodo del gráfico subyacente.

Enlaces entre lenguajes y externos

El análisis poliglota fusiona gráficos de lenguajes, pero muchas relaciones RPC, FFI, subprocesos y de host/contrato no son visibles en la sintaxis del código fuente. Declárelas de manera 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" external = true # required when either endpoint is unresolved

Las referencias pueden ser IDs de nodo exactos o nombres/sufijos únicos. Las referencias ambiguas,
los endpoints internos desconocidos, los valores de enum no válidos y el TOML mal formado lanzan
`ValueError`. Establecer `external = true` explícitamente permite endpoints no resueltos
y crea nodos proxy. Este archivo es una interfaz de configuración pública estable.

### Limitaciones del análisis

- `entrypoint_paths_to()` informa sobre la alcanzabilidad del grafo de llamadas, no sobre el flujo de datos
  controlado por el atacante. Utilice los resultados de taint del preanálisis como una señal separada gruesa; Trailmark
  aún no realiza análisis de taint interprocedimental.
- TypeScript resuelve llamadas directas y receptores simples asignados con
  `new ConcreteClass()`. El envío de interfaces a través de manifiestos, nombres de propiedades computadas,
  contenedores de inyección de dependencias y otros mecanismos dinámicos sigue siendo un esfuerzo máximo.
- El soporte SQL está orientado a PostgreSQL y extrae esquemas, tablas, vistas,
  funciones, procedimientos y dependencias de rutinas/vistas. No es un validador completo
  de dialecto 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 coinciden exhaustivamente con valores de enumeración deben agregar casos para ellos.

Desarrollo```bash

Install package and dev dependencies

uv sync --all-groups

Lint and format

uv run ruff check --fix uv run ruff format

Type check

uv tool install ty && ty check

Tests

uv run pytest -q tests/

Mutation testing (on macOS, set this env var to avoid rustworkx fork segfaults)

OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES uv run mutmut run

## Licencia

Apache-2.0

Categorías