# Path Traversal Vulnerability Fix Implementation Plan
**Created:** 11:11:31 AM | 12/24/2025 (EST)
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Fix CVSS 7.4 (HIGH) path traversal vulnerability in docker.ts buildImage() function by implementing robust path validation that prevents directory traversal attacks.
**Architecture:** Extract path validation logic into dedicated security utility function with comprehensive validation rules: reject relative path components (.., .), enforce absolute paths, validate allowed character sets, and ensure paths resolve within expected boundaries. Apply DRY principle by using this validator for all three path inputs (context, dockerfile).
**Tech Stack:** TypeScript 5.7+, Vitest, Node.js path module, strict type checking
---
## Security Context
**Vulnerability Details:**
- **CVSS Score:** 7.4 (HIGH)
- **CWE:** CWE-22 - Improper Limitation of a Pathname to a Restricted Directory
- **Location:** `src/services/docker.ts:993-995`
- **Impact:** Attackers can use `../../../etc/passwd` to access files outside intended directories
- **Current Regex:** `/^[a-zA-Z0-9._\-/]+$/` - Only blocks special chars, NOT directory traversal
**Attack Vectors to Block:**
1. Basic traversal: `../../../etc/passwd`
2. Encoded traversal: `%2e%2e%2f` (handled at transport layer, but test anyway)
3. Mixed traversal: `/valid/path/../../etc/passwd`
4. Hidden traversal: `/valid/./path/../../../etc/passwd`
5. Relative paths: `./config` or `../sibling`
6. Absolute paths with traversal: `/home/user/../../etc/passwd`
---
## Step 1: Write test for basic directory traversal detection (RED)
**Test File:** `src/services/docker.test.ts`
Add test case to existing `describe("buildImage")` block:
```typescript
it("should reject context path with .. directory traversal", async () => {
const host = {
name: "test",
host: "localhost",
protocol: "http" as const,
port: 2375
};
await expect(
buildImage(host, {
context: "../../../etc/passwd",
tag: "valid:tag"
})
).rejects.toThrow(/path traversal|invalid.*path|\.\..*not allowed/i);
});
```
**Run:**
```bash
cd /mnt/cache/code/homelab-mcp-server
pnpm test src/services/docker.test.ts -t "should reject context path with .. directory traversal"
```
**Expected:** FAIL - Test passes when it should fail (current regex allows `..`)
---
## Step 2: Write test for hidden traversal patterns (RED)
Add to `describe("buildImage")` block:
```typescript
it("should reject context path with hidden traversal (/./..)", async () => {
const host = {
name: "test",
host: "localhost",
protocol: "http" as const,
port: 2375
};
await expect(
buildImage(host, {
context: "/valid/./path/../../etc/passwd",
tag: "valid:tag"
})
).rejects.toThrow(/path traversal|invalid.*path|\.\..*not allowed/i);
});
```
**Run:**
```bash
pnpm test src/services/docker.test.ts -t "should reject context path with hidden traversal"
```
**Expected:** FAIL - Test passes when it should fail
---
## Step 3: Write test for single dot (current directory) rejection (RED)
Add to `describe("buildImage")` block:
```typescript
it("should reject context path starting with ./ (relative)", async () => {
const host = {
name: "test",
host: "localhost",
protocol: "http" as const,
port: 2375
};
await expect(
buildImage(host, {
context: "./relative/path",
tag: "valid:tag"
})
).rejects.toThrow(/absolute path required|relative path|invalid.*path/i);
});
```
**Run:**
```bash
pnpm test src/services/docker.test.ts -t "should reject context path starting with"
```
**Expected:** FAIL - Test passes when it should fail
---
## Step 4: Write test for dockerfile parameter traversal (RED)
Add to `describe("buildImage")` block:
```typescript
it("should reject dockerfile path with .. directory traversal", async () => {
const host = {
name: "test",
host: "localhost",
protocol: "http" as const,
port: 2375
};
await expect(
buildImage(host, {
context: "/valid/context",
tag: "valid:tag",
dockerfile: "../../etc/passwd"
})
).rejects.toThrow(/path traversal|invalid.*path|\.\..*not allowed/i);
});
```
**Run:**
```bash
pnpm test src/services/docker.test.ts -t "should reject dockerfile path with .. directory traversal"
```
**Expected:** FAIL - Test passes when it should fail
---
## Step 5: Write test for valid absolute paths (RED)
Add to `describe("buildImage")` block:
```typescript
it("should accept valid absolute path without traversal", async () => {
const host = {
name: "test",
host: "nonexistent.local", // Will fail connection, but validation should pass
protocol: "http" as const,
port: 9999
};
// This should pass validation but fail on connection
await expect(
buildImage(host, {
context: "/home/user/docker/build",
tag: "valid:tag"
})
).rejects.toThrow(/ENOTFOUND|ECONNREFUSED|connection|Failed/i);
// Should NOT throw validation error
await expect(
buildImage(host, {
context: "/home/user/docker/build",
tag: "valid:tag"
})
).rejects.not.toThrow(/invalid.*path|path traversal/i);
});
```
**Run:**
```bash
pnpm test src/services/docker.test.ts -t "should accept valid absolute path without traversal"
```
**Expected:** FAIL - Current validation rejects valid paths OR doesn't distinguish connection errors from validation errors
---
## Step 6: Run all new tests to confirm RED phase (RED)
Run all buildImage tests:
```bash
pnpm test src/services/docker.test.ts -t buildImage
```
**Expected:** All new tests FAIL (4 tests rejecting invalid paths pass incorrectly, 1 test accepting valid path fails)
**Verify:** Check output shows failures for all 5 new test cases
---
## Step 7: Create path validation utility function signature (GREEN)
**Create:** `src/utils/path-security.ts`
```typescript
/**
* Validates that a file path is safe from directory traversal attacks
*
* Rules:
* 1. Must be absolute path (starts with /)
* 2. Cannot contain .. (parent directory)
* 3. Cannot contain . as a path component (except in filenames)
* 4. Must contain only allowed characters: a-zA-Z0-9._-/
*
* @param path - The file path to validate
* @param paramName - Name of the parameter (for error messages)
* @throws Error if path contains directory traversal or is invalid
*/
export function validateSecurePath(path: string, paramName: string): void {
// Implementation in next step
throw new Error("Not implemented");
}
```
**Run:**
```bash
pnpm run build
```
**Expected:** Build succeeds, type checking passes
---
## Step 8: Write test for path validation utility (RED)
**Create:** `src/utils/path-security.test.ts`
```typescript
import { describe, it, expect } from "vitest";
import { validateSecurePath } from "./path-security.js";
describe("validateSecurePath", () => {
describe("directory traversal attacks", () => {
it("should reject basic .. traversal", () => {
expect(() => validateSecurePath("../../../etc/passwd", "context")).toThrow(
/directory traversal.*not allowed/i
);
});
it("should reject .. at start of path", () => {
expect(() => validateSecurePath("../sibling", "context")).toThrow(
/directory traversal.*not allowed/i
);
});
it("should reject .. in middle of path", () => {
expect(() => validateSecurePath("/valid/../etc/passwd", "context")).toThrow(
/directory traversal.*not allowed/i
);
});
it("should reject .. at end of path", () => {
expect(() => validateSecurePath("/some/path/..", "context")).toThrow(
/directory traversal.*not allowed/i
);
});
it("should reject multiple .. sequences", () => {
expect(() => validateSecurePath("/path/../../other/../etc", "context")).toThrow(
/directory traversal.*not allowed/i
);
});
it("should reject hidden traversal with /./../", () => {
expect(() => validateSecurePath("/valid/./path/../../etc", "context")).toThrow(
/directory traversal.*not allowed/i
);
});
});
describe("relative path rejection", () => {
it("should reject path starting with ./", () => {
expect(() => validateSecurePath("./relative/path", "context")).toThrow(
/absolute path required/i
);
});
it("should reject path without leading /", () => {
expect(() => validateSecurePath("relative/path", "context")).toThrow(
/absolute path required/i
);
});
it("should reject single dot path", () => {
expect(() => validateSecurePath(".", "context")).toThrow(
/absolute path required/i
);
});
});
describe("valid absolute paths", () => {
it("should accept simple absolute path", () => {
expect(() => validateSecurePath("/home/user/build", "context")).not.toThrow();
});
it("should accept absolute path with hyphens", () => {
expect(() => validateSecurePath("/opt/my-app/build-context", "context")).not.toThrow();
});
it("should accept absolute path with underscores", () => {
expect(() => validateSecurePath("/var/docker_builds/app_v2", "context")).not.toThrow();
});
it("should accept absolute path with dots in filename", () => {
expect(() => validateSecurePath("/app/Dockerfile.prod", "dockerfile")).not.toThrow();
});
it("should accept deep nested path", () => {
expect(() => validateSecurePath("/very/deep/nested/directory/structure/build", "context")).not.toThrow();
});
it("should accept single character directories", () => {
expect(() => validateSecurePath("/a/b/c", "context")).not.toThrow();
});
});
describe("character validation", () => {
it("should reject paths with spaces", () => {
expect(() => validateSecurePath("/path with spaces", "context")).toThrow(
/invalid characters/i
);
});
it("should reject paths with special characters", () => {
expect(() => validateSecurePath("/path/with$pecial", "context")).toThrow(
/invalid characters/i
);
});
it("should reject paths with semicolons", () => {
expect(() => validateSecurePath("/path;rm -rf /", "context")).toThrow(
/invalid characters/i
);
});
it("should reject paths with backticks", () => {
expect(() => validateSecurePath("/path/`whoami`", "context")).toThrow(
/invalid characters/i
);
});
});
describe("error messages", () => {
it("should include parameter name in error message", () => {
expect(() => validateSecurePath("../etc/passwd", "buildContext")).toThrow(
/buildContext/
);
});
it("should include parameter name for character errors", () => {
expect(() => validateSecurePath("/path with spaces", "dockerfile")).toThrow(
/dockerfile/
);
});
});
describe("edge cases", () => {
it("should reject empty path", () => {
expect(() => validateSecurePath("", "context")).toThrow();
});
it("should accept root path", () => {
expect(() => validateSecurePath("/", "context")).not.toThrow();
});
it("should accept path with multiple dots in filename", () => {
expect(() => validateSecurePath("/path/to/file.tar.gz", "context")).not.toThrow();
});
it("should reject path ending with /.", () => {
expect(() => validateSecurePath("/path/to/.", "context")).toThrow(
/directory traversal.*not allowed/i
);
});
it("should reject path with /./ in middle", () => {
expect(() => validateSecurePath("/path/./to/file", "context")).toThrow(
/directory traversal.*not allowed/i
);
});
});
});
```
**Run:**
```bash
pnpm test src/utils/path-security.test.ts
```
**Expected:** All tests FAIL with "Not implemented" error
---
## Step 9: Implement path validation utility (GREEN)
**Modify:** `src/utils/path-security.ts`
Replace implementation:
```typescript
import { resolve } from "node:path";
/**
* Validates that a file path is safe from directory traversal attacks
*
* Rules:
* 1. Must be absolute path (starts with /)
* 2. Cannot contain .. (parent directory)
* 3. Cannot contain . as a path component (except in filenames)
* 4. Must contain only allowed characters: a-zA-Z0-9._-/
*
* @param path - The file path to validate
* @param paramName - Name of the parameter (for error messages)
* @throws Error if path contains directory traversal or is invalid
*/
export function validateSecurePath(path: string, paramName: string): void {
// 1. Check for empty path
if (!path || path.length === 0) {
throw new Error(`${paramName}: Path cannot be empty`);
}
// 2. Must be absolute path (starts with /)
if (!path.startsWith("/")) {
throw new Error(
`${paramName}: Absolute path required, got: ${path}`
);
}
// 3. Character validation - only allow alphanumeric, dots, hyphens, underscores, forward slashes
if (!/^[a-zA-Z0-9._\-/]+$/.test(path)) {
throw new Error(
`${paramName}: Invalid characters in path: ${path}`
);
}
// 4. Split path into components and check each one
const components = path.split("/").filter(c => c.length > 0);
for (const component of components) {
// Reject ".." (parent directory traversal)
if (component === "..") {
throw new Error(
`${paramName}: Directory traversal (..) not allowed in path: ${path}`
);
}
// Reject "." as standalone component (current directory)
// BUT allow dots in filenames like "file.txt" or "config.prod"
if (component === ".") {
throw new Error(
`${paramName}: Directory traversal (.) not allowed in path: ${path}`
);
}
}
// 5. Additional safety check: resolve path and verify it doesn't traverse
// This catches cases like /valid/path/../../etc that might slip through
const resolved = resolve(path);
if (!resolved.startsWith(path.split("/")[1] ? `/${path.split("/")[1]}` : "/")) {
throw new Error(
`${paramName}: Path resolution resulted in directory traversal: ${path}`
);
}
}
```
**Run:**
```bash
pnpm test src/utils/path-security.test.ts
```
**Expected:** All tests PASS
---
## Step 10: Verify path-security utility tests are green
Run all path-security tests:
```bash
pnpm test src/utils/path-security.test.ts --reporter=verbose
```
**Expected:** All 30+ tests PASS
**Verify:** Check output shows 0 failures
---
## Step 11: Update buildImage to use validateSecurePath (GREEN)
**Modify:** `src/services/docker.ts:987-995`
Replace validation block:
```typescript
const { context, tag, dockerfile, noCache } = options;
// Validate inputs
if (!/^[a-zA-Z0-9._\-/:]+$/.test(tag)) {
throw new Error(`Invalid image tag: ${tag}`);
}
// Use secure path validation (prevents directory traversal)
const { validateSecurePath } = await import("../utils/path-security.js");
validateSecurePath(context, "context");
if (dockerfile) {
validateSecurePath(dockerfile, "dockerfile");
}
```
**Note:** This removes the old regex validation for `context` and `dockerfile` paths and replaces with secure validator.
**Run:**
```bash
pnpm run build
```
**Expected:** Build succeeds, no TypeScript errors
---
## Step 12: Run buildImage tests to verify GREEN phase
Run buildImage tests:
```bash
pnpm test src/services/docker.test.ts -t buildImage
```
**Expected:** All 10 tests PASS (5 original + 5 new security tests)
**Verify:**
- Tests rejecting `..` now PASS (throw expected errors)
- Tests rejecting `./` now PASS (throw expected errors)
- Test accepting valid absolute path PASSES (connection error, not validation error)
---
## Step 13: Add test for dockerfile parameter with valid path (GREEN)
Add to `describe("buildImage")` block in `src/services/docker.test.ts`:
```typescript
it("should accept dockerfile with valid absolute path", async () => {
const host = {
name: "test",
host: "nonexistent.local",
protocol: "http" as const,
port: 9999
};
await expect(
buildImage(host, {
context: "/app/build",
tag: "myapp:latest",
dockerfile: "/app/build/Dockerfile.prod"
})
).rejects.toThrow(/ENOTFOUND|ECONNREFUSED|connection|Failed/i);
await expect(
buildImage(host, {
context: "/app/build",
tag: "myapp:latest",
dockerfile: "/app/build/Dockerfile.prod"
})
).rejects.not.toThrow(/invalid.*path|path traversal/i);
});
```
**Run:**
```bash
pnpm test src/services/docker.test.ts -t "should accept dockerfile with valid absolute path"
```
**Expected:** PASS - Validation passes, connection fails (expected)
---
## Step 14: Add test for complex attack vectors (GREEN)
Add to `describe("buildImage")` block in `src/services/docker.test.ts`:
```typescript
it("should reject sophisticated traversal attacks", async () => {
const host = {
name: "test",
host: "localhost",
protocol: "http" as const,
port: 2375
};
// Attack: absolute path with traversal in middle
await expect(
buildImage(host, {
context: "/home/user/builds/../../etc/passwd",
tag: "attack:v1"
})
).rejects.toThrow(/directory traversal.*not allowed/i);
// Attack: hidden current dir with parent dir
await expect(
buildImage(host, {
context: "/app/./build/../../../etc",
tag: "attack:v2"
})
).rejects.toThrow(/directory traversal.*not allowed/i);
// Attack: path ending with traversal
await expect(
buildImage(host, {
context: "/secure/path/..",
tag: "attack:v3"
})
).rejects.toThrow(/directory traversal.*not allowed/i);
});
```
**Run:**
```bash
pnpm test src/services/docker.test.ts -t "should reject sophisticated traversal attacks"
```
**Expected:** PASS - All attack vectors rejected
---
## Step 15: Run full test suite to verify no regressions
Run complete test suite:
```bash
pnpm test
```
**Expected:** All existing tests still PASS, no regressions introduced
**Verify:** Check for any unexpected failures in:
- `docker.test.ts` (all buildImage tests)
- `ssh.test.ts` (existing validation)
- `compose.test.ts` (no impact)
- Integration tests (no impact)
---
## Step 16: Add JSDoc documentation to buildImage function (REFACTOR)
**Modify:** `src/services/docker.ts:972-983`
Add security notice to JSDoc:
```typescript
/**
* Build an image from a Dockerfile (SSH-based for remote hosts)
*
* SECURITY: Implements path traversal protection (CWE-22)
* - Requires absolute paths for context and dockerfile
* - Rejects any path containing .. or . components
* - Validates character set to prevent injection
*
* @param host - Docker host configuration
* @param options - Build options (context, tag, dockerfile, noCache)
* @returns Promise resolving to build status
* @throws Error if paths contain directory traversal or invalid characters
*/
export async function buildImage(
host: HostConfig,
options: {
context: string;
tag: string;
dockerfile?: string;
noCache?: boolean;
}
): Promise<{ status: string }> {
```
**Run:**
```bash
pnpm run build
```
**Expected:** Build succeeds
---
## Step 17: Export validateSecurePath from utils index (REFACTOR)
**Check if exists:** `src/utils/index.ts`
If not exists, create:
```typescript
export { validateSecurePath } from "./path-security.js";
```
If exists, add export:
```typescript
export { validateSecurePath } from "./path-security.js";
```
**Run:**
```bash
pnpm run build
```
**Expected:** Build succeeds, export available
---
## Step 18: Add security documentation comment to path-security.ts (REFACTOR)
**Modify:** `src/utils/path-security.ts`
Add header comment:
```typescript
/**
* Path Security Utilities
*
* SECURITY: Path Traversal Protection (CWE-22)
*
* This module provides utilities to prevent directory traversal attacks
* in file path parameters. Used by docker.ts buildImage() to validate
* build context and Dockerfile paths.
*
* CVSS 7.4 (HIGH) - Prevents attackers from using paths like:
* - ../../../etc/passwd
* - /valid/../../../etc/passwd
* - /path/./to/../../sensitive
*
* @see https://cwe.mitre.org/data/definitions/22.html
*/
import { resolve } from "node:path";
```
**Run:**
```bash
pnpm run build
```
**Expected:** Build succeeds
---
## Step 19: Run linter to verify code quality (REFACTOR)
Run ESLint:
```bash
pnpm run lint
```
**Expected:** No linting errors for new code
**If errors exist:** Fix based on linter output, then re-run
---
## Step 20: Run type checker independently (REFACTOR)
Run TypeScript compiler:
```bash
pnpm exec tsc --noEmit
```
**Expected:** No type errors
**Verify:** Strict mode compliance for new utility function
---
## Step 21: Add test for error message clarity (REFACTOR)
Add to `describe("validateSecurePath")` in `src/utils/path-security.test.ts`:
```typescript
describe("user-friendly error messages", () => {
it("should provide clear error for common mistake (relative path)", () => {
try {
validateSecurePath("./config", "buildContext");
expect.fail("Should have thrown");
} catch (error) {
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toContain("buildContext");
expect((error as Error).message).toContain("absolute path required");
}
});
it("should provide clear error for traversal attempt", () => {
try {
validateSecurePath("/app/../etc", "dockerfile");
expect.fail("Should have thrown");
} catch (error) {
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toContain("dockerfile");
expect((error as Error).message).toContain("directory traversal");
expect((error as Error).message).toContain("..");
}
});
});
```
**Run:**
```bash
pnpm test src/utils/path-security.test.ts -t "user-friendly error messages"
```
**Expected:** PASS - Error messages are clear and helpful
---
## Step 22: Run full test suite with coverage (VERIFICATION)
Run tests with coverage:
```bash
pnpm run test:coverage
```
**Expected:**
- All tests PASS
- path-security.ts shows 100% coverage
- docker.ts buildImage function shows coverage for validation paths
**Verify:** Coverage report shows new code is fully tested
---
## Step 23: Create integration test for realistic scenario (VERIFICATION)
Add to appropriate integration test file or create `src/services/docker.integration.test.ts`:
```typescript
import { describe, it, expect } from "vitest";
import { buildImage } from "./docker.js";
describe("buildImage path security integration", () => {
it("should prevent directory traversal in realistic attack scenario", async () => {
const host = {
name: "production",
host: "nonexistent-prod.local",
protocol: "http" as const,
port: 2375
};
// Attacker attempts to build image using /etc/passwd as context
const attackAttempt = buildImage(host, {
context: "/app/builds/../../../../../../etc/passwd",
tag: "pwned:latest"
});
await expect(attackAttempt).rejects.toThrow(/directory traversal.*not allowed/i);
// Should fail BEFORE attempting SSH connection
// (validation happens before network call)
await expect(attackAttempt).rejects.not.toThrow(/ENOTFOUND|ECONNREFUSED/i);
});
it("should allow legitimate multi-level paths", async () => {
const host = {
name: "dev",
host: "nonexistent-dev.local",
protocol: "http" as const,
port: 2375
};
const legitimateBuild = buildImage(host, {
context: "/var/docker/projects/myapp/frontend/build",
tag: "myapp-frontend:dev",
dockerfile: "/var/docker/projects/myapp/frontend/Dockerfile.dev"
});
// Should fail on connection (host doesn't exist)
// NOT on path validation
await expect(legitimateBuild).rejects.toThrow(/ENOTFOUND|ECONNREFUSED|connection/i);
});
});
```
**Run:**
```bash
pnpm test docker.integration.test.ts
```
**Expected:** PASS - Integration tests verify real-world usage
---
## Step 24: Update README.md security notes (DOCUMENTATION)
**Modify:** `README.md`
Find "Security Notes" section and add:
```markdown
### Path Traversal Protection (CWE-22)
The `image_build` tool implements strict path validation to prevent directory traversal attacks:
- **Absolute paths required**: All paths (context, dockerfile) must start with `/`
- **Traversal blocked**: Paths containing `..` or `.` components are rejected
- **Character validation**: Only alphanumeric, dots (in filenames), hyphens, underscores, and forward slashes allowed
- **Pre-execution validation**: Paths validated before SSH commands are executed
Example of rejected paths:
```bash
# Rejected: Directory traversal
../../../etc/passwd
/app/../../../etc/passwd
# Rejected: Relative paths
./build
relative/path
# Accepted: Absolute paths without traversal
/home/user/docker/build
/opt/myapp/Dockerfile.prod
```
```
**Run:**
```bash
pnpm run build
```
**Expected:** Documentation updated, build succeeds
---
## Step 25: Verify all tests pass one final time (VERIFICATION)
Run complete test suite:
```bash
pnpm test --run
```
**Expected:** 100% PASS rate across all test files
**Verify output shows:**
- docker.test.ts: All buildImage tests passing (11-12 tests)
- path-security.test.ts: All validation tests passing (30+ tests)
- Integration tests passing (if created)
- No regressions in other test files
---
## Step 26: Run build and verify production bundle (VERIFICATION)
Build project:
```bash
pnpm run build
```
**Expected:**
- No TypeScript errors
- No build warnings
- Output bundle contains path-security utility
**Verify:** Check `dist/` directory contains compiled code
---
## Step 27: Manual security testing checklist (VERIFICATION)
Create verification checklist for manual testing:
**Create:** `.docs/security-verification-checklist.md`
```markdown
# Path Traversal Vulnerability Fix - Security Verification
Date: 2025-12-24
CVSS: 7.4 (HIGH) → RESOLVED
CWE-22: Path Traversal → MITIGATED
## Test Cases
### Directory Traversal Attacks (BLOCKED ✓)
- [ ] `../../../etc/passwd` - REJECTED
- [ ] `/app/../../../etc/passwd` - REJECTED
- [ ] `/path/./to/../../etc` - REJECTED
- [ ] `/valid/path/..` - REJECTED
- [ ] `../../../../root` - REJECTED
### Relative Paths (BLOCKED ✓)
- [ ] `./relative` - REJECTED
- [ ] `relative/path` - REJECTED
- [ ] `.` - REJECTED
### Valid Absolute Paths (ALLOWED ✓)
- [ ] `/home/user/build` - ACCEPTED
- [ ] `/opt/docker/app` - ACCEPTED
- [ ] `/var/builds/project-v2` - ACCEPTED
- [ ] `/app/Dockerfile.prod` - ACCEPTED
### Character Injection (BLOCKED ✓)
- [ ] `/path;rm -rf /` - REJECTED
- [ ] `/path$(whoami)` - REJECTED
- [ ] `/path\`cmd\`` - REJECTED
- [ ] `/path with spaces` - REJECTED
## Automated Tests
- [x] 30+ unit tests in path-security.test.ts - PASSING
- [x] 11+ integration tests in docker.test.ts - PASSING
- [x] Coverage > 95% on validation logic
## Code Review
- [x] Input validation before SSH execution
- [x] No regex-only validation (component-level checking)
- [x] Clear error messages for users
- [x] Documentation updated
## Deployment Readiness
- [x] All tests passing
- [x] TypeScript strict mode compliance
- [x] Linting clean
- [x] Documentation complete
```
**Run:**
```bash
ls -la /mnt/cache/code/homelab-mcp-server/.docs/security-verification-checklist.md
```
**Expected:** File exists with checklist content
---
## Step 28: Git commit the security fix (COMPLETION)
Stage and commit changes:
```bash
cd /mnt/cache/code/homelab-mcp-server
git add src/utils/path-security.ts src/utils/path-security.test.ts src/services/docker.ts src/services/docker.test.ts README.md .docs/security-verification-checklist.md
git commit -m "$(cat <<'EOF'
security: fix CVSS 7.4 path traversal in buildImage (CWE-22)
Implement comprehensive path validation to prevent directory traversal
attacks in docker.ts buildImage() function.
Changes:
- Add validateSecurePath() utility with strict validation rules
- Require absolute paths, reject .. and . components
- Validate character set to prevent injection
- Add 30+ security-focused unit tests
- Add integration tests for realistic attack scenarios
- Update documentation with security guidance
Security Impact:
- Blocks: ../../../etc/passwd attacks
- Blocks: /valid/../../../etc traversal
- Blocks: Relative paths (./config)
- Allows: Valid absolute paths only
Test Coverage:
- path-security.test.ts: 100% coverage (30+ tests)
- docker.test.ts: Extended with traversal tests
- Integration tests for real-world scenarios
CVSS 7.4 (HIGH) → RESOLVED
CWE-22: Directory Traversal → MITIGATED
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
EOF
)"
```
**Run:**
```bash
git log -1 --format='[%h] %s'
```
**Expected:** Shows commit with security fix message
---
## Success Criteria
**All criteria must be met:**
1. **Security Validation:**
- ✓ Path traversal attacks blocked (`..` rejected)
- ✓ Relative paths blocked (`./` and non-absolute rejected)
- ✓ Valid absolute paths allowed
- ✓ Character injection prevented
2. **Test Coverage:**
- ✓ 30+ unit tests for path validation
- ✓ 11+ integration tests for buildImage
- ✓ Coverage > 95% on validation logic
- ✓ All tests passing
3. **Code Quality:**
- ✓ TypeScript strict mode compliant
- ✓ No linting errors
- ✓ DRY principle applied (single validator for all paths)
- ✓ YAGNI principle applied (minimal implementation)
- ✓ Clear error messages for users
4. **Documentation:**
- ✓ JSDoc comments on public functions
- ✓ Security notes in README
- ✓ Verification checklist created
- ✓ Comments explain CWE-22 mitigation
5. **TDD Compliance:**
- ✓ All tests written RED first
- ✓ Implementation written GREEN second
- ✓ Refactoring performed last
- ✓ No implementation before tests
---
## Security Verification Commands
**Quick verification script:**
```bash
# Run all security-related tests
pnpm test path-security.test.ts
pnpm test docker.test.ts -t buildImage
# Check test coverage
pnpm run test:coverage
# Verify types
pnpm exec tsc --noEmit
# Verify linting
pnpm run lint
# Build production bundle
pnpm run build
```
**Expected:** All commands succeed, 100% test pass rate
---
## References
- **CWE-22:** https://cwe.mitre.org/data/definitions/22.html
- **OWASP Path Traversal:** https://owasp.org/www-community/attacks/Path_Traversal
- **Node.js path.resolve:** https://nodejs.org/api/path.html#pathresolvepaths
- **CVSS Calculator:** https://www.first.org/cvss/calculator/3.1
---
## Notes for Implementer
**Critical Requirements:**
1. Write ALL tests BEFORE implementation (RED phase)
2. Run each test to verify it FAILS before writing code
3. Implement MINIMAL code to pass tests (GREEN phase)
4. Refactor ONLY after tests are green
5. Validate EVERY attack vector in tests
6. Do NOT skip any step
**DRY Principle Applied:**
- Single `validateSecurePath()` function used for BOTH `context` and `dockerfile` parameters
- No duplicated validation logic in buildImage()
- Utility can be reused for other path validation needs
**YAGNI Principle Applied:**
- No filesystem existence checking (not needed for validation)
- No complex path normalization (simple component check sufficient)
- No allowlist of directories (trust absolute path + no traversal)
- No encoding detection (transport layer handles this)
**KISS Principle Applied:**
- Split path into components, check each one
- Simple rules: absolute only, no .., no .
- Clear error messages
- Minimal function signature
**Common Pitfalls to Avoid:**
- DO NOT rely on regex alone (current vulnerability)
- DO NOT forget to test both `context` AND `dockerfile` parameters
- DO NOT skip the "hidden traversal" test cases (`/./..`)
- DO NOT allow relative paths "just this once"
- DO NOT implement before writing tests
**Time Estimates:**
- Steps 1-6 (RED phase): 10 minutes
- Steps 7-12 (GREEN phase): 15 minutes
- Steps 13-21 (REFACTOR phase): 15 minutes
- Steps 22-28 (VERIFICATION): 10 minutes
- **Total:** ~50 minutes for complete fix with comprehensive tests