@@ -2,9 +2,9 @@ import { randomBytes } from "node:crypto";
22import { execFile } from "node:child_process" ;
33import { promisify } from "node:util" ;
44import { mkdir , realpath , rm , stat } from "node:fs/promises" ;
5- import { basename , join } from "node:path" ;
5+ import { basename , join , relative , resolve } from "node:path" ;
66import type { ServerConfig } from "./config.js" ;
7- import { assertAllowedPath } from "./roots.js" ;
7+ import { assertAllowedPath , isPathInsideRoot } from "./roots.js" ;
88
99const execFileAsync = promisify ( execFile ) ;
1010
@@ -93,7 +93,7 @@ export async function createManagedWorktree(input: {
9393async function resolveGitRoot ( path : string , allowedRoots : string [ ] ) : Promise < string > {
9494 try {
9595 const output = await git ( [ "rev-parse" , "--show-toplevel" ] , path ) ;
96- return assertAllowedPath ( await realpath ( output . trim ( ) ) , allowedRoots ) ;
96+ return await assertGitRootAllowed ( output . trim ( ) , allowedRoots ) ;
9797 } catch ( error ) {
9898 if ( isGitUnavailable ( error ) ) {
9999 throw new GitWorktreeError (
@@ -109,6 +109,25 @@ async function resolveGitRoot(path: string, allowedRoots: string[]): Promise<str
109109 }
110110}
111111
112+ async function assertGitRootAllowed ( gitRoot : string , allowedRoots : string [ ] ) : Promise < string > {
113+ try {
114+ return assertAllowedPath ( gitRoot , allowedRoots ) ;
115+ } catch {
116+ const canonicalGitRoot = await realpath ( gitRoot ) ;
117+ for ( const allowedRoot of allowedRoots ) {
118+ const canonicalAllowedRoot = await realpath ( allowedRoot ) . catch ( ( ) => undefined ) ;
119+ if ( ! canonicalAllowedRoot || ! isPathInsideRoot ( canonicalGitRoot , canonicalAllowedRoot ) ) {
120+ continue ;
121+ }
122+
123+ const logicalGitRoot = resolve ( allowedRoot , relative ( canonicalAllowedRoot , canonicalGitRoot ) ) ;
124+ return assertAllowedPath ( logicalGitRoot , allowedRoots ) ;
125+ }
126+
127+ return assertAllowedPath ( canonicalGitRoot , allowedRoots ) ;
128+ }
129+ }
130+
112131async function resolveBaseCommit ( sourceRoot : string , baseRef : string ) : Promise < string > {
113132 try {
114133 return ( await git ( [ "rev-parse" , "--verify" , `${ baseRef } ^{commit}` ] , sourceRoot ) ) . trim ( ) ;
0 commit comments