From 8566971728e3355a4024dcf40dd370a7a2a93189 Mon Sep 17 00:00:00 2001 From: "aikido-autofix[bot]" <119856028+aikido-autofix[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 10:58:44 +0000 Subject: [PATCH] fix(security): autofix Potential file inclusion attack via reading file --- src/scanner/walk.ts | 3 ++ tests/unit/scanner/walk.test.ts | 50 ++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/scanner/walk.ts b/src/scanner/walk.ts index d341da6..977e1b5 100644 --- a/src/scanner/walk.ts +++ b/src/scanner/walk.ts @@ -25,6 +25,9 @@ export async function walkDirectory( visited: Set = new Set() ): Promise { // Normalize and resolve path to handle symlinks + if (root.includes('..') || (root.startsWith('/'))) { + throw new Error(`Invalid directory path provided`); + } const normalizedRoot = path.resolve(root); // Check for symlink loops diff --git a/tests/unit/scanner/walk.test.ts b/tests/unit/scanner/walk.test.ts index 81819db..c287a5b 100644 --- a/tests/unit/scanner/walk.test.ts +++ b/tests/unit/scanner/walk.test.ts @@ -3,7 +3,7 @@ * Graduated from story-32, story-43, and story-54 */ import { describe, it, expect } from "vitest"; -import { filterWorkItemDirectories, buildWorkItemList, normalizePath } from "@/scanner/walk"; +import { filterWorkItemDirectories, buildWorkItemList, normalizePath, walkDirectory } from "@/scanner/walk"; import type { DirectoryEntry } from "@/types"; describe("filterWorkItemDirectories", () => { @@ -159,3 +159,51 @@ describe("normalizePath", () => { expect(normalized).toBe(unixPath); }); }); + +describe("walkDirectory - Path Traversal Security", () => { + /** + * Security tests for path traversal vulnerability mitigation + * Tests various path traversal attack vectors + */ + + it("GIVEN path with parent directory traversal WHEN walking THEN throws error", async () => { + // Given: Path with .. attempting to traverse up + const maliciousPath = "../../../etc/passwd"; + + // When/Then: Should reject the path + await expect(walkDirectory(maliciousPath)).rejects.toThrow("Invalid directory path provided"); + }); + + it("GIVEN path with embedded parent directory WHEN walking THEN throws error", async () => { + // Given: Path with .. in the middle + const maliciousPath = "specs/../../../etc/passwd"; + + // When/Then: Should reject the path + await expect(walkDirectory(maliciousPath)).rejects.toThrow("Invalid directory path provided"); + }); + + it("GIVEN absolute path WHEN walking THEN throws error", async () => { + // Given: Absolute path starting with / + const absolutePath = "/etc/passwd"; + + // When/Then: Should reject absolute paths + await expect(walkDirectory(absolutePath)).rejects.toThrow("Invalid directory path provided"); + }); + + it("GIVEN path with multiple parent traversals WHEN walking THEN throws error", async () => { + // Given: Path with multiple .. sequences + const maliciousPath = "../../../../../../etc/shadow"; + + // When/Then: Should reject the path + await expect(walkDirectory(maliciousPath)).rejects.toThrow("Invalid directory path provided"); + }); + + it("GIVEN valid relative path WHEN walking THEN accepts path", async () => { + // Given: Valid relative path without traversal + const validPath = "specs/doing"; + + // When/Then: Should accept valid relative paths + // This will fail if directory doesn't exist, but won't fail the security check + await expect(walkDirectory(validPath)).rejects.toThrow(/Failed to walk directory/); + }); +});