diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index e945d01abfb..5473937fa5d 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -25,3 +25,4 @@ updates:
- dependency-name: "com.google.errorprone:error_prone_core"
versions: [ ">= 2.43.0" ] # versions 2.43.x and later require Java 21
- dependency-name: "com.diffplug.spotless*" # newer versions lead to runtime error, will need to update the common zap plugin first
+ - dependency-name: "org.jgrapht:jgrapht-core" # Also used by AJAX Spider and newer versions have breaking changes.
diff --git a/LEGALNOTICE.md b/LEGALNOTICE.md
index ee05ff2a85c..23efba5d370 100644
--- a/LEGALNOTICE.md
+++ b/LEGALNOTICE.md
@@ -32,32 +32,32 @@ and subject to their respective licenses.
| Library | License |
|-------------------------------------|---------------------------|
| commons-beanutils-1.11.0.jar | Apache 2.0 |
-| commons-codec-1.20.0.jar | Apache 2.0 |
+| commons-codec-1.21.0.jar | Apache 2.0 |
| commons-collections-3.2.2.jar | Apache 2.0 |
| commons-configuration-1.10.jar | Apache 2.0 |
| commons-csv-1.14.1.jar | Apache 2.0 |
| commons-httpclient-3.1.jar | Apache 2.0 |
| commons-io-2.21.0.jar | Apache 2.0 |
| commons-lang-2.6.jar | Apache 2.0 |
-| commons-lang3-3.19.0.jar | Apache 2.0 |
-| commons-logging-1.3.5.jar | Apache 2.0 |
-| commons-text-1.14.0.jar | Apache 2.0 |
+| commons-lang3-3.20.0.jar | Apache 2.0 |
+| commons-logging-1.3.6.jar | Apache 2.0 |
+| commons-text-1.15.0.jar | Apache 2.0 |
| ezmorph-1.0.6.jar | Apache 2.0 |
-| flatlaf-3.7.jar | Apache 2.0 |
-| flatlaf-swingx-3.7.jar | Apache 2.0 |
+| flatlaf-3.7.1.jar | Apache 2.0 |
+| flatlaf-swingx-3.7.1.jar | Apache 2.0 |
| harlib-1.1.3.jar | Apache 2.0 |
| hsqldb-2.7.4.jar | BSD |
| jackson-core-asl-1.9.13.jar | Apache 2.0 |
-| javahelp-2.0.05.jar | GPL + classpath exception |
| java-semver-0.10.2.jar | MIT |
+| javahelp-2.0.05.jar | GPL + classpath exception |
| jericho-html-3.4.jar | EPL / LGPL dual license |
| jfreechart-1.5.6.jar | LGPL |
| jgrapht-core-0.9.2.jar | LGPL 2.1 |
| json-lib-2.4-jdk15.jar | MIT + "Good, Not Evil" |
-| log4j-1.2-api-2.25.3.jar | Apache 2.0 |
-| log4j-api-2.25.3.jar | Apache 2.0 |
-| log4j-core-2.25.3.jar | Apache 2.0 |
-| log4j-jul-2.25.3.jar | Apache 2.0 |
-| rsyntaxtextarea-3.6.0.jar | BSD-3 clause |
+| log4j-1.2-api-2.25.4.jar | Apache 2.0 |
+| log4j-api-2.25.4.jar | Apache 2.0 |
+| log4j-core-2.25.4.jar | Apache 2.0 |
+| log4j-jul-2.25.4.jar | Apache 2.0 |
+| rsyntaxtextarea-3.6.2.jar | BSD-3 clause |
| swingx-all-1.6.5-1.jar | LGPL 2.1 |
-| xom-1.3.9.jar | LGPL |
+| xom-1.4.0.jar | LGPL |
diff --git a/buildSrc/src/main/java/org/zaproxy/zap/tasks/UpdateLegalNotice.java b/buildSrc/src/main/java/org/zaproxy/zap/tasks/UpdateLegalNotice.java
new file mode 100644
index 00000000000..491701098de
--- /dev/null
+++ b/buildSrc/src/main/java/org/zaproxy/zap/tasks/UpdateLegalNotice.java
@@ -0,0 +1,163 @@
+/*
+ * Zed Attack Proxy (ZAP) and its related class files.
+ *
+ * ZAP is an HTTP/HTTPS proxy for assessing web application security.
+ *
+ * Copyright 2026 The ZAP Development Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.zaproxy.zap.tasks;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.GradleException;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.Property;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.PathSensitive;
+import org.gradle.api.tasks.PathSensitivity;
+import org.gradle.api.tasks.TaskAction;
+
+/**
+ * A task that updates the third-party library table in LEGALNOTICE.md.
+ *
+ *
Reads library licenses from a YAML mapping file and replaces the table (from the table header
+ * to end of file) in the legal notice file with a freshly generated one.
+ */
+public abstract class UpdateLegalNotice extends DefaultTask {
+
+ private static final String LIBRARY_HEADER = "Library";
+ private static final String LICENSE_HEADER = "License";
+
+ /** The runtime classpath configuration to inspect for third-party libraries. */
+ @InputFiles
+ @PathSensitive(PathSensitivity.NAME_ONLY)
+ public abstract Property getConfiguration();
+
+ /**
+ * The YAML file mapping Maven {@code groupId:artifactId} coordinates to their license strings.
+ */
+ @InputFile
+ @PathSensitive(PathSensitivity.NONE)
+ public abstract RegularFileProperty getLicensesFile();
+
+ /** The legal notice file to update. */
+ @OutputFile
+ public abstract RegularFileProperty getLegalNoticeFile();
+
+ @TaskAction
+ public void update() throws IOException {
+ Set rows =
+ createRows(
+ getConfiguration().get().getResolvedConfiguration().getResolvedArtifacts(),
+ getLicensesFile().get().getAsFile().toPath());
+
+ int libWidth = Math.max(LIBRARY_HEADER.length(), 35);
+ int licWidth = LICENSE_HEADER.length();
+ for (TableRow row : rows) {
+ libWidth = Math.max(libWidth, row.fileName().length());
+ licWidth = Math.max(licWidth, row.licenseName().length());
+ }
+
+ Path legalNoticeFile = getLegalNoticeFile().get().getAsFile().toPath();
+
+ StringBuilder fileContent = new StringBuilder();
+ fileContent.append(readMainContent(legalNoticeFile));
+
+ String rowPattern = "| %-" + libWidth + "s | %-" + licWidth + "s |\n";
+ appendRow(fileContent, rowPattern, LIBRARY_HEADER, LICENSE_HEADER);
+ appendSeparator(fileContent, libWidth, licWidth);
+ for (TableRow row : rows) {
+ appendRow(fileContent, rowPattern, row.fileName(), row.licenseName());
+ }
+
+ Files.writeString(legalNoticeFile, fileContent);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Set createRows(Set artifacts, Path licensesFile)
+ throws IOException {
+ Map yaml =
+ new ObjectMapper(new YAMLFactory()).readValue(licensesFile.toFile(), Map.class);
+ Map licenseMap = (Map) yaml.get("licenses");
+ if (licenseMap == null) {
+ throw new GradleException(
+ "Licenses YAML file is missing the 'licenses' key: " + licensesFile);
+ }
+
+ Set rows = new TreeSet<>(Comparator.comparing(TableRow::fileName));
+ for (ResolvedArtifact artifact : artifacts) {
+ ModuleComponentIdentifier id =
+ (ModuleComponentIdentifier) artifact.getId().getComponentIdentifier();
+ String coordinates = id.getGroup() + ":" + id.getModule();
+ String fileName = artifact.getFile().getName();
+ String license = licenseMap.get(coordinates);
+ if (license == null) {
+ throw new GradleException(
+ "No license mapping found for '"
+ + coordinates
+ + "' (JAR: "
+ + fileName
+ + "). Add it to: "
+ + licensesFile.getFileName());
+ }
+ rows.add(new TableRow(fileName, license));
+ }
+
+ return rows;
+ }
+
+ private static String readMainContent(Path legalNoticeFile) throws IOException {
+ String content = Files.readString(legalNoticeFile);
+ int tableStart = content.indexOf("\n| " + LIBRARY_HEADER);
+ if (tableStart == -1) {
+ throw new GradleException(
+ "Could not find table header ('| "
+ + LIBRARY_HEADER
+ + "') in: "
+ + legalNoticeFile);
+ }
+ return content.substring(0, tableStart + 1);
+ }
+
+ private static void appendRow(
+ StringBuilder fileContent, String rowPattern, String library, String license) {
+ fileContent.append(String.format(rowPattern, library, license));
+ }
+
+ private static void appendSeparator(StringBuilder fileContent, int libWidth, int licWidth) {
+ fileContent
+ .append('|')
+ .append("-".repeat(libWidth + 2))
+ .append("|")
+ .append("-".repeat(licWidth + 2))
+ .append("|")
+ .append('\n');
+ }
+
+ private record TableRow(String fileName, String licenseName) {}
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 174b14386aa..38dbeb44a27 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,22 +1,22 @@
[versions]
-errorprone = "2.36.0"
-flatlaf = "3.7"
-log4j = "2.25.3"
+errorprone = "2.42.0"
+flatlaf = "3.7.1"
+log4j = "2.25.4"
[libraries]
-bndAnnotation = "biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0"
-bytebuddy = "net.bytebuddy:byte-buddy:1.18.0"
+bndAnnotation = "biz.aQute.bnd:biz.aQute.bnd.annotation:7.2.3"
+bytebuddy = "net.bytebuddy:byte-buddy:1.18.8"
commons-beanutils = "commons-beanutils:commons-beanutils:1.11.0"
-commons-codec = "commons-codec:commons-codec:1.20.0"
+commons-codec = "commons-codec:commons-codec:1.21.0"
commons-collections = "commons-collections:commons-collections:3.2.2"
commons-configuration = "commons-configuration:commons-configuration:1.10"
commons-csv = "org.apache.commons:commons-csv:1.14.1"
commons-httpclient = "commons-httpclient:commons-httpclient:3.1"
commons-io = "commons-io:commons-io:2.21.0"
commons-lang = "commons-lang:commons-lang:2.6"
-commons-lang3 = "org.apache.commons:commons-lang3:3.19.0"
-commons-logging = "commons-logging:commons-logging:1.3.5"
-commons-text = "org.apache.commons:commons-text:1.14.0"
+commons-lang3 = "org.apache.commons:commons-lang3:3.20.0"
+commons-logging = "commons-logging:commons-logging:1.3.6"
+commons-text = "org.apache.commons:commons-text:1.15.0"
errorprone-core = { module = "com.google.errorprone:error_prone_core", version.ref = "errorprone" }
findbugsAnnotations = "com.google.code.findbugs:findbugs-annotations:3.0.1"
flatlaf = { module = "com.formdev:flatlaf", version.ref = "flatlaf" }
@@ -34,23 +34,23 @@ log4j-api12 = { module = "org.apache.logging.log4j:log4j-1.2-api", version.ref =
log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" }
log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" }
log4j-slf4j = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" }
-rsyntaxtextarea = "com.fifesoft:rsyntaxtextarea:3.6.0"
+rsyntaxtextarea = "com.fifesoft:rsyntaxtextarea:3.6.2"
swingx = "org.swinglabs.swingx:swingx-all:1.6.5-1"
-xom = "xom:xom:1.3.9"
+xom = "xom:xom:1.4.0"
-assertj-core = "org.assertj:assertj-core:3.27.6"
+assertj-core = "org.assertj:assertj-core:3.27.7"
assertj-swing = "org.assertj:assertj-swing:3.17.1"
hamcrest-core = "org.hamcrest:hamcrest-core:3.0"
-junit-jupiter = "org.junit.jupiter:junit-jupiter:6.0.1"
+junit-jupiter = "org.junit.jupiter:junit-jupiter:6.0.3"
junit-platformLauncher = { module = "org.junit.platform:junit-platform-launcher" }
-mockito = "org.mockito:mockito-junit-jupiter:5.20.0"
+mockito = "org.mockito:mockito-junit-jupiter:5.23.0"
[plugins]
crowdin = "org.zaproxy.crowdin:0.6.0"
cyclonedx.id = "org.cyclonedx.bom"
-dependencyUpdates = "com.github.ben-manes.versions:0.52.0"
-errorprone = "net.ltgt.errorprone:4.1.0"
+dependencyUpdates = "com.github.ben-manes.versions:0.54.0"
+errorprone = "net.ltgt.errorprone:5.1.0"
japicmp.id = "me.champeau.gradle.japicmp"
-sonarqube = "org.sonarqube:6.0.1.5171"
+sonarqube = "org.sonarqube:7.2.3.7755"
spotless.id = "com.diffplug.spotless"
zaproxy-common.id = "org.zaproxy.common"
diff --git a/zap/gradle/thirdPartyLicenses.yaml b/zap/gradle/thirdPartyLicenses.yaml
new file mode 100644
index 00000000000..37d32ad811d
--- /dev/null
+++ b/zap/gradle/thirdPartyLicenses.yaml
@@ -0,0 +1,33 @@
+# Maps Maven groupId:artifactId to the license of the distributed library.
+# Used by the updateLegalNotice Gradle task to regenerate the table in LEGALNOTICE.md.
+licenses:
+ com.fifesoft:rsyntaxtextarea: "BSD-3 clause"
+ com.formdev:flatlaf: "Apache 2.0"
+ com.formdev:flatlaf-swingx: "Apache 2.0"
+ com.github.zafarkhaja:java-semver: "MIT"
+ commons-beanutils:commons-beanutils: "Apache 2.0"
+ commons-codec:commons-codec: "Apache 2.0"
+ commons-collections:commons-collections: "Apache 2.0"
+ commons-configuration:commons-configuration: "Apache 2.0"
+ commons-httpclient:commons-httpclient: "Apache 2.0"
+ commons-io:commons-io: "Apache 2.0"
+ commons-lang:commons-lang: "Apache 2.0"
+ commons-logging:commons-logging: "Apache 2.0"
+ edu.umass.cs.benchlab:harlib: "Apache 2.0"
+ javax.help:javahelp: "GPL + classpath exception"
+ net.htmlparser.jericho:jericho-html: "EPL / LGPL dual license"
+ net.sf.ezmorph:ezmorph: "Apache 2.0"
+ net.sf.json-lib:json-lib: 'MIT + "Good, Not Evil"'
+ org.apache.commons:commons-csv: "Apache 2.0"
+ org.apache.commons:commons-lang3: "Apache 2.0"
+ org.apache.commons:commons-text: "Apache 2.0"
+ org.apache.logging.log4j:log4j-1.2-api: "Apache 2.0"
+ org.apache.logging.log4j:log4j-api: "Apache 2.0"
+ org.apache.logging.log4j:log4j-core: "Apache 2.0"
+ org.apache.logging.log4j:log4j-jul: "Apache 2.0"
+ org.codehaus.jackson:jackson-core-asl: "Apache 2.0"
+ org.hsqldb:hsqldb: "BSD"
+ org.jfree:jfreechart: "LGPL"
+ org.jgrapht:jgrapht-core: "LGPL 2.1"
+ org.swinglabs.swingx:swingx-all: "LGPL 2.1"
+ xom:xom: "LGPL"
diff --git a/zap/zap.gradle.kts b/zap/zap.gradle.kts
index 802ad716174..cdce4fbe844 100644
--- a/zap/zap.gradle.kts
+++ b/zap/zap.gradle.kts
@@ -3,6 +3,7 @@ import me.champeau.gradle.japicmp.JapicmpTask
import org.zaproxy.gradle.spotless.ValidateImports
import org.zaproxy.zap.japicmp.AcceptMethodAbstractNowDefaultRule
import org.zaproxy.zap.tasks.GradleBuildWithGitRepos
+import org.zaproxy.zap.tasks.UpdateLegalNotice
import org.zaproxy.zap.tasks.internal.JapicmpExcludedData
import java.time.LocalDate
import java.util.stream.Collectors
@@ -332,3 +333,12 @@ fun zapJar(version: String): File {
group = oldGroup
}
}
+
+tasks.register("updateLegalNotice") {
+ group = "ZAP Misc"
+ description = "Updates the third-party library table in LEGALNOTICE.md."
+
+ configuration.set(configurations.runtimeClasspath)
+ licensesFile.set(file("gradle/thirdPartyLicenses.yaml"))
+ legalNoticeFile.set(rootProject.file("LEGALNOTICE.md"))
+}