trailmark 0.1.3__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {trailmark-0.1.3 → trailmark-0.2.2}/AGENTS.md +25 -13
- {trailmark-0.1.3 → trailmark-0.2.2}/PKG-INFO +172 -16
- trailmark-0.2.2/README.md +448 -0
- trailmark-0.2.2/docs/entrypoint-patterns.md +924 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/pyproject.toml +1 -1
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/__init__.py +1 -1
- trailmark-0.2.2/src/trailmark/analysis/diff.py +280 -0
- trailmark-0.2.2/src/trailmark/analysis/entrypoints.py +1398 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/cli.py +139 -3
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/models/__init__.py +0 -4
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/models/annotations.py +0 -23
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/_common.py +63 -31
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/c/parser.py +0 -5
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/cairo/parser.py +0 -5
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/cpp/parser.py +5 -5
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/csharp/parser.py +3 -6
- trailmark-0.2.2/src/trailmark/parsers/dart/__init__.py +5 -0
- trailmark-0.2.2/src/trailmark/parsers/dart/parser.py +498 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/erlang/parser.py +0 -5
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/go/parser.py +0 -9
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/haskell/parser.py +0 -5
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/java/parser.py +0 -5
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/javascript/parser.py +2 -11
- trailmark-0.2.2/src/trailmark/parsers/kotlin/__init__.py +5 -0
- trailmark-0.2.2/src/trailmark/parsers/kotlin/parser.py +315 -0
- trailmark-0.2.2/src/trailmark/parsers/objc/__init__.py +5 -0
- trailmark-0.2.2/src/trailmark/parsers/objc/parser.py +456 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/php/parser.py +0 -5
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/python/parser.py +0 -5
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/rust/parser.py +0 -5
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/solidity/parser.py +0 -5
- trailmark-0.2.2/src/trailmark/parsers/swift/__init__.py +5 -0
- trailmark-0.2.2/src/trailmark/parsers/swift/parser.py +317 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/typescript/parser.py +0 -13
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/query/api.py +251 -3
- trailmark-0.2.2/tests/test_cli_parser.py +262 -0
- trailmark-0.2.2/tests/test_common_parser.py +154 -0
- trailmark-0.2.2/tests/test_dart_parser.py +95 -0
- trailmark-0.2.2/tests/test_detector_tags.py +295 -0
- trailmark-0.2.2/tests/test_diff.py +520 -0
- trailmark-0.2.2/tests/test_entrypoints.py +985 -0
- trailmark-0.2.2/tests/test_hidden_apis.py +125 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_javascript_parser.py +13 -0
- trailmark-0.2.2/tests/test_kotlin_parser.py +113 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_models.py +0 -15
- trailmark-0.2.2/tests/test_objc_parser.py +88 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_parser_edge_cases.py +4 -9
- trailmark-0.2.2/tests/test_polyglot.py +130 -0
- trailmark-0.2.2/tests/test_readme.py +305 -0
- trailmark-0.2.2/tests/test_swift_parser.py +95 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/uv.lock +109 -157
- trailmark-0.1.3/README.md +0 -292
- {trailmark-0.1.3 → trailmark-0.2.2}/.github/dependabot.yml +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/.github/workflows/ci.yml +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/.github/workflows/mutation.yml +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/.github/workflows/publish.yml +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/.github/workflows/semgrep.yml +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/.github/workflows/zizmor.yml +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/.gitignore +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/CONTRIBUTING.md +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/LICENSE +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/NOTICE +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/analysis/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/analysis/augment.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/analysis/preanalysis.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/diagram.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/models/edges.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/models/graph.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/models/nodes.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/base.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/c/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/cairo/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/circom/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/circom/parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/cpp/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/csharp/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/erlang/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/go/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/haskell/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/java/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/javascript/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/masm/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/masm/parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/php/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/python/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/ruby/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/ruby/parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/rust/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/solidity/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/typescript/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/py.typed +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/query/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/storage/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/storage/graph_store.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/circom/LICENSE +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/circom/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/circom/binding.c +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/circom/grammar.js +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/circom/src/parser.c +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/circom/src/tree_sitter/parser.h +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/LICENSE +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/binding.c +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/grammar.js +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/src/parser.c +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/src/tree_sitter/alloc.h +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/src/tree_sitter/array.h +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/src/tree_sitter/parser.h +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/__init__.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_augment.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_c_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_cairo_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_circom_grammar.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_circom_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_cli.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_cpp_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_csharp_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_diagram_script.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_erlang_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_go_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_haskell_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_java_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_masm_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_php_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_preanalysis.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_property_based.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_python_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_query.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_query_edge_cases.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_ruby_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_rust_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_solidity_parser.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_storage.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_storage_edge_cases.py +0 -0
- {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_typescript_parser.py +0 -0
|
@@ -5,9 +5,10 @@ Development context for AI agents working on this codebase.
|
|
|
5
5
|
## Project Overview
|
|
6
6
|
|
|
7
7
|
Trailmark parses source code into directed graphs of functions, classes,
|
|
8
|
-
calls, and semantic metadata for security analysis. It supports
|
|
9
|
-
|
|
10
|
-
rustworkx for graph traversal.
|
|
8
|
+
calls, and semantic metadata for security analysis. It supports more than
|
|
9
|
+
twenty language frontends via tree-sitter-language-pack plus bundled
|
|
10
|
+
custom grammars, and uses rustworkx for graph traversal. Treat
|
|
11
|
+
`README.md` as the authoritative public feature list.
|
|
11
12
|
|
|
12
13
|
## Architecture
|
|
13
14
|
|
|
@@ -19,17 +20,22 @@ CodeGraph (data) -> GraphStore (indexed storage) -> QueryEngine (facade)
|
|
|
19
20
|
- **GraphStore** wraps CodeGraph in a rustworkx PyDiGraph. Validates
|
|
20
21
|
node existence. Returns model objects.
|
|
21
22
|
- **QueryEngine** resolves names to node IDs, delegates to GraphStore,
|
|
22
|
-
returns
|
|
23
|
+
and returns JSON-serializable data structures for user-facing APIs.
|
|
23
24
|
|
|
24
25
|
## Key Conventions
|
|
25
26
|
|
|
26
|
-
-
|
|
27
|
-
|
|
27
|
+
- Query/read methods should return `False`, `[]`, or empty sets for
|
|
28
|
+
missing nodes where practical. Factory methods and invalid-input paths
|
|
29
|
+
may raise `ValueError` or exit via the CLI.
|
|
30
|
+
- QueryEngine returns JSON-serializable data structures; GraphStore
|
|
31
|
+
returns model objects and node IDs.
|
|
28
32
|
- Node IDs follow `module:function`, `module:Class`, `module:Class.method`.
|
|
29
33
|
- Edge confidence: `certain`, `inferred`, `uncertain`.
|
|
30
34
|
- Annotation sources: `"llm"`, `"docstring"`, `"manual"`.
|
|
31
35
|
- Frozen dataclasses for immutable data. `CodeGraph` is mutable.
|
|
32
36
|
- No relative (`..`) imports. Use `from trailmark.*`.
|
|
37
|
+
- Update `README.md` and `tests/test_readme.py` when documented behavior
|
|
38
|
+
changes.
|
|
33
39
|
|
|
34
40
|
## File Layout
|
|
35
41
|
|
|
@@ -46,6 +52,7 @@ src/trailmark/
|
|
|
46
52
|
python/ # One subpackage per language
|
|
47
53
|
javascript/
|
|
48
54
|
...
|
|
55
|
+
analysis/ # Higher-level passes: entrypoints, diff, augment, preanalysis
|
|
49
56
|
storage/
|
|
50
57
|
graph_store.py # GraphStore: rustworkx-backed indexed storage
|
|
51
58
|
query/
|
|
@@ -57,10 +64,11 @@ tests/ # pytest test suite
|
|
|
57
64
|
## Running Checks
|
|
58
65
|
|
|
59
66
|
```bash
|
|
60
|
-
uv
|
|
61
|
-
uv run ruff
|
|
62
|
-
uv run
|
|
63
|
-
|
|
67
|
+
uv sync --all-groups
|
|
68
|
+
uv run ruff check src/ tests/
|
|
69
|
+
uv run ruff format --check src/ tests/
|
|
70
|
+
uv tool install ty && ty check
|
|
71
|
+
uv run pytest -q tests/
|
|
64
72
|
```
|
|
65
73
|
|
|
66
74
|
## Mutation Testing
|
|
@@ -85,6 +93,10 @@ This is not needed on Linux/CI (Ubuntu).
|
|
|
85
93
|
- Follow the three-layer pattern: add to CodeGraph first, then GraphStore
|
|
86
94
|
(with validation), then QueryEngine (with name resolution and dict
|
|
87
95
|
conversion).
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
- Analysis passes live under `trailmark.analysis` and should usually
|
|
97
|
+
persist results as annotations or named subgraphs rather than
|
|
98
|
+
bypassing the graph model.
|
|
99
|
+
- Add tests at each relevant layer: `test_models.py`, `test_storage.py`,
|
|
100
|
+
`test_query.py`, plus focused parser/analysis/doc regression tests.
|
|
101
|
+
- Update `README.md` and the matching regression tests if adding or
|
|
102
|
+
changing user-facing API.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trailmark
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Parse source code into a queryable graph of functions, classes, calls, and semantic annotations
|
|
5
5
|
Project-URL: Homepage, https://github.com/trailofbits/trailmark
|
|
6
6
|
Project-URL: Repository, https://github.com/trailofbits/trailmark
|
|
@@ -60,7 +60,7 @@ A language-specific parser walks the directory, parses each file into a tree-sit
|
|
|
60
60
|
| Language | Extensions | Key constructs |
|
|
61
61
|
| --- | --- | --- |
|
|
62
62
|
| Python | `.py` | functions, classes, methods |
|
|
63
|
-
| JavaScript | `.js`, `.jsx` | functions, classes, arrow functions |
|
|
63
|
+
| JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` | functions, classes, arrow functions |
|
|
64
64
|
| TypeScript | `.ts`, `.tsx` | functions, classes, interfaces, enums |
|
|
65
65
|
| PHP | `.php` | functions, classes, interfaces, traits |
|
|
66
66
|
| Ruby | `.rb` | methods, classes, modules |
|
|
@@ -76,6 +76,10 @@ A language-specific parser walks the directory, parses each file into a tree-sit
|
|
|
76
76
|
| Haskell | `.hs` | functions, data types, type classes, instances |
|
|
77
77
|
| Erlang | `.erl` | functions, records, behaviours, modules |
|
|
78
78
|
| Miden Assembly | `.masm` | procedures, entrypoints, constants, invocations |
|
|
79
|
+
| Swift | `.swift` | functions, classes, structs, enums, protocols, extensions |
|
|
80
|
+
| Objective-C | `.m`, `.mm`, `.h` | C functions, classes, methods (selector-based naming) |
|
|
81
|
+
| Kotlin | `.kt`, `.kts` | functions, classes, interfaces, data classes, objects, methods |
|
|
82
|
+
| Dart | `.dart` | functions, classes, abstract classes, methods, constructors |
|
|
79
83
|
|
|
80
84
|
```mermaid
|
|
81
85
|
flowchart TD
|
|
@@ -114,15 +118,27 @@ The `GraphStore` loads the `CodeGraph` into a rustworkx `PyDiGraph` and builds b
|
|
|
114
118
|
The `QueryEngine` provides a high-level API over the indexed graph:
|
|
115
119
|
|
|
116
120
|
| Method | Description |
|
|
117
|
-
|
|
118
|
-
| `callers_of(name)` |
|
|
119
|
-
| `callees_of(name)` |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| `callers_of(name)` | Direct callers of the named target |
|
|
123
|
+
| `callees_of(name)` | Direct callees of the named source |
|
|
124
|
+
| `ancestors_of(name)` | Every function that can transitively reach the target (upward slice) |
|
|
125
|
+
| `reachable_from(name)` | Every function transitively reachable from the source |
|
|
120
126
|
| `paths_between(src, dst)` | All simple call paths between two nodes |
|
|
127
|
+
| `entrypoint_paths_to(name)` | Paths from any detected entrypoint to the target |
|
|
121
128
|
| `attack_surface()` | Entrypoints tagged with trust level and asset value |
|
|
122
129
|
| `complexity_hotspots(n)` | Functions with cyclomatic complexity ≥ n |
|
|
123
|
-
| `
|
|
130
|
+
| `functions_that_raise(exc)` | Functions whose parser-detected exception list includes `exc` |
|
|
131
|
+
| `annotate(name, kind, description, source)` | Add a semantic annotation to a node |
|
|
124
132
|
| `annotations_of(name, kind=None)` | Get annotations for a node, optionally filtered by kind |
|
|
133
|
+
| `nodes_with_annotation(kind)` | Every node tagged with the given annotation kind |
|
|
125
134
|
| `clear_annotations(name, kind=None)` | Remove annotations from a node |
|
|
135
|
+
| `diff_against(other)` | Structural diff of this engine's graph vs. another |
|
|
136
|
+
| `preanalysis()` | Run the built-in pre-analysis passes and store annotations/subgraphs |
|
|
137
|
+
| `augment_sarif(path)` | Merge SARIF findings into the graph |
|
|
138
|
+
| `augment_weaudit(path)` | Merge weAudit findings into the graph |
|
|
139
|
+
| `findings(kind=None)` | Return nodes carrying finding-style annotations |
|
|
140
|
+
| `subgraph(name)` | Return the nodes in a named subgraph |
|
|
141
|
+
| `subgraph_names()` | List every named subgraph currently on the graph |
|
|
126
142
|
| `summary()` | Node counts, edge counts, dependencies |
|
|
127
143
|
| `to_json()` | Full graph export |
|
|
128
144
|
|
|
@@ -227,15 +243,27 @@ graph TD
|
|
|
227
243
|
|
|
228
244
|
## Installation
|
|
229
245
|
|
|
246
|
+
The examples below track the current development branch. For the latest
|
|
247
|
+
published package, install from PyPI. For the exact feature set described
|
|
248
|
+
here, install from a checkout and run commands via `uv run`.
|
|
249
|
+
|
|
230
250
|
```bash
|
|
251
|
+
# Latest published release
|
|
231
252
|
uv pip install trailmark
|
|
253
|
+
|
|
254
|
+
# Current checkout / development branch
|
|
255
|
+
uv sync --all-groups
|
|
232
256
|
```
|
|
233
257
|
|
|
234
|
-
Requires Python ≥ 3.
|
|
258
|
+
Requires Python ≥ 3.12.
|
|
235
259
|
|
|
236
260
|
## Usage
|
|
237
261
|
|
|
238
262
|
```bash
|
|
263
|
+
# Report the installed version
|
|
264
|
+
trailmark --version # or: trailmark -V
|
|
265
|
+
trailmark version # subcommand form
|
|
266
|
+
|
|
239
267
|
# Full JSON graph (Python, the default)
|
|
240
268
|
trailmark analyze path/to/project
|
|
241
269
|
|
|
@@ -243,33 +271,148 @@ trailmark analyze path/to/project
|
|
|
243
271
|
trailmark analyze --language rust path/to/project
|
|
244
272
|
trailmark analyze --language javascript path/to/project
|
|
245
273
|
|
|
274
|
+
# Polyglot: auto-detect and merge every supported language found in the
|
|
275
|
+
# tree, or pass an explicit comma-separated list.
|
|
276
|
+
trailmark analyze --language auto path/to/project
|
|
277
|
+
trailmark analyze --language python,rust,solidity path/to/project
|
|
278
|
+
|
|
246
279
|
# Summary statistics
|
|
247
280
|
trailmark analyze --summary path/to/project
|
|
248
281
|
|
|
249
282
|
# Complexity hotspots (threshold >= 10)
|
|
250
283
|
trailmark analyze --complexity 10 path/to/project
|
|
284
|
+
|
|
285
|
+
# Augment the graph with external findings (SARIF from static analyzers,
|
|
286
|
+
# weAudit findings from the VS Code extension). Each --sarif / --weaudit
|
|
287
|
+
# flag is repeatable. Add --json to print the augmented graph.
|
|
288
|
+
trailmark augment --sarif results.sarif path/to/project
|
|
289
|
+
trailmark augment --weaudit findings.json path/to/project
|
|
290
|
+
trailmark augment --sarif a.sarif --sarif b.sarif --json path/to/project
|
|
291
|
+
|
|
292
|
+
# List detected entrypoints (attack surface). Uses heuristic detection
|
|
293
|
+
# (main() functions, pyproject.toml [project.scripts]) plus an optional
|
|
294
|
+
# override file at .trailmark/entrypoints.toml (see below).
|
|
295
|
+
trailmark entrypoints path/to/project
|
|
296
|
+
trailmark entrypoints --json path/to/project
|
|
297
|
+
|
|
298
|
+
# Structural diff between two code graphs. Accepts directory paths or
|
|
299
|
+
# git refs (branches, tags, commits). Surfaces added/removed nodes,
|
|
300
|
+
# call-edge changes, and — most usefully — attack-surface changes.
|
|
301
|
+
trailmark diff before/ after/
|
|
302
|
+
trailmark diff --repo . main HEAD # compare git refs
|
|
303
|
+
trailmark diff --json before/ after/ # machine-readable output
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Entrypoint detection
|
|
307
|
+
|
|
308
|
+
Trailmark automatically populates `graph.entrypoints` so `attack_surface()`, taint propagation, and privilege-boundary crossing have data to work with. Detection runs in four layers, each overriding the last:
|
|
309
|
+
|
|
310
|
+
1. **Generic `main` heuristic.** Any function named `main` in any language. Tagged `user_input` / `trusted_internal` / `low`.
|
|
311
|
+
2. **Framework-aware scan.** Decorator, attribute, and visibility patterns per language — see the table below.
|
|
312
|
+
3. **`pyproject.toml [project.scripts]`.** Explicit CLI targets get an upgraded trust/asset classification.
|
|
313
|
+
4. **Repo-local override file.** Hand-curated entrypoints in `.trailmark/entrypoints.toml` always win.
|
|
314
|
+
|
|
315
|
+
Framework coverage:
|
|
316
|
+
|
|
317
|
+
| Language | Frameworks detected |
|
|
318
|
+
| --- | --- |
|
|
319
|
+
| Python | Flask, FastAPI, aiohttp, Click, Typer, Celery |
|
|
320
|
+
| JavaScript / TypeScript | NestJS, Next.js (App Router + Pages API), AWS Lambda |
|
|
321
|
+
| Java | Spring MVC / WebFlux, JAX-RS, Kafka listeners, servlets |
|
|
322
|
+
| C# | ASP.NET Core, Azure Functions |
|
|
323
|
+
| PHP | Symfony `#[Route]` attributes + legacy annotations |
|
|
324
|
+
| Rust | actix-web, rocket, FFI exports (`#[no_mangle]`, `pub extern "C"`), async-main attributes |
|
|
325
|
+
| Solidity | `external` / `public` visibility |
|
|
326
|
+
| Cairo / StarkNet | `#[external]`, `#[view]`, `#[l1_handler]`, `#[constructor]` |
|
|
327
|
+
| Circom | `component main` declarations |
|
|
328
|
+
| Miden Assembly | `export.<name>` directives |
|
|
329
|
+
| Haskell | top-level `main ::` / `main =` |
|
|
330
|
+
| Erlang | functions listed in `-export([...])` |
|
|
331
|
+
| Swift | `@main` app attribute |
|
|
332
|
+
| Objective-C | `UIApplicationDelegate` lifecycle selectors (e.g. `application:openURL:options:`) |
|
|
333
|
+
| Kotlin | Spring MVC / WebFlux annotations (shared with Java), Android component lifecycle methods (`onCreate`, `onReceive`, `onBind`, ...) |
|
|
334
|
+
| Dart | `@pragma('vm:entry-point')` native-callable markers |
|
|
335
|
+
| Go | `http.HandleFunc` / `http.Handle` stdlib registrations, gin/chi/echo-style `<router>.GET/POST/...` handler registrations |
|
|
336
|
+
| Ruby | Rails controller actions (classes inheriting `ApplicationController` / `ActionController::*`), Sidekiq worker `perform` methods |
|
|
337
|
+
| C / C++ | `extern "C"` linkage, `__attribute__((visibility("default")))`, `__declspec(dllexport)` |
|
|
338
|
+
|
|
339
|
+
For anything the heuristics miss, declare entrypoints explicitly in `.trailmark/entrypoints.toml` at the project root. The file supports both single-node and rule-based entries:
|
|
340
|
+
|
|
341
|
+
```toml
|
|
342
|
+
# Single-node entry
|
|
343
|
+
[[entrypoint]]
|
|
344
|
+
node = "my_module:handle_request" # node id, or "module.path:function"
|
|
345
|
+
kind = "api" # user_input | api | database | file_system | third_party
|
|
346
|
+
trust = "untrusted_external" # untrusted_external | semi_trusted_external | trusted_internal
|
|
347
|
+
asset_value = "high" # high | medium | low
|
|
348
|
+
description = "HTTP POST /auth"
|
|
349
|
+
|
|
350
|
+
# Rule: every PHP script under public_html/ is a web-exposed entrypoint.
|
|
351
|
+
[[entrypoint]]
|
|
352
|
+
file_glob = "public_html/**/*.php"
|
|
353
|
+
kind = "user_input"
|
|
354
|
+
trust = "untrusted_external"
|
|
355
|
+
asset_value = "high"
|
|
356
|
+
description = "Web-exposed PHP script"
|
|
357
|
+
|
|
358
|
+
# Rule: any function that takes a PSR-7 request object.
|
|
359
|
+
[[entrypoint]]
|
|
360
|
+
param_type = "ServerRequestInterface"
|
|
361
|
+
kind = "api"
|
|
362
|
+
trust = "untrusted_external"
|
|
363
|
+
asset_value = "high"
|
|
364
|
+
description = "PSR-7 HTTP handler"
|
|
365
|
+
|
|
366
|
+
# Rule: functions named `handle_*`.
|
|
367
|
+
[[entrypoint]]
|
|
368
|
+
name_regex = "^handle_"
|
|
369
|
+
kind = "api"
|
|
370
|
+
trust = "untrusted_external"
|
|
371
|
+
|
|
372
|
+
# Rule: conditions compose with AND — web.py files AND name starts with handle_.
|
|
373
|
+
[[entrypoint]]
|
|
374
|
+
file_glob = "public/*.py"
|
|
375
|
+
name_regex = "^handle_"
|
|
376
|
+
kind = "api"
|
|
377
|
+
trust = "untrusted_external"
|
|
251
378
|
```
|
|
252
379
|
|
|
380
|
+
Later entries override earlier ones when two rules tag the same node, so place broad rules first and specific corrections after.
|
|
381
|
+
|
|
382
|
+
See [docs/entrypoint-patterns.md](docs/entrypoint-patterns.md) for the full reference, including frameworks not yet implemented (Express / Koa / Fastify, Laravel, Cobra, axum, warp, clap, and others) with grep-ready patterns contributors can use to add new detectors.
|
|
383
|
+
|
|
253
384
|
### Programmatic API
|
|
254
385
|
|
|
255
386
|
```python
|
|
256
387
|
from trailmark.query.api import QueryEngine
|
|
257
388
|
|
|
389
|
+
# Single-language (default) or auto-detect + merge across all languages
|
|
258
390
|
engine = QueryEngine.from_directory("path/to/project")
|
|
391
|
+
engine = QueryEngine.from_directory("path/to/project", language="auto")
|
|
392
|
+
engine = QueryEngine.from_directory("path/to/project", language="python,rust")
|
|
259
393
|
|
|
260
|
-
#
|
|
394
|
+
# Direct neighbors
|
|
261
395
|
engine.callers_of("handle_request")
|
|
262
|
-
|
|
263
|
-
# What does this function call?
|
|
264
396
|
engine.callees_of("handle_request")
|
|
265
397
|
|
|
266
|
-
#
|
|
398
|
+
# Transitive slicing — who could reach this sink, or what could it reach?
|
|
399
|
+
engine.ancestors_of("Auth._check_sig")
|
|
400
|
+
engine.reachable_from("handle_request")
|
|
401
|
+
|
|
402
|
+
# Attack-surface paths from any detected entrypoint
|
|
403
|
+
engine.entrypoint_paths_to("Auth._check_sig")
|
|
404
|
+
|
|
405
|
+
# All call paths between two nodes
|
|
267
406
|
engine.paths_between("handle_request", "Auth._check_sig")
|
|
268
407
|
|
|
269
408
|
# Functions with cyclomatic complexity >= 10
|
|
270
409
|
engine.complexity_hotspots(10)
|
|
271
410
|
|
|
272
|
-
#
|
|
411
|
+
# What functions can raise a given exception? (uses parser-detected
|
|
412
|
+
# exception_types; no runtime tracing required)
|
|
413
|
+
engine.functions_that_raise("PermissionError")
|
|
414
|
+
|
|
415
|
+
# Add and query semantic annotations
|
|
273
416
|
from trailmark.models.annotations import AnnotationKind
|
|
274
417
|
|
|
275
418
|
engine.annotate(
|
|
@@ -278,10 +421,23 @@ engine.annotate(
|
|
|
278
421
|
"Caller has already authenticated the session token",
|
|
279
422
|
source="llm",
|
|
280
423
|
)
|
|
281
|
-
|
|
282
|
-
# Retrieve annotations
|
|
283
424
|
engine.annotations_of("handle_request")
|
|
284
|
-
engine.
|
|
425
|
+
engine.nodes_with_annotation(AnnotationKind.FINDING)
|
|
426
|
+
|
|
427
|
+
# Diff against an earlier snapshot of the same codebase
|
|
428
|
+
before = QueryEngine.from_directory("before/")
|
|
429
|
+
diff = engine.diff_against(before)
|
|
430
|
+
# diff contains: summary_delta, nodes {added/removed/modified},
|
|
431
|
+
# edges {added/removed}, entrypoints {added/removed/modified}
|
|
432
|
+
|
|
433
|
+
# Run the built-in audit-oriented preanalysis passes
|
|
434
|
+
engine.preanalysis()
|
|
435
|
+
engine.findings()
|
|
436
|
+
engine.subgraph_names()
|
|
437
|
+
|
|
438
|
+
# Programmatic augmentation hooks for external tooling
|
|
439
|
+
engine.augment_sarif("results.sarif")
|
|
440
|
+
engine.augment_weaudit("findings.json")
|
|
285
441
|
```
|
|
286
442
|
|
|
287
443
|
## Development
|
|
@@ -298,7 +454,7 @@ uv run ruff format
|
|
|
298
454
|
uv tool install ty && ty check
|
|
299
455
|
|
|
300
456
|
# Tests
|
|
301
|
-
uv run pytest -q
|
|
457
|
+
uv run pytest -q tests/
|
|
302
458
|
|
|
303
459
|
# Mutation testing (on macOS, set this env var to avoid rustworkx fork segfaults)
|
|
304
460
|
OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES uv run mutmut run
|