appsmithorg/appsmith · critical · AppsmithPluginException
PE-PLG-5000
PE-PLG-5000
Error message
SECURITY: Unable to resolve real path for {} while validating Git root containment What it means
Thrown by FileUtilsCEImpl.validatePathIsWithinGitRoot when resolving symbolic links for the symlink-aware containment check (the defense for GHSA-fqwc-g9wm-5895). The code intentionally fails closed: if toRealPathResolvingExistingPrefix(gitRoot) or toRealPathResolvingExistingPrefix(target) raises an IOException (permissions denied, dangling symlink, I/O error on the deepest existing ancestor), it logs the error and throws AppsmithPluginException(PLUGIN_ERROR, code PE-PLG-5000) rather than letting the operation proceed with an unvalidated path.
Source
Thrown at app/server/appsmith-git/src/main/java/com/appsmith/git/files/FileUtilsCEImpl.java:374
if (!normalizedTarget.startsWith(gitRoot)) {
throwPathTraversal(normalizedTarget, gitRoot);
}
// 2. Symlink-aware containment check (GHSA-fqwc-g9wm-5895) — blocks symbolic links committed
// inside a repository that point outside the Git root. Path.normalize() above is purely
// lexical and does NOT resolve symlinks, whereas every downstream file I/O sink follows
// them. Resolve the real (symlink-free) path before comparing. Fails closed on I/O error.
try {
Path realGitRoot = toRealPathResolvingExistingPrefix(gitRoot);
Path realTarget = toRealPathResolvingExistingPrefix(normalizedTarget);
if (!realTarget.startsWith(realGitRoot)) {
throwPathTraversal(realTarget, realGitRoot);
}
} catch (IOException e) {
String errorMessage = "SECURITY: Unable to resolve real path for " + normalizedTarget
+ " while validating Git root containment";
log.error(errorMessage, e);
throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, errorMessage);
}
}
private void throwPathTraversal(Path attemptedPath, Path gitRoot) {
String errorMessage = "SECURITY: Path traversal detected. Attempted to access " + attemptedPath
+ " which is outside the Git root " + gitRoot;
log.error(errorMessage);
throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, errorMessage);
}
/**
* Resolves symbolic links in the longest existing prefix of {@code path} and re-appends the
* remaining (not-yet-created) path segments lexically. {@link Path#toRealPath} cannot be used
* directly because it requires the whole path to exist, while file writes legitimately target
* paths that do not exist yet. By resolving the deepest existing ancestor we still detect any
* symlink along the existing portion (including when {@code path} itself is a symlink) while
* supporting yet-to-be-created files.
*/View on GitHub (pinned to 8cd9021c24)
Solutions
- Verify the configured git root path exists and is readable/executable by the Appsmith server user: ls -la <gitRoot>.
- Find and remove dangling symlinks inside the repo root: find <gitRoot> -xtype l.
- Check filesystem/container permissions for the volume holding the git root; remount with correct uid/gid.
- Re-run the git operation after fixing permissions; the check fails closed so it will pass once resolution succeeds.
- Inspect the server log: the IOException is logged with the SECURITY message and shows the exact path that failed to resolve.
Example fix
// before # git root unreadable by appsmith user sudo chown -R appsmith:appsmith /data/gitroot // after # readable + resolvable; containment check now succeeds ls -la /data/gitroot # confirms ownership and no dangling links
Defensive patterns
Strategy: validation
Validate before calling
# Pre-flight: confirm the git root and ancestors are readable/resolvable. git_root="$(config_get_gitRootPath)" [ -d "$git_root" ] || echo "git root missing" find "$git_root" -xtype l -print # list dangling symlinks stat -c '%U:%G %a %n' "$git_root" # check ownership/perm
Type guard
// Java caller-side guard before invoking FileUtils
Path root = Paths.get(config.getGitRootPath()).toAbsolutePath().normalize();
if (!Files.isDirectory(root) || !Files.isReadable(root)) {
throw new IllegalStateException("Git root not readable: " + root);
} Try / catch
try {
fileUtils.validatePathIsWithinGitRoot(target);
} catch (AppsmithPluginException e) {
if (e.getCode() == AppsmithPluginError.PLUGIN_ERROR) {
log.error("Git root containment check failed (I/O); verify permissions/symlinks", e);
}
throw e;
} Prevention
- Ensure the configured git root exists and is readable/executable by the Appsmith server user.
- Periodically remove dangling symlinks: find <gitRoot> -xtype l -delete.
- Mount container volumes with the correct uid/gid for the server user.
- Never widen or bypass the check to 'make it work'; fix the underlying I/O issue.
- Watch the server log: the IOException is printed with the SECURITY message.
When it happens
Trigger: A git operation (commit, pull, connect, import, file read/write) on a path whose existing ancestor cannot be resolved: the git root or a target directory is on a filesystem that returns I/O errors on toRealPath, a symlink in the path is dangling, or the process lacks read/execute permission on an ancestor directory.
Common situations: Misconfigured git root path (gitServiceConfig.getGitRootPath) pointing at an unreadable/nonexistent location; container volume-mount permission issues; a symlink left inside the repo from a prior clone that now dangles; restrictive filesystem ACLs on the data root.
Related errors
- PE-PLG-5003
- You specified ${type} in your env, but the file "${file}" ca
- Not enough space available at /appsmith-stacks. Please ensur
- Could not find the contents of the backup archive.
- The ${path} path must start with 'https://'.
AI-assisted analysis of appsmithorg/appsmith@8cd9021c24 (2026-08-12).
Data as JSON: /api/errors/6f3944d94b32fdb9.
Report an issue: GitHub.