@@ -3,9 +3,7 @@ package mcp
33import (
44 "context"
55 "fmt"
6- "os"
76 "path/filepath"
8- "strings"
97
108 "github.com/mark3labs/mcp-go/mcp"
119 "github.com/tae2089/trace"
@@ -405,27 +403,83 @@ func validateRepoRootWithin(repoRoot, configuredRepoRoot, workspaceRoot string)
405403 if repoRoot == "" {
406404 return "" , fmt .Errorf ("repo_root is required" )
407405 }
408- allowed := configuredRepoRoot
409- if allowed == "" {
410- allowed = workspaceRoot
411- }
412- if allowed == "" {
406+ allowedRoots := configuredAnalysisRoots (configuredRepoRoot , workspaceRoot )
407+ if len (allowedRoots ) == 0 {
413408 return "" , fmt .Errorf ("analysis repo root is not configured" )
414409 }
415410 repo , err := canonicalPath (repoRoot )
416411 if err != nil {
417412 return "" , fmt .Errorf ("invalid repo_root: %w" , err )
418413 }
419- base , err := canonicalPath ( allowed )
414+ allowed , err := validatePathWithinAllowedRoots ( repo , allowedRoots )
420415 if err != nil {
421416 return "" , fmt .Errorf ("invalid configured repo root: %w" , err )
422417 }
423- if repo != base && ! strings . HasPrefix ( repo , base + string ( os . PathSeparator )) {
418+ if ! allowed {
424419 return "" , fmt .Errorf ("repo_root %q is outside configured analysis root" , repoRoot )
425420 }
426421 return repo , nil
427422}
428423
424+ func configuredAnalysisRoots (repoRoot , workspaceRoot string ) []string {
425+ roots := make ([]string , 0 , 2 )
426+ for _ , root := range []string {repoRoot , workspaceRoot } {
427+ if root == "" {
428+ continue
429+ }
430+ if ! sliceContainsString (roots , root ) {
431+ roots = append (roots , root )
432+ }
433+ }
434+ return roots
435+ }
436+
437+ func sliceContainsString (values []string , target string ) bool {
438+ for _ , value := range values {
439+ if value == target {
440+ return true
441+ }
442+ }
443+ return false
444+ }
445+
446+ func validatePathWithinAllowedRoots (target string , allowedRoots []string ) (bool , error ) {
447+ for _ , root := range allowedRoots {
448+ base , err := canonicalPath (root )
449+ if err != nil {
450+ return false , err
451+ }
452+ within , err := isWithinRoot (base , target )
453+ if err != nil {
454+ return false , err
455+ }
456+ if within {
457+ return true , nil
458+ }
459+ }
460+ return false , nil
461+ }
462+
463+ func isWithinRoot (root , target string ) (bool , error ) {
464+ rel , err := filepath .Rel (root , target )
465+ if err != nil {
466+ return false , err
467+ }
468+ if rel == "." {
469+ return true , nil
470+ }
471+ if rel == ".." {
472+ return false , nil
473+ }
474+ if len (rel ) >= 3 && rel [:3 ] == ".." + string (filepath .Separator ) {
475+ return false , nil
476+ }
477+ if filepath .IsAbs (rel ) {
478+ return false , nil
479+ }
480+ return true , nil
481+ }
482+
429483func canonicalPath (path string ) (string , error ) {
430484 abs , err := filepath .Abs (path )
431485 if err != nil {
0 commit comments