diff --git a/agent/internal/workloads/winparse.go b/agent/internal/workloads/winparse.go index f1ec245..fe32377 100644 --- a/agent/internal/workloads/winparse.go +++ b/agent/internal/workloads/winparse.go @@ -36,9 +36,13 @@ func servicePath(pathName string) string { if end := strings.IndexByte(s[1:], '"'); end >= 0 { return s[1 : 1+end] } - return strings.TrimPrefix(s, `"`) + // No closing quote: a malformed or truncated PathName. Fall back to + // the unquoted handling below on the text after the opening quote, + // so this yields a bare path rather than a path plus trailing + // argument text. + s = s[1:] } - if i := strings.Index(strings.ToLower(s), ".exe"); i >= 0 { + if i := exeBoundaryIndex(s); i >= 0 { return s[:i+len(".exe")] } if i := strings.IndexAny(s, " \t"); i >= 0 { @@ -47,6 +51,29 @@ func servicePath(pathName string) string { return s } +// exeBoundaryIndex finds the first ".exe" (case-insensitive) in s that +// actually ends the executable name — followed by end-of-string, whitespace, +// or a double quote — rather than continuing into a longer segment such as +// ".exec". It returns -1 when no such occurrence exists, so a path like +// `C:\Program Files\Ad.exec\tool.com -flag` is not misparsed by matching the +// ".exe" inside "Ad.exec" and silently dropping the real filename. +func exeBoundaryIndex(s string) int { + lower := strings.ToLower(s) + from := 0 + for { + rel := strings.Index(lower[from:], ".exe") + if rel < 0 { + return -1 + } + idx := from + rel + end := idx + len(".exe") + if end == len(s) || s[end] == ' ' || s[end] == '\t' || s[end] == '"' { + return idx + } + from = idx + 1 + } +} + // parseServices turns the collector's JSON into workloads. // // systemRoot is a parameter rather than an environment read so this is testable diff --git a/agent/internal/workloads/winparse_test.go b/agent/internal/workloads/winparse_test.go index 2ed186c..4817c22 100644 --- a/agent/internal/workloads/winparse_test.go +++ b/agent/internal/workloads/winparse_test.go @@ -9,6 +9,14 @@ func TestServicePath(t *testing.T) { {`C:\Vantage\vantage-agent.exe`, `C:\Vantage\vantage-agent.exe`}, {`"C:\no\args.exe"`, `C:\no\args.exe`}, {``, ``}, + // ".exe" appearing inside an earlier segment ("Ad.exec") must not be + // treated as the end of the executable — that would drop the real + // filename and arguments. + {`C:\Program Files\Ad.exec\tool.com -flag`, `C:\Program`}, + // An unterminated quote falls back to the unquoted handling on the + // text after the opening quote, yielding a bare path rather than a + // path plus trailing argument text. + {`"C:\Program Files\Contoso\svc.exe -service`, `C:\Program Files\Contoso\svc.exe`}, } for _, c := range cases { if got := servicePath(c.in); got != c.want {