Trailmark Query Patterns for Security Analysis
Common patterns for using Trailmark in security reviews.
Overview
Trailmark Query Patterns for Security Analysis
Common patterns for using Trailmark in security reviews.
1. Mapping Attack Surface
Find all entrypoints and trace what they can reach:
from trailmark.query.api import QueryEngine
engine = QueryEngine.from_directory("{targetDir}")
# All entrypoints
for ep in engine.attack_surface():
print(f"{ep['node_id']}: {ep['trust_level']} ({ep['kind']})")
2. Complexity Hotspots
High-complexity functions are more likely to contain bugs:
for hotspot in engine.complexity_hotspots(threshold=10):
loc = hotspot["location"]
print(
f"{hotspot['id']} "
f"complexity={hotspot['cyclomatic_complexity']} "
f"{loc['file_path']}:{loc['start_line']}"
)
3. Call Path Analysis
Find how user input reaches a sensitive function:
paths = engine.paths_between("handle_request", "execute_query")
for path in paths:
print(" -> ".join(path))
4. Caller Analysis
Find all callers of a security-sensitive function to check if they all validate input properly:
callers = engine.callers_of("execute_query")
for caller in callers:
print(f"{caller['id']} at {caller['location']['file_path']}:{caller['location']['start_line']}")
5. Reachability from Entrypoints
Check if a function is reachable from any entrypoint:
surface = engine.attack_surface()
paths = engine.paths_between("main", "sensitive_function_id")
if paths:
print(f"Reachable via {len(paths)} path(s)")
else:
print("Not reachable from any entrypoint")
6. Full Graph Export
Export for use with other tools:
json_str = engine.to_json()
with open("graph.json", "w") as f:
f.write(json_str)
7. Multi-Language Analysis
Analyze non-Python projects by specifying the language:
from trailmark.query.api import QueryEngine
engine = QueryEngine.from_directory("{targetDir}", language="rust")
engine = QueryEngine.from_directory("{targetDir}", language="go")
engine = QueryEngine.from_directory("{targetDir}", language="typescript")
Supported --language values: python, javascript, typescript, php,
ruby, c, cpp, c_sharp, java, go, rust, solidity, cairo,
haskell, circom, erlang.
8. CLI Patterns
# Quick summary (Python, default)
uv run trailmark analyze --summary {targetDir}
# Analyze other languages
uv run trailmark analyze --language rust --summary {targetDir}
uv run trailmark analyze --language go --complexity 8 {targetDir}
# Full JSON output for piping to other tools
uv run trailmark analyze {targetDir} | jq '.nodes | to_entries[] | select(.value.cyclomatic_complexity > 10)'
9. Annotation Workflow
Add semantic annotations after analyzing code with an LLM. Annotations persist on the in-memory graph and can be queried later:
from trailmark.models import AnnotationKind
# Add annotations (returns False if node not found)
engine.annotate("handle_request", AnnotationKind.ASSUMPTION, "input is URL-encoded", source="llm")
engine.annotate("validate_token", AnnotationKind.PRECONDITION, "token is non-empty string", source="llm")
# Query annotations on a specific function
for ann in engine.annotations_of("handle_request"):
print(f"[{ann['kind']}] {ann['description']} (source: {ann['source']})")
# Filter by kind
assumptions = engine.annotations_of("handle_request", kind=AnnotationKind.ASSUMPTION)
# Clear annotations (all, or by kind)
engine.clear_annotations("handle_request", kind=AnnotationKind.ASSUMPTION)
engine.clear_annotations("handle_request")
Annotation kinds: ASSUMPTION, PRECONDITION, POSTCONDITION, INVARIANT.
Pre-analysis adds: BLAST_RADIUS, PRIVILEGE_BOUNDARY, TAINT_PROPAGATION.
Source convention: Use "llm" for LLM-inferred annotations, "docstring"
for annotations extracted from source, "manual" for human-added annotations.