Skip to content

Commit 02ad12f

Browse files
authored
Merge pull request #4 from serj/pre-public
Fix docs, licenses and minor issues before going public
2 parents 9b62f2c + 9b76442 commit 02ad12f

9 files changed

Lines changed: 104 additions & 18 deletions

File tree

README.md

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# JCapsLock
22

3+
[![JitPack](https://jitpack.io/v/serj/jcapslock.svg)](https://jitpack.io/#serj/jcapslock)
4+
35
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.
46

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

3436
## Installation
3537

36-
Add the plugin to your project's `pom.xml`:
38+
JCapsLock is available via [JitPack](https://jitpack.io). Maven Central availability planned for 1.0 release (early 2026).
39+
40+
Add the JitPack repository and plugin to your `pom.xml`:
3741

3842
```xml
39-
<plugin>
40-
<groupId>com.github.serj</groupId>
41-
<artifactId>mvn-capslock</artifactId>
42-
<version>1.0-SNAPSHOT</version>
43-
</plugin>
43+
<pluginRepositories>
44+
<pluginRepository>
45+
<id>jitpack.io</id>
46+
<url>https://jitpack.io</url>
47+
</pluginRepository>
48+
</pluginRepositories>
49+
50+
<build>
51+
<plugins>
52+
<plugin>
53+
<groupId>com.github.serj</groupId>
54+
<artifactId>capslock-maven-plugin</artifactId>
55+
<version>COMMIT_HASH</version>
56+
</plugin>
57+
</plugins>
58+
</build>
4459
```
4560

46-
Or run directly:
61+
Replace `COMMIT_HASH` with the latest version from the JitPack badge above.
62+
63+
Then run:
4764

4865
```bash
4966
mvn capslock:analyze
@@ -53,6 +70,8 @@ mvn capslock:analyze
5370

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

5776
## Capability Snapshot Workflow
5877

SECURITY.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Security Policy
2+
3+
## Disclaimer
4+
5+
JCapsLock is currently a research project and is not intended for production use. Use at your own risk.
6+
7+
## Reporting Issues
8+
9+
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.
10+
11+
For issues in the upstream CapsLock protobuf definitions, please see [Google's CapsLock security policy](https://github.com/google/capslock/blob/main/SECURITY.md).

agent/src/main/java/com/github/serj/jcapslock/agent/PolicyChecker.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ public static void check(String capability) {
4545

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

5052
List<StackTraceElement> appFrames = collectAppFrames();
5153
if (appFrames.isEmpty()) {
@@ -94,7 +96,7 @@ private static String findViolator(List<StackTraceElement> frames, String[] bloc
9496
for (StackTraceElement frame : frames) {
9597
String className = frame.getClassName();
9698
for (String pkg : blockedPackages) {
97-
if (className.startsWith(pkg)) {
99+
if (className.equals(pkg) || className.startsWith(pkg + ".")) {
98100
return className;
99101
}
100102
}

core/src/main/java/com/github/serj/jcapslock/capability/CapabilityMapper.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ private static void processLines(Stream<String> lines) {
112112

113113
/**
114114
* Parse a single line from the capability mapping file.
115+
* Format: method <fully_qualified_method> <CAPABILITY>
116+
* CAPABILITY_SAFE is handled specially - adds to SAFE_METHODS/SAFE_CLASSES.
115117
*/
116118
private static void parseLine(String line) {
117119
String[] parts = line.split("\\s+");

core/src/main/java/com/github/serj/jcapslock/snapshot/SnapshotManager.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.io.File;
77
import java.io.IOException;
88
import java.nio.file.Files;
9+
import java.nio.file.Path;
910

1011
/**
1112
* Manages reading and writing capability snapshots.
@@ -79,16 +80,14 @@ public void writeSnapshot(File projectRoot, CapabilityInfoList snapshot) throws
7980
* @throws IOException if writing fails
8081
*/
8182
public void writeSnapshotToFile(File snapshotFile, CapabilityInfoList snapshot) throws IOException {
82-
// Create parent directory if needed
83-
File parentDir = snapshotFile.getParentFile();
84-
if (parentDir != null && !parentDir.exists()) {
85-
if (!parentDir.mkdirs()) {
86-
throw new IOException("Failed to create directory: " + parentDir);
87-
}
83+
Path path = snapshotFile.toPath();
84+
Path parentDir = path.getParent();
85+
if (parentDir != null) {
86+
Files.createDirectories(parentDir);
8887
}
8988

9089
String json = jsonPrinter.print(snapshot);
91-
Files.writeString(snapshotFile.toPath(), json);
90+
Files.writeString(path, json);
9291
}
9392

9493
/**
File renamed without changes.

docs/caveats.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# JCapsLock Analysis: Key Limitations
2+
3+
JCapsLock provides static analysis of Java libraries and their capabilities, but users should understand several important constraints.
4+
5+
## Primary Caveats
6+
7+
**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.
8+
9+
**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).
10+
11+
**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.
12+
13+
**Unsafe Pointer Operations**: Although typically used simply, `sun.misc.Unsafe` theoretically enables arbitrary behavior. The tool treats this as a reportable capability.
14+
15+
**Dynamic Class Loading**: Classes loaded via `Class.forName` or custom classloaders cannot be fully analyzed, so dynamic loading is flagged as a capability.
16+
17+
**Native Code**: JCapsLock cannot analyze native code called via JNI or JNA, reporting these as `CGO` capabilities.
18+
19+
## Maven-Specific Considerations
20+
21+
**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.
22+
23+
**Optional Dependencies**: Libraries often declare optional dependencies for features you may not use. Use `-Dcapslock.includeOptional=false` to exclude them from analysis.
24+
25+
## Runtime Enforcement (First Experiments)
26+
27+
JCapsLock includes an experimental runtime agent that can monitor and block capabilities at runtime. While functional, there are unresolved issues being actively worked on:
28+
29+
**Performance Impact**: The instrumentation adds overhead to capability-checked methods. The impact varies depending on how frequently these methods are called.
30+
31+
**Multithreading**: Execution paths can escape enforcement checks in multithreaded scenarios. Thread handoffs and asynchronous callbacks may not be fully traced.
32+
33+
See [agent/README.md](../agent/README.md) for current usage and limitations.
34+
35+
## See Also
36+
37+
- [Go Capslock Caveats](https://github.com/google/capslock/blob/main/docs/caveats.md) - Caveats for the original Go implementation

maven-plugin/src/main/java/com/github/serj/jcapslock/maven/OptionalDependencyResolver.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,11 @@ private List<File> resolveArtifactWithTransitives(OptionalDependency optDep) {
180180
*/
181181
private File downloadJarDirectly(OptionalDependency optDep) {
182182
String relativePath = JavaUtils.getArtifactPath(optDep.groupId(), optDep.artifactId(), optDep.version(), "jar");
183-
Path localPath = Path.of(localRepositoryPath, relativePath);
183+
Path localPath = Path.of(localRepositoryPath, relativePath).normalize();
184+
if (!localPath.startsWith(Path.of(localRepositoryPath))) {
185+
log.warn("Skipping suspicious artifact path: " + relativePath);
186+
return null;
187+
}
184188

185189
if (Files.exists(localPath)) {
186190
log.debug("Found in local repository: " + localPath);
@@ -257,7 +261,10 @@ private List<OptionalDependency> fetchUsingProjectBuilder(Artifact artifact) {
257261
*/
258262
private List<OptionalDependency> fetchFromLocalPom(Artifact artifact) {
259263
Path pomPath = Path.of(localRepositoryPath,
260-
JavaUtils.getArtifactPath(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(), "pom"));
264+
JavaUtils.getArtifactPath(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(), "pom")).normalize();
265+
if (!pomPath.startsWith(Path.of(localRepositoryPath))) {
266+
return Collections.emptyList();
267+
}
261268

262269
if (Files.exists(pomPath)) {
263270
try {

pom.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@
1111

1212
<name>JCapsLock Parent</name>
1313
<description>Java capability analysis tool for Maven dependencies</description>
14+
<url>https://github.com/serj/jcapslock</url>
15+
16+
<licenses>
17+
<license>
18+
<name>Apache License 2.0</name>
19+
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
20+
<distribution>repo</distribution>
21+
</license>
22+
</licenses>
1423

1524
<modules>
1625
<module>core</module>

0 commit comments

Comments
 (0)