Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
trailmark — Build and query a graph database representation of source code | Kitploit
Tools/GitHubGitHub/trailofbits/trailmark
Static AnalysisVulnerability AnalysisCode AnalysisFuzzingBinary AnalysisLearning & Education
GitHubtrailofbits/trailmark

trailmark

Build and query a graph database representation of source code

View Repository
46329261 day agoReviewed by Kitploit

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

Trailmark

CI Mutation Testing

Trailmark parses source code into a graph of functions, types, calls, and other relationships, then lets you ask structural questions about it:

  • Which entrypoints can reach this function, and along which call paths?
  • What changed in the attack surface between two commits?
  • Which functions write this Solidity storage variable?
  • Where did call resolution stop, so that reachability results are incomplete?

It supports 30 languages through tree-sitter, stores the graph in rustworkx, and exposes it through a command-line tool and a Python API. Trailmark was built for security review: every edge records whether it came straight from syntax or from a heuristic, and calls it could not resolve stay in the graph.

The long-term vision is to combine this graph with mutation testing and coverage-guided fuzzing to identify gaps between assumptions and test coverage that are reachable from user input.

Features

  • Code graphs. Functions, methods, classes, structs, traits, contracts, and modules, connected by calls, inheritance, containment, and type use. --language auto merges every supported language in a tree into one graph.
  • Attack surface. Framework-aware entrypoint detection (Flask, FastAPI, Spring, ASP.NET Core, actix-web, NestJS, Solidity visibility, and more), each tagged with a trust level and asset value. Override or extend detection with .trailmark/entrypoints.toml.
  • Reachability. Direct callers and callees, transitive slices, and bounded call paths from entrypoints to a target.
  • Structural diffs. Compare two directories or git refs and report added or removed functions, call edges, and entrypoints.
  • Diagrams. Mermaid call graphs, class hierarchies, module dependencies, containment views, complexity maps, and entrypoint-to-target flows.
  • Findings on the graph. Import SARIF and weAudit results, attach your own annotations, and run preanalysis passes (blast radius, privilege boundaries, coarse taint) that store their results as annotations and named subgraphs.
  • Visible gaps. Calls that cannot be resolved become proxy nodes instead of disappearing, and trailmark analyze --resolution counts them.
  • Links Trailmark cannot see. Declare RPC, FFI, and subprocess edges in .trailmark/links.toml, or import a call graph produced by a binary-analysis tool.
  • Solidity storage access. reads and writes edges between functions and the contract state variables they touch.

Install

uv tool install trailmark        # the CLI
uv add trailmark                 # the library, in a uv-managed project

Requires Python ≥ 3.12. With Nix, nix run github:trailofbits/trailmark runs the CLI directly.

This README and the guides track the main branch. The changelog lists what each published release includes, and the CLI and Python API guides note the version that added each newer feature.

Most grammars are downloaded and cached on first use. The bundled Circom, Miden Assembly, and Verus grammars are compiled on first use, which needs a C compiler. See Installation for offline machines, TLS-inspecting proxies, and building from a checkout.

Quick start

# Node and edge counts for a Python project (Python is the default language)
trailmark analyze --summary path/to/project

# Full JSON graph of every supported language in the tree
trailmark analyze --language auto path/to/project > graph.json

# Detected entrypoints with trust level and asset value
trailmark entrypoints --language auto path/to/project

# How the attack surface changed between two git refs
trailmark diff --repo . main HEAD

# Mermaid call graph around one function
trailmark diagram -t path/to/project -T call-graph -f handle_request

# How many call sites could not be resolved, grouped by callee name
trailmark analyze --language rust --resolution path/to/project

The same questions from Python:

from trailmark.query.api import QueryEngine

engine = QueryEngine.from_directory("path/to/project", language="auto")

engine.attack_surface()                    # entrypoints and their trust levels
engine.callers_of("verify_signature")      # direct callers
engine.ancestors_of("verify_signature")    # everything that can reach it
engine.entrypoint_paths_to("verify_signature")
engine.complexity_hotspots(10)             # cyclomatic complexity >= 10

Names can be bare (verify_signature), qualified (Auth.verify), or full node IDs (auth:Auth.verify). Query results are plain dicts and lists that serialize directly to JSON.

How it works

flowchart LR
    A["Source files"] --> B["tree-sitter parsers"]
    B --> C["CodeGraph"]
    C --> D["GraphStore (rustworkx)"]
    D --> E["QueryEngine"]
    E --> F["CLI / Python API"]

    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. Parse. A parser for each language walks the directory and extracts nodes (functions, types, modules), edges (calls, inheritance, containment, and so on), and metadata (parameters, return types, branches, cyclomatic complexity, docstrings, raised exceptions). A linking pass then resolves calls across files, links types to their declarations, and applies .trailmark/links.toml.
  2. Index. GraphStore loads the graph into a rustworkx directed graph for fast traversal. QueryEngine.from_directory() also runs entrypoint detection.
  3. Query. QueryEngine resolves names to node IDs, runs traversals, and returns JSON-serializable results.

Example

Given app.py:

class Auth:
    def verify(self, token):
        return self._check_sig(token)

    def _check_sig(self, token): ...


def handle_request(req):
    auth = Auth()
    if auth.verify(req.token):
        return process(req)
    return deny()


def process(req): ...
def deny(): ...

Trailmark produces this graph (module containment edges omitted):

graph TD
    HR["app:handle_request"] -->|calls| A["app:Auth"]
    HR -->|calls| PV["proxy.unresolved:auth.verify"]
    HR -->|calls| P["app:process"]
    HR -->|calls| D["app:deny"]
    AV["app:Auth.verify"] -->|calls| CS["app:Auth._check_sig"]
    A -->|contains| AV
    A -->|contains| CS

    classDef fn fill:#007bff26,stroke:#007bff,color:#007bff
    classDef cls fill:#6f42c126,stroke:#6f42c1,color:#6f42c1
    classDef proxy fill:#dc354526,stroke:#dc3545,color:#dc3545

    class HR,P,D fn
    class A,AV,CS cls
    class PV proxy

self._check_sig(...) resolves because self names the enclosing class. auth.verify(...) does not, because Trailmark does not infer that the local variable auth holds an Auth. The call is kept as a proxy node rather than dropped. As a result, engine.ancestors_of("Auth._check_sig") returns only app:Auth.verify, and engine.unresolved_calls("handle_request") shows the exact call site where the path was lost. See Graph model for every node kind, edge kind, and ID rule, and Call resolution for working with proxies.

Supported languages

Download Tool