Theo monitors access privileges originating from third-party dependencies via static analysis. It identifies sensitive Java API calls (filesystem, network, reflection, process execution, etc.) made by each dependency, determines which of those are reachable from the client project's code, detects changes across runs, and checks dependencies for known CVEs.
| Module | Description |
|---|---|
theo-commons |
Shared utilities: sensitive API definitions (sensitive_apis.json), package matching |
theo-static-maven-plugin |
Maven plugin with preprocess, analyze, cve-check, snapshot, and history goals |
package-static-analyzer |
CLI tool that analyzes a single package JAR for sensitive API usage |
package-miner |
Batch analysis tool for mining Maven Central packages |
Build all modules:
mvn clean install
Add the plugin to the target project's pom.xml:
<build>
<plugins>
<plugin>
<groupId>io.github.sparkrew</groupId>
<artifactId>theo-static-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
</plugin>
</plugins>
</build>Run the analysis:
mvn theo-static:analyze -Dtheo.packageNames=com.example.app
Run the analysis with CVE checking:
mvn theo-static:cve-check -Dtheo.packageNames=com.example.app
preprocess-- Builds a package-to-dependency map from Maven's resolved dependencies.analyze-- Analyzes all dependencies for sensitive API usage, determines client project reachability, detects changes from the previous run, and generates HTML reports.cve-check-- Runs the full analysis (everythinganalyzedoes) and then queries OSV.dev for known vulnerabilities, augmenting reports with CVE badges and unaudited capability indicators.snapshot-- Saves the current analysis as a named snapshot in the history folder (~/.theo/history/).history-- Generates ahistory.htmltable showing the presence of reachable sensitive APIs across all saved snapshots.
| Property | Default | Description |
|---|---|---|
theo.packageNames |
(required) | Comma-separated package names of the client project |
theo.skipSameGroupId |
true |
Skip dependencies that share the client project's groupId |
theo.directOnly |
false |
When true, only analyze direct dependencies (skip transitive) |
theo.reachableOnly |
false |
When true, only generate the reachable report |
theo.verbose |
true |
Show all changes in CLI output; when false, only reachable changes |
theo.cacheDir |
~/.theo/cache |
Persistent cache directory |
theo.reportDir |
target/theo-report |
Report output directory |
theo.analyzerJarPath |
(auto-resolved) | Path to the package-static-analyzer JAR |
theo.historyDir |
~/.theo/history |
History folder for snapshots |
theo.snapshotLabel |
(auto: version-N) | Custom label for a snapshot |
theo.compareWith |
0 |
Compare against the Nth most recent snapshot (0 = use last run) |
theo.failOnChange |
false |
Fail the build if reachable sensitive APIs have additions or modifications |
To include same-groupId dependencies in the analysis:
mvn theo-static:analyze -Dtheo.packageNames=com.example.app -Dtheo.skipSameGroupId=false
The analyze goal generates three HTML reports in target/theo-report/. Sensitive APIs are grouped by category (Filesystem, Network, Process) and subcategory, based on the categorization from Table II of the paper.
all-dependencies.html-- Every sensitive API from every dependency, grouped by category, with access type (direct/indirect) and reachable api highlighting.reachable.html-- Only sensitive APIs reachable from the client project's code, with full call paths and decompiled source. Includes a breakdown of direct vs transitive dependencies, and transitive deps show which direct dependency pulled them in.changes.html-- What changed since the last run (added, modified, removed dependencies and their sensitive API changes).history.html-- (generated by thehistorygoal) Table showing reachable sensitive APIs across all saved snapshots, with D/I access type and dependency versions.pom-theo-suggestions.xml-- (generated alongside the project POM when changes are detected) Contains<dependencyManagement>entries to revert version-changed dependencies that introduced new reachable sensitive APIs. Includes dependency trail comments for transitive deps.
When running cve-check, the reports are augmented with CVE badges (linked to advisories) placed under the relevant category using CWE-based mapping. APIs reachable from the client with no known CVE in their category get an "unaudited capability" badge indicating the associated CWE.
To track how reachable sensitive APIs evolve over time, save snapshots after analysis runs:
mvn theo-static:analyze -Dtheo.packageNames=com.example.app
mvn theo-static:snapshot
By default snapshots will be named according to the project version. A custom name can be passed with the flag snapshotLabel as given below.
mvn theo-static:snapshot -Dtheo.snapshotLabel=v2.3.0-release
Generate the history table from all saved snapshots:
mvn theo-static:history
To compare the current run against a specific snapshot instead of the automatic last-run:
mvn theo-static:analyze -Dtheo.packageNames=com.example.app -Dtheo.compareWith=2
Use theo.failOnChange to enforce a review gate in CI. When enabled, the build fails if reachable sensitive APIs have additions or modifications (removals are allowed):
mvn theo-static:analyze -Dtheo.packageNames=com.example.app -Dtheo.failOnChange=true
When the build fails, Theo generates pom-theo-suggestions.xml next to the project POM. This file contains <dependencyManagement> entries that pin dependencies back to their previous versions. For transitive dependencies, it includes comments showing the dependency trail and which direct dependency pulled them in.
To accept the changes and update the baseline, run a snapshot:
mvn theo-static:snapshot
Analysis results are cached in ~/.theo/cache/ by default (configurable via theo.cacheDir). The cache survives mvn clean and avoids re-analyzing unchanged dependencies.
The first run can take a few minutes to complete becuase it analyzes all the dependencies one by one, but the subsequent runs will be faster with caching.
Change detection compares the current run against the previous one stored in the cache. For accurate diffs, use the same mode consistently. Switching between reachableOnly and full analysis between runs will produce misleading changes because the two summaries have different scopes.
Like many other tools with caches, Theo also suffers from the age-old problem of cache invalidation. If you see any weird results between runs, please clear the cache (rm -rf ~/.theo/cache/) and try again.
For design decisions and architecture details, see Design.md.