Skip to content
Merged
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
.idea
.idea

.vscode
29 changes: 23 additions & 6 deletions pkg/assessor/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,29 @@ func (a CacheAssessor) Assess(fileMap deckodertypes.FileMap) ([]*types.Assessmen
dirName := filepath.Dir(filename)
dirBase := filepath.Base(dirName)

// match Directory
if utils.StringInSlice(dirBase+"/", reqDirs) || utils.StringInSlice(dirName+"/", reqDirs) {
if _, ok := detectedDir[dirName]; ok {
// match Directory - check if any component of the directory path matches a required dir
matched := utils.StringInSlice(dirBase+"/", reqDirs) || utils.StringInSlice(dirName+"/", reqDirs)
reportDir := dirName

if !matched {
// Check if any directory component in the path matches a required directory
// and find the top-level occurrence to report
parts := strings.Split(dirName, "/")
for i, part := range parts {
if utils.StringInSlice(part+"/", reqDirs) {
matched = true
// Report only up to and including the first required directory component
reportDir = strings.Join(parts[:i+1], "/")
break
}
}
}

if matched {
if _, ok := detectedDir[reportDir]; ok {
continue
}
detectedDir[dirName] = struct{}{}
detectedDir[reportDir] = struct{}{}

// Skip uncontrollable dependency directory e.g) npm : node_modules, php: composer
if inIgnoreDir(filename) {
Expand All @@ -47,8 +64,8 @@ func (a CacheAssessor) Assess(fileMap deckodertypes.FileMap) ([]*types.Assessmen
assesses,
&types.Assessment{
Code: types.InfoDeletableFiles,
Filename: dirName,
Desc: fmt.Sprintf("Suspicious directory : %s ", dirName),
Filename: reportDir,
Desc: fmt.Sprintf("Suspicious directory : %s ", reportDir),
})

}
Expand Down
153 changes: 153 additions & 0 deletions pkg/assessor/cache/cache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package cache

import (
"testing"

"github.com/SpazioDati/dockle/pkg/log"
"github.com/SpazioDati/dockle/pkg/types"
deckodertypes "github.com/goodwithtech/deckoder/types"
)

func init() {
// Initialize logger for tests
log.InitLogger(false, false)
}

func TestAssess(t *testing.T) {
tests := []struct {
name string
fileMap deckodertypes.FileMap
expectedCount int
expectedFiles []string
}{
{
name: "detects suspicious directories anywhere in filesystem",
fileMap: deckodertypes.FileMap{
"root/.cache/pip/http-v2/1/2/3/file": {},
"root/.aws/credentials": {},
"/root/.git/config": {},
"home/user/.npm/registry": {},
".cache/direct": {},
},
expectedCount: 5,
expectedFiles: []string{"root/.cache", "root/.aws", "/root/.git", "home/user/.npm", ".cache"},
},
{
name: "detects required files by basename",
fileMap: deckodertypes.FileMap{
"root/Dockerfile": {},
"app/docker-compose.yml": {},
"home/.vimrc": {},
"project/.DS_Store": {},
},
expectedCount: 4,
expectedFiles: []string{"root/Dockerfile", "app/docker-compose.yml", "home/.vimrc", "project/.DS_Store"},
},
{
name: "reports only top-level suspicious directory once",
fileMap: deckodertypes.FileMap{
"root/.cache/file1": {},
"root/.cache/pip/file2": {},
"root/.cache/pip/http-v2/deep/file": {},
},
expectedCount: 1,
expectedFiles: []string{"root/.cache"},
},
{
name: "ignores uncontrollable directories",
fileMap: deckodertypes.FileMap{
"app/node_modules/.cache/file": {},
"lib/vendor/.git/config": {},
},
expectedCount: 0,
expectedFiles: []string{},
},
{
name: "handles edge cases",
fileMap: deckodertypes.FileMap{
"usr/bin/app": {},
"etc/config.conf": {},
},
expectedCount: 0,
expectedFiles: []string{},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Reset detectedDir map before each test
detectedDir = map[string]struct{}{}

assessor := CacheAssessor{}
assessments, err := assessor.Assess(tt.fileMap)

if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if len(assessments) != tt.expectedCount {
t.Errorf("expected %d assessments, got %d", tt.expectedCount, len(assessments))
for i, a := range assessments {
t.Logf(" [%d] %s", i, a.Filename)
}
}

// Verify expected files are present
assessmentMap := make(map[string]*types.Assessment)
for _, a := range assessments {
assessmentMap[a.Filename] = a
}

for _, expectedFile := range tt.expectedFiles {
if _, found := assessmentMap[expectedFile]; !found {
t.Errorf("expected assessment for '%s' not found", expectedFile)
}
}

// Verify all have correct code
for _, a := range assessments {
if a.Code != types.InfoDeletableFiles {
t.Errorf("wrong code for %s: got %v, want %v", a.Filename, a.Code, types.InfoDeletableFiles)
}
}
})
}
}

func TestInIgnoreDir(t *testing.T) {
tests := []struct {
name string
filename string
expected bool
}{
{
name: "File in node_modules",
filename: "app/node_modules/.cache/file",
expected: true,
},
{
name: "File in vendor",
filename: "lib/vendor/.git/config",
expected: true,
},
{
name: "File not in ignore dir",
filename: "app/.cache/file",
expected: false,
},
{
name: "File with node_modules in name but not as directory",
filename: "app/my-node_modules-file.txt",
expected: false, // Only matches "node_modules/" with trailing slash
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := inIgnoreDir(tt.filename)
if result != tt.expected {
t.Errorf("inIgnoreDir(%s) = %v, expected %v", tt.filename, result, tt.expected)
}
})
}
}
16 changes: 8 additions & 8 deletions pkg/scanner/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"os"
"path/filepath"
"strings"

"github.com/goodwithtech/deckoder/analyzer"
"github.com/goodwithtech/deckoder/extractor"
Expand Down Expand Up @@ -105,14 +106,13 @@ func createPathPermissionFilterFunc(filenames, extensions []string, permissions
return true, nil
}

// Check with file directory name
fileDir := filepath.Dir(filePath)
if _, ok := requiredDirNames[fileDir]; ok {
return true, nil
}
fileDirBase := filepath.Base(fileDir)
if _, ok := requiredDirNames[fileDirBase]; ok {
return true, nil
// Check if any directory component in the path matches a required directory
// This catches .cache/, .git/, etc. anywhere in the filesystem tree
parts := strings.Split(filePath, "/")
for _, part := range parts {
if _, ok := requiredDirNames[part]; ok {
return true, nil
}
}

fi := h.FileInfo()
Expand Down
Loading