Skip to content

RequireFilesExist falsely reports existing files as missing when path contains .. segments (macOS) #977

Description

@ciechanowiec

Affected version

3.6.2, 3.5.0, 3.4.1

Bug description

Summary

RequireFilesExist reports an existing, readable, non-symlink file as missing when the configured path contains .. segments that traverse a firmlinked directory on macOS (e.g. /Users). The rule's symlink heuristic calls Path.toRealPath(), which throws on such paths; the resulting exception is caught and the file is conservatively classified as a symlink, causing the rule to fail.

Affected versions

Reproduced with maven-enforcer-plugin:

  • 3.6.2
  • 3.5.0
  • 3.4.1

Environment

  • OS: macOS (Darwin 25.3.0, APFS, /Users is a firmlinked directory)
  • Architecture: aarch64 (Apple Silicon)
  • JDK: project's ${maven.compiler.release} (reproduced under standard Temurin JDK)
  • Maven: 3.9.x

Steps to reproduce

  1. On macOS, place a file at /Users/<user>/projects/repo/README.adoc.

  2. From a Maven module at /Users/<user>/projects/repo/src/a/b/pom.xml, configure:

    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-enforcer-plugin</artifactId>
      <version>3.6.2</version>
      <executions>
        <execution>
          <id>require-readme</id>
          <phase>validate</phase>
          <goals><goal>enforce</goal></goals>
          <configuration>
            <rules>
              <requireFilesExist>
                <files>
                  <file>${project.basedir}/../../../README.adoc</file>
                </files>
              </requireFilesExist>
            </rules>
          </configuration>
        </execution>
      </executions>
    </plugin>
  3. Run mvn enforcer:enforce@require-readme.

Expected

Build succeeds. The path resolves to an existing regular file (verifiable via ls, [ -f ... ], Files.exists(), and File.exists()).

Actual

[ERROR] Rule 0: org.apache.maven.enforcer.rules.files.RequireFilesExist failed with message:
[ERROR] Some required files are missing:
[ERROR] /Users/<user>/projects/repo/src/a/b/../../../README.adoc

The same rule succeeds when the <file> value is the canonical path with .. collapsed (/Users/<user>/projects/repo/README.adoc).

Root cause

In enforcer-rules, RequireFilesExist treats files as missing if they are symlinks:

@Override
boolean checkFile(File file) {
    if (file == null) {
        return true;
    }
    return file.exists() && !isSymlink(file);
}

private static boolean isSymlink(File file) {
    try {
        return Files.isSymbolicLink(file.toPath())
            || !Files.isSameFile(file.toPath(), file.toPath().toRealPath());
    } catch (Exception e) {
        return true; // ← any exception is treated as "this is a symlink"
    }
}

On macOS, Path.toRealPath() can throw IOException (or a JDK-internal exception) when the input path traverses firmlinked system directories such as /Users via .. segments, even when the file itself exists, is a regular file, and is not a symlink. The catch-all then classifies the file as a symlink and the rule rejects it.

This is reproducible with a tiny Java snippet:

Path p = Paths.get("/Users/<user>/projects/repo/src/a/b/../../../README.adoc");
System.out.println(Files.exists(p));        // true
System.out.println(p.toFile().exists());    // true
System.out.println(Files.isSymbolicLink(p));// false
System.out.println(p.toRealPath());         // throws on affected macOS setups

Impact

Any multi-module project on macOS that uses RequireFilesExist with a path expressed relative to ${project.basedir} (e.g. checking a file at the repository root from a nested module) will get a spurious build failure. There is no diagnostic clue: the message says the file is missing, but it is not.

Suggested fixes

Any one of the following would resolve the issue:

  1. Drop the toRealPath()-based symlink check from RequireFilesExist. RequireFilesExist's contract is "this file must exist" — symlink-vs-regular-file is arguably out of scope. If symlink rejection is required, expose it as an opt-in property.
  2. Use Files.isSymbolicLink(path) only. Treating the file as a symlink only when Files.isSymbolicLink() returns true is sufficient for the documented contract and avoids toRealPath() failures on ..-containing paths entirely.
  3. Normalize the path before the symlink check. Calling path.toAbsolutePath().normalize() before toRealPath() collapses .. segments and avoids the macOS-specific throw.
  4. Stop swallowing exceptions as "symlink". If toRealPath() is retained, an IOException should not silently flip the verdict to "missing"; either fail with the underlying error or fall back to Files.isSymbolicLink().

Workaround

Replace the rule with maven-antrun-plugin, which uses Ant's <available> and resolves .. correctly:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <executions>
    <execution>
      <id>require-readme</id>
      <phase>validate</phase>
      <goals><goal>run</goal></goals>
      <configuration>
        <target>
          <fail message="Required file not found: ${project.basedir}/../../../README.adoc">
            <condition>
              <not>
                <available file="${project.basedir}/../../../README.adoc"/>
              </not>
            </condition>
          </fail>
        </target>
      </configuration>
    </execution>
  </executions>
</plugin>

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions