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
33 changes: 26 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# JCapsLock

[![JitPack](https://jitpack.io/v/serj/jcapslock.svg)](https://jitpack.io/#serj/jcapslock)

A Java implementation of Google's [Capslock](https://github.com/google/capslock) capability analysis tool. JCapsLock analyzes your project's dependencies to identify what privileged operations they can perform - file I/O, network access, code execution, native calls, and more.

This helps you understand the security implications of your dependency tree before a supply chain attack happens, not after.
Expand Down Expand Up @@ -33,17 +35,32 @@ mvn capslock:check

## Installation

Add the plugin to your project's `pom.xml`:
JCapsLock is available via [JitPack](https://jitpack.io). Maven Central availability planned for 1.0 release (early 2026).

Add the JitPack repository and plugin to your `pom.xml`:

```xml
<plugin>
<groupId>com.github.serj</groupId>
<artifactId>mvn-capslock</artifactId>
<version>1.0-SNAPSHOT</version>
</plugin>
<pluginRepositories>
<pluginRepository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</pluginRepository>
</pluginRepositories>

<build>
<plugins>
<plugin>
<groupId>com.github.serj</groupId>
<artifactId>capslock-maven-plugin</artifactId>
<version>COMMIT_HASH</version>
</plugin>
</plugins>
</build>
```

Or run directly:
Replace `COMMIT_HASH` with the latest version from the JitPack badge above.

Then run:

```bash
mvn capslock:analyze
Expand All @@ -53,6 +70,8 @@ mvn capslock:analyze

- **[USAGE.md](USAGE.md)** - Detailed usage guide and configuration options
- **[maven-plugin/README.md](maven-plugin/README.md)** - Maven plugin reference
- **[docs/caveats.md](docs/caveats.md)** - Analysis limitations and caveats
- **[Go Capslock Caveats](https://github.com/google/capslock/blob/main/docs/caveats.md)** - Caveats for the original Go implementation

## Capability Snapshot Workflow

Expand Down
11 changes: 11 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Security Policy

## Disclaimer

JCapsLock is currently a research project and is not intended for production use. Use at your own risk.

## Reporting Issues

If you discover a security issue, please report it privately using GitHub's "Report a vulnerability" feature in the Security tab of this repository. I'm happy to address issues as time permits.

For issues in the upstream CapsLock protobuf definitions, please see [Google's CapsLock security policy](https://github.com/google/capslock/blob/main/SECURITY.md).
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ public static void check(String capability) {

private static void doCheck(String capability) {
String blocked = System.getProperty(PROP_PREFIX + capability);
String[] blockedPackages = (blocked != null) ? blocked.split(",") : null;
String[] blockedPackages = (blocked != null)
? java.util.Arrays.stream(blocked.split(",")).map(String::trim).toArray(String[]::new)
: null;

List<StackTraceElement> appFrames = collectAppFrames();
if (appFrames.isEmpty()) {
Expand Down Expand Up @@ -94,7 +96,7 @@ private static String findViolator(List<StackTraceElement> frames, String[] bloc
for (StackTraceElement frame : frames) {
String className = frame.getClassName();
for (String pkg : blockedPackages) {
if (className.startsWith(pkg)) {
if (className.equals(pkg) || className.startsWith(pkg + ".")) {
return className;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ private static void processLines(Stream<String> lines) {

/**
* Parse a single line from the capability mapping file.
* Format: method <fully_qualified_method> <CAPABILITY>
* CAPABILITY_SAFE is handled specially - adds to SAFE_METHODS/SAFE_CLASSES.
*/
private static void parseLine(String line) {
String[] parts = line.split("\\s+");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

/**
* Manages reading and writing capability snapshots.
Expand Down Expand Up @@ -79,16 +80,14 @@ public void writeSnapshot(File projectRoot, CapabilityInfoList snapshot) throws
* @throws IOException if writing fails
*/
public void writeSnapshotToFile(File snapshotFile, CapabilityInfoList snapshot) throws IOException {
// Create parent directory if needed
File parentDir = snapshotFile.getParentFile();
if (parentDir != null && !parentDir.exists()) {
if (!parentDir.mkdirs()) {
throw new IOException("Failed to create directory: " + parentDir);
}
Path path = snapshotFile.toPath();
Path parentDir = path.getParent();
if (parentDir != null) {
Files.createDirectories(parentDir);
}

String json = jsonPrinter.print(snapshot);
Files.writeString(snapshotFile.toPath(), json);
Files.writeString(path, json);
}

/**
Expand Down
File renamed without changes.
37 changes: 37 additions & 0 deletions docs/caveats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# JCapsLock Analysis: Key Limitations

JCapsLock provides static analysis of Java libraries and their capabilities, but users should understand several important constraints.

## Primary Caveats

**False Positives**: The analysis assumes all code paths execute, even conditional branches. For example, a method may contain an if statement where `Files.readAllBytes` is called if one of the method's parameters is true, potentially reporting capabilities that never actually occur.

**Reflection Challenges**: Use of `java.lang.reflect` creates analysis difficulties. The tool flags reflection usage as its own capability to avoid missing potential capabilities without notification. In Java, reflection is commonly used by dependency injection frameworks (Spring, Guice) and serialization libraries (Jackson, Gson).

**External Program Execution**: When code uses `Runtime.exec`, `ProcessBuilder`, or loads classes dynamically, the analysis cannot determine what capabilities these might have, so it is reported to the user as a capability of its own.

**Unsafe Pointer Operations**: Although typically used simply, `sun.misc.Unsafe` theoretically enables arbitrary behavior. The tool treats this as a reportable capability.

**Dynamic Class Loading**: Classes loaded via `Class.forName` or custom classloaders cannot be fully analyzed, so dynamic loading is flagged as a capability.

**Native Code**: JCapsLock cannot analyze native code called via JNI or JNA, reporting these as `CGO` capabilities.

## Maven-Specific Considerations

**Dependency Resolution**: Run JCapsLock from your project directory to ensure dependency resolution matches your Maven build. Active profiles and dependency mediation affect which versions are analyzed.

**Optional Dependencies**: Libraries often declare optional dependencies for features you may not use. Use `-Dcapslock.includeOptional=false` to exclude them from analysis.

## Runtime Enforcement (First Experiments)

JCapsLock includes an experimental runtime agent that can monitor and block capabilities at runtime. While functional, there are unresolved issues being actively worked on:

**Performance Impact**: The instrumentation adds overhead to capability-checked methods. The impact varies depending on how frequently these methods are called.

**Multithreading**: Execution paths can escape enforcement checks in multithreaded scenarios. Thread handoffs and asynchronous callbacks may not be fully traced.

See [agent/README.md](../agent/README.md) for current usage and limitations.

## See Also

- [Go Capslock Caveats](https://github.com/google/capslock/blob/main/docs/caveats.md) - Caveats for the original Go implementation
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,11 @@ private List<File> resolveArtifactWithTransitives(OptionalDependency optDep) {
*/
private File downloadJarDirectly(OptionalDependency optDep) {
String relativePath = JavaUtils.getArtifactPath(optDep.groupId(), optDep.artifactId(), optDep.version(), "jar");
Path localPath = Path.of(localRepositoryPath, relativePath);
Path localPath = Path.of(localRepositoryPath, relativePath).normalize();
if (!localPath.startsWith(Path.of(localRepositoryPath))) {
log.warn("Skipping suspicious artifact path: " + relativePath);
return null;
}

if (Files.exists(localPath)) {
log.debug("Found in local repository: " + localPath);
Expand Down Expand Up @@ -257,7 +261,10 @@ private List<OptionalDependency> fetchUsingProjectBuilder(Artifact artifact) {
*/
private List<OptionalDependency> fetchFromLocalPom(Artifact artifact) {
Path pomPath = Path.of(localRepositoryPath,
JavaUtils.getArtifactPath(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(), "pom"));
JavaUtils.getArtifactPath(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(), "pom")).normalize();
if (!pomPath.startsWith(Path.of(localRepositoryPath))) {
return Collections.emptyList();
}

if (Files.exists(pomPath)) {
try {
Expand Down
9 changes: 9 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@

<name>JCapsLock Parent</name>
<description>Java capability analysis tool for Maven dependencies</description>
<url>https://github.com/serj/jcapslock</url>

<licenses>
<license>
<name>Apache License 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>

<modules>
<module>core</module>
Expand Down