업데이트로 돌아가기
New releaseJul 24, 2026

trailmark v0.5.0

소스 코드의 그래프 데이터베이스 표현을 구축하고 쿼리합니다

공유

Trailmark

CI Mutation Testing

소스 코드를 파싱하여 함수, 클래스, 호출 및 보안 분석을 위한 의미 주석의 쿼리 가능한 그래프로 만듭니다.

Trailmark는 언어에 구애받지 않는 AST 파싱을 위해 tree-sitter를 사용하고, 고성능 그래프 탐색을 위해 rustworkx를 사용합니다. 장기적인 비전은 이 그래프를 변이 테스트 및 커버리지 가이드 퍼징과 결합하여 사용자 입력으로부터 접근 가능한 가정과 테스트 커버리지 사이의 격차를 식별하는 것입니다.

작동 방식

Trailmark는 파싱, 인덱싱, 쿼리의 세 단계로 작동합니다.```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. 파싱

언어별 파서가 디렉터리를 탐색하고 각 파일을 tree-sitter AST로 파싱하여 다음을 추출합니다:

- **노드** — 함수, 메서드, 클래스, 구조체, 인터페이스, 트레이트, 열거형, 모듈, 네임스페이스
- **에지** — 호출, 상속, 구현, 포함, 임포트
- **메타데이터** — 타입 어노테이션, 순환 복잡도, 분기, 독스트링, 예외 타입

### 지원 언어

| 언어 | 확장자 | 주요 구조 |
| --- | --- | --- |
| 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

Node ID는 module:function, module:Class, module:Class.method 스키마를 따르며 명확한 조회를 제공합니다. 디렉터리 파싱은 고유한 정의가 존재할 때 bare cross-file 호출을 해결합니다. 모호한 cross-file 호출은 원래 최선의 대상에 남겨두고 uncertain으로 표시됩니다. 에지 신뢰도는 certain(직접 호출, self.method()), inferred(self가 아닌 객체의 속성 접근), 또는 uncertain(동적 디스패치 또는 모호한 해석)으로 태깅됩니다.

2. 인덱스

GraphStoreCodeGraph를 rustworkx PyDiGraph에 로드하고 빠른 탐색을 위해 양방향 ID/인덱스 매핑을 구축합니다.

3. 쿼리

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)발화 스타일 주석을 가진 노드 반환
subgraph(name)명명된 서브그래프의 노드 반환
subgraph_edges(name)명명된 서브그래프 내 유도 에지 반환
subgraph_names()현재 그래프에 있는 모든 명명된 서브그래프 나열
summary()노드 수, 에지 수, 종속성
to_json()전체 그래프 내보내기

데이터 모델```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 + 레거시 어노테이션 |
| Rust | actix-web, rocket, FFI exports (`#[no_mangle]`, `pub extern "C"`), async-main 속성 |
| Solidity | `external` / `public` 가시성 |
| Cairo / StarkNet | `#[external]`, `#[view]`, `#[l1_handler]`, `#[constructor]` |
| Circom | `component main` 선언 |
| Miden Assembly | `export.<name>` 지시문 |
| Haskell | 최상위 `main ::` / `main =` |
| Erlang | `-export([...])`에 나열된 함수 |
| Swift | `@main` 앱 속성 |
| Objective-C | `UIApplicationDelegate` 수명 주기 선택자 (예: `application:openURL:options:`) |
| Kotlin | Spring MVC / WebFlux 어노테이션 (Java와 공유), Android 구성 요소 수명 주기 메서드 (`onCreate`, `onReceive`, `onBind`, ...) |
| Dart | `@pragma('vm:entry-point')` 네이티브 호출 가능 표시자 |
| Go | `http.HandleFunc` / `http.Handle` 표준 라이브러리 등록, gin/chi/echo-style `<router>.GET/POST/...` 핸들러 등록 |
| Ruby | Rails 컨트롤러 액션 (`ApplicationController` / `ActionController::*` 상속 클래스), Sidekiq worker `perform` 메서드 |
| C / C++ | `extern "C"` 링키지, `__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 탐지는 서명 regexes 대신 파서 메타데이터를 사용합니다. Interface 선언은 제외되며, 파생된 override는 일치하는 base 구현을 억제합니다. 구체적인 publicexternal 함수는 viewpure 함수를 포함하여 진입점으로 남아 있습니다. 이들의 solidity_visibilitysolidity_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

참조는 정확한 노드 ID 또는 고유한 이름/접미사일 수 있습니다. 모호한 참조, 알 수 없는 내부 엔드포인트, 잘못된 열거형 값, 잘못된 형식의 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

카테고리