diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index afe58ca6..167b108f 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -18,5 +18,8 @@ jobs:
with:
distribution: 'oracle'
java-version: '17'
+ - name: Build project
+ # We need to build the agent first, because the tests depend on it
+ run: mvn clean install -DskipTests
- name: Run tests
- run: mvn clean install
+ run: mvn test
diff --git a/classfile-fingerprint/src/test/resources-its/io/github/algomaster99/it/GenerateMojoIT/load_jdk_class/pom.xml b/classfile-fingerprint/src/test/resources-its/io/github/algomaster99/it/GenerateMojoIT/load_jdk_class/pom.xml
new file mode 100644
index 00000000..398fea21
--- /dev/null
+++ b/classfile-fingerprint/src/test/resources-its/io/github/algomaster99/it/GenerateMojoIT/load_jdk_class/pom.xml
@@ -0,0 +1,43 @@
+
+
+ 4.0.0
+
+ org.example
+ jdk_class
+ 1.0-SNAPSHOT
+
+
+ 15
+ 15
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.6.0
+
+
+
+ com.sun.CustomClass
+
+
+
+ jar-with-dependencies
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
+
diff --git a/classfile-fingerprint/src/test/resources-its/io/github/algomaster99/it/GenerateMojoIT/load_jdk_class/src/main/java/com/sun/CustomClass.java b/classfile-fingerprint/src/test/resources-its/io/github/algomaster99/it/GenerateMojoIT/load_jdk_class/src/main/java/com/sun/CustomClass.java
new file mode 100644
index 00000000..b4086908
--- /dev/null
+++ b/classfile-fingerprint/src/test/resources-its/io/github/algomaster99/it/GenerateMojoIT/load_jdk_class/src/main/java/com/sun/CustomClass.java
@@ -0,0 +1,7 @@
+package com.sun;
+
+public class CustomClass {
+ public static void main(String[] args) {
+ System.out.println("Hello World!");
+ }
+}
diff --git a/pom.xml b/pom.xml
index 8a87d1d1..25b3380f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,6 +62,11 @@
junit-jupiter-api
5.10.0
+
+ org.apache.maven.shared
+ maven-invoker
+ 3.2.0
+
diff --git a/watchdog-agent/pom.xml b/watchdog-agent/pom.xml
index 9b9a79dc..c0ffdc8c 100644
--- a/watchdog-agent/pom.xml
+++ b/watchdog-agent/pom.xml
@@ -50,6 +50,14 @@
asm-util
9.5
+
+ org.junit.jupiter
+ junit-jupiter-api
+
+
+ org.apache.maven.shared
+ maven-invoker
+
@@ -96,6 +104,18 @@
package
+
+
+ shade-for-test
+
+ shade
+
+ package
+
+ ${project.build.directory}/classes
+ ${project.artifactId}
+
+
diff --git a/watchdog-agent/src/test/java/AgentTest.java b/watchdog-agent/src/test/java/AgentTest.java
new file mode 100644
index 00000000..d22a012d
--- /dev/null
+++ b/watchdog-agent/src/test/java/AgentTest.java
@@ -0,0 +1,113 @@
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.github.algomaster99.Terminator;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.List;
+import org.apache.maven.shared.invoker.DefaultInvocationRequest;
+import org.apache.maven.shared.invoker.DefaultInvoker;
+import org.apache.maven.shared.invoker.InvocationRequest;
+import org.apache.maven.shared.invoker.InvocationResult;
+import org.apache.maven.shared.invoker.Invoker;
+import org.apache.maven.shared.invoker.MavenInvocationException;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+public class AgentTest {
+ @Disabled("Should be worked upon after the input is from an SBOM and not maven project")
+ @Test
+ void shouldDisallowLoadingCustomJDKClass() throws MavenInvocationException, IOException, InterruptedException {
+ // contract: watchdog-agent should detect if the class masquerading as an internal class
+
+ Path project = Paths.get("src/test/resources/load_jdk_class");
+
+ recursiveDeleteOnShutdownHook(project.resolve("target"));
+
+ InvocationRequest request = new DefaultInvocationRequest();
+ File pomFile = project.resolve("pom.xml").toFile();
+ request.setPomFile(pomFile);
+ request.setGoals(List.of("clean", "package"));
+
+ Invoker invoker = new DefaultInvoker();
+ InvocationResult result = invoker.execute(request);
+
+ assertThat(result.getExitCode()).isEqualTo(0);
+
+ String fingerprintFile =
+ project.resolve("target").resolve("classfile.sha256.jsonl").toString();
+ deleteContentsOfFile(fingerprintFile);
+
+ String agentArgs = "fingerprints=" + fingerprintFile;
+ String[] cmd = {
+ "java",
+ "-javaagent:" + getAgentPath(agentArgs),
+ "-jar",
+ project.resolve("target")
+ .resolve("jdk_class-1.0-SNAPSHOT-jar-with-dependencies.jar")
+ .toString()
+ };
+ ProcessBuilder pb = new ProcessBuilder(cmd);
+ pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
+ pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
+ pb.redirectError(ProcessBuilder.Redirect.INHERIT);
+
+ Process p = pb.start();
+ int exitCode = p.waitFor();
+
+ assertThat(exitCode).isEqualTo(1);
+ }
+
+ private static void deleteContentsOfFile(String file) throws InterruptedException, IOException {
+ String[] deleteFile = {"rm", "-f", file};
+ Runtime.getRuntime().exec(deleteFile).waitFor();
+
+ String[] createFile = {"touch", file};
+ Runtime.getRuntime().exec(createFile).waitFor();
+ }
+
+ private static String getAgentPath(String agentArgs) throws IOException {
+ String tempDir = System.getProperty("java.io.tmpdir");
+ Path traceCollector = Path.of(tempDir, "watchdog-agent.jar");
+
+ try (InputStream traceCollectorStream = Terminator.class.getResourceAsStream("/watchdog-agent.jar")) {
+ Files.copy(traceCollectorStream, traceCollector, StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ return traceCollector.toAbsolutePath() + "=" + agentArgs;
+ }
+
+ private static void recursiveDeleteOnShutdownHook(final Path path) {
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ try {
+ Files.walkFileTree(path, new SimpleFileVisitor<>() {
+ @Override
+ public FileVisitResult visitFile(Path file, @SuppressWarnings("unused") BasicFileAttributes attrs)
+ throws IOException {
+ Files.deleteIfExists(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
+ if (e == null) {
+ Files.deleteIfExists(dir);
+ return FileVisitResult.CONTINUE;
+ }
+ // directory iteration failed
+ throw e;
+ }
+ });
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to delete " + path, e);
+ }
+ }));
+ }
+}
diff --git a/watchdog-agent/src/test/resources/load_jdk_class/pom.xml b/watchdog-agent/src/test/resources/load_jdk_class/pom.xml
new file mode 100644
index 00000000..398fea21
--- /dev/null
+++ b/watchdog-agent/src/test/resources/load_jdk_class/pom.xml
@@ -0,0 +1,43 @@
+
+
+ 4.0.0
+
+ org.example
+ jdk_class
+ 1.0-SNAPSHOT
+
+
+ 15
+ 15
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.6.0
+
+
+
+ com.sun.CustomClass
+
+
+
+ jar-with-dependencies
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
+
diff --git a/watchdog-agent/src/test/resources/load_jdk_class/src/main/java/com/sun/CustomClass.java b/watchdog-agent/src/test/resources/load_jdk_class/src/main/java/com/sun/CustomClass.java
new file mode 100644
index 00000000..b4086908
--- /dev/null
+++ b/watchdog-agent/src/test/resources/load_jdk_class/src/main/java/com/sun/CustomClass.java
@@ -0,0 +1,7 @@
+package com.sun;
+
+public class CustomClass {
+ public static void main(String[] args) {
+ System.out.println("Hello World!");
+ }
+}