CVE-2025-67030 is a directory traversal vulnerability in the extractFile method of org.codehaus.plexus.util.Expand, a class distributed in the Maven artifact org.codehaus.plexus:plexus-utils. A crafted archive entry can escape the intended extraction directory because affected versions validate the destination with a string-prefix comparison that does not reliably represent a filesystem containment boundary.
The vulnerable code does not directly execute a command, evaluate a script, or start a new process. Its immediate security primitive is an unauthorized filesystem write outside the selected extraction directory. The public CVE description states that the weakness can allow arbitrary code execution, but that outcome requires an additional execution path: the escaped file must land in a location that is subsequently loaded, executed, packaged, sourced, or trusted by another component. Treating every detected JAR as instant remote code execution would overstate the evidence. Treating the issue as harmless because it begins with a file write would understate what an archive-processing component can do inside a privileged build environment. (GitHub)
The affected ranges are unusually important to state precisely. The GitHub Advisory Database identifies versions earlier than 3.6.1 on the 3.x line, and versions from 4.0.0 up to but excluding 4.0.3 on the 4.x line. The fixed releases are 3.6.1 and 4.0.3. It is therefore incorrect to describe every version earlier than 4.0.3 as vulnerable: 3.6.1 is a patched 3.x release. (GitHub)
The broader operational risk comes from where plexus-utils tends to appear. Java teams may encounter it as an application dependency, a test dependency, a Maven plugin dependency, a Maven core library, a build extension, a code-generation tool dependency, a component embedded in a CI image, or a library shaded into another JAR. Those are different dependency planes with different classloaders and different remediation controls. A clean application dependency tree does not prove that a Maven plugin, build extension, runner image, or standalone tool is clean.
CVE-2025-67030 at a glance
| Frage | Verified answer |
|---|---|
| What is affected? | org.codehaus.plexus:plexus-utils |
| Vulnerable code | org.codehaus.plexus.util.Expand.extractFile |
| Weakness class | CWE-22, improper limitation of a pathname to a restricted directory |
| Affected 3.x versions | Versions earlier than 3.6.1 |
| Affected 4.x versions | 4.0.0, 4.0.1, and 4.0.2 |
| Festgelegte Versionen | 3.6.1 and 4.0.3 |
| Immediate primitive | Writing an archive entry outside the intended extraction directory |
| Required attacker influence | Control over, or meaningful influence on, an archive processed by the affected code path |
| Maximum downstream impact | File overwrite, build contamination, credential or configuration impact, and possible code execution when another mechanism consumes the written file |
| NVD score | 8.8 High |
| Red Hat ADP score shown by NVD | 8.3 High |
| Main fix | Canonicalize both paths and enforce a separator-aware directory boundary |
| Official patch commit | 6d780b3378829318ba5c2d29547e0012d5b29642 |
The GitHub advisory was published on March 25, 2026 and classifies the vulnerability as High severity. NVD also reports an 8.8 High assessment, while displaying a separate 8.3 High assessment contributed by Red Hat. The scores differ because the underlying vectors make different assumptions about user interaction, scope, and impact. (GitHub)
The disclosure and release timeline
The project issue that led to the fix was opened on September 2, 2025. It identified the use of getAbsolutePath().startsWith(...) in Expand.extractFile and argued that the check did not establish a secure containment boundary. The report specifically called out string-prefix matching, inconsistent path representations, and possible problems involving similar path prefixes. (GitHub)
The mainline fix was developed in October 2025. A backport of the same change was merged into the plexus-utils 3.x branch on November 9, 2025. That backport explicitly recorded that it was cherry-picked from the main fix commit. The public CVE and GitHub advisory followed in March 2026. (GitHub)
The project released plexus-utils 4.0.3 on March 27, 2026, with the release notes listing the Zip Slip fix. Version 3.6.1 followed on April 1, 2026 and carried the corresponding fix for the 3.x line. (GitHub)
Apache Maven 3.9.15, released on April 13, 2026, upgraded its own plexus-utils dependency to 3.6.1 specifically to remove the CVE from the Maven distribution. Apache advised users already running Maven 3.9.x to upgrade. As of July 2026, Maven’s official history also lists 3.9.16 as the subsequent stable 3.9.x release and 3.10.0-rc-1 as a release candidate. Production teams should generally prefer a current supported stable release rather than adopting a release candidate solely to address this vulnerability. (Apache Maven)
This sequence creates an important distinction between code repair and consumable remediation. A fix can exist in a repository months before a patched artifact is published under the version line an organization uses. Security teams should record both dates:
- the date the vulnerable code was corrected;
- the date a supported, consumable release containing the correction became available.
A repository commit alone does not replace the JAR already stored in a runner image, plugin distribution, artifact cache, or developer workstation.
The vulnerable path check
The affected implementation resolved an archive entry against a destination directory and then compared the two absolute-path strings:
File f = FileUtils.resolveFile(dir, entryName);
if (!f.getAbsolutePath().startsWith(dir.getAbsolutePath())) {
throw new IOException(
"Entry '" + entryName + "' outside the target directory."
);
}
At first glance, the code appears to ask the right question: does the destination file begin with the extraction directory? The problem is that String.startsWith answers a lexical question, not a filesystem question.
Suppose the extraction root is:
/tmp/build/extract
Now suppose an archive entry resolves to:
/tmp/build/extract-sibling/probe.txt
The second string begins with the characters /tmp/build/extract, so startsWith returns wahr. Yet extract-sibling is not a child directory of extract. It is a sibling whose name happens to share the same textual prefix.
The project’s pull request illustrated the same failure using /tmp/app as the root and /tmp/app-data/evil.txt as the escaped destination. Because the old comparison did not append a directory separator, it could not distinguish “inside /tmp/app/” from “inside another directory whose name begins with app." (GitHub)
This is the central, demonstrable defect behind CVE-2025-67030. It does not depend on a deeply nested path or a write to a global system directory. A nearby sibling directory with a matching prefix is sufficient to show that the authorization boundary is false.
Absolute paths do not solve containment
An absolute path tells the program where a path begins in the filesystem namespace. It does not, by itself, prove that two paths have been reduced to an equivalent and security-safe representation.
Path inputs may contain:
.components;..components;- repeated separators;
- platform-specific separators;
- symbolic links in parent directories;
- case differences on case-insensitive filesystems;
- paths whose textual prefix resembles another directory;
- filesystem aliases or mount behavior that cannot be understood by a simple string comparison.
MITRE’s CWE-22 guidance treats path canonicalization and validation ordering as security-relevant. The essential rule is to transform paths into the representation on which the security decision will be made, then enforce the boundary. Validating one representation and using another can allow a path to change meaning after the check. (CWE)
Canonicalization is not a magic answer to every filesystem race or archive format feature. It is, however, the correct foundation for addressing the specific mismatch in this implementation.
The official fix
The patch replaced the old absolute-path string test with canonical paths and a separator-aware comparison:
File f = FileUtils.resolveFile(dir, entryName);
try {
String canonicalDirPath = dir.getCanonicalPath();
String canonicalFilePath = f.getCanonicalPath();
if (!canonicalFilePath.startsWith(
canonicalDirPath + File.separator)
&& !canonicalFilePath.equals(canonicalDirPath)) {
throw new IOException(
"Entry '" + entryName
+ "' outside the target directory."
);
}
} catch (IOException e) {
throw new IOException(
"Failed to verify entry path for '" + entryName + "'",
e
);
}
The patch establishes two allowed cases:
- The candidate path is exactly the canonical destination directory.
- The candidate path begins with the canonical destination followed by an actual filesystem separator.
The second rule is what prevents the sibling-prefix bypass. If the destination is /tmp/build/extract, then an accepted child must begin with:
/tmp/build/extract/
The escaped sibling path begins with:
/tmp/build/extract-sibling/
It therefore fails the separator-aware test.
The project added extensive regression coverage with the change. The tests include parent-directory traversal, absolute-path behavior, similar-prefix sibling directories, and legitimate archive extraction. That test design matters because it verifies both halves of the security requirement: malicious paths must fail, and valid files must continue to extract. (GitHub)
Why filtering ../ is not a sufficient fix
A common response to archive traversal is to reject any entry name containing ../. That can stop a narrow set of examples, but it is not a dependable filesystem boundary.
First, path syntax differs across operating systems. Code that rejects only forward-slash traversal may behave differently when a backslash is interpreted as a separator. Second, validation may happen before decoding, normalization, or separator conversion. Third, absolute paths do not need .. to escape a relative extraction root. Fourth, an entry can interact with symbolic links or preexisting filesystem state. Fifth, the sibling-prefix flaw in CVE-2025-67030 exists because the final resolved destination was authorized incorrectly, not merely because the input contained a particular substring.
A secure extraction decision should be made against the resolved destination, not against a blacklist of suspicious characters. Character validation can still be useful as defense in depth, especially when an application has a narrow filename grammar, but it should not replace a component-aware containment test.
Archive handling also has risks outside this CVE:
- excessive decompressed size;
- extreme compression ratios;
- very large entry counts;
- duplicate filenames;
- filename collisions after normalization;
- special file types;
- symbolic or hard links;
- overwritten existing files;
- dangerous permissions or timestamps;
- extraction into executable or web-accessible locations;
- time-of-check to time-of-use changes;
- resource exhaustion during parsing.
OWASP’s testing guidance explicitly treats archive directory traversal and archive symlink behavior as separate concerns. Its file-upload guidance also warns that ZIP archives can carry multiple attack classes and recommends limits that account for decompressed size, not only the compressed upload size. (OWASP-Stiftung)
Upgrading plexus-utils fixes the confirmed Expand.extractFile boundary check. It does not eliminate the need to limit archive size, control overwrite behavior, isolate extraction, handle links correctly, or reduce the privileges of the extracting process.
The real exploitability chain
A version alert is the beginning of triage, not the end. For CVE-2025-67030 to create a meaningful security outcome, several conditions usually need to align.
The vulnerable component must be present
A vulnerable version may be declared directly, resolved transitively, supplied by a Maven plugin, loaded from Maven core, provided by a build extension, copied into a CI image, or bundled inside another artifact.
Presence is necessary, but not sufficient.
The affected class must be reachable
The relevant class is org.codehaus.plexus.util.Expand. A project can carry plexus-utils for unrelated utilities without using archive extraction. An accurate reachability investigation should determine whether Expand is loaded and whether the vulnerable extraction method can execute in the relevant workflow.
An attacker must influence the archive
The attack surface is much greater when the archive originates from:
- an unauthenticated upload;
- a pull request from an untrusted contributor;
- an external package registry;
- a third-party vendor;
- an artifact mirror outside the organization’s control;
- a build job parameter;
- a remote repository;
- a customer-supplied extension bundle;
- a plugin marketplace;
- an automated ingestion pipeline.
The risk is lower when archives are produced internally, cryptographically verified, and generated in a trusted pipeline. “Internal” should not be accepted as proof by itself, however. Compromised developer credentials, dependency substitution, poisoned caches, and malicious pull requests can move hostile content across nominally internal boundaries.
The extraction process must have useful write permissions
The escaped file can be created only where the Java process is allowed to write. A containerized, non-root build with an isolated temporary filesystem has a smaller blast radius than a persistent runner operating as a privileged service account.
High-value writable locations may include:
- other project workspaces;
- shared dependency caches;
- generated-source directories;
- test-resource directories;
- packaging inputs;
- script directories;
- user startup files;
- application configuration directories;
- plugin or extension directories;
- locations later copied into a container image;
- directories consumed by a deployment stage.
The existence of a traversal primitive does not guarantee access to all of these locations. The process account, mount configuration, operating system controls, and job isolation determine the actual boundary.
Another mechanism may be needed for code execution
A file write becomes code execution only when a subsequent behavior uses the written file as code or configuration with executable effect. Examples include a later shell step sourcing a script, a packaging job including a replaced executable, a service loading a modified configuration, or a plugin loader discovering a newly written component.
This distinction is why the advisory’s arbitrary-code wording should be read as a potential impact, not as proof that calling Expand.execute() immediately launches attacker-controlled code. The primitive is serious because build systems frequently connect file creation to later execution. The connection must still be demonstrated in each environment.
A practical severity matrix
| Umwelt | Archive trust | Process privilege | Downstream use | Practical priority |
|---|---|---|---|---|
| Vulnerable JAR exists only in an unused cache | No active input path | Keine | Keine | Remove during normal remediation, then confirm it is not loaded |
Application includes plexus-utils but never calls Expand | Varies | Application account | No affected extraction path found | Patch, but record reduced reachability with evidence |
| Build plugin extracts only signed internal archives | Controlled | Isolated runner | Output remains inside disposable workspace | Medium to high, depending on trust controls |
| CI processes archives from external pull requests | Untrusted | Persistent runner | Files influence later build steps | Hoch |
| Build job holds release or cloud credentials | Untrusted or mixed | Credentialed runner | Escaped files can alter scripts or published output | High to critical in local context |
| Service automatically extracts customer archives | Untrusted | Long-lived service | Files may reach application or executable paths | Hoch |
| Shared runner writes to common caches or workspaces | Untrusted or mixed | Cross-project write access | Other jobs consume modified files | High due to lateral contamination |
| Patched version is resolved and safe regression test passes | Untrusted | Geringstes Privileg | Boundary enforced | Residual archive risks still require controls |
The table should not be converted mechanically into a universal severity score. Its purpose is to identify which environmental facts change the result.
Understanding the CVSS disagreement
NVD and CISA-ADP display an 8.8 High score with the vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Red Hat’s assessment shown on the NVD page is 8.3 High with:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L
The first model assumes user interaction and high impact within an unchanged scope. The second removes the user-interaction requirement, changes scope, and assigns lower individual confidentiality, integrity, and availability impacts. (NVD)
Those vectors describe different generalized scenarios. Archive traversal libraries are often embedded in products that expose them through very different workflows:
- A developer manually imports and extracts a package.
- A CI system automatically processes an artifact.
- A web service accepts and expands an upload.
- A plugin installer downloads and installs an extension.
- A code generator extracts a template bundle during a build.
“User interaction” can be reasonable in the first workflow and inaccurate in the second. Likewise, the impact may remain within the process’s normal security authority, or it may cross into another build, service, or trust domain through a shared filesystem.
An internal assessment should answer concrete questions instead of choosing the highest public score by default:
- Who can provide the archive?
- Is extraction automatic?
- Which operating-system identity performs it?
- Which paths can that identity write?
- Are those paths shared with other jobs?
- What executes or publishes files afterward?
- Does the runner hold credentials?
- Can the attacker observe whether extraction succeeded?
- Can the same input be submitted repeatedly?
- Is the environment ephemeral and reset after the job?
A version scanner can answer none of those questions by itself.
Why the Java build chain is a special risk environment
Build systems routinely perform actions that production applications are designed to avoid. They download code, resolve plugins, unpack archives, generate sources, invoke compilers, run shell commands, create packages, authenticate to artifact repositories, sign releases, and sometimes deploy directly to cloud environments.
That makes the build plane both powerful and deceptive. A dependency used “only during build” may never appear in the final application container, yet it may execute in an environment with broader credentials than the application itself.
Project dependencies
A normal Maven project may include plexus-utils directly:
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.6.0</version>
</dependency>
It can also arrive transitively through another library. Maven resolves transitive dependencies automatically and applies dependency mediation when multiple versions are requested. A direct declaration or dependency-management rule can influence the selected project dependency. (Apache Maven)
This is the dependency graph most software composition analysis tools display first. It is not the only graph that matters.
Maven plugin dependencies
Build plugins run in plugin class realms and can bring their own dependencies. A plugin may load plexus-utils even when the application’s compile and runtime dependency trees do not contain it.
This separation produces a common remediation error:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.6.1</version>
</dependency>
</dependencies>
</dependencyManagement>
That rule can manage project dependencies. It does not automatically rewrite every transitive dependency of every build plugin. Apache Maven’s dependency documentation explicitly notes that dependency management does not affect the transitive dependencies of plugins in the same way it governs project dependencies. (Apache Maven)
A vulnerable plugin realm therefore needs separate investigation. The best fix is for the plugin maintainer to publish a corrected version. A temporary user-side override may be possible by adding an explicit dependency within the plugin declaration, but it must be tested for binary compatibility.
Maven core and the Maven distribution
The Maven binary distribution contains its own libraries. Apache Maven 3.9.15 upgraded its bundled plexus-utils to 3.6.1 to remove CVE-2025-67030 from that distribution. Maven’s generated dependency documentation confirms that the 3.9.15 distribution uses plexus-utils 3.6.1. (Apache Maven)
Upgrading Maven addresses that copy. It does not prove that a project, plugin, extension, shaded tool, or CI image contains no other vulnerable copy.
Maven 3.9 also changed older behavior involving automatic injection of plexus-utils into plugin classpaths. Apache’s release notes explain that Maven 2.x injected an old plexus-utils dependency and Maven 3.x preserved that behavior for compatibility until Maven 3.9 removed it. Affected plugins are expected to declare their own dependency explicitly. This history helps explain why plexus-utils can appear in surprising places and why plugin behavior may differ across Maven generations. (Apache Maven)
Maven extensions
Core extensions can be loaded before normal project execution and can influence repository resolution, lifecycle behavior, transport, or classloading. They may be declared in .mvn/extensions.xml, installed through a corporate Maven distribution, or injected through environment configuration.
An extension dependency may not appear in the project’s normal runtime tree. It must be inspected as part of the build environment.
Gradle project and buildscript dependencies
Gradle also separates application configurations from build logic. runtimeClasspath may be clean while a plugin or legacy buildscript classpath contains an affected JAR.
Gradle’s official dependency tools distinguish these views. The dependencies task renders dependency graphs, dependencyInsight identifies why a particular component was selected in a specific configuration, and buildEnvironment shows the buildscript classpath. (Gradle)
CI images and persistent runners
A CI container may include Maven, custom plugins, organization-wide extensions, prewarmed repositories, code-generation tools, or copied JARs. Updating pom.xml does not rebuild that image.
Persistent runners add another concern: a traversal write can outlive the job. A file placed in a shared cache, workspace parent, tool directory, or user home may influence later builds even after the malicious archive is gone.
Shaded and repackaged components
A vendor can relocate or embed plexus-utils classes inside another JAR. A scanner that searches only for the Maven coordinate may miss a shaded copy because the original POM metadata is absent or the classes have been relocated.
Class-level inspection, SBOM generation during packaging, and vendor advisories may be needed when ordinary dependency resolution does not explain where Expand.class came from.
How to find vulnerable copies in Maven
Start with the ordinary project graph:
mvn -q dependency:tree \
-Dincludes=org.codehaus.plexus:plexus-utils
A result such as this establishes a resolved project dependency:
com.example:build-service:jar:1.0.0
\- com.example:archive-helper:jar:2.4.0
\- org.codehaus.plexus:plexus-utils:jar:3.6.0:compile
The command identifies the path that introduced the artifact. It does not prove that Expand is called, and it does not enumerate every plugin or Maven-core copy.
Generate the effective POM to inspect inherited plugin declarations, parent configuration, and declared plugin dependencies:
mvn help:effective-pom \
-Dverbose \
-Doutput=effective-pom.xml
grep -n -C 5 "plexus-utils" effective-pom.xml
The effective POM is valuable when a vulnerable dependency originates in a corporate parent POM or a plugin configuration that is not visible in the repository’s immediate pom.xml. It still may not show every transitive library ultimately selected in a plugin realm.
For a build goal that actually runs the suspected plugin, Maven debug output can expose plugin classpath information:
mvn -X verify 2>&1 \
| grep -n -C 4 "plexus-utils"
Do not treat the absence of a grep match as a formal proof. Logging varies by Maven and plugin behavior. When the result matters, capture the complete debug log and identify the relevant class realm rather than relying only on a filtered line.
Inspect the Maven installation itself:
mvn -version
On macOS or Linux, the Maven home printed by that command can then be searched:
find "/path/from-maven-version" \
-type f \
-name 'plexus-utils-*.jar' \
-print
For a typical local Maven repository:
find "${HOME}/.m2/repository/org/codehaus/plexus/plexus-utils" \
-type f \
-name 'plexus-utils-*.jar' \
-print
A local-repository result is only a cache inventory. A vulnerable JAR in .m2 may not be selected by the current build. Conversely, deleting it from .m2 does not fix a POM, plugin, or image that will download it again.
To inspect Maven extensions:
find .mvn \
-maxdepth 2 \
-type f \
-print
cat .mvn/extensions.xml 2>/dev/null
Also inspect organization-level Maven wrappers, initialization scripts, custom distributions, and CI bootstrap steps. The command executed by a developer may not be the same Maven installation used by the release pipeline.
How to find vulnerable copies in Gradle
Render the normal dependency graph:
./gradlew dependencies
Focus on a specific runtime configuration:
./gradlew dependencyInsight \
--dependency plexus-utils \
--configuration runtimeClasspath
For compile-time exposure:
./gradlew dependencyInsight \
--dependency plexus-utils \
--configuration compileClasspath
For legacy buildscript dependencies and plugin-supporting libraries:
./gradlew buildEnvironment
Gradle’s dependencyInsight report explains the origin of the selected version and the reason it won conflict resolution. That distinction matters when a build requests a patched version in one place but another constraint or platform selects an older version. (Gradle)
Modern Gradle plugin resolution can involve additional isolated classloaders. Inspect plugin versions, convention plugins, included builds, buildSrc, and organization-maintained build logic. A clean runtimeClasspath does not establish that build-time code is safe.
Searching JARs, containers, and SBOMs
A broad filesystem search can locate named JARs:
find . \
-type f \
-name 'plexus-utils-*.jar' \
-print
Inside an unpacked container filesystem:
find rootfs \
-type f \
-name 'plexus-utils-*.jar' \
-print
Named-file searches miss shaded components. To search for the affected class in ordinary JARs:
find . -type f -name '*.jar' -print0 |
while IFS= read -r -d '' jarfile; do
if jar tf "$jarfile" 2>/dev/null |
grep -q 'org/codehaus/plexus/util/Expand.class'; then
printf '%s\n' "$jarfile"
fi
done
This is still not conclusive version identification. A class may have been copied or modified. When Maven metadata exists inside the JAR, inspect it:
unzip -p suspected.jar \
META-INF/maven/org.codehaus.plexus/plexus-utils/pom.properties \
2>/dev/null
Typical output contains:
groupId=org.codehaus.plexus
artifactId=plexus-utils
version=3.6.0
For CycloneDX-style JSON SBOMs, a package URL query can help:
jq '
.. |
objects |
select(
.purl? and
(.purl | contains(
"pkg:maven/org.codehaus.plexus/plexus-utils@"
))
) |
{
name,
version,
purl
}
' bom.json
An SBOM result is strongest when it records the build-stage components as well as the final runtime image. Many production SBOM pipelines intentionally omit build tools to reduce noise. That design is unsuitable for a vulnerability whose most consequential exposure may exist only during compilation or packaging.
From component presence to proven reachability
A useful finding should separate five evidence levels.
| Evidence level | What it proves | What it does not prove |
|---|---|---|
| Vulnerable JAR found on disk | A vulnerable artifact is stored somewhere | That any build loads it |
| Dependency graph selects the version | A project or configuration resolves it | That the affected class is called |
| Plugin or classloader evidence | A build process loads the JAR | That untrusted archives reach Expand |
| Call-path evidence | The affected extraction code can execute | That a boundary escape succeeds in the environment |
| Harmless boundary test | The environment exhibits or blocks the write | That a real attacker has exploited it |
The distinction prevents two common failures. The first is closing a valid issue merely because a static scanner cannot show a remote request path. The second is assigning emergency incident status to every cache entry without establishing that it participates in a build.
Static reachability analysis may help when bytecode and call relationships are available. It is less reliable when reflection, plugin containers, dependency injection, custom classloaders, or dynamically selected goals are involved. Build systems use all of those patterns.
Dynamic evidence may include:
- Maven plugin-realm logs;
- Java class-load logging;
- stack traces from an isolated test;
- code search for
new Expand(); - references to
org.codehaus.plexus.util.Expand; - wrappers that invoke the class indirectly;
- filesystem evidence from a safe regression test.
For source repositories, begin with direct references:
rg -n \
'org\.codehaus\.plexus\.util\.Expand|new\s+Expand\s*\(' \
.
No match does not prove the class is unreachable. A plugin can instantiate it inside a dependency whose source is not in the repository.
Safe PoC for the directory boundary
The following demonstration is intentionally limited. It creates a temporary directory, builds a ZIP containing one harmless text file, and attempts to place that file into a sibling directory under the same temporary parent. It does not target /etc, a user home directory, an application folder, a plugin directory, or any remote system.
Its purpose is to test the exact sibling-prefix boundary described in the project fix.
Use a disposable directory:
mkdir plexus-boundary-lab
cd plexus-boundary-lab
mkdir -p src/main/java
Create pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>local.defensive.lab</groupId>
<artifactId>plexus-boundary-lab</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>11</maven.compiler.release>
<project.build.sourceEncoding>
UTF-8
</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.6.0</version>
</dependency>
</dependencies>
</project>
Create src/main/java/BoundaryProbe.java:
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.codehaus.plexus.util.Expand;
public final class BoundaryProbe {
private BoundaryProbe() {
}
public static void main(String[] args) throws Exception {
Path labRoot =
Files.createTempDirectory("plexus-cve-2025-67030-");
Path extractionRoot = labRoot.resolve("extract");
Path siblingRoot = labRoot.resolve("extract-sibling");
Path archive = labRoot.resolve("boundary-probe.zip");
Files.createDirectories(extractionRoot);
Files.createDirectories(siblingRoot);
try (ZipOutputStream output =
new ZipOutputStream(
Files.newOutputStream(archive))) {
ZipEntry entry =
new ZipEntry(
"../extract-sibling/probe.txt");
output.putNextEntry(entry);
output.write(
"harmless boundary probe\n"
.getBytes(StandardCharsets.UTF_8));
output.closeEntry();
}
Expand expand = new Expand();
expand.setSrc(archive.toFile());
expand.setDest(extractionRoot.toFile());
boolean extractionRejected = false;
try {
expand.execute();
} catch (Exception expectedOnPatchedVersion) {
extractionRejected = true;
System.out.println(
"Extraction rejected: "
+ expectedOnPatchedVersion.getMessage());
}
Path escapedProbe =
siblingRoot.resolve("probe.txt");
System.out.println("Lab root: " + labRoot);
System.out.println(
"Extraction rejected: " + extractionRejected);
System.out.println(
"Sibling probe exists: "
+ Files.exists(escapedProbe));
if (Files.exists(escapedProbe)) {
System.out.println(
"BOUNDARY FAILURE: a file was created "
+ "outside the extraction root.");
} else {
System.out.println(
"BOUNDARY HELD: no sibling file was created.");
}
if (args.length > 0
&& "--cleanup".equals(args[0])) {
try (var paths = Files.walk(labRoot)) {
paths.sorted(
Comparator.reverseOrder())
.forEach(path -> {
try {
Files.deleteIfExists(path);
} catch (Exception ignored) {
// Lab cleanup only.
}
});
}
}
}
}
Compile the project and copy its runtime dependencies:
mvn -q clean package \
dependency:copy-dependencies \
-DoutputDirectory=target/dependency
Run it on macOS or Linux:
java \
-cp 'target/classes:target/dependency/*' \
BoundaryProbe
With the vulnerable 3.6.0 dependency, the expected boundary result is:
Sibling probe exists: true
BOUNDARY FAILURE: a file was created outside the extraction root.
The exact exception and logging behavior can vary, but the decisive evidence is whether probe.txt appears in extract-sibling rather than remaining under extract.
Now change the dependency version in pom.xml:
<version>3.6.1</version>
Rebuild from a clean state:
mvn -q clean package \
dependency:copy-dependencies \
-DoutputDirectory=target/dependency
Run the same test:
java \
-cp 'target/classes:target/dependency/*' \
BoundaryProbe
The patched behavior should reject the archive entry and leave the sibling file absent:
Extraction rejected: true
Sibling probe exists: false
BOUNDARY HELD: no sibling file was created.
This test is safe for three reasons:
- Every path is created under a newly generated system temporary directory.
- The payload is a plain text marker with no executable content.
- The destination is a dedicated sibling directory created by the test itself.
It demonstrates the vulnerable authorization decision without writing to a real application, user profile, build cache, or operating-system path. It should still be run only in an isolated lab or disposable CI job. A production build server is not an appropriate place to confirm vulnerable behavior.
The project’s own regression tests use the same defensive principle: create controlled archives, assert that unsafe entries throw an exception, and confirm that no file appears outside the extraction root. (GitHub)
A related defensive walkthrough for Python archive extraction applies a similar evidence-first method: prove the boundary failure with a harmless marker rather than converting the primitive into a weaponized payload. That cross-ecosystem approach is described in a Penligent archive-boundary validation example.

