Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ A language-specific parser walks the directory, parses each file into a tree-sit
| Swift | `.swift` | functions, classes, structs, enums, protocols, extensions |
| Objective-C | `.m`, `.mm`, `.h` | C functions, classes, methods (selector-based naming) |
| Kotlin | `.kt`, `.kts` | functions, classes, interfaces, data classes, objects, methods |
| Dart | `.dart` | functions, classes, abstract classes, methods, constructors |

```mermaid
flowchart TD
Expand Down Expand Up @@ -295,6 +296,7 @@ Framework coverage:
| Swift | `@main` app attribute |
| Objective-C | `UIApplicationDelegate` lifecycle selectors (e.g. `application:openURL:options:`) |
| Kotlin | Spring MVC / WebFlux annotations (shared with Java), Android component lifecycle methods (`onCreate`, `onReceive`, `onBind`, ...) |
| Dart | `@pragma('vm:entry-point')` native-callable markers |

For anything the heuristics miss, declare entrypoints explicitly in `.trailmark/entrypoints.toml` at the project root:

Expand Down
36 changes: 36 additions & 0 deletions src/trailmark/analysis/entrypoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@
}
)

# Dart — `@pragma('vm:entry-point')` marks native-callable functions,
# often FFI or platform-channel callback targets. These are attacker-
# reachable from the host platform.
_DART_VM_ENTRY_POINT = re.compile(
r"^\s*@\s*pragma\s*\(\s*['\"]vm:entry-point['\"]",
)

_KIND_BY_NAME = {k.value: k for k in EntrypointKind}
_TRUST_BY_NAME = {t.value: t for t in TrustLevel}
_ASSET_BY_NAME = {a.value: a for a in AssetValue}
Expand Down Expand Up @@ -283,6 +290,8 @@ def _detect_for_unit(
return _detect_objc(unit)
if path.endswith((".kt", ".kts")):
return _detect_kotlin(cache, unit, path)
if path.endswith(".dart"):
return _detect_dart(cache, unit, path)
return None


Expand Down Expand Up @@ -710,6 +719,33 @@ def _detect_kotlin(
return None


def _detect_dart(
cache: _SourceCache,
unit: CodeUnit,
path: str,
) -> EntrypointTag | None:
"""Detect Dart entrypoints.

Today covers ``@pragma('vm:entry-point')``, the explicit Dart marker
for functions invoked from native code (FFI callbacks, platform-
channel handlers, deferred loading targets). Flutter lifecycle
methods on ``StatefulWidget`` / ``StatelessWidget`` (``build``,
``initState``, ``dispose``) are not flagged here because they
execute in-process and aren't directly attacker-reachable —
add them via the override file if you want them surfaced.
"""
decorators = cache.decorators_above(path, unit.location.start_line)
for line in decorators:
if _DART_VM_ENTRY_POINT.match(line):
return EntrypointTag(
kind=EntrypointKind.API,
trust_level=TrustLevel.UNTRUSTED_EXTERNAL,
description="Dart @pragma('vm:entry-point') native callback",
asset_value=AssetValue.HIGH,
)
return None


def _detect_objc(unit: CodeUnit) -> EntrypointTag | None:
"""Detect Objective-C entrypoints: AppDelegate selectors and extern C.

Expand Down
5 changes: 5 additions & 0 deletions src/trailmark/parsers/dart/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Dart language parser for Trailmark."""

from trailmark.parsers.dart.parser import DartParser

__all__ = ["DartParser"]
Loading
Loading