From 0c0756230dbc5a2e4bd5c6b754c71676b713c62f Mon Sep 17 00:00:00 2001 From: Mario Meyer Date: Mon, 16 Mar 2026 10:51:42 -0400 Subject: [PATCH] fix: prevent cross-framework route false positives in HTTP linker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scope source-based route extractors to their target file extensions (Go→.go, Express→.js/.ts, Laravel→.php, Ktor→.kt/.kts) to prevent cross-framework regex matches. The Ktor regex `\b(get|...)\("..."\)` was matching PHP `Cache::get("cache_key")` calls as routes because `\b` treats `::` as a word boundary. Also add a secondary guard in extractLaravelRoutes to skip paths containing `$` or `:` characters (cache keys, interpolated strings). --- internal/httplink/httplink.go | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/internal/httplink/httplink.go b/internal/httplink/httplink.go index b4eb4fd..f75d544 100644 --- a/internal/httplink/httplink.go +++ b/internal/httplink/httplink.go @@ -375,14 +375,25 @@ func (l *Linker) discoverRoutes(rootPath string) []RouteHandler { // C# ASP.NET: check attribute decorators routes = append(routes, extractASPNetRoutes(f)...) - // Source-based route discovery (Go gin, Express.js, PHP Laravel, Kotlin Ktor) + // Source-based route discovery — scoped by file extension to avoid + // cross-framework false positives (e.g. Ktor regex matching PHP Cache::get) if f.FilePath != "" && f.StartLine > 0 && f.EndLine > 0 { source := readSourceLines(rootPath, f.FilePath, f.StartLine, f.EndLine) if source != "" { - routes = append(routes, extractGoRoutes(f, source)...) - routes = append(routes, extractExpressRoutes(f, source)...) - routes = append(routes, extractLaravelRoutes(f, source)...) - routes = append(routes, extractKtorRoutes(f, source)...) + fp := f.FilePath + if strings.HasSuffix(fp, ".go") { + routes = append(routes, extractGoRoutes(f, source)...) + } + if strings.HasSuffix(fp, ".js") || strings.HasSuffix(fp, ".ts") || + strings.HasSuffix(fp, ".mjs") || strings.HasSuffix(fp, ".mts") { + routes = append(routes, extractExpressRoutes(f, source)...) + } + if strings.HasSuffix(fp, ".php") { + routes = append(routes, extractLaravelRoutes(f, source)...) + } + if strings.HasSuffix(fp, ".kt") || strings.HasSuffix(fp, ".kts") { + routes = append(routes, extractKtorRoutes(f, source)...) + } } } @@ -711,8 +722,15 @@ func extractLaravelRoutes(f *store.Node, source string) []RouteHandler { handlerRef = atm[3] // method name from "Controller@method" } + path := rm[2] + // Skip non-route strings that match the regex but contain characters + // invalid in URL paths (e.g., cache keys, interpolated expressions). + if strings.ContainsAny(path, "$:") { + continue + } + routes = append(routes, RouteHandler{ - Path: rm[2], + Path: path, Method: strings.ToUpper(rm[1]), FunctionName: f.Name, QualifiedName: f.QualifiedName,