Interpreting PoC results correctly
A successful vulnerable-version test proves that the library can write outside the chosen extraction directory under the lab’s filesystem conditions. It does not prove that a production service exposes the same method to an unauthenticated attacker.
A failed test also requires interpretation. Possible reasons include:
- the environment loaded a different JAR than the POM suggests;
- dependency mediation selected a patched version;
- the JAR was replaced in a plugin realm;
- the operating system denied the write;
- another wrapper rejected the archive first;
- the test path did not reproduce the affected prefix relationship;
- the execution never reached
Expand; - a security control quarantined the archive.
Record the loaded version and class source during testing. In Java, a simple diagnostic can identify where the class came from:
System.out.println(
Expand.class
.getProtectionDomain()
.getCodeSource()
.getLocation()
);
Add that line to the lab when dependency ambiguity exists. It is stronger evidence than assuming the first matching JAR in a filesystem search is the one actually used.
Detection in build and application environments
CVE-2025-67030 does not have a universal network indicator. Detection should focus on component identity, archive-processing paths, and filesystem consequences.
Software composition analysis
SCA should identify:
pkg:maven/org.codehaus.plexus/plexus-utils
Flag:
- 3.x versions earlier than 3.6.1;
- 4.0.0 through 4.0.2.
The scanner must include build dependencies and plugin environments. A scan of the final production container can legitimately report no vulnerable component even though the release was built by a vulnerable Maven distribution or plugin.
Build-log evidence
Retain:
- Maven and Java versions;
- plugin versions;
- resolved dependency output;
- classpath or plugin-realm information where available;
- hashes of build images;
- archive source URLs or artifact coordinates;
- hashes of downloaded archives;
- the operating-system identity running extraction.
These records make later incident analysis possible. Without them, a team may know that a vulnerable tool existed but be unable to determine which jobs used it.
Filesystem boundary monitoring
On sensitive runners, monitor writes by build processes outside expected workspace and temporary roots. The useful policy is not “alert on all Java writes,” which produces excessive noise. It is “alert when a build process writes into locations that the job should not modify.”
Candidate protected areas include:
- neighboring project workspaces;
- CI controller directories;
- runner configuration;
- user startup files;
- tool installation directories;
- shared plugin locations;
- signing configuration;
- deployment scripts;
- cross-project caches.
The exact list must reflect the runner architecture. A containerized job with a read-only root filesystem needs different monitoring from a long-lived virtual machine.
Artifact integrity
A traversal write may alter a file that is later packaged rather than executed locally. Compare:
- generated artifacts against a clean rebuild;
- packaged files against declared source inputs;
- checksums across independent builders;
- release manifests against expected contents;
- signed provenance against actual build parameters.
Reproducible builds are particularly useful here. A difference does not automatically prove exploitation, but it gives responders a bounded set of files to investigate.
Cache integrity
Shared caches increase the persistence and cross-project effect of a write. Consider immutable or content-addressed caches where practical. Where mutable caches are unavoidable:
- segregate them by project or trust level;
- prevent pull-request jobs from writing release caches;
- validate cache keys and content;
- expire caches after security incidents;
- avoid loading executable code directly from unverified cache entries.
Remediation paths
The primary remediation is straightforward:
- Upgrade the 3.x line to 3.6.1 or later.
- Upgrade the 4.x line to 4.0.3 or later.
Both releases explicitly include the Zip Slip correction. (GitHub)
The implementation details become more complicated when the component is not a direct application dependency.
Direct Maven dependency
Declare a patched version:
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.6.1</version>
</dependency>
Applications already on the 4.x line should remain on a compatible patched 4.x release rather than moving backward to 3.6.1 merely because both contain the security fix.
Transitive Maven dependency
A project-level management rule can pin a patched version:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.6.1</version>
</dependency>
</dependencies>
</dependencyManagement>
Then confirm the selected version:
mvn -q dependency:tree \
-Dincludes=org.codehaus.plexus:plexus-utils
A version override is not automatically compatible with every parent library. Run unit tests, integration tests, packaging, and the exact CI goals used for release.
Maven plugin dependency
When the vulnerable copy belongs to a plugin, first upgrade the plugin to a release that declares a patched plexus-utils version.
When no corrected plugin release exists, a temporary explicit plugin dependency may work:
<plugin>
<groupId>com.example.build</groupId>
<artifactId>archive-processing-plugin</artifactId>
<version>2.4.0</version>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.6.1</version>
</dependency>
</dependencies>
</plugin>
This should not be applied blindly across all plugins. Plugin classpaths can contain tightly coupled libraries, and replacing one component may create linkage errors or behavioral changes. Preserve the override as a visible temporary control and remove it after the plugin maintainer ships a supported fix.
Maven distribution
Upgrade the Maven installation used by developers and CI. Maven 3.9.15 was the release that explicitly replaced its bundled plexus-utils with 3.6.1. A later supported stable 3.9.x release includes subsequent Maven fixes and is generally preferable for normal production use. (Apache Maven)
Confirm the actual runner binary:
mvn -version
Do not rely on a Dockerfile change until the image has been rebuilt, deployed, and selected by the job.
Gradle dependency
For a normal Gradle project dependency:
dependencies {
constraints {
implementation(
"org.codehaus.plexus:plexus-utils:3.6.1"
) {
because(
"Fixes CVE-2025-67030"
)
}
}
}
Then inspect the relevant configuration:
./gradlew dependencyInsight \
--dependency plexus-utils \
--configuration runtimeClasspath
A project constraint may not control a plugin’s isolated classpath. Validate buildEnvironment, plugin versions, convention builds, and any custom tooling separately.
CI image
Rebuild the image from a clean base, update Maven or other build tools, and remove prewarmed vulnerable artifacts where they could be selected.
A minimal verification sequence is:
docker build \
--no-cache \
-t java-builder:cve-2025-67030-fixed \
.
docker run --rm \
java-builder:cve-2025-67030-fixed \
mvn -version
Then inspect the image:
docker run --rm \
java-builder:cve-2025-67030-fixed \
sh -lc '
find / -type f \
-name "plexus-utils-*.jar" \
2>/dev/null
'
The search can report unused cache entries. Follow it with classpath and build-goal validation.
Compensating controls when immediate patching is impossible
A patch is preferable because the defect exists in the path authorization logic. Compensating controls should reduce exposure while the upgrade is tested.
Reject untrusted archives
Do not pass user-controlled, pull-request-controlled, or remotely downloaded archives into the affected extraction path.
Where archives must be accepted:
- require authentication;
- verify expected publisher identity;
- verify signatures or trusted hashes;
- enforce content and size policies;
- quarantine before processing;
- record provenance;
- separate high-trust and low-trust workflows.
A signature proves who signed the archive, not that the archive is safe. It is useful only when the signer and release process are trusted.
Use a disposable extraction environment
Run extraction in an ephemeral container or virtual machine with:
- no release credentials;
- no cloud instance credentials;
- no SSH agent;
- no shared home directory;
- no writable host mounts;
- no Docker socket;
- no cross-project cache;
- a read-only root filesystem where feasible;
- a dedicated writable temporary directory;
- resource limits.
Copy only validated outputs into the next stage.
Restrict write permissions
The process should not be able to modify:
- build scripts;
- plugin directories;
- CI configuration;
- release keys;
- deployment manifests;
- other workspaces;
- shared executable paths.
Filesystem permissions do not repair the vulnerable boundary, but they reduce the consequences of an escape.
Separate build and release identities
A pull-request build should not have the same credentials as a release build. The archive-processing stage should not hold permissions to publish packages, sign artifacts, deploy infrastructure, or modify production repositories.
Validate extracted output
After extraction:
- enumerate every resulting file;
- reject unexpected paths and types;
- enforce file-count and size limits;
- reject symbolic links when not required;
- move approved files into a clean directory;
- avoid executing directly from the extraction directory.
These controls should remain after patching because they address archive risks beyond CVE-2025-67030.

