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 22:17:53 +0100
Commit message: chore: return the original input path, and add a test

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 94b96580e94..6073d43a245 100644
--- a/packages/backend-common/src/paths.test.ts
+++ b/packages/backend-common/src/paths.test.ts
@@ -14,11 +14,10 @@
  * 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();
@@ -40,13 +39,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', async () => {
+    it('should resolve to the full path if the target is inside the directory', () => {
       expect(resolveSafeChildPath(workspacePath, './README.md')).toEqual(
-        `${await fs.realpath(workspacePath)}/README.md`,
+        `${workspacePath}/README.md`,
       );
     });
 
     it('should throw an error if the path is a symlink to a directory outside of the base path', () => {
       mockDir.addContent({
@@ -58,7 +57,13 @@ describe('paths', () => {
         resolveSafeChildPath(workspacePath, './symlink/index.md'),
       ).toThrow(
         'Relative path is not allowed to refer to a directory outside its parent',
       );
     });
+
+    it('should not throw an error when a folder is referenced that doesnt already exist', () => {
+      expect(resolveSafeChildPath(workspacePath, 'template')).toEqual(
+        `${workspacePath}/template`,
+      );
+    });
   });
 });
diff --git a/packages/backend-common/src/paths.ts b/packages/backend-common/src/paths.ts
index b5d4b331fd4..c77f2326337 100644
--- a/packages/backend-common/src/paths.ts
+++ b/packages/backend-common/src/paths.ts
@@ -70,11 +70,12 @@ export function resolveSafeChildPath(base: string, path: string): string {
     throw new NotAllowedError(
       'Relative path is not allowed to refer to a directory outside its parent',
     );
   }
 
-  return targetPath;
+  // Don't return the resolved path as the original could be a symlink
+  return resolvePath(base, path);
 }
 
 function resolveRealPath(path: string): string {
   try {
     return realPath(path);

Vulnerable 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`,
  return targetPath;
Fixed code:     it('should resolve to the full path if the target is inside the directory', () => {
        `${workspacePath}/README.md`,

    it('should not throw an error when a folder is referenced that doesnt already exist', () => {
      expect(resolveSafeChildPath(workspacePath, 'template')).toEqual(
        `${workspacePath}/template`,
      );
    });
  // Don't return the resolved path as the original could be a symlink
  return resolvePath(base, path);