
소스 코드의 그래프 데이터베이스 표현을 구축하고 쿼리합니다
# Trailmark
[](https://github.com/trailofbits/trailmark/actions/workflows/ci.yml)
[](https://github.com/trailofbits/trailmark/actions/workflows/mutation.yml)
소스 코드를 함수, 클래스, 호출, 시맨틱 주석으로 이루어진 쿼리 가능한 그래프로 파싱하여 보안 분석에 활용합니다.
Trailmark는 언어에 구애받지 않는 AST 파싱을 위해 [tree-sitter](https://tree-sitter.github.io/)를, 고성능 그래프 순회를 위해 [rustworkx](https://www.rustworkx.org/)를 사용합니다. 장기적인 비전은 이 그래프를 뮤테이션 테스팅 및 커버리지 기반 퍼징과 결합하여, 사용자 입력으로부터 도달 가능한 가정과 테스트 커버리지 사이의 격차를 식별하는 것입니다.
## 작동 방식
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. 파싱
언어별 파서가 디렉터리를 순회하며 각 파일을 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 블록, `verus!` 블록 |
| 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` | 스키마, 테이블, 뷰, 함수, 프로시저 |
Rust 파서는 `verus! { ... }` 블록에서 본문이 있는 일반 함수, `spec` 함수, `proof` 함수를 추출합니다. Verus 문법은 현재 중괄호로 구분된 블록만 지원하며, `verus!(...)`와 `verus![...]`는 경고와 함께 건너뜁니다. 인라인 모듈은 정규화된 ID(예: `file.inner:function`)를 유지하고, 메서드는 impl 또는 trait 소유자를 유지합니다. 각 블록은 독립적으로 파싱되며, 구문 오류가 발생하면 파일과 블록 위치를 포함한 경고를 생성합니다. `vstd::prelude::verus!`와 같은 정규화된 호출도 지원됩니다. 로컬 헬퍼 함수는 ID에 자신을 둘러싼 함수를 유지합니다(예: `file:outer.inner`). 이러한 헬퍼에 대한 호출과 `use super::*`와 같은 상대 임포트는 파일에 존재하는 선언으로 해석됩니다. 서로 다른 블록에서 반복되는 로컬 이름에는 노드를 구분하기 위해 바이트 오프셋 접미사가 붙으며, 호출은 블록 범위를 존중합니다. 로컬 구조체, 열거형, 트레이트, 인라인 모듈도 둘러싼 함수를 유지합니다(예: `file:outer.Guard.drop`, `file:outer.local.helper`). 이들의 본문은 별도의 호출과 복잡도를 가집니다. 알려진 로컬 타입과 모듈에 대한 명시적 호출은 파일 내에서 해석됩니다. 단순 호출은 함수 및 모듈 범위를 검색하며, 연관 항목은 `Type::method`와 같은 경로를 사용합니다. 점 표기 호출은 수신자가 알려진 구조체나 열거형을 가리킬 때만 해석됩니다. `self.method()`를 포함한 다른 값 수신자는 해석되지 않은 상태로 남습니다. 암시적 소멸자 호출과 수신자 타입 추론은 모델링되지 않습니다. 이러한 규칙은 Verus 블록뿐만 아니라 일반 Rust에도 적용됩니다. 오프셋 접미사가 있는 선언 앞의 코드를 편집하면 해당 ID가 변경되므로, `trailmark diff`는 해당 선언을 제거된 것과 추가된 것으로 보고할 수 있습니다.
Trailmark는 임의의 매크로를 확장하지 않습니다. 불투명 매크로나 함수 본문 내부의 리터럴 Verus 호출은 경고와 함께 건너뜁니다. 호출되지 않은 `macro_rules!` 정의는 확장되지 않습니다. 본문이 없는 함수 시그니처와 `requires`/`ensures` 절의 호출은 추출되지 않습니다.
```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
```
노드 ID는 명확한 조회를 위해 `module:function`, `module:Class`, `module:Class.method` 체계를 따릅니다. 디렉터리 파싱은 고유한 정의가 존재할 때 파일 간 직접 호출(bare cross-file call)을 해석하며, 모호한 파일 간 호출은 원래의 최선 추정 대상으로 남겨두고 `uncertain`으로 표시합니다. 엣지 신뢰도는 `certain`(직접 호출, `self.method()`), `inferred`(비-self 객체에 대한 속성 접근), 또는 `uncertain`(동적 디스패치 또는 모호한 해석)으로 태그됩니다.
### 2. 인덱스
`GraphStore`는 `CodeGraph`를 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)` | 결과(finding) 스타일 주석을 가진 노드 반환 |
| `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>`과 같은 프록시 노드로 구체화되므로, 순회 결과에서 소스 분석이 해석에 실패한 지점을 조용히 해당 엣지를 누락시키는 대신 보여줄 수 있습니다. 단일 언어 파싱을 포함한 디렉터리 파싱은 저장소 링크도 적용하고 `TYPE_USES` 엣지를 구체화합니다. 바이너리 분석 지원은 외부 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` 1.22.1 이상을 요구합니다.
루트 소유의 문법 캐시는 그룹 또는 기타 사용자 쓰기 권한이 없는 경우 지원됩니다.
현재 릴리스는 문법 다운로드에 플랫폼 인증서 저장소를 사용합니다. TLS 검사 또는
오프라인 환경에서는 일치하는 플랫폼에서
`python -c "import tree_sitter_language_pack as p; p.download_all()"`을 실행하여 패키지 캐시를 미리 채운 다음,
생성된 `tree-sitter-language-pack` 캐시 디렉터리를 대상 머신에 복사하십시오. `HTTPS_PROXY`도
사용됩니다. SQL 문법은 `tree-sitter-sql` wheel 의존성으로 제공되며 해당 캐시를 사용하지 않습니다.
번들로 제공되는 Circom, Miden Assembly, Verus 문법은 처음 사용할 때 컴파일됩니다.
이를 위해서는 C 컴파일러(`cc`), Python 개발 헤더, 그리고 설치된 문법 디렉터리에 대한 쓰기 권한이
필요합니다. Verus 블록이 없는 Rust 파일은 컴파일을 트리거하지 않습니다. Verus 문법을 빌드하거나
로드할 수 없는 경우, Trailmark는 경고를 기록하고 Verus 블록을 건너뛰며 일반 Rust 파싱을 계속합니다.
Verus 바인딩 캐시에는 컴파일된 소스 내용, Python ABI, 플랫폼, 컴파일러 옵션이 포함됩니다.
입력이 변경되면 새 바인딩이 선택되며, 완료된 빌드는 원자적으로 게시됩니다. 컴파일러 실패 시
경고에 종료 상태와 stderr가 포함됩니다.
Git 체크아웃은 Verus 문법 정의를 유지하고 Tree-sitter CLI 0.27.1을 사용하여 빌드 중에 C 파서를
생성합니다. 체크아웃에서 `uv sync` 또는 `uv build`를 실행하기 전에 npm 설치 스크립트를 활성화한
상태로 `npm ci`를 실행하십시오. 생성에는 Node.js가 필요합니다. 생성된 파서와 헤더는 gitignore됩니다.
wheel 및 소스 배포판에는 이러한 생성된 소스가 포함되므로, 게시된 패키지를 설치하는 데는 Node.js나
Tree-sitter CLI가 필요하지 않습니다. 편집 가능한 `uv` 설치에서는 빌드 훅을 다시 실행하여 누락된
생성 파일을 복원합니다. 입력이 변경되지 않은 경우 기존 출력을 재사용합니다.
## 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()`, 오염 전파(taint propagation), 권한 경계 교차에 사용할 데이터가 확보됩니다. 탐지는 네 개의 계층으로 실행되며, 각 계층은 이전 계층을 덮어씁니다:
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 리스너, 서블릿 |
| C# | ASP.NET Core, Azure Functions |
| PHP | Symfony `#[Route]` 속성 + 레거시 어노테이션 |
| Rust | actix-web, rocket, FFI 내보내기 (`#[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 스타일 `<router>.GET/POST/...` 핸들러 등록 |
| Ruby | Rails 컨트롤러 액션 (`ApplicationController` / `ActionController::*`를 상속하는 클래스), Sidekiq 워커 `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"
```
두 규칙이 동일한 노드에 태그를 지정할 경우 나중 항목이 이전 항목을 덮어쓰므로, 광범위한 규칙을 먼저 배치하고 구체적인 수정 사항을 그 뒤에 배치하세요.
아직 구현되지 않은 프레임워크(Express / Koa / Fastify, Laravel, Cobra, axum, warp, clap 등)를 포함한 전체 참조는 [docs/entrypoint-patterns.md](https://github.com/trailofbits/trailmark/blob/main/docs/entrypoint-patterns.md)를 참조하세요. 기여자가 새로운 탐지기를 추가하는 데 사용할 수 있는 grep 준비 패턴이 포함되어 있습니다.
Solidity 탐지는 시그니처 정규식이 아닌 파서 메타데이터를 사용합니다. 인터페이스 선언은 제외되며, 파생된 오버라이드는 기본 엔트리포인트를 제거하는 대신 `solidity_overridden_by`로 일치하는 기본 구현에 주석을 추가합니다. 구체적인 `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"
target_external = true # required because target is unresolved
```
참조는 정확한 노드 ID 또는 고유한 이름/접미사일 수 있습니다. 모호한 참조,
알 수 없는 내부 엔드포인트, 잘못된 열거형 값, 그리고 형식이 잘못된 TOML은
`ValueError`를 발생시킵니다. `source_external = true` 또는 `target_external = true`를
설정하면 해당 미해결 엔드포인트를 명시적으로 허용하고 프록시 노드를 생성합니다. 이 파일은
안정적인 공개 구성 인터페이스입니다.
### 분석 한계
- `entrypoint_paths_to()`는 공격자가 제어하는 데이터 흐름이 아니라 호출 그래프 도달 가능성을
보고합니다. 사전 분석 테인트 결과를 별도의 거친 신호로 사용하십시오. Trailmark는
아직 절차 간 테인트 분석을 수행하지 않습니다.
- TypeScript는 직접 호출과 `new ConcreteClass()`로 할당된 단순한 수신자를 해석합니다.
매니페스트를 통한 인터페이스 디스패치, 계산된 속성 이름, 의존성 주입 컨테이너, 그리고
기타 동적 메커니즘은 최선의 노력(best-effort) 수준으로 유지됩니다.
- 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에서 추가되었습니다.
enum 값을 완전히 매칭하는 소비자는 이들에 대한 케이스를 추가해야 합니다.
## 개발
```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
```
Mutation 보고서는 `coverage/gremlins/`에 기록됩니다. Pull request는 `tests/mutation_baseline.json`에 명시된 Rust/Verus 파일을 변이하고, 해당 파일에서 새로 추가되거나 변경된 함수를 게이트합니다. 전체 저장소 캠페인은 main에 대한 push와 mutation 워크플로의 수동 트리거를 통해 실행됩니다. 변경되지 않은 레거시 함수는 소스 해시로 식별되며, 편집 시 자동으로 게이트에 진입합니다. 게이트는 검토되지 않은 생존자, 누락된 테스트/결과, 오류, 타임아웃을 거부합니다. 예외에는 사유와 함수 및 변이의 지문이 필요하며, 해당 함수를 변경하려면 그 예외를 다시 검토해야 합니다.
러너는 pytest-gremlins 1.9.0을 고정하고, 픽스처와 파라미터화된 테스트를 포함한 모든 변이체에 대해 실제 pytest 서브프로세스를 사용합니다. 각 변이체는 선택된 전체 테스트 스위트를 유지합니다: 컴포넌트 단위 테스트가 먼저 실행되고, 이어서 나머지 테스트가 실행되며 첫 번째 실패에서 중단됩니다. 공유 헬퍼 변이체는 모든 언어의 테스트를 유지합니다. 테스트는 기존 파일에 그대로 남으며, `tests/mutation_order.py`가 순서를 정의하고 알 수 없는 컴포넌트는 수집 순서를 유지합니다. 러너는 중복되는 커버리지 사전 스캔을 건너뛰고, 깨끗한 기준선과 각각의 서로 다른 계측 테스트 순서를 확인하며, 변이된 코드를 로드할 때 패키지 메타데이터와 정확한 파라미터 ID를 보존합니다.
이러한 호환성 조치는 업스트림 러너 및 선택 문제를 해결합니다. 해당 제어 테스트는 플러그인을 업그레이드하기 전에 통과해야 합니다.
스냅샷 업데이트(`TRAILMARK_UPDATE_SNAPSHOTS=1`)는 CI 및 mutation 실행 중에 비활성화됩니다. 로컬 캠페인에 집중하려면 쉼표로 구분된 소스 경로와 함께 `--targets`를 전달하고, 이어서 관련 테스트 파일을 전달하십시오. `--workers=4`는 이미 병렬 실행을 활성화합니다. pytest의 원래 순서와 비교하려면 `--test-order=collection`을 사용하십시오.
## 라이선스
Apache-2.0