CVE-2022-41967: Improper Restriction of XML External Entity Reference (CWE-611)
value: Dragonfly is a Java runtime dependency management library. Dragonfly v0.3.0-SNAPSHOT does not configure DocumentBuilderFactory to prevent XML external entity (XXE) attacks. This issue is patched in 0.3.1-SNAPSHOT. As a workaround, since Dragonfly only parses XML `SNAPSHOT` versions are being resolved, this vulnerability may be avoided by not trying to resolve `SNAPSHOT` versions.
CWE description: The product processes an XML document that can contain XML entities with URIs that resolve to documents outside of the intended sphere of control, causing the product to embed incorrect documents into its output.
CVSS v3 base score: 7.5
Published: 2022-12-28T00:15Z
Repository: https://github.com/HyperaDev/Dragonfly
Language: Java
Files changed: LICENSE, SECURITY.md, pom.xml, src/main/java/dev/hypera/dragonfly/Dragonfly.java, src/main/java/dev/hypera/dragonfly/DragonflyBuilder.java, src/main/java/dev/hypera/dragonfly/downloaders/impl/MavenDownloader.java, src/main/java/dev/hypera/dragonfly/exceptions/DownloadFailureException.java, src/main/java/dev/hypera/dragonfly/exceptions/DragonflyException.java, src/main/java/dev/hypera/dragonfly/exceptions/LoadFailureException.java, src/main/java/dev/hypera/dragonfly/exceptions/RelocationFailureException.java, src/main/java/dev/hypera/dragonfly/exceptions/ResolveFailureException.java, src/main/java/dev/hypera/dragonfly/loading/DependencyLoader.java, src/main/java/dev/hypera/dragonfly/loading/DragonflyClassLoader.java, src/main/java/dev/hypera/dragonfly/relocation/DependencyRelocator.java, src/main/java/dev/hypera/dragonfly/relocation/Relocation.java, src/main/java/dev/hypera/dragonfly/resolvers/impl/MavenResolver.java, src/main/java/dev/hypera/dragonfly/resolvers/impl/MavenSnapshotResolver.java
Commit date: 2022-12-24 03:23:57 +1100
Commit message: fix: CVE-2022-41967

Dragonfly v0.3.0-SNAPSHOT fails to properly configure the
DocumentBuilderFactory to prevent XML enternal entity (XXE) attacks when
parsing maven-metadata.xml files provided by external Maven repositories
during "SNAPSHOT" version resolution.

This patches CVE-2022-41967 by disabling features which may lead to XXE.
If you are currently using v0.3.0-SNAPSHOT it is STRONGLY advised to
update Dragonfly to v0.3.1-SNAPSHOT just to be safe.

Diff: diff --git a/AUTHORS b/AUTHORS
deleted file mode 100644
index 67c9810..0000000
--- a/AUTHORS
+++ /dev/null
@@ -1,8 +0,0 @@
-#  This is a list of entities who own copyright to portions of Chameleon's code.
-#  Please keep this list sorted alphabetically.
-#
-#  Acceptable formats:
-#    Organization Name
-#    My Name <myname@email.com>
-
-Joshua Sing <joshua@hypera.dev>
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 64045e6..3632f2d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,8 +1,8 @@
 MIT License
 
-Copyright (c) 2021 Joshua Sing <joshua@hypera.dev>
+Copyright (c) 2021-2022 Joshua Sing <joshua@hypera.dev>
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
diff --git a/SECURITY.md b/SECURITY.md
index 2572920..45bea55 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -5,11 +5,11 @@
 
 -----------
 
 ### Supported Versions
 | Version | Supported          |
-| ------- | ------------------ |
-| 0.3.0   | :white_check_mark: |
+|---------| ------------------ |
+| 0.3.x   | :white_check_mark: |
 | < 0.3   | :x:                |
 
 ### Reporting a Vulnerability
 Please direct all security related issues to [security@hypera.dev](mailto:security@hypera.dev).
diff --git a/pom.xml b/pom.xml
index 0f74581..debe78b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,11 +27,11 @@
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
 
   <groupId>dev.hypera</groupId>
   <artifactId>Dragonfly</artifactId>
-  <version>0.3.0-SNAPSHOT</version>
+  <version>0.3.1-SNAPSHOT</version>
   <packaging>jar</packaging>
 
   <name>Dragonfly</name>
   <description>Runtime dependency management library</description>
   <url>https://github.com/HyperaOfficial/Dragonfly</url>
@@ -119,11 +119,11 @@
 
   <dependencies>
     <dependency>
       <groupId>org.jetbrains</groupId>
       <artifactId>annotations</artifactId>
-      <version>22.0.0</version>
+      <version>23.1.0</version>
     </dependency>
     <dependency>
       <groupId>me.lucko</groupId>
       <artifactId>jar-relocator</artifactId>
       <version>1.5</version>
