@@ -15,8 +15,36 @@ import { ensureDir } from "./fs.js";
1515import { gitIgnoreEnsure } from "./gitignore.js" ;
1616import { resolveRuntimeHost } from "./host.js" ;
1717import { sanitizeFilename } from "./sanitize.js" ;
18+ import { resolve as pathResolve , normalize , relative } from "node:path" ;
1819const dbg = genaiscriptDebug ( "dirs" ) ;
1920
21+ /**
22+ * Validates a path segment to prevent directory traversal attacks
23+ * @param segment - The path segment to validate
24+ * @returns The sanitized segment
25+ * @throws Error if the segment contains path traversal attempts
26+ */
27+ function validatePathSegment ( segment : string ) : string {
28+ if ( ! segment || typeof segment !== 'string' ) {
29+ throw new Error ( "Invalid path segment" ) ;
30+ }
31+
32+ // Normalize the segment to resolve any relative path components
33+ const normalized = normalize ( segment ) ;
34+
35+ // Check for path traversal attempts
36+ if ( normalized . includes ( '..' ) || normalized . startsWith ( '/' ) || normalized . includes ( ':' ) ) {
37+ throw new Error ( `Path traversal attempt detected in segment: ${ segment } ` ) ;
38+ }
39+
40+ // Additional security: ensure no null bytes
41+ if ( segment . includes ( '\0' ) ) {
42+ throw new Error ( "Null byte detected in path segment" ) ;
43+ }
44+
45+ return sanitizeFilename ( segment ) ;
46+ }
47+
2048/**
2149 * Constructs a resolved file path within the `.genaiscript` directory of the project.
2250 *
@@ -25,11 +53,21 @@ const dbg = genaiscriptDebug("dirs");
2553 */
2654export function dotGenaiscriptPath ( ...segments : string [ ] ) {
2755 const runtimeHost = resolveRuntimeHost ( ) ;
28- return resolve (
29- runtimeHost . projectFolder ( ) ,
30- GENAISCRIPT_FOLDER ,
31- ...segments . map ( ( s ) => sanitizeFilename ( s ) ) ,
32- ) ;
56+ const projectFolder = runtimeHost . projectFolder ( ) ;
57+ const genaiscriptBase = pathResolve ( projectFolder , GENAISCRIPT_FOLDER ) ;
58+
59+ // Validate and sanitize all segments
60+ const validatedSegments = segments . map ( validatePathSegment ) ;
61+
62+ const fullPath = pathResolve ( genaiscriptBase , ...validatedSegments ) ;
63+
64+ // Ensure the resolved path is still within the .genaiscript directory
65+ const relativePath = relative ( genaiscriptBase , fullPath ) ;
66+ if ( relativePath . startsWith ( '..' ) || relativePath . startsWith ( '/' ) ) {
67+ throw new Error ( `Path traversal attempt detected: resolved path ${ fullPath } is outside of allowed directory` ) ;
68+ }
69+
70+ return fullPath ;
3371}
3472
3573/**
0 commit comments