Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ jobs:
cache: maven

- name: Build and Test
run: mvn verify --batch-mode
run: mvn clean test verify --batch-mode
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ target/
.idea/compiler.xml
.idea/libraries/
.idea/artifacts/
.idea/encodings.xml

# Sensitive or high-churn files
.idea/dataSources/
Expand Down Expand Up @@ -101,4 +102,4 @@ examples/**/invoker.properties
### Internal development files ###
CLAUDE.md
QILIN_PROGRESS.md
.claude/
.claude/
6 changes: 0 additions & 6 deletions .idea/encodings.xml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ public class PolicyChecker {
/**
* Reentrancy guard to prevent infinite recursion when instrumented methods
* (like System.getProperty, Thread.getStackTrace) are called from within check().
* NOTE: Cannot use ThreadLocal.withInitial(() -> false) because lambda creation
* triggers instrumented methods before IN_CHECK is initialized.
*/
private static final ThreadLocal<Boolean> IN_CHECK = new ThreadLocal<Boolean>() {
private static final ThreadLocal<Boolean> IN_CHECK = new ThreadLocal<>() {
@Override
protected Boolean initialValue() {
return Boolean.FALSE;
Expand Down Expand Up @@ -68,15 +66,16 @@ private static void doCheck(String capability) {
}
}

private static final String POLICY_CHECKER_CLASS = PolicyChecker.class.getName();
/** Prefix for excluding all agent package classes from capability checks */
private static final String AGENT_PACKAGE_PREFIX = PolicyChecker.class.getPackageName() + ".";

private static List<StackTraceElement> collectAppFrames() {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
List<StackTraceElement> appFrames = new ArrayList<>();
for (int i = 2; i < stack.length; i++) {
String className = stack[i].getClassName();
// Filter out JDK packages and PolicyChecker itself (to avoid self-reference in logs)
if (!JavaUtils.isJdkPackage(className) && !className.equals(POLICY_CHECKER_CLASS)) {
// Filter out JDK packages and agent classes (to avoid self-reference and recursion)
if (!JavaUtils.isJdkPackage(className) && !className.startsWith(AGENT_PACKAGE_PREFIX)) {
appFrames.add(stack[i]);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.github.serj.jcapslock.agent;
package com.github.serj.jcapslock.policy;

import capslock.proto.CapabilityOuterClass.Capability;
import com.github.serj.jcapslock.agent.PolicyChecker;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -9,6 +10,7 @@

/**
* Tests for PolicyChecker enforcement.
* This test is outside the agent package so it's not excluded from capability checks.
*/
class PolicyEnforcementTest {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,25 +153,22 @@ public String formatDiff(DiffResult diff) {
}
}

if (!diff.addedCapabilities().isEmpty()) {
sb.append("\nNew capabilities:\n");
for (Map.Entry<String, Set<Capability>> entry : diff.addedCapabilities().entrySet()) {
String pkg = entry.getKey();
for (Capability cap : entry.getValue()) {
sb.append(" + ").append(cap.name())
.append(" in ").append(pkg).append("\n");
}
// Use same wording as Go CapsLock (compare.go)
for (Map.Entry<String, Set<Capability>> entry : diff.addedCapabilities().entrySet()) {
String pkg = entry.getKey();
for (Capability cap : entry.getValue()) {
sb.append("Package ").append(pkg)
.append(" has new capability ").append(cap.name())
.append(" compared to the baseline.\n");
}
}

if (!diff.removedCapabilities().isEmpty()) {
sb.append("\nRemoved capabilities:\n");
for (Map.Entry<String, Set<Capability>> entry : diff.removedCapabilities().entrySet()) {
String pkg = entry.getKey();
for (Capability cap : entry.getValue()) {
sb.append(" - ").append(cap.name())
.append(" in ").append(pkg).append("\n");
}
for (Map.Entry<String, Set<Capability>> entry : diff.removedCapabilities().entrySet()) {
String pkg = entry.getKey();
for (Capability cap : entry.getValue()) {
sb.append("Package ").append(pkg)
.append(" no longer has capability ").append(cap.name())
.append(" which was in the baseline.\n");
}
}

Expand Down
15 changes: 9 additions & 6 deletions core/src/main/resources/interesting/openjdk-sec-manager.cm
Original file line number Diff line number Diff line change
Expand Up @@ -302,20 +302,17 @@ method java.awt.Desktop.print CAPABILITY_EXEC

# ===== CAPABILITY_ARBITRARY_EXECUTION =====
# From: java.lang.ClassLoader (checkCreateClassLoader)
# Note: Only methods that load/define new code are ARBITRARY_EXECUTION.
# Accessors (getParent, get*ClassLoader) and Class.forName are REFLECT,
# similar to Go CapsLock treating reflect.Value.Call as REFLECT, not ARBITRARY.
method java.lang.ClassLoader.<init> CAPABILITY_ARBITRARY_EXECUTION
method java.lang.ClassLoader.defineClass CAPABILITY_ARBITRARY_EXECUTION
method java.lang.ClassLoader.getParent CAPABILITY_ARBITRARY_EXECUTION
method java.lang.ClassLoader.getPlatformClassLoader CAPABILITY_ARBITRARY_EXECUTION
method java.lang.ClassLoader.getSystemClassLoader CAPABILITY_ARBITRARY_EXECUTION

# From: java.net.URLClassLoader
method java.net.URLClassLoader.<init> CAPABILITY_ARBITRARY_EXECUTION
method java.net.URLClassLoader.newInstance CAPABILITY_ARBITRARY_EXECUTION
method java.net.URLClassLoader.addURL CAPABILITY_ARBITRARY_EXECUTION

# From: java.lang.Class.forName (with custom classloader)
method java.lang.Class.forName CAPABILITY_ARBITRARY_EXECUTION

# From: java.lang.invoke.MethodHandles.Lookup (RuntimePermission defineClass)
method java.lang.invoke.MethodHandles$Lookup.defineClass CAPABILITY_ARBITRARY_EXECUTION
method java.lang.invoke.MethodHandles$Lookup.defineHiddenClass CAPABILITY_ARBITRARY_EXECUTION
Expand Down Expand Up @@ -399,6 +396,12 @@ method java.lang.Class.newInstance CAPABILITY_REFLECT
method java.lang.Class.getClassLoader CAPABILITY_REFLECT
method java.lang.Class.forName CAPABILITY_REFLECT

# From: java.lang.ClassLoader (accessors only - not code loading)
# Similar to Go's reflect.Value.Call - works with existing classpath, not arbitrary code
method java.lang.ClassLoader.getParent CAPABILITY_REFLECT
method java.lang.ClassLoader.getPlatformClassLoader CAPABILITY_REFLECT
method java.lang.ClassLoader.getSystemClassLoader CAPABILITY_REFLECT

# From: java.lang.reflect.AccessibleObject (checkPermission ReflectPermission)
method java.lang.reflect.AccessibleObject.setAccessible CAPABILITY_REFLECT
method java.lang.reflect.AccessibleObject.trySetAccessible CAPABILITY_REFLECT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ void testFormatDiff() {
String formatted = differ.formatDiff(diff);

assertNotNull(formatted);
assertTrue(formatted.contains("New capabilities"));
assertTrue(formatted.contains("has new capability"));
assertTrue(formatted.contains("CAPABILITY_NETWORK"));
assertTrue(formatted.contains("com.example:lib:1.0"));
}
Expand Down
Loading
Loading