The vulnerability exists in the resolveSafeChildPath function, which is responsible for ensuring that file paths resolve to a location within a specified base directory. The root cause was an insecure implementation of path resolution that failed to properly handle symbolic link (symlink) chains and dangling symlinks.
The vulnerability stemmed from the resolveRealPath helper function within packages/backend-plugin-api/src/paths.ts. This function would try to resolve a path using fs.realpathSync, but if the path didn't exist, it would silently return the original, unresolved path. An attacker could exploit this by creating a symlink that pointed to a non-existent file outside the safe directory. When resolveSafeChildPath was called with this symlink, the flawed resolveRealPath would fail to resolve it and return the symlink path itself, which isChildPath would then incorrectly approve.
The patch addresses this by completely removing the insecure resolveRealPath function from packages/backend-plugin-api/src/paths.ts and shifting the responsibility of path resolution to the isChildPath function in @backstage/cli-common. A new, more robust resolveRealPath function was introduced in packages/cli-common/src/isChildPath.ts. This new function correctly handles symlink chains and dangling symlinks by recursively resolving symlink targets and walking up the directory tree to resolve paths even when parts of the path do not yet exist. By ensuring all paths are fully and securely resolved before comparison, the patch effectively closes the path traversal vulnerability.