Rootstock is a passive macOS attack-path discovery tool. It maps local security boundaries such as TCC grants, entitlements, code signing, Keychain ACLs, XPC trust, persistence, and enterprise identity artifacts into an analyzable graph.
Think BloodHound-style graph analysis for macOS-native trust relationships, with a collector that stays local and read-only.
- TCC grants - camera, microphone, Full Disk Access, Apple Events, and other privacy permissions
- Entitlements - code-signing privileges that weaken or bypass macOS security boundaries
- Code signing - hardened runtime, library validation, team identifiers, and certificate metadata
- Injection paths - per-app assessment of DYLD, Electron, and related injection viability
- Persistence - LaunchAgents, LaunchDaemons, login items, shell hooks, and related startup paths
- Keychain and XPC trust - ACL metadata, service relationships, and trust edges
- Enterprise context - Active Directory binding, Kerberos artifacts, MDM profiles, and BloodHound interop
- Vulnerability context - CVE/EPSS/KEV enrichment and ATT&CK technique mapping
The maintained public surface is the Swift collector, Python/Neo4j graph
pipeline, modules/cve-scan/, synthetic examples, this README, and the
documentation index.
Completed audit packets, one-off plans, status files, investigation notes, deprecated drafts, generated reports, generated viewers, real scan outputs, and historical roadmaps are not part of public documentation set. Keep local copies in ignored private or archive paths.
The repository intentionally tracks synthetic fixtures, synthetic screenshots,
the public .env.example, and analyzer scope in .codacy.yaml. Local analyzer
state, benchmark results, release binaries, databases, real host inventories,
and security reports are ignored.
Rootstock has three active components:
collector/- a SwiftPM package that runs on the Mac being assessed and emits one portable scan JSON file.graph/- a Python Neo4j pipeline that imports scan JSON, creates graph relationships, classifies risk, runs queries, writes reports, and can serve an authenticated viewer API.modules/cve-scan/- a scoped Python evidence module that writes local CVE artifacts and arootstock-export.jsonbridge for graph import.
examples/demo-scan.json is the synthetic demo fixture source of truth.
Generated demo reports and viewers are local artifacts.
The viewer source is maintained as graph/viewer_template.html,
graph/viewer.css, and graph/viewer.js. Both graph/viewer.py and the live
server assemble those files into the same dependency-free interface; generated
offline viewers remain single HTML files.
The scan JSON is the main contract. When collector output changes, keep
collector/schema/scan-result.schema.json, graph/models.py, importer code,
and at least one synthetic fixture in sync:
python3 scripts/validate-scan.py examples/demo-scan.jsonCollector:
- macOS 14 Sonoma or later
- Swift 5.9+ / Xcode 15+
Graph pipeline:
- Python 3.10+
- Neo4j 5.x, Docker recommended
cd collector
swift build -c releasecollector/.build/release/RootstockCLI --output scan.jsonUseful variants:
collector/.build/release/RootstockCLI --output scan.json --verbose
collector/.build/release/RootstockCLI --output scan.json --modules tcc
collector/.build/release/RootstockCLI --output scan.json --modules entitlements,codesigning
sudo collector/.build/release/RootstockCLI --output scan.jsonmacOS 15+ can require Full Disk Access for TCC database reads. Grant FDA to the terminal or run the collector with the required privileges when you need TCC grants.
python3 scripts/validate-scan.py scan.jsonpython3 -m pip install -r graph/requirements.txt
(cd graph && NEO4J_AUTH=neo4j/CHANGE_ME docker compose up -d)
NEO4J_PASSWORD=CHANGE_ME bash graph/pipeline.sh scan.jsonImport a prebuilt cve-scan artifact in the same run:
NEO4J_PASSWORD=CHANGE_ME bash graph/pipeline.sh scan.json \
--cve-scan-export modules/cve-scan/runs/local/rootstock-export.jsonStart the authenticated API/viewer:
ROOTSTOCK_API_TOKEN=CHANGE_ME_API_TOKEN NEO4J_PASSWORD=CHANGE_ME \
bash graph/pipeline.sh scan.json --serve 8000Environment variables:
NEO4J_URI,NEO4J_USER,NEO4J_PASSWORD- Neo4j connectionROOTSTOCK_API_TOKEN- required for/api/*routes
CVE network refresh is opt-in with --refresh-cve; the default pipeline uses
cached/static enrichment.
| Output | Description |
|---|---|
examples/demo-scan.json |
Synthetic collector scan fixture |
examples/cve-scan-export.json |
Synthetic cve-scan graph fixture |
modules/cve-scan/docs/example-outcome.md |
Example cve-scan artifact shape |
Generate local demo report/viewer artifacts with:
bash examples/regenerate.shThe generated files are written under examples/generated/ and ignored by git.
# Swift collector
(cd collector && swift test --parallel)
# Python graph
ruff check graph/ scripts/ examples/ docs/
(cd graph && pytest tests)
# Contract fixture
python3 scripts/validate-scan.py examples/demo-scan.json
# cve-scan module
(cd modules/cve-scan && ruff check . && pytest)
# Shell entry points
shellcheck examples/regenerate.sh graph/pipeline.sh scripts/*.sh tests/integration/*.sh
shellcheck modules/cve-scan/scripts/perf-smoke.sh
bash tests/integration/test_benchmark_script.shGraph behavior that depends on import, inference, query, report, or API semantics needs a reachable Neo4j test database:
(cd graph && NEO4J_AUTH=neo4j/CHANGE_ME docker compose up -d)
(cd graph && ROOTSTOCK_REQUIRE_NEO4J=1 NEO4J_PASSWORD=CHANGE_ME pytest tests -v --tb=short)
NEO4J_PASSWORD=CHANGE_ME bash tests/integration/test_full_pipeline.shDo not claim graph runtime behavior verified unless the Neo4j lane ran.
- Documentation index
- Product contract
- Interface design system
- Frontend and report interface
- Threat model
- FAQ
- Neo4j Browser quickstart
- cve-scan module guide
- Quality gates
- Contributing
- Security policy
| macOS Version | Collector | User TCC.db | System TCC.db | Notes |
|---|---|---|---|---|
| 14 Sonoma | Full | Normal read | Requires FDA | Primary supported baseline |
| 15 Sequoia | Full | Requires FDA | Requires FDA | Kernel-enforced TCC access |
| 26 Tahoe | Full | Requires FDA | Requires FDA | Year-based macOS versioning |
| < 14 | Not supported | Not supported | Not supported | Out of scope |
Rootstock is passive and local-first. The collector reads macOS metadata and writes a local scan artifact. It must not upload scans, collect telemetry, or perform active exploitation. See docs/THREAT_MODEL.md.
Treat real scan JSON, graph exports, generated viewers, reports, Neo4j volumes, screenshots, package inventories, and cve-scan outputs as confidential local data.
Rootstock is licensed under GPLv3. See LICENSE.
@software{rootstock2026,
title = {Rootstock: Graph-Based Attack Path Discovery for macOS Security Boundaries},
author = {Sebastian J. Spicker},
year = {2026},
url = {https://github.com/sebastianspicker/rootstock},
note = {Open-source research tool, Cologne University of Music}
}