Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions pkg/utils/datasource_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ func (e *DatasourceEvent) GetExePath() string {
logger.L().Warning("GetExePath - error reading field exepath", helpers.String("eventType", string(e.EventType)), helpers.Error(err))
return ""
}
return exepath
return NormalizePath(exepath)
}

func (e *DatasourceEvent) GetExitCode() uint32 {
Expand Down Expand Up @@ -579,7 +579,7 @@ func (e *DatasourceEvent) GetFullPath() string {
return ""
}
}
return path
return NormalizePath(path)
}

func (e *DatasourceEvent) GetGid() *uint32 {
Expand Down Expand Up @@ -658,7 +658,7 @@ func (e *DatasourceEvent) GetNewPath() string {
logger.L().Warning("GetNewPath - error reading field newpath", helpers.String("eventType", string(e.EventType)), helpers.Error(err))
return ""
}
return newPath
return NormalizePath(newPath)
}

func (e *DatasourceEvent) GetNumAnswers() int {
Expand All @@ -676,7 +676,7 @@ func (e *DatasourceEvent) GetOldPath() string {
logger.L().Warning("GetOldPath - error reading field oldpath", helpers.String("eventType", string(e.EventType)), helpers.Error(err))
return ""
}
return oldPath
return NormalizePath(oldPath)
}

func (e *DatasourceEvent) GetOpcode() int {
Expand Down Expand Up @@ -710,7 +710,7 @@ func (e *DatasourceEvent) GetPath() string {
logger.L().Warning("GetPath - error reading field fname", helpers.String("eventType", string(e.EventType)), helpers.Error(err))
return ""
}
return path
return NormalizePath(path)
}

func (e *DatasourceEvent) GetPcomm() string {
Expand Down
72 changes: 72 additions & 0 deletions pkg/utils/normalize_path_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package utils

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestNormalizePath(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "empty path",
input: "",
expected: "",
},
{
name: "dot path",
input: ".",
expected: "/",
},
{
name: "absolute path",
input: "/etc/passwd",
expected: "/etc/passwd",
},
{
name: "headless proc path (task)",
input: "/46/task/46/fd",
expected: "/proc/46/task/46/fd",
},
{
name: "headless proc path (fd)",
input: "/46/fd/3",
expected: "/proc/46/fd/3",
},
{
name: "already absolute proc path",
input: "/proc/46/fd/3",
expected: "/proc/46/fd/3",
},
{
name: "relative path (not dot)",
input: "usr/bin/ls",
expected: "/usr/bin/ls",
},
{
name: "relative path with ./",
input: "./config",
expected: "/config",
},
{
name: "path with redundant slashes",
input: "/etc//passwd",
expected: "/etc/passwd",
},
{
name: "path with dot components",
input: "/usr/./bin/../lib",
expected: "/usr/lib",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NormalizePath(tt.input)
assert.Equal(t, tt.expected, got)
})
}
}
34 changes: 34 additions & 0 deletions pkg/utils/path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package utils

import (
"path"
"regexp"
"strings"
)

var headlessProcRegex = regexp.MustCompile(`^/\d+/(task|fd)/`)

// NormalizePath normalizes a path by:
// 1. Prepending "/proc" to "headless" proc paths (e.g. /46/task/46/fd -> /proc/46/task/46/fd)
// 2. Ensuring it starts with "/" if it's not empty
// 3. Converting "." to "/"
// 4. Cleaning the path (removing redundant slashes, dot-dots, etc.)
func NormalizePath(p string) string {
if p == "" {
return ""
}

if p == "." {
return "/"
}

if headlessProcRegex.MatchString(p) {
p = "/proc" + p
}

if !strings.HasPrefix(p, "/") {
p = "/" + p
}
Comment on lines +9 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n pkg/utils/path.go | head -40

Repository: kubescape/node-agent

Length of output: 940


🏁 Script executed:

#!/bin/bash
python - <<'PY'
import re, posixpath

# Current regex in the code
current_regex = re.compile(r"^/\d+/(task|fd)/")

# Proposed regex in the fix
proposed_regex = re.compile(r"^/\d+/(task|fd)(/|$)")

def test_current(p):
    """Simulates current implementation"""
    if p == "": return ""
    if p == ".": return "/"
    if current_regex.match(p):
        p = "/proc" + p
    if not p.startswith("/"):
        p = "/" + p
    return posixpath.normpath(p)

def test_proposed(p):
    """Simulates proposed implementation"""
    if p == "": return ""
    if p == ".": return "/"
    if not p.startswith("/"):
        p = "/" + p
    if proposed_regex.match(p):
        p = "/proc" + p
    return posixpath.normpath(p)

test_cases = [
    "/46/task/46/fd",      # Full headless proc path with content
    "/46/fd",              # Headless proc path without trailing slash
    "/46/task",            # Headless proc path without trailing slash
    "46/task/46/fd",       # Headless path without leading slash
    "/46/task/",           # With trailing slash
    "/46/fd/",             # With trailing slash
    "/normal/path",        # Regular path
    "/123/notask/456",     # Should not match
]

print("Test Case                    | Current Result            | Proposed Result           | Issue")
print("-" * 100)
for tc in test_cases:
    curr = test_current(tc)
    prop = test_proposed(tc)
    issue = "❌ Missing /proc" if curr.startswith("/") and not curr.startswith("/proc") and any(x in tc for x in ["task", "fd"]) and curr == "/" + tc.lstrip("/") else ""
    print(f"{tc:28} | {curr:25} | {prop:25} | {issue}")
PY

Repository: kubescape/node-agent

Length of output: 1021


Headless /proc detection misses valid edge cases due to regex requiring trailing slash.

The regex ^/\d+/(task|fd)/ requires a trailing slash after task or fd, causing valid headless proc paths like /46/fd, /46/task, and 46/task/46/fd (before leading slash normalization) to bypass /proc prepending. This breaks the "always full" guarantee.

Fix by changing the regex to ^/\d+/(task|fd)(/|$) to match paths ending with either "/" or end-of-string, and move the regex check after the leading slash normalization so 46/task/46/fd is correctly recognized after becoming /46/task/46/fd.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/utils/path.go` around lines 9 - 31, The headless /proc detection in
NormalizePath misses cases because headlessProcRegex requires a trailing slash
and runs before leading-slash normalization; update headlessProcRegex to match
either a trailing slash or end-of-string (use pattern like ^/\d+/(task|fd)(/|$))
and move the MatchString check to after the leading-slash normalization in
NormalizePath so inputs like "46/task/46/fd", "/46/fd" and "/46/task" are
correctly detected and have "/proc" prepended.


return path.Clean(p)
}
Loading