Skip to content
Closed
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 @@ -397,7 +397,7 @@ func (e *DatasourceEvent) GetEventType() EventType {

func (e *DatasourceEvent) GetExePath() string {
exepath, _ := e.getFieldAccessor("exepath").String(e.Data)
return exepath
return NormalizePath(exepath)
}

func (e *DatasourceEvent) GetExitCode() uint32 {
Expand Down Expand Up @@ -439,7 +439,7 @@ func (e *DatasourceEvent) GetFullPath() string {
if path == "" {
path, _ = e.getFieldAccessor("fname").String(e.Data)
}
return path
return NormalizePath(path)
}

func (e *DatasourceEvent) GetGid() *uint32 {
Expand Down Expand Up @@ -490,7 +490,7 @@ func (e *DatasourceEvent) GetNamespace() string {

func (e *DatasourceEvent) GetNewPath() string {
newPath, _ := e.getFieldAccessor("newpath").String(e.Data)
return newPath
return NormalizePath(newPath)
}

func (e *DatasourceEvent) GetNumAnswers() int {
Expand All @@ -500,7 +500,7 @@ func (e *DatasourceEvent) GetNumAnswers() int {

func (e *DatasourceEvent) GetOldPath() string {
oldPath, _ := e.getFieldAccessor("oldpath").String(e.Data)
return oldPath
return NormalizePath(oldPath)
}

func (e *DatasourceEvent) GetOpcode() int {
Expand All @@ -526,7 +526,7 @@ func (e *DatasourceEvent) GetPath() string {
return e.GetFullPath()
}
path, _ := e.getFieldAccessor("fname").String(e.Data)
return path
return NormalizePath(path)
}

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

import (
"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: "terminal headless proc fd path",
input: "/46/fd",
expected: "/proc/46/fd",
},
{
name: "terminal headless proc task path",
input: "/46/task",
expected: "/proc/46/task",
},
{
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) {
if got := NormalizePath(tt.input); got != tt.expected {
t.Errorf("NormalizePath(%q) = %q, want %q", tt.input, got, tt.expected)
}
})
}
}
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
}

return path.Clean(p)
}
Comment on lines +16 to +34
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:

#!/bin/bash
# Check for all usages of GetOldPath, GetNewPath, GetPath, GetFullPath, GetExePath
# to identify potential breaking changes where path comparisons or hashes are performed

echo "=== Checking hash/identifier calculations using path getters ==="
rg -n -C3 'CalculateSHA256|Hash.*Get(Old|New|Full)?Path|Get(Old|New|Full)?Path.*Hash'

echo ""
echo "=== Checking string comparisons on path getters ==="
rg -n -C3 'strings\.(HasPrefix|HasSuffix|Contains|Compare).*Get(Old|New|Full|Exe)?Path'

echo ""
echo "=== Checking equality comparisons on path getters ==="
rg -n -C3 'Get(Old|New|Full|Exe)?Path\(\)\s*[!=]='

Repository: kubescape/node-agent

Length of output: 8890


🏁 Script executed:

# Find all usages of NormalizePath
echo "=== All usages of NormalizePath ==="
rg -n "NormalizePath" --type go

echo ""
echo "=== LinkEvent interface/struct definition ==="
rg -n "type LinkEvent|func.*GetOldPath|func.*GetNewPath" --type go -A2

Repository: kubescape/node-agent

Length of output: 2143


Implementation looks correct; verify downstream hash compatibility.

The normalization logic is well-structured and correctly handles the documented cases. However, the path normalization is applied in the GetOldPath() and GetNewPath() methods (see pkg/utils/datasource_event.go lines 493, 503 and pkg/utils/struct_event.go lines 282, 290), which are used directly in hash calculations for symlinks and hardlinks in pkg/containerprofilemanager/v1/event_reporting.go (lines 103, 116). Since these getters now return normalized paths, existing stored profile hashes will no longer match hashes computed from new events.

This is a breaking change: paths that were previously relative (now get / prefix), . (now become /), or headless proc patterns (now get /proc prefix) will all produce different hash values.

Consider whether a data migration or versioning strategy is needed for existing profiles.

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

In `@pkg/utils/path.go` around lines 16 - 34, Normalizing paths in NormalizePath()
changes outputs of GetOldPath() and GetNewPath(), which are used for hash
calculations in pkg/containerprofilemanager/v1/event_reporting.go
(symlink/hardlink hashing) and will break compatibility with existing stored
profile hashes; update the code by either: 1) preserving previous hashing
behavior for compatibility (use original unnormalized path values when computing
hashes in the functions that call GetOldPath/GetNewPath in event_reporting.go),
or 2) implementing a versioning/migration strategy (add a profile hash version
field and migrate or accept dual-hash checks) so existing profiles remain valid;
locate NormalizePath, GetOldPath/GetNewPath, and the hash computation sites in
event_reporting.go (around the symlink/hardlink handling) and change the hashing
callers to use the pre-normalized path or add versioned hash handling/migration
logic accordingly.

10 changes: 5 additions & 5 deletions pkg/utils/struct_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func (e *StructEvent) GetEventType() EventType {
}

func (e *StructEvent) GetExePath() string {
return e.ExePath
return NormalizePath(e.ExePath)
}

func (e *StructEvent) GetExitCode() uint32 {
Expand All @@ -247,7 +247,7 @@ func (e *StructEvent) GetFlagsRaw() uint32 {
}

func (e *StructEvent) GetFullPath() string {
return e.FullPath
return NormalizePath(e.FullPath)
}

func (e *StructEvent) GetGid() *uint32 {
Expand Down Expand Up @@ -279,15 +279,15 @@ func (e *StructEvent) GetNamespace() string {
}

func (e *StructEvent) GetNewPath() string {
return e.NewPath
return NormalizePath(e.NewPath)
}

func (e *StructEvent) GetNumAnswers() int {
return e.NumAnswers
}

func (e *StructEvent) GetOldPath() string {
return e.OldPath
return NormalizePath(e.OldPath)
}

func (e *StructEvent) GetOpcode() int {
Expand All @@ -311,7 +311,7 @@ func (e *StructEvent) GetPath() string {
if e.FullPathTracing {
return e.GetFullPath()
}
return e.Path
return NormalizePath(e.Path)
}

func (e *StructEvent) GetPcomm() string {
Expand Down
Loading