Patch verification checklist
A reliable remediation closes every copy that participates in the workflow.
Confirm the declared version
For Maven:
mvn -q dependency:tree \
-Dincludes=org.codehaus.plexus:plexus-utils
For Gradle:
./gradlew dependencyInsight \
--dependency plexus-utils \
--configuration runtimeClasspath
Confirm the build-tool version
mvn -version
Record the Maven home and Java runtime.
Inspect plugin and extension paths
Rückblick:
- effective POM;
- plugin declarations;
- plugin dependencies;
.mvn/extensions.xml;- custom Maven distributions;
buildSrc;- Gradle convention plugins;
- included builds;
- CI bootstrap scripts.
Remove stale artifacts
Delete or replace vulnerable cached copies only after fixing the declaration or tool that requested them. Otherwise, they will return during the next build.
Use clean, newly provisioned runners for high-assurance validation.
Rebuild CI images
A source-control change does not modify an already published image tag. Produce a new image digest and update the job configuration to reference it.
Run the harmless boundary test
Run the local sibling-directory probe against the exact component and environment used by the build. Confirm:
- the unsafe entry is rejected;
- no file exists in the sibling directory;
- legitimate archive extraction still succeeds.
Capture evidence
Store:
- dependency-tree output;
- class origin;
- Maven or Gradle version;
- image digest;
- safe PoC output;
- final SBOM;
- test results;
- remediation ticket;
- affected asset list.
For teams using automated or AI-assisted security validation, the useful output is not merely “CVE found.” It is a chain connecting the component to the build phase, classloader, archive trust boundary, process privileges, safe reproduction, and patched retest. Sträflich is relevant to evidence-oriented authorized testing workflows of this kind, but automated analysis should not replace verification of the actual dependency and execution path.
Incident response when untrusted archives were processed
A vulnerable version does not establish that exploitation occurred. If a reachable workflow processed attacker-influenced archives, however, the investigation should go beyond upgrading the dependency.
Preserve the input
Retain:
- the original archive;
- its cryptographic hash;
- source URL or upload record;
- submitting identity;
- processing timestamp;
- job identifier;
- runner identity;
- extraction logs.
Do not repeatedly open the archive with unrelated desktop tools. Analyze it in an isolated environment.
Establish the write boundary
Determine:
- the intended extraction root;
- the user account running Java;
- writable sibling and parent directories;
- mounted host paths;
- shared caches;
- files modified during the processing window.
Search for files whose creation or modification time aligns with the job, while accounting for archive-preserved timestamps.
Review executable and trusted locations
Prioritize:
- shell initialization files;
- build scripts;
- Maven and Gradle configuration;
- plugin and extension directories;
- generated-source trees;
- packaging inputs;
- deployment manifests;
- service configuration;
- scheduled-task definitions;
- shared caches;
- files loaded by later pipeline stages.
The relevant locations are those writable by the process, not a generic list of operating-system paths.
Rebuild from clean infrastructure
When release integrity is uncertain:
- create new runners;
- rebuild base images;
- clear affected caches;
- rebuild artifacts from reviewed source;
- compare outputs;
- revoke questionable releases;
- publish replacement artifacts when necessary.
Rotate exposed credentials
If the affected job had access to repository tokens, cloud credentials, signing material, deployment secrets, or package-publishing credentials, rotate them according to the evidence and potential access path.
A traversal write does not automatically reveal a secret. Rotation is justified when a malicious file could have altered code or configuration that later executed under the credentialed identity.
State conclusions precisely
Use language such as:
- “A vulnerable and reachable extraction path was confirmed.”
- “The supplied archive was processed by the affected version.”
- “A safe reproduction demonstrated the same boundary failure.”
- “No unauthorized files were identified in the retained telemetry.”
- “Available telemetry is insufficient to exclude historical exploitation.”
Do not replace the final sentence with “not exploited” when the logs, filesystem history, or runner state no longer exist.
Related archive traversal CVEs
CVE-2018-1002200 in plexus-archiver
CVE-2018-1002200 affected plexus-archiver versions before 3.6.0. NVD describes a classic Zip Slip condition in which ../ components in archive entries could write arbitrary files outside the destination directory. (NVD)
It is relevant because it affected a neighboring component in the Plexus ecosystem and addressed the same broad trust boundary: archive entry names must not determine unrestricted filesystem destinations.
It is not the same vulnerability:
| Einzelheiten | CVE-2018-1002200 | CVE-2025-67030 |
|---|---|---|
| Komponente | plexus-archiver | plexus-utils |
| Affected logic | Archive extraction in the archiver component | Expand.extractFile |
| Published weakness | Traversal through archive entry paths | Incorrect resolved-path containment check |
| Historical fix line | plexus-archiver 3.6.0 | plexus-utils 3.6.1 and 4.0.3 |
| Defensive lesson | Reject archive destinations outside the root | Canonicalize and enforce a component boundary, not a raw string prefix |
Teams should not close CVE-2025-67030 merely because they previously remediated CVE-2018-1002200. The Maven coordinates and fixed versions are different.
CVE-2025-4517 in Python tarfile
CVE-2025-4517 concerns arbitrary filesystem writes outside the extraction directory in affected Python tarfile filtering behavior. The affected versions and implementation are unrelated to plexus-utils, but the defensive pattern is similar: archive safety depends on the final filesystem destination and the semantics of the archive format, not just a superficial inspection of entry names. (NVD)
The recurrence of these issues across Java, Python, Go, package managers, plugin installers, and deployment tools shows why archive extraction should be treated as a security boundary in its own right.
Common mistakes during remediation
Declaring every finding to be direct RCE
The advisory describes potential arbitrary code execution. The proven primitive is an escaped file write. Report both facts:
- what the library directly permits;
- what additional behavior is required for execution.
This produces a more credible risk statement and helps the owner identify the real attack path.
Upgrading only Apache Maven
A newer Maven distribution fixes Maven’s bundled copy. It does not necessarily replace:
- project dependencies;
- plugin dependencies;
- core extensions;
- custom tools;
- shaded libraries;
- JARs stored in CI images.
Inventory every execution plane.
Updating only dependencyManagement
Project dependency management does not automatically govern plugin transitive dependencies. Inspect the plugin classpath and upgrade the plugin where possible. (Apache Maven)
Scanning only the final application
A build-only dependency may disappear from the release artifact while still executing with repository, signing, or deployment credentials during the build.
Generate a build-environment SBOM or retain separate inventories for:
- builder image;
- build plugins;
- application runtime;
- release tooling.
Treating cache presence as exploitability
An old JAR in .m2 is not proof that it was loaded. Use dependency resolution, plugin-realm, class-source, or execution evidence.
Treating “not reachable” as permanent
Reachability can change when:
- a plugin goal is enabled;
- a new archive source is introduced;
- a build profile is activated;
- a parent POM changes;
- a CI image is updated;
- a feature begins processing uploads;
- a dependency starts calling
Expand.
Patch even when current reachability appears limited, then retain the reachability assessment as prioritization evidence rather than as a permanent exception.
Blocking only ../
Security decisions should be based on the resolved destination. Input filtering can supplement that check, but cannot replace it.
Ignoring the separator boundary
Canonicalization without component-aware comparison can still leave a prefix error. The secure decision must distinguish:
/safe/root/file.txt
von:
/safe/root-other/file.txt
Reusing a potentially contaminated runner
Replacing the JAR does not remove files that may already have been written outside an old extraction directory. High-risk investigations should recreate the runner and caches from trusted sources.
Forgetting functional regression tests
The security fix should block unsafe entries without breaking valid extraction. Test:
- nested directories;
- ordinary files;
- empty directories;
- expected absolute or relative behavior;
- platform-specific paths;
- existing-file handling;
- large but permitted archives.
The project itself added both rejection tests and a normal extraction test with the fix. (GitHub)
Operational prioritization
A practical remediation order is:
- Internet-facing services that automatically extract untrusted archives.
- Pull-request and community build jobs that process contributor-controlled files.
- Persistent or shared runners with cross-project writable storage.
- Build jobs holding release, signing, cloud, or deployment credentials.
- Plugin installers and extension managers.
- Internal build tools that process third-party packages.
- Runtime applications where the component is present but reachability is unclear.
- Unused caches and development artifacts.
This order should be adjusted when a lower-listed asset has a verified execution chain and a higher-listed asset has strong isolation.
The most useful ownership split is often:
- application teams remediate direct and transitive project dependencies;
- build-platform teams remediate Maven distributions and CI images;
- plugin owners remediate plugin classpaths;
- security engineering provides the safe regression test and triage criteria;
- incident response evaluates historical archive processing;
- release engineering validates artifact integrity.
Without explicit ownership, each group can assume another team’s upgrade closed the issue.
Frequently asked questions
Which plexus-utils versions are affected by CVE-2025-67030?
- Affected 3.x line: Versions earlier than 3.6.1.
- Affected 4.x line: Versions from 4.0.0 up to but excluding 4.0.3.
- Fixed 3.x version: 3.6.1.
- Fixed 4.x version: 4.0.3.
- Important nuance: Do not describe all versions below 4.0.3 as vulnerable because 3.6.1 is patched. (GitHub)
Does CVE-2025-67030 provide direct remote code execution?
- The direct primitive is a file write outside the intended extraction directory.
- An attacker must be able to influence an archive processed by the vulnerable method.
- The Java process must have write permission to a useful destination.
- Code execution requires a later mechanism that loads, sources, packages, or executes the escaped file.
- A remote service that automatically extracts attacker-controlled archives may expose a remote attack path.
- Package presence alone does not prove remote code execution.
Is upgrading Apache Maven enough?
- Maven 3.9.15 upgraded its bundled plexus-utils dependency to 3.6.1.
- Upgrading Maven addresses the copy shipped with that Maven distribution.
- It does not automatically replace copies brought by project dependencies, plugins, extensions, standalone tools, shaded JARs, or CI images.
- Check the actual Maven binary used by CI with
mvn -version. - Prefer a current supported stable Maven release after compatibility testing. (Apache Maven)
How can I find a transitive plexus-utils dependency?
- Run Maven’s filtered dependency tree:
mvn -q dependency:tree \
-Dincludes=org.codehaus.plexus:plexus-utils
- For Gradle, use:
./gradlew dependencyInsight \
--dependency plexus-utils \
--configuration runtimeClasspath
- Inspect plugin and buildscript classpaths separately.
- Search Maven home, CI images, local repositories, extensions, and shaded JARs.
- Confirm which JAR is actually loaded before making a reachability conclusion.
Does Maven dependencyManagement fix vulnerable plugin dependencies?
- Not necessarily.
dependencyManagementprimarily controls project dependency mediation.- Maven plugin dependencies operate in plugin class realms.
- Upgrade the affected plugin first.
- When no plugin fix exists, an explicit dependency under
<plugin><dependencies>may be a temporary option. - Test any override because the plugin may depend on behavior from its original library version. (Apache Maven)
How can I validate the fix without weaponizing the vulnerability?
- Use a temporary local directory.
- Create a harmless ZIP containing a text marker.
- Attempt to place it in a sibling directory with a shared name prefix.
- Confirm the vulnerable version creates the marker outside the root.
- Confirm 3.6.1 or 4.0.3 rejects the entry.
- Do not target system, application, user-profile, or production paths.
- Record the loaded class source so the test proves which JAR was exercised.
What can I do when I cannot upgrade immediately?
- Stop processing untrusted archives through the affected path.
- Isolate extraction in a disposable, unprivileged environment.
- Remove release and cloud credentials from that stage.
- Use a dedicated writable temporary filesystem.
- Prevent access to shared workspaces and caches.
- Verify archive provenance and signatures.
- Enumerate and validate all extracted files before moving them forward.
- Schedule the version upgrade because compensating controls do not repair the defective boundary check.
Is build-only exposure less serious than runtime exposure?
- Not automatically.
- Build systems may hold stronger credentials than production applications.
- A compromised build can alter published artifacts without leaving the vulnerable JAR in the final package.
- Shared runners and caches can spread impact across projects.
- Build-only exposure may be lower when the runner is ephemeral, unprivileged, credential-free, and restricted to trusted archives.
- Priority should be based on trust, privileges, persistence, and downstream artifact use—not on the label “build dependency.”
Closing assessment
CVE-2025-67030 is a clear example of why path containment cannot be reduced to string prefix matching. The affected plexus-utils code resolved an archive destination and then authorized it using a textual relationship that did not represent the filesystem boundary. A sibling directory with the same prefix could pass the check even though it was outside the extraction root.
The immediate action is to move affected 3.x deployments to at least 3.6.1 and affected 4.x deployments to at least 4.0.3. The complete action is broader: identify every project, plugin, extension, Maven distribution, CI image, and embedded tool that can load the class; determine whether attacker-influenced archives reach it; reduce the privileges of archive-processing jobs; and verify the patch with a harmless boundary test.
A scanner can identify the version. Only environment-specific evidence can establish whether the archive path is reachable, what the process can overwrite, and whether that write can influence a later build or execution stage.