diff --git a/src/main/java/dev/hypera/dragonfly/Dragonfly.java b/src/main/java/dev/hypera/dragonfly/Dragonfly.java
index 3b99701..ef59387 100644
--- a/src/main/java/dev/hypera/dragonfly/Dragonfly.java
+++ b/src/main/java/dev/hypera/dragonfly/Dragonfly.java
@@ -38,27 +38,28 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import org.jetbrains.annotations.ApiStatus.Internal;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Main Dragonfly class.
  * @author Joshua Sing <joshua@hypera.dev>
  */
 public class Dragonfly {
 
-	private static final String VERSION = "0.3.0-SNAPSHOT";
+	private static final @NotNull String VERSION = "0.3.1-SNAPSHOT";
 
 	private final int timeout;
-	private final Path directory;
-	private final Set<String> repositories;
-	private final Consumer<Status> statusHandler;
+	private final @NotNull Path directory;
+	private final @NotNull Set<String> repositories;
+	private final @NotNull Consumer<Status> statusHandler;
 
-	private final DependencyDownloader dependencyDownloader = new DependencyDownloader(this);
-	private final DependencyRelocator dependencyRelocator;
-	private final DependencyLoader dependencyLoader;
+	private final @NotNull DependencyDownloader dependencyDownloader = new DependencyDownloader(this);
+	private final @NotNull DependencyRelocator dependencyRelocator;
+	private final @NotNull DependencyLoader dependencyLoader;
 
 
 	@Internal
 	protected Dragonfly(int timeout, IClassLoader classLoader, Path directory, Set<String> repositories, boolean delete, Consumer<Status> statusHandler) throws IOException {
 		this.timeout = timeout;
@@ -72,21 +73,21 @@ public class Dragonfly {
 		if (!Files.exists(directory)) {
 			Files.createDirectories(directory);
 		}
 	}
 
-	public static String getVersion() {
+	public static @NotNull String getVersion() {
 		return VERSION;
 	}
 
 	/**
 	 * Download, relocate and load dependencies.
 	 *
 	 * @param dependencies Dependencies to be loaded.
 	 * @return If the load was successful, in the form of a {@link CompletableFuture<Boolean>}.
 	 */
-	public CompletableFuture<Boolean> load(Dependency... dependencies) {
+	public @NotNull CompletableFuture<Boolean> load(@NotNull Dependency... dependencies) {
 		return CompletableFuture.supplyAsync(() -> {
 			try {
 				statusHandler.accept(Status.STARTING);
 				List<Dependency> dependencyList = Arrays.stream(dependencies)
 						.sorted(Comparator.comparingInt(Dependency::getPriority)).collect(Collectors.toList());
@@ -135,24 +136,24 @@ public class Dragonfly {
 
 	public int getTimeout() {
 		return timeout;
 	}
 
-	public Path getDirectory() {
+	public @NotNull Path getDirectory() {
 		return directory;
 	}
 
-	public Set<String> getRepositories() {
+	public @NotNull Set<String> getRepositories() {
 		return repositories;
 	}
 
 	/**
 	 * Get {@link DependencyDownloader}, for internal use only.
 	 *
 	 * @return Stored instance of {@link DependencyDownloader}.
 	 */
 	@Internal
-	public DependencyDownloader getDependencyDownloader() {
+	public @NotNull DependencyDownloader getDependencyDownloader() {
 		return dependencyDownloader;
 	}
 
 }
diff --git a/src/main/java/dev/hypera/dragonfly/DragonflyBuilder.java b/src/main/java/dev/hypera/dragonfly/DragonflyBuilder.java
index 6c4b8e8..3d35043 100644
--- a/src/main/java/dev/hypera/dragonfly/DragonflyBuilder.java
+++ b/src/main/java/dev/hypera/dragonfly/DragonflyBuilder.java
@@ -30,10 +30,11 @@ import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.function.Consumer;
+import org.jetbrains.annotations.Contract;
 import org.jetbrains.annotations.NotNull;
 
 /**
  * {@link Dragonfly} builder.
  * @author Joshua Sing <joshua@hypera.dev>
@@ -113,10 +114,11 @@ public class DragonflyBuilder {
 	/**
 	 * Builds a new Dragonfly instance using the provided settings.
 	 *
 	 * @return New {@link Dragonfly} instance.
 	 */
+	@Contract(value = "-> new", pure = true)
 	public @NotNull Dragonfly build() {
 		try {
 			return new Dragonfly(timeout, classLoader, directory, repositories, deleteOnRelocate, statusHandler);
 		} catch (IOException ex) {
 			throw new IllegalStateException(ex);
diff --git a/src/main/java/dev/hypera/dragonfly/downloaders/impl/MavenDownloader.java b/src/main/java/dev/hypera/dragonfly/downloaders/impl/MavenDownloader.java
index eca6c92..767165a 100644
--- a/src/main/java/dev/hypera/dragonfly/downloaders/impl/MavenDownloader.java
+++ b/src/main/java/dev/hypera/dragonfly/downloaders/impl/MavenDownloader.java
@@ -39,12 +39,12 @@ import org.jetbrains.annotations.NotNull;
  * @author Joshua Sing <joshua@hypera.dev>
  */
 @Downloader(MavenDependency.class)
 public class MavenDownloader implements IDownloader<MavenDependency> {
 
-	private final MavenResolver resolver = new MavenResolver();
-	private final MavenSnapshotResolver snapshotResolver = new MavenSnapshotResolver();
+	private final @NotNull MavenResolver resolver = new MavenResolver();
+	private final @NotNull MavenSnapshotResolver snapshotResolver = new MavenSnapshotResolver();
 
 	@Override
 	public void download(@NotNull Dragonfly dragonfly, @NotNull MavenDependency dependency) throws DownloadFailureException {
 		String url;
 		if (dependency.getVersion().contains("-SNAPSHOT")) {
diff --git a/src/main/java/dev/hypera/dragonfly/exceptions/DownloadFailureException.java b/src/main/java/dev/hypera/dragonfly/exceptions/DownloadFailureException.java
index 1880a5c..41e818f 100644
--- a/src/main/java/dev/hypera/dragonfly/exceptions/DownloadFailureException.java
+++ b/src/main/java/dev/hypera/dragonfly/exceptions/DownloadFailureException.java
@@ -21,28 +21,32 @@
  *  SOFTWARE.
  */
 
 package dev.hypera.dragonfly.exceptions;
 
+import org.jetbrains.annotations.NotNull;
+
 public class DownloadFailureException extends DragonflyException {
 
+	private static final long serialVersionUID = 5648475409314204882L;
+
 	public DownloadFailureException() {
 		super();
 	}
 
-	public DownloadFailureException(String message) {
+	public DownloadFailureException(@NotNull String message) {
 		super(message);
 	}
 
-	public DownloadFailureException(String message, Throwable cause) {
+	public DownloadFailureException(@NotNull String message, @NotNull Throwable cause) {
 		super(message, cause);
 	}
 
-	public DownloadFailureException(Throwable cause) {
+	public DownloadFailureException(@NotNull Throwable cause) {
 		super(cause);
 	}
 
-	protected DownloadFailureException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+	protected DownloadFailureException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
 		super(message, cause, enableSuppression, writableStackTrace);
 	}
 
 }
diff --git a/src/main/java/dev/hypera/dragonfly/exceptions/DragonflyException.java b/src/main/java/dev/hypera/dragonfly/exceptions/DragonflyException.java
index 62ec283..79252da 100644
--- a/src/main/java/dev/hypera/dragonfly/exceptions/DragonflyException.java
+++ b/src/main/java/dev/hypera/dragonfly/exceptions/DragonflyException.java
@@ -21,32 +21,36 @@
  *  SOFTWARE.
  */
 
 package dev.hypera.dragonfly.exceptions;
 
+import org.jetbrains.annotations.NotNull;
+
 /**
  * Dragonfly Exception.
  * @author Joshua Sing <joshua@hypera.dev>
  */
 public class DragonflyException extends Exception {
 
+	private static final long serialVersionUID = 3565376065959848642L;
+
 	public DragonflyException() {
 		super();
 	}
 
-	public DragonflyException(String message) {
+	public DragonflyException(@NotNull String message) {
 		super(message);
 	}
 
-	public DragonflyException(String message, Throwable cause) {
+	public DragonflyException(@NotNull String message, @NotNull Throwable cause) {
 		super(message, cause);
 	}
 
-	public DragonflyException(Throwable cause) {
+	public DragonflyException(@NotNull Throwable cause) {
 		super(cause);
 	}
 
-	protected DragonflyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+	protected DragonflyException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
 		super(message, cause, enableSuppression, writableStackTrace);
 	}
 
 }
diff --git a/src/main/java/dev/hypera/dragonfly/exceptions/LoadFailureException.java b/src/main/java/dev/hypera/dragonfly/exceptions/LoadFailureException.java
index 27fae4f..87850f6 100644
--- a/src/main/java/dev/hypera/dragonfly/exceptions/LoadFailureException.java
+++ b/src/main/java/dev/hypera/dragonfly/exceptions/LoadFailureException.java
@@ -21,28 +21,32 @@
  *  SOFTWARE.
  */
 
 package dev.hypera.dragonfly.exceptions;
 
+import org.jetbrains.annotations.NotNull;
+
 public class LoadFailureException extends DragonflyException {
 
+	private static final long serialVersionUID = -8555618454694039541L;
+
 	public LoadFailureException() {
 		super();
 	}
 
-	public LoadFailureException(String message) {
+	public LoadFailureException(@NotNull String message) {
 		super(message);
 	}
 
-	public LoadFailureException(String message, Throwable cause) {
+	public LoadFailureException(@NotNull String message, @NotNull Throwable cause) {
 		super(message, cause);
 	}
 
-	public LoadFailureException(Throwable cause) {
+	public LoadFailureException(@NotNull Throwable cause) {
 		super(cause);
 	}
 
-	protected LoadFailureException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+	protected LoadFailureException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
 		super(message, cause, enableSuppression, writableStackTrace);
 	}
 
 }
diff --git a/src/main/java/dev/hypera/dragonfly/exceptions/RelocationFailureException.java b/src/main/java/dev/hypera/dragonfly/exceptions/RelocationFailureException.java
index cccf256..9a2ca58 100644
--- a/src/main/java/dev/hypera/dragonfly/exceptions/RelocationFailureException.java
+++ b/src/main/java/dev/hypera/dragonfly/exceptions/RelocationFailureException.java
@@ -21,28 +21,32 @@
  *  SOFTWARE.
  */
 
 package dev.hypera.dragonfly.exceptions;
 
+import org.jetbrains.annotations.NotNull;
+
 public class RelocationFailureException extends DragonflyException {
 
+	private static final long serialVersionUID = -2817144091943660838L;
+
 	public RelocationFailureException() {
 		super();
 	}
 
-	public RelocationFailureException(String message) {
+	public RelocationFailureException(@NotNull String message) {
 		super(message);
 	}
 
-	public RelocationFailureException(String message, Throwable cause) {
+	public RelocationFailureException(@NotNull String message, @NotNull Throwable cause) {
 		super(message, cause);
 	}
 
-	public RelocationFailureException(Throwable cause) {
+	public RelocationFailureException(@NotNull Throwable cause) {
 		super(cause);
 	}
 
-	protected RelocationFailureException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+	protected RelocationFailureException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
 		super(message, cause, enableSuppression, writableStackTrace);
 	}
 
 }
diff --git a/src/main/java/dev/hypera/dragonfly/exceptions/ResolveFailureException.java b/src/main/java/dev/hypera/dragonfly/exceptions/ResolveFailureException.java
index 24f25b3..1f82577 100644
--- a/src/main/java/dev/hypera/dragonfly/exceptions/ResolveFailureException.java
+++ b/src/main/java/dev/hypera/dragonfly/exceptions/ResolveFailureException.java
@@ -21,28 +21,32 @@
  *  SOFTWARE.
  */
 
 package dev.hypera.dragonfly.exceptions;
 
+import org.jetbrains.annotations.NotNull;
+
 public class ResolveFailureException extends DownloadFailureException {
 
+	private static final long serialVersionUID = -8393996338726021911L;
+
 	public ResolveFailureException() {
 		super();
 	}
 
-	public ResolveFailureException(String message) {
+	public ResolveFailureException(@NotNull String message) {
 		super(message);
 	}
 
-	public ResolveFailureException(String message, Throwable cause) {
+	public ResolveFailureException(@NotNull String message, @NotNull Throwable cause) {
 		super(message, cause);
 	}
 
-	public ResolveFailureException(Throwable cause) {
+	public ResolveFailureException(@NotNull Throwable cause) {
 		super(cause);
 	}
 
-	protected ResolveFailureException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+	protected ResolveFailureException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
 		super(message, cause, enableSuppression, writableStackTrace);
 	}
 
 }
diff --git a/src/main/java/dev/hypera/dragonfly/loading/DependencyLoader.java b/src/main/java/dev/hypera/dragonfly/loading/DependencyLoader.java
index 940dc5c..aa46e4c 100644
--- a/src/main/java/dev/hypera/dragonfly/loading/DependencyLoader.java
+++ b/src/main/java/dev/hypera/dragonfly/loading/DependencyLoader.java
@@ -27,34 +27,35 @@ import dev.hypera.dragonfly.Dragonfly;
 import dev.hypera.dragonfly.dependency.Dependency;
 import dev.hypera.dragonfly.exceptions.LoadFailureException;
 import java.net.MalformedURLException;
 import java.util.List;
 import org.jetbrains.annotations.ApiStatus.Internal;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Dependency loader.
  *
  * @author Joshua Sing <joshua@hypera.dev>
  */
 public class DependencyLoader {
 
-	private final Dragonfly dragonfly;
-	private final IClassLoader classLoader;
+	private final @NotNull Dragonfly dragonfly;
+	private final @NotNull IClassLoader classLoader;
 
 	@Internal
-	public DependencyLoader(Dragonfly dragonfly, IClassLoader classLoader) {
+	public DependencyLoader(@NotNull Dragonfly dragonfly, @NotNull IClassLoader classLoader) {
 		this.dragonfly = dragonfly;
 		this.classLoader = classLoader;
 	}
 
 	/**
 	 * Attempt to load a list of dependencies.
 	 *
 	 * @param dependencies Dependencies to be loaded.
 	 * @throws LoadFailureException if something went wrong while loading the dependencies.
 	 */
-	public void load(List<Dependency> dependencies) throws LoadFailureException {
+	public void load(@NotNull List<Dependency> dependencies) throws LoadFailureException {
 		for (Dependency dependency : dependencies) {
 			load(dependency);
 		}
 	}
 
@@ -62,11 +63,11 @@ public class DependencyLoader {
 	 * Attempt to load a dependency.
 	 *
 	 * @param dependency Dependency to be loaded.
 	 * @throws LoadFailureException if something went wrong while loading the dependency.
 	 */
-	private void load(Dependency dependency) throws LoadFailureException {
+	private void load(@NotNull Dependency dependency) throws LoadFailureException {
 		try {
 			classLoader.addURL(dragonfly.getDirectory().resolve(dependency.getFileName()).toUri().toURL());
 		} catch (MalformedURLException ex) {
 			throw new LoadFailureException(ex);
 		}
diff --git a/src/main/java/dev/hypera/dragonfly/loading/DragonflyClassLoader.java b/src/main/java/dev/hypera/dragonfly/loading/DragonflyClassLoader.java
index 5cb38b6..889e039 100644
--- a/src/main/java/dev/hypera/dragonfly/loading/DragonflyClassLoader.java
+++ b/src/main/java/dev/hypera/dragonfly/loading/DragonflyClassLoader.java
@@ -24,30 +24,31 @@
 package dev.hypera.dragonfly.loading;
 
 import java.net.URL;
 import java.net.URLClassLoader;
 import org.jetbrains.annotations.ApiStatus.Internal;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Dragonfly class loader, a child-first {@link URLClassLoader} used for loading Dragonfly's internal dependencies.
  *
  * @author Joshua Sing <joshua@hypera.dev>
  */
 @Internal
 public class DragonflyClassLoader extends URLClassLoader {
 
-	public DragonflyClassLoader(ClassLoader classLoader) {
+	public DragonflyClassLoader(@NotNull ClassLoader classLoader) {
 		super(new URL[0], classLoader);
 	}
 
 	@Override
-	public void addURL(URL url) {
+	public void addURL(@NotNull URL url) {
 		super.addURL(url);
 	}
 
 	@Override
-	protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+	protected @NotNull Class<?> loadClass(@NotNull String name, boolean resolve) throws ClassNotFoundException {
 		Class<?> loadedClass = findLoadedClass(name);
 		if (null == loadedClass) {
 			try {
 				loadedClass = findClass(name);
 			} catch (ClassNotFoundException ex) {
diff --git a/src/main/java/dev/hypera/dragonfly/relocation/DependencyRelocator.java b/src/main/java/dev/hypera/dragonfly/relocation/DependencyRelocator.java
index 9132a76..bfb88db 100644
--- a/src/main/java/dev/hypera/dragonfly/relocation/DependencyRelocator.java
+++ b/src/main/java/dev/hypera/dragonfly/relocation/DependencyRelocator.java
@@ -39,33 +39,34 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import org.jetbrains.annotations.ApiStatus.Internal;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Dependency relocator.
  *
  * @author Joshua Sing <joshua@hypera.dev>
  */
 public class DependencyRelocator {
 
-	private static final String RELOCATED_FILENAME = "%s-relocated.jar";
+	private static final @NotNull String RELOCATED_FILENAME = "%s-relocated.jar";
 
-	private final Dragonfly dragonfly;
+	private final @NotNull Dragonfly dragonfly;
 	private final boolean deleteOld;
 
-	private final DragonflyClassLoader classLoader;
-	private final DependencyLoader dependencyLoader;
+	private final @NotNull DragonflyClassLoader classLoader;
+	private final @NotNull DependencyLoader dependencyLoader;
 
 	private boolean dependenciesLoaded;
 
-	private Constructor<?> constructor;
-	private Method method;
+	private @Nullable Constructor<?> constructor;
+	private @Nullable Method method;
 
 	@Internal
-	public DependencyRelocator(Dragonfly dragonfly, boolean deleteOld) {
+	public DependencyRelocator(@NotNull Dragonfly dragonfly, boolean deleteOld) {
 		this.dragonfly = dragonfly;
 		this.deleteOld = deleteOld;
 		this.classLoader = new DragonflyClassLoader(getClass().getClassLoader());
 		this.dependencyLoader = new DependencyLoader(dragonfly, classLoader::addURL);
 	}
diff --git a/src/main/java/dev/hypera/dragonfly/relocation/Relocation.java b/src/main/java/dev/hypera/dragonfly/relocation/Relocation.java
index d88c529..8b80923 100644
--- a/src/main/java/dev/hypera/dragonfly/relocation/Relocation.java
+++ b/src/main/java/dev/hypera/dragonfly/relocation/Relocation.java
@@ -30,26 +30,26 @@ import org.jetbrains.annotations.NotNull;
  *
  * @author Joshua Sing <joshua@hypera.dev>
  */
 public class Relocation {
 
-	private final String from;
-	private final String to;
+	private final @NotNull String from;
+	private final @NotNull String to;
 
-	private Relocation(String from, String to) {
+	private Relocation(@NotNull String from, @NotNull String to) {
 		this.from = from.replace("\\.", ".");
 		this.to = to;
 	}
 
 	public static @NotNull Relocation of(@NotNull String from, @NotNull String to) {
 		return new Relocation(from, to);
 	}
 
-	public String getFrom() {
+	public @NotNull String getFrom() {
 		return from;
 	}
 
-	public String getTo() {
+	public @NotNull String getTo() {
 		return to;
 	}
 
 }
diff --git a/src/main/java/dev/hypera/dragonfly/resolvers/impl/MavenResolver.java b/src/main/java/dev/hypera/dragonfly/resolvers/impl/MavenResolver.java
index 16c9d1f..4f01643 100644
--- a/src/main/java/dev/hypera/dragonfly/resolvers/impl/MavenResolver.java
+++ b/src/main/java/dev/hypera/dragonfly/resolvers/impl/MavenResolver.java
@@ -38,11 +38,11 @@ import org.jetbrains.annotations.Nullable;
  *
  * @author Joshua Sing <joshua@hypera.dev>
  */
 public class MavenResolver implements IResolver<MavenDependency> {
 
-	private static final String FORMAT = "%s%s/%s/%s/%s-%s.jar";
+	private static final @NotNull String FORMAT = "%s%s/%s/%s/%s-%s.jar";
 
 	@Override
 	public @Nullable String resolve(@NotNull Dragonfly dragonfly, @NotNull MavenDependency dependency) throws ResolveFailureException {
 		Set<String> urls = getUrls(dragonfly, dependency);
 		if (urls.isEmpty()) {
diff --git a/src/main/java/dev/hypera/dragonfly/resolvers/impl/MavenSnapshotResolver.java b/src/main/java/dev/hypera/dragonfly/resolvers/impl/MavenSnapshotResolver.java
index 83043b8..c08df23 100644
--- a/src/main/java/dev/hypera/dragonfly/resolvers/impl/MavenSnapshotResolver.java
+++ b/src/main/java/dev/hypera/dragonfly/resolvers/impl/MavenSnapshotResolver.java
@@ -25,30 +25,51 @@ package dev.hypera.dragonfly.resolvers.impl;
 
 import dev.hypera.dragonfly.Dragonfly;
 import dev.hypera.dragonfly.dependency.impl.MavenDependency;
 import dev.hypera.dragonfly.exceptions.ResolveFailureException;
 import dev.hypera.dragonfly.resolvers.IResolver;
+import java.io.IOException;
 import java.io.StringReader;
 import java.util.Set;
 import java.util.stream.Collectors;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.xml.sax.InputSource;
+import sun.tools.jstat.ParserException;
 
 /**
  * Maven snapshot resolver.
  *
  * @author Joshua Sing <joshua@hypera.dev>
  */
 public class MavenSnapshotResolver implements IResolver<MavenDependency> {
 
-	private static final String FORMAT = "%s%s/%s/%s/maven-metadata.xml";
-	private static final String OUTPUT_FORMAT = "%s/%s-%s-%s-%s.jar";
+	private static final @NotNull String FORMAT = "%s%s/%s/%s/maven-metadata.xml";
+	private static final @NotNull String OUTPUT_FORMAT = "%s/%s-%s-%s-%s.jar";
+	private final @NotNull DocumentBuilderFactory documentBuilderFactory;
+
+	public MavenSnapshotResolver() {
+		try {
+			/* The below is an attempt to create an XML parser while preventing XML External Entity attacks */
+			/* Read more: https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#java */
+			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+			factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+			factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+			factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+			factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+			factory.setXIncludeAware(false);
+			factory.setExpandEntityReferences(false);
+			this.documentBuilderFactory = factory;
+		} catch (ParserConfigurationException ex) {
+			throw new RuntimeException("Failed to create DocumentBuilderFactory", ex);
+		}
+	}
 
 	@Override
 	public @Nullable String resolve(@NotNull Dragonfly dragonfly, @NotNull MavenDependency dependency) throws ResolveFailureException {
 		if (!dependency.getVersion().contains("SNAPSHOT")) {
 			throw new ResolveFailureException("Cannot resolve a dependency as a snapshot if it isn't a snapshot");
@@ -70,11 +91,11 @@ public class MavenSnapshotResolver implements IResolver<MavenDependency> {
 			if (null == data) {
 				throw new ResolveFailureException("Cannot resolve dependency: " + dependency);
 			}
 
 			try {
-				DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+				DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
 				Document document = builder.parse(new InputSource(new StringReader(data)));
 				Element root = document.getDocumentElement();
 				Element snapshotData = (Element) root.getElementsByTagName("snapshot").item(0);
 
 				String timestamp = snapshotData.getElementsByTagName("timestamp").item(0).getTextContent();

Vulnerable code: #  This is a list of entities who own copyright to portions of Chameleon's code.
#  Please keep this list sorted alphabetically.
#
#  Acceptable formats:
#    Organization Name
#    My Name <myname@email.com>

Joshua Sing <joshua@hypera.dev>
Copyright (c) 2021 Joshua Sing <joshua@hypera.dev>
| ------- | ------------------ |
| 0.3.0   | :white_check_mark: |
  <version>0.3.0-SNAPSHOT</version>
      <version>22.0.0</version>
	private static final String VERSION = "0.3.0-SNAPSHOT";
	private final Path directory;
	private final Set<String> repositories;
	private final Consumer<Status> statusHandler;
	private final DependencyDownloader dependencyDownloader = new DependencyDownloader(this);
	private final DependencyRelocator dependencyRelocator;
	private final DependencyLoader dependencyLoader;
	public static String getVersion() {
	public CompletableFuture<Boolean> load(Dependency... dependencies) {
	public Path getDirectory() {
	public Set<String> getRepositories() {
	public DependencyDownloader getDependencyDownloader() {
	private final MavenResolver resolver = new MavenResolver();
	private final MavenSnapshotResolver snapshotResolver = new MavenSnapshotResolver();
	public DownloadFailureException(String message) {
	public DownloadFailureException(String message, Throwable cause) {
	public DownloadFailureException(Throwable cause) {
	protected DownloadFailureException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
	public DragonflyException(String message) {
	public DragonflyException(String message, Throwable cause) {
	public DragonflyException(Throwable cause) {
	protected DragonflyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
	public LoadFailureException(String message) {
	public LoadFailureException(String message, Throwable cause) {
	public LoadFailureException(Throwable cause) {
	protected LoadFailureException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
	public RelocationFailureException(String message) {
	public RelocationFailureException(String message, Throwable cause) {
	public RelocationFailureException(Throwable cause) {
	protected RelocationFailureException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
	public ResolveFailureException(String message) {
	public ResolveFailureException(String message, Throwable cause) {
	public ResolveFailureException(Throwable cause) {
	protected ResolveFailureException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
	private final Dragonfly dragonfly;
	private final IClassLoader classLoader;
	public DependencyLoader(Dragonfly dragonfly, IClassLoader classLoader) {
	public void load(List<Dependency> dependencies) throws LoadFailureException {
	private void load(Dependency dependency) throws LoadFailureException {
	public DragonflyClassLoader(ClassLoader classLoader) {
	public void addURL(URL url) {
	protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
	private static final String RELOCATED_FILENAME = "%s-relocated.jar";
	private final Dragonfly dragonfly;
	private final DragonflyClassLoader classLoader;
	private final DependencyLoader dependencyLoader;
	private Constructor<?> constructor;
	private Method method;
	public DependencyRelocator(Dragonfly dragonfly, boolean deleteOld) {
	private final String from;
	private final String to;
	private Relocation(String from, String to) {
	public String getFrom() {
	public String getTo() {
	private static final String FORMAT = "%s%s/%s/%s/%s-%s.jar";
	private static final String FORMAT = "%s%s/%s/%s/maven-metadata.xml";
	private static final String OUTPUT_FORMAT = "%s/%s-%s-%s-%s.jar";
				DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Fixed code: Copyright (c) 2021-2022 Joshua Sing <joshua@hypera.dev>
|---------| ------------------ |
| 0.3.x   | :white_check_mark: |
  <version>0.3.1-SNAPSHOT</version>
      <version>23.1.0</version>
import org.jetbrains.annotations.NotNull;
	private static final @NotNull String VERSION = "0.3.1-SNAPSHOT";
	private final @NotNull Path directory;
	private final @NotNull Set<String> repositories;
	private final @NotNull Consumer<Status> statusHandler;
	private final @NotNull DependencyDownloader dependencyDownloader = new DependencyDownloader(this);
	private final @NotNull DependencyRelocator dependencyRelocator;
	private final @NotNull DependencyLoader dependencyLoader;
	public static @NotNull String getVersion() {
	public @NotNull CompletableFuture<Boolean> load(@NotNull Dependency... dependencies) {
	public @NotNull Path getDirectory() {
	public @NotNull Set<String> getRepositories() {
	public @NotNull DependencyDownloader getDependencyDownloader() {
import org.jetbrains.annotations.Contract;
	@Contract(value = "-> new", pure = true)
	private final @NotNull MavenResolver resolver = new MavenResolver();
	private final @NotNull MavenSnapshotResolver snapshotResolver = new MavenSnapshotResolver();
import org.jetbrains.annotations.NotNull;

	private static final long serialVersionUID = 5648475409314204882L;

	public DownloadFailureException(@NotNull String message) {
	public DownloadFailureException(@NotNull String message, @NotNull Throwable cause) {
	public DownloadFailureException(@NotNull Throwable cause) {
	protected DownloadFailureException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
import org.jetbrains.annotations.NotNull;

	private static final long serialVersionUID = 3565376065959848642L;

	public DragonflyException(@NotNull String message) {
	public DragonflyException(@NotNull String message, @NotNull Throwable cause) {
	public DragonflyException(@NotNull Throwable cause) {
	protected DragonflyException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
import org.jetbrains.annotations.NotNull;

	private static final long serialVersionUID = -8555618454694039541L;

	public LoadFailureException(@NotNull String message) {
	public LoadFailureException(@NotNull String message, @NotNull Throwable cause) {
	public LoadFailureException(@NotNull Throwable cause) {
	protected LoadFailureException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
import org.jetbrains.annotations.NotNull;

	private static final long serialVersionUID = -2817144091943660838L;

	public RelocationFailureException(@NotNull String message) {
	public RelocationFailureException(@NotNull String message, @NotNull Throwable cause) {
	public RelocationFailureException(@NotNull Throwable cause) {
	protected RelocationFailureException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
import org.jetbrains.annotations.NotNull;

	private static final long serialVersionUID = -8393996338726021911L;

	public ResolveFailureException(@NotNull String message) {
	public ResolveFailureException(@NotNull String message, @NotNull Throwable cause) {
	public ResolveFailureException(@NotNull Throwable cause) {
	protected ResolveFailureException(@NotNull String message, @NotNull Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
import org.jetbrains.annotations.NotNull;
	private final @NotNull Dragonfly dragonfly;
	private final @NotNull IClassLoader classLoader;
	public DependencyLoader(@NotNull Dragonfly dragonfly, @NotNull IClassLoader classLoader) {
	public void load(@NotNull List<Dependency> dependencies) throws LoadFailureException {
	private void load(@NotNull Dependency dependency) throws LoadFailureException {
import org.jetbrains.annotations.NotNull;
	public DragonflyClassLoader(@NotNull ClassLoader classLoader) {
	public void addURL(@NotNull URL url) {
	protected @NotNull Class<?> loadClass(@NotNull String name, boolean resolve) throws ClassNotFoundException {
import org.jetbrains.annotations.Nullable;
	private static final @NotNull String RELOCATED_FILENAME = "%s-relocated.jar";
	private final @NotNull Dragonfly dragonfly;
	private final @NotNull DragonflyClassLoader classLoader;
	private final @NotNull DependencyLoader dependencyLoader;
	private @Nullable Constructor<?> constructor;
	private @Nullable Method method;
	public DependencyRelocator(@NotNull Dragonfly dragonfly, boolean deleteOld) {
	private final @NotNull String from;
	private final @NotNull String to;
	private Relocation(@NotNull String from, @NotNull String to) {
	public @NotNull String getFrom() {
	public @NotNull String getTo() {
	private static final @NotNull String FORMAT = "%s%s/%s/%s/%s-%s.jar";
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import sun.tools.jstat.ParserException;
	private static final @NotNull String FORMAT = "%s%s/%s/%s/maven-metadata.xml";
	private static final @NotNull String OUTPUT_FORMAT = "%s/%s-%s-%s-%s.jar";
	private final @NotNull DocumentBuilderFactory documentBuilderFactory;

	public MavenSnapshotResolver() {
		try {
			/* The below is an attempt to create an XML parser while preventing XML External Entity attacks */
			/* Read more: https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#java */
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
			factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
			factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
			factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
			factory.setXIncludeAware(false);
			factory.setExpandEntityReferences(false);
			this.documentBuilderFactory = factory;
		} catch (ParserConfigurationException ex) {
			throw new RuntimeException("Failed to create DocumentBuilderFactory", ex);
		}
	}
				DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();