CVE-2024-26150: Insufficient Information (NVD-CWE-noinfo)
value: `@backstage/backend-common` is a common functionality library for backends for Backstage, an open platform for building developer portals. In `@backstage/backend-common` prior to versions 0.21.1, 0.20.2, and 0.19.10, paths checks with the `resolveSafeChildPath` utility were not exhaustive enough, leading to risk of path traversal vulnerabilities if symlinks can be injected by attackers. This issue is patched in `@backstage/backend-common` versions 0.21.1, 0.20.2, and 0.19.10.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-02-23T16:15Z
Repository: https://github.com/backstage/backstage
Language: TypeScript
Files changed: packages/backend-common/src/paths.test.ts, packages/backend-common/src/paths.ts
Commit date: 2024-02-22 21:19:34 +0100
Commit message: fix: make sure to construct the target from the resolved base path too

Signed-off-by: blam <ben@blam.sh>

Diff: diff --git a/packages/backend-common/src/paths.test.ts b/packages/backend-common/src/paths.test.ts
index a9a557c8fe6..94b96580e94 100644
--- a/packages/backend-common/src/paths.test.ts
+++ b/packages/backend-common/src/paths.test.ts
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
 import { createMockDirectory } from '@backstage/backend-test-utils';
 import { resolveSafeChildPath } from './paths';
+import fs from 'fs/promises';
 
 describe('paths', () => {
   describe('resolveSafeChildPath', () => {
     const mockDir = createMockDirectory();
     const secondDirectory = createMockDirectory();
@@ -39,13 +40,13 @@ describe('paths', () => {
       ).toThrow(
         'Relative path is not allowed to refer to a directory outside its parent',
       );
     });
 
-    it('should resolve to the full path if the target is inside the directory', () => {
+    it('should resolve to the full path if the target is inside the directory', async () => {
       expect(resolveSafeChildPath(workspacePath, './README.md')).toEqual(
-        `${workspacePath}/README.md`,
+        `${await fs.realpath(workspacePath)}/README.md`,
       );
     });
 
     it('should throw an error if the path is a symlink to a directory outside of the base path', () => {
       mockDir.addContent({
diff --git a/packages/backend-common/src/paths.ts b/packages/backend-common/src/paths.ts
index 0e784a60db7..b5d4b331fd4 100644
--- a/packages/backend-common/src/paths.ts
+++ b/packages/backend-common/src/paths.ts
@@ -61,13 +61,14 @@ export function resolvePackagePath(name: string, ...paths: string[]) {
  * @param base - The base directory to resolve the path from.
  * @param path - The target path, relative or absolute
  * @returns A path that is guaranteed to point to or within the base path.
  */
 export function resolveSafeChildPath(base: string, path: string): string {
-  const targetPath = resolvePath(base, path);
+  const resolvedBasePath = resolveRealPath(base);
+  const targetPath = resolvePath(resolvedBasePath, path);
 
-  if (!isChildPath(resolveRealPath(base), resolveRealPath(targetPath))) {
+  if (!isChildPath(resolvedBasePath, resolveRealPath(targetPath))) {
     throw new NotAllowedError(
       'Relative path is not allowed to refer to a directory outside its parent',
     );
   }
 

Vulnerable code:     it('should resolve to the full path if the target is inside the directory', () => {
        `${workspacePath}/README.md`,
  const targetPath = resolvePath(base, path);
  if (!isChildPath(resolveRealPath(base), resolveRealPath(targetPath))) {
Fixed code: import fs from 'fs/promises';
    it('should resolve to the full path if the target is inside the directory', async () => {
        `${await fs.realpath(workspacePath)}/README.md`,
  const resolvedBasePath = resolveRealPath(base);
  const targetPath = resolvePath(resolvedBasePath, path);
  if (!isChildPath(resolvedBasePath, resolveRealPath(targetPath))) {