From ee980c10e788abe61c7414c08856a930cbf61427 Mon Sep 17 00:00:00 2001 From: Sergej Dechand Date: Thu, 18 Dec 2025 16:58:49 +0100 Subject: [PATCH 1/5] Add a security check for maven packages in case of theoretical attack from mvn repo maintainer --- .../jcapslock/maven/OptionalDependencyResolver.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/maven-plugin/src/main/java/com/github/serj/jcapslock/maven/OptionalDependencyResolver.java b/maven-plugin/src/main/java/com/github/serj/jcapslock/maven/OptionalDependencyResolver.java index c2a8f6c..b43789c 100644 --- a/maven-plugin/src/main/java/com/github/serj/jcapslock/maven/OptionalDependencyResolver.java +++ b/maven-plugin/src/main/java/com/github/serj/jcapslock/maven/OptionalDependencyResolver.java @@ -180,7 +180,11 @@ private List 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); @@ -257,7 +261,10 @@ private List fetchUsingProjectBuilder(Artifact artifact) { */ private List 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 { From 27ef695012b73fa54194e48c607fe11f8413d2c9 Mon Sep 17 00:00:00 2001 From: Sergej Dechand Date: Thu, 18 Dec 2025 17:04:26 +0100 Subject: [PATCH 2/5] Fix minor package matching issue --- .../java/com/github/serj/jcapslock/agent/PolicyChecker.java | 6 ++++-- .../github/serj/jcapslock/capability/CapabilityMapper.java | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/agent/src/main/java/com/github/serj/jcapslock/agent/PolicyChecker.java b/agent/src/main/java/com/github/serj/jcapslock/agent/PolicyChecker.java index c791e1a..ce72d56 100644 --- a/agent/src/main/java/com/github/serj/jcapslock/agent/PolicyChecker.java +++ b/agent/src/main/java/com/github/serj/jcapslock/agent/PolicyChecker.java @@ -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 appFrames = collectAppFrames(); if (appFrames.isEmpty()) { @@ -94,7 +96,7 @@ private static String findViolator(List 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; } } diff --git a/core/src/main/java/com/github/serj/jcapslock/capability/CapabilityMapper.java b/core/src/main/java/com/github/serj/jcapslock/capability/CapabilityMapper.java index 6f3aef1..936a9d6 100644 --- a/core/src/main/java/com/github/serj/jcapslock/capability/CapabilityMapper.java +++ b/core/src/main/java/com/github/serj/jcapslock/capability/CapabilityMapper.java @@ -112,6 +112,8 @@ private static void processLines(Stream lines) { /** * Parse a single line from the capability mapping file. + * Format: method + * CAPABILITY_SAFE is handled specially - adds to SAFE_METHODS/SAFE_CLASSES. */ private static void parseLine(String line) { String[] parts = line.split("\\s+"); From 25402251d03550f108e7292387ca8b885fb97054 Mon Sep 17 00:00:00 2001 From: Sergej Dechand Date: Thu, 18 Dec 2025 17:40:35 +0100 Subject: [PATCH 3/5] Document some caveats and LICENSES --- README.md | 33 +++++++++++++++---- .../jcapslock/snapshot/SnapshotManager.java | 13 ++++---- pom.xml | 9 +++++ 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8cd9ac1..8bcc25f 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 - - com.github.serj - mvn-capslock - 1.0-SNAPSHOT - + + + jitpack.io + https://jitpack.io + + + + + + + com.github.serj + capslock-maven-plugin + COMMIT_HASH + + + ``` -Or run directly: +Replace `COMMIT_HASH` with the latest version from the JitPack badge above. + +Then run: ```bash mvn capslock:analyze @@ -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 diff --git a/core/src/main/java/com/github/serj/jcapslock/snapshot/SnapshotManager.java b/core/src/main/java/com/github/serj/jcapslock/snapshot/SnapshotManager.java index 666cac7..7a5aff9 100644 --- a/core/src/main/java/com/github/serj/jcapslock/snapshot/SnapshotManager.java +++ b/core/src/main/java/com/github/serj/jcapslock/snapshot/SnapshotManager.java @@ -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. @@ -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); } /** diff --git a/pom.xml b/pom.xml index a9e546c..f5bf5c5 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,15 @@ JCapsLock Parent Java capability analysis tool for Maven dependencies + https://github.com/serj/jcapslock + + + + Apache License 2.0 + https://www.apache.org/licenses/LICENSE-2.0 + repo + + core From 6214bf3d739ec304199d42516dd624934cdd8738 Mon Sep 17 00:00:00 2001 From: Sergej Dechand Date: Thu, 18 Dec 2025 17:47:13 +0100 Subject: [PATCH 4/5] Add caveats and docs --- USAGE.md => docs/README.md | 0 docs/caveats.md | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) rename USAGE.md => docs/README.md (100%) create mode 100644 docs/caveats.md diff --git a/USAGE.md b/docs/README.md similarity index 100% rename from USAGE.md rename to docs/README.md diff --git a/docs/caveats.md b/docs/caveats.md new file mode 100644 index 0000000..9424d87 --- /dev/null +++ b/docs/caveats.md @@ -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 \ No newline at end of file From 9b7644243e231dc010aef81d3c093fee6c196e51 Mon Sep 17 00:00:00 2001 From: Sergej Dechand Date: Thu, 18 Dec 2025 17:53:22 +0100 Subject: [PATCH 5/5] Add security md --- SECURITY.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..c8eecdd --- /dev/null +++ b/SECURITY.md @@ -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). \ No newline at end of file