Назад к обновлениям
New releaseJul 24, 2026

trailmark v0.5.0

Создавайте и запрашивайте графовое представление исходного кода.

Поделиться

Trailmark

CI Mutation Testing

Разбирайте исходный код в запрашиваемые графы функций, классов, вызовов и семантических аннотаций для анализа безопасности.

Trailmark использует tree-sitter для языково-независимого разбора AST и rustworkx для высокопроизводительного обхода графов. Долгосрочная цель — объединить этот граф с мутационным тестированием и фазингом, управляемым покрытием, чтобы выявить пробелы между предположениями и тестовым покрытием, которые достижимы из пользовательского ввода.

Как это работает

Trailmark работает в три фазы: parse, index и 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. Парсинг

Языковой парсер обходит директорию, анализирует каждый файл в AST tree-sitter и извлекает:

- **Узлы** — функции, методы, классы, структуры, интерфейсы, трейты, перечисления, модули, пространства имён
- **Рёбра** — вызовы, наследование, реализация, включение, импорты
- **Метаданные** — аннотации типов, цикломатическая сложность, ветвления, строки документации, типы исключений

### Поддерживаемые языки

| Язык | Расширения | Ключевые конструкции |
| --- | --- | --- |
| Python | `.py` | функции, классы, методы |
| JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` | функции, классы, стрелочные функции |
| TypeScript | `.ts`, `.tsx` | функции, классы, интерфейсы, перечисления |
| PHP | `.php` | функции, классы, интерфейсы, трейты |
| Ruby | `.rb` | методы, классы, модули |
| C | `.c`, `.h` | функции, структуры, перечисления |
| C++ | `.cpp`, `.hpp`, `.cc`, `.hh`, `.cxx`, `.hxx` | функции, классы, структуры, пространства имён |
| C# | `.cs` | методы, классы, интерфейсы, структуры, перечисления, пространства имён |
| Java | `.java` | методы, классы, интерфейсы, перечисления |
| Go | `.go` | функции, методы, структуры, интерфейсы |
| Rust | `.rs` | функции, структуры, трейты, перечисления, блоки impl |
| Solidity | `.sol` | контракты, интерфейсы, библиотеки, функции, модификаторы, структуры, перечисления |
| Cairo | `.cairo` | функции, трейты, структуры, перечисления, блоки impl, контракты StarkNet |
| Circom | `.circom` | шаблоны, функции, сигналы, компоненты |
| Haskell | `.hs` | функции, типы данных, классы типов, экземпляры |
| Erlang | `.erl` | функции, записи, поведения, модули |
| Miden Assembly | `.masm` | процедуры, точки входа, константы, вызовы |
| Swift | `.swift` | функции, классы, структуры, перечисления, протоколы, расширения |
| Objective-C | `.m`, `.mm`, `.h` | C-функции, классы, методы (именование на основе селекторов) |
| Kotlin | `.kt`, `.kts` | функции, классы, интерфейсы, классы данных, объекты, методы |
| Dart | `.dart` | функции, классы, абстрактные классы, методы, конструкторы |
| Move | `.move` | модули, функции, импорты, прямые вызовы |
| Tact | `.tact` | контракты, структуры, приёмники, функции |
| Func | `.fc`, `.func` | функции, включения, прямые вызовы |
| Sway | `.sw` | интерфейсы ABI, структуры, методы impl, функции |
| Rego | `.rego` | пакеты, импорты, правила политик, вызовы правил |
| Proto | `.proto` | сервисы, RPC, сообщения, поля, перечисления |
| Thrift | `.thrift` | сервисы, функции, структуры, поля, перечисления |
| GraphQL | `.graphql`, `.gql` | типы объектов, корневые операции, поля, перечисления |
| SQL | `.sql` | схемы, таблицы, представления, функции, процедуры |```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

Идентификаторы узлов следуют схеме module:function, module:Class или module:Class.method для однозначного поиска. Разбор директорий разрешает прямые кросс-файловые вызовы, когда существует уникальное определение; неоднозначные кросс-файловые вызовы оставляются с исходной целевой функцией по наилучшему предположению и помечаются как uncertain. Уверенность ребер помечается как certain (прямые вызовы, self.method()), inferred (доступ к атрибутам не-self объектов) или uncertain (динамическая диспетчеризация или неоднозначное разрешение).

2. Index

GraphStore загружает CodeGraph в rustworkx PyDiGraph и строит двунаправленные отображения ID/индексов для быстрого обхода.

3. Query

QueryEngine предоставляет высокоуровневый API для индексированного графа:

МетодОписание
callers_of(name)Прямые вызывающие объекты для указанного целевого объекта
callees_of(name)Прямые вызываемые объекты от указанного источника
ancestors_of(name)Все функции, которые могут транзитивно достичь целевого объекта (восходящий срез)
reachable_from(name)Все функции, транзитивно достижимые из источника
paths_between(src, dst)Все простые пути вызовов между двумя узлами
connect_subgraphs(source, target)Пути, соединяющие два именованных подграфа
entrypoint_paths_to(name)Пути от любой обнаруженной точки входа к целевому объекту
attack_surface()Точки входа, помеченные уровнем доверия, стоимостью актива и атрибутами парсера, если они присутствуют
complexity_hotspots(n)Функции с цикломатической сложностью ≥ n
functions_that_raise(exc)Функции, чей обнаруженный парсером список исключений включает exc
generic_parameters(name)Параметры универсального типа, объявленные узлом
type_references(name)Ссылки на типы параметров, возвращаемых значений, исключений и ограничений универсального типа
annotate(name, kind, description, source)Добавить семантическую аннотацию к узлу
annotations_of(name, kind=None)Получить аннотации для узла, опционально отфильтрованные по типу
nodes_with_annotation(kind)Все узлы, помеченные указанным типом аннотации
clear_annotations(name, kind=None)Удалить аннотации из узла
diff_against(other)Структурная разница графа этого движка по сравнению с другим
preanalysis()Запустить встроенные проходы предварительного анализа и сохранить аннотации/подграфы
augment_sarif(path)Объединить результаты SARIF в граф
augment_weaudit(path)Объединить результаты weAudit в граф
augment_binary(path)Объединить внешний JSON-файл графа анализа бинарного кода
findings(kind=None)Вернуть узлы, содержащие аннотации типа "finding"
subgraph(name)Вернуть узлы в именованном подграфе
subgraph_edges(name)Вернуть индуцированные ребра внутри именованного подграфа
subgraph_names()Перечислить все именованные подграфы, присутствующие в графе
summary()Количество узлов, количество ребер, зависимости
to_json()Полный экспорт графа

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
**Типы узлов:** `function`, `method`, `class`, `module`, `struct`, `interface`, `trait`, `enum`, `namespace`, `contract`, `library`, `template`, `proxy`

**Источники узлов:** `source`, `proxy`, `binary`, `synthetic`

**Типы рёбер:** `calls`, `inherits`, `implements`, `contains`, `imports`, `resolves_to`, `type_uses`, `specializes`, `corresponds_to`

**Уверенность рёбер:** `certain`, `inferred`, `uncertain`

Неразрешённые вызовы материализуются в виде прокси-узлов, таких как
`proxy.unresolved:<raw-symbol>`, чтобы результаты обхода могли показать, где анализ исходного кода потерял разрешение, вместо того чтобы молча отбрасывать это ребро. Поддержка бинарного анализа импортирует внешние JSON-графы вызовов; Trailmark сам не дизассемблирует исполняемые файлы.

### Пример графа

Дан следующий код на 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 создаёт граф, такой как:```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
## Установка

В примерах ниже отслеживается текущая ветка разработки. Для последней
опубликованной версии установите из PyPI. Для точного набора функций, описанных
здесь, установите из копии репозитория и запускайте команды через `uv run`.```bash
# Latest published release
uv pip install trailmark

# Current checkout / development branch
uv sync --all-groups

Требуется Python ≥ 3.12.

Trailmark использует tree-sitter-language-pack для большинства грамматик. Текущие релизы используют хранилище сертификатов платформы для загрузки грамматик. В средах с проверкой TLS или офлайн-средах предварительно заполните кеш пакетов с помощью python -c "import tree_sitter_language_pack as p; p.download_all()" на подходящей платформе, затем скопируйте полученную директорию кеша tree-sitter-language-pack на целевую машину. HTTPS_PROXY также учитывается. Грамматика SQL поставляется как зависимость колеса tree-sitter-sql и не использует этот кеш.

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

### Обнаружение точек входа

Trailmark автоматически заполняет `graph.entrypoints`, чтобы `attack_surface()`, распространение меток и пересечение границ привилегий имели данные для работы. Обнаружение выполняется в четыре слоя, каждый из которых переопределяет предыдущий:

1. **Общая эвристика `main`.** Любая функция с именем `main` в любом языке. Помечается как `user_input` / `trusted_internal` / `low`.
2. **Сканирование с учётом фреймворка.** Шаблоны декораторов, атрибутов и видимости для каждого языка — см. таблицу ниже.
3. **`pyproject.toml [project.scripts]`.** Явные цели CLI получают повышенную оценку доверия и классификацию активов.
4. **Локальный файл переопределения в репозитории.** Точки входа, созданные вручную в `.trailmark/entrypoints.toml`, всегда имеют приоритет.

Покрытие фреймворков:

| Язык | Обнаруженные фреймворки |
| --- | --- |
| 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 | functions listed in `-export([...])` |
| Swift | `@main` app attribute |
| Objective-C | `UIApplicationDelegate` lifecycle selectors (e.g. `application:openURL:options:`) |
| Kotlin | Spring MVC / WebFlux annotations (shared with Java), Android component lifecycle methods (`onCreate`, `onReceive`, `onBind`, ...) |
| Dart | `@pragma('vm:entry-point')` native-callable markers |
| Go | `http.HandleFunc` / `http.Handle` stdlib registrations, gin/chi/echo-style `<router>.GET/POST/...` handler registrations |
| Ruby | Rails controller actions (classes inheriting `ApplicationController` / `ActionController::*`), Sidekiq worker `perform` methods |
| C / C++ | `extern "C"` linkage, `__attribute__((visibility("default")))`, `__declspec(dllexport)` |

Для всего, что не удалось обнаружить эвристикам, объявляйте точки входа явно в файле `.trailmark/entrypoints.toml` в корне проекта. Файл поддерживает как одноузловые, так и основанные на правилах записи:```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"

Более поздние записи переопределяют более ранние, когда два правила помечают один и тот же узел, поэтому размещайте широкие правила сначала, а конкретные исправления после.

Смотрите docs/entrypoint-patterns.md для полного справочника, включая еще не реализованные фреймворки (Express / Koa / Fastify, Laravel, Cobra, axum, warp, clap и другие) с готовыми шаблонами для grep, которые участники могут использовать для добавления новых детекторов.

Обнаружение Solidity использует метаданные парсера, а не регулярные выражения сигнатур. Объявления интерфейсов исключаются, а производное переопределение подавляет соответствующую базовую реализацию. Конкретные функции public и external остаются точками входа, включая функции view и pure; их атрибуты solidity_visibility и solidity_mutability возвращаются функцией attack_surface(), чтобы вызывающие могли различать доступ только для чтения. attack_surface() включает атрибуты точки входа, зависящие от парсера, когда они прикреплены к соответствующему узлу графа.

Межъязыковые и внешние ссылки

Полиглотный парсер объединяет языковые графы, но многие отношения RPC, FFI, подпроцессов и хост/контракт не видны в синтаксисе исходного кода. Объявляйте их детерминированно в .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

Ссылки могут быть точными идентификаторами узлов или уникальными именами/суффиксами. Неоднозначные ссылки, неизвестные внутренние конечные точки, недопустимые значения перечислений и некорректный TOML вызывают `ValueError`. Установка `external = true` явно разрешает неопределённые конечные точки и создаёт прокси-узлы. Этот файл является стабильным общедоступным интерфейсом конфигурации.

### Ограничения анализа

- `entrypoint_paths_to()` сообщает о достижимости графа вызовов, а не о потоке данных, контролируемом атакующим. Используйте результаты предварительного анализа уязвимостей как грубый отдельный сигнал; Trailmark пока не выполняет межпроцедурный анализ распространения меток.
- TypeScript разрешает прямые вызовы и простые получатели, назначенные с помощью `new ConcreteClass()`. Диспетчеризация интерфейсов через манифесты, вычисляемые имена свойств, контейнеры внедрения зависимостей и другие динамические механизмы остаётся в режиме «наилучшего приближения».
- Поддержка SQL ориентирована на PostgreSQL и извлекает схемы, таблицы, представления, функции, процедуры и зависимости процедур/представлений. Это не полный валидатор диалекта SQL или анализатор семантики запросов.

### Программный API```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 и PROCEDURE являются аддитивными в версии v0.5.0; потребители, исчерпывающе сопоставляющие значения перечислений, должны добавить для них случаи.

Разработка```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

## Лицензия

Apache-2.0

Категории