Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
7a68a9e
Changed identification of docker files to be case insensitive on file…
cx-andre-pereira Mar 11, 2026
b835b6c
removed legacy redundant function 'isDockerfile' from analyzer
cx-andre-pereira Mar 11, 2026
da487cd
Improved dockerfile identification to account for relevant folder nam…
cx-andre-pereira Mar 11, 2026
8e17353
Fixed 'dockerfile' keyword not being recognized as a valid file exten…
cx-andre-pereira Mar 12, 2026
17d6b14
Minor optimization
cx-andre-pereira Mar 12, 2026
087df77
Initial test files/cases plus minor changes to supported dockerfile f…
cx-andre-pereira Mar 12, 2026
11ca942
Added new helper function 'isDockerfileExtension' to get_extension ut…
cx-andre-pereira Mar 12, 2026
8d4adfb
reverted accidental query change, fixed linting errors, fixed test er…
cx-andre-pereira Mar 12, 2026
bb88ff3
linting fix and optimized case of file named dockerfile without exten…
cx-andre-pereira Mar 12, 2026
813c9f6
More changes to fix go lint, d variable so 'dockerfile' is not used t…
cx-andre-pereira Mar 12, 2026
f1147e3
Added samples for case insensitive testing on dockerfiles, added E2E …
cx-andre-pereira Mar 13, 2026
f47018c
fix for E2E
cx-andre-pereira Mar 13, 2026
1c59974
Changed relevant functions to always treat/set the extension of valid…
cx-andre-pereira Mar 15, 2026
51b5a52
Removed last mention of 'dockerfile' without dot notation
cx-andre-pereira Mar 16, 2026
122bd04
Changed 'gitignore' check for better check order in 'GetExtension' fu…
cx-andre-pereira Mar 16, 2026
2da32f6
Slightly more restrictive check to FROM command to ensure it has a tr…
cx-andre-pereira Mar 16, 2026
1bfe126
Updates to functions, removed unnecessary if statement on scan.go and…
cx-andre-pereira Mar 17, 2026
944a70f
fix previous commit
cx-andre-pereira Mar 17, 2026
3d5c2c9
fix analyzer uni tests
cx-andre-pereira Mar 17, 2026
6da5da5
simplified new if condition
cx-andre-pereira Mar 17, 2026
c3c0968
lint fix
cx-andre-pereira Mar 17, 2026
194c47f
fixed analyze unit tests, with names ending in 'gitignore' no longer …
cx-andre-pereira Mar 17, 2026
fa26908
Case-insensitive unit tests for dockerfile samples
cx-andre-pereira Mar 17, 2026
8355e51
Slight changes to new test
cx-andre-pereira Mar 17, 2026
50980a7
Slight simplification of new docker/parser unit test
cx-andre-pereira Mar 17, 2026
f6f7986
Merge branch 'master' into AST-140477--Improvement-to-dockerfile-scan…
cx-andre-pereira Mar 17, 2026
c538a38
Mini fix on insensitive_sample
cx-andre-pereira Mar 19, 2026
e033293
Merge branch 'AST-140477--Improvement-to-dockerfile-scanning' of http…
cx-andre-pereira Mar 19, 2026
051e791
Changed E2E to 106 to fix merge conflict
cx-andre-pereira Mar 19, 2026
fd8e7f9
Merge branch 'master' into AST-140477--Improvement-to-dockerfile-scan…
cx-andre-pereira Mar 19, 2026
3d9d583
fix E2E tests
cx-andre-pereira Mar 19, 2026
1d3bc44
Final E2E fix
cx-andre-pereira Mar 19, 2026
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
415 changes: 415 additions & 0 deletions e2e/fixtures/E2E_CLI_105_RESULT.json

Large diffs are not rendered by default.

1,750 changes: 1,750 additions & 0 deletions e2e/fixtures/E2E_CLI_106_PAYLOAD.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion e2e/testcases/e2e-cli-075_ansible_host_detected.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package testcases
// should perform the scan successfully detect ansible and return result 40
func init() { //nolint
testSample := TestCase{
Name: "should perform a valid scan and and detect ansible [E2E-CLI-075]",
Name: "should perform a valid scan and detect ansible [E2E-CLI-075]",
Args: args{
Args: []cmdArgs{
[]string{"scan", "-o", "/path/e2e/output",
Expand Down
31 changes: 31 additions & 0 deletions e2e/testcases/e2e-cli-106_valid_dockerfile_detected.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package testcases

// E2E-CLI-106 - KICS scan
// should perform the scan successfully detect all valid dockerfile documents and return result 50
func init() { //nolint
testSample := TestCase{
Name: "should perform a valid scan with all dockerfile documents parsed [E2E-CLI-106]",
Args: args{
Args: []cmdArgs{
[]string{"scan", "-o", "/path/e2e/output",
"--output-name", "E2E_CLI_106_RESULT",
"-p", "/path/test/fixtures/dockerfile",
"-p", "/path/test/fixtures/negative_dockerfile",
"--payload-path", "/path/e2e/output/E2E_CLI_106_PAYLOAD.json",
},
},
ExpectedResult: []ResultsValidation{
{
ResultsFile: "E2E_CLI_106_RESULT",
ResultsFormats: []string{"json"},
},
},
ExpectedPayload: []string{
"E2E_CLI_106_PAYLOAD.json",
},
},
WantStatus: []int{50},
}

Tests = append(Tests, testSample)
}
68 changes: 17 additions & 51 deletions pkg/analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,22 +98,20 @@ var (
listKeywordsGoogleDeployment = []string{"resources"}
armRegexTypes = []string{"blueprint", "templateArtifact", "roleAssignmentArtifact", "policyAssignmentArtifact"}
possibleFileTypes = map[string]bool{
".yml": true,
".yaml": true,
".json": true,
".dockerfile": true,
"Dockerfile": true,
"possibleDockerfile": true,
".debian": true,
".ubi8": true,
".tf": true,
"tfvars": true,
".proto": true,
".sh": true,
".cfg": true,
".conf": true,
".ini": true,
".bicep": true,
".yml": true,
".yaml": true,
".json": true,
".dockerfile": true,
".debian": true,
".ubi8": true,
".tf": true,
"tfvars": true,
".proto": true,
".sh": true,
".cfg": true,
".conf": true,
".ini": true,
".bicep": true,
}
supportedRegexes = map[string][]string{
"azureresourcemanager": append(armRegexTypes, arm),
Expand Down Expand Up @@ -430,26 +428,18 @@ func (a *analyzerInfo) worker( //nolint: gocyclo
}()

ext, errExt := utils.GetExtension(a.filePath)

if errExt == nil {
linesCount, _ := utils.LineCounter(a.filePath, a.fallbackMinifiedFileLOC)

switch ext {
// Dockerfile (direct identification)
case ".dockerfile", "Dockerfile":
// Dockerfile
case ".dockerfile":
if a.isAvailableType(dockerfile) {
results <- dockerfile
locCount <- linesCount
fileInfo <- fileTypeInfo{filePath: a.filePath, fileType: dockerfile, locCount: linesCount}
}
// Dockerfile (indirect identification)
case "possibleDockerfile", ".ubi8", ".debian":
if a.isAvailableType(dockerfile) && isDockerfile(a.filePath) {
results <- dockerfile
locCount <- linesCount
fileInfo <- fileTypeInfo{filePath: a.filePath, fileType: dockerfile, locCount: linesCount}
} else {
unwanted <- a.filePath
}
// Terraform
case ".tf", "tfvars":
if a.isAvailableType(terraform) {
Expand Down Expand Up @@ -487,30 +477,6 @@ func (a *analyzerInfo) worker( //nolint: gocyclo
}
}

func isDockerfile(path string) bool {
content, err := os.ReadFile(filepath.Clean(path))
if err != nil {
log.Error().Msgf("failed to analyze file: %s", err)
return false
}

regexes := []*regexp.Regexp{
regexp.MustCompile(`\s*FROM\s*`),
regexp.MustCompile(`\s*RUN\s*`),
}

check := true

for _, regex := range regexes {
if !regex.Match(content) {
check = false
break
}
}

return check
}

// overrides k8s match when all regexes pass for azureresourcemanager key and extension is set to json
func needsOverride(check bool, returnType, key, ext string) bool {
if check && returnType == kubernetes && key == arm && ext == json {
Expand Down
3 changes: 1 addition & 2 deletions pkg/analyzer/analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ func TestAnalyzer_Analyze(t *testing.T) {
wantExclude: []string{
filepath.FromSlash("../../test/fixtures/gitignore/positive.dockerfile"),
filepath.FromSlash("../../test/fixtures/gitignore/secrets.tf"),
filepath.FromSlash("../../test/fixtures/gitignore/gitignore"),
},
typesFromFlag: []string{""},
excludeTypesFromFlag: []string{""},
Expand All @@ -167,7 +166,7 @@ func TestAnalyzer_Analyze(t *testing.T) {
filepath.FromSlash("../../test/fixtures/gitignore"),
},
wantTypes: []string{"dockerfile", "kubernetes", "terraform"},
wantExclude: []string{filepath.FromSlash("../../test/fixtures/gitignore/gitignore")},
wantExclude: []string{},
typesFromFlag: []string{""},
excludeTypesFromFlag: []string{""},
wantLOC: 42,
Expand Down
4 changes: 2 additions & 2 deletions pkg/parser/docker/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (p *Parser) Parse(_ string, fileContent []byte) ([]model.Document, []int, e
for _, child := range parsed.AST.Children {
child.Value = strings.ToLower(child.Value)
if child.Value == "from" {
fromValue = strings.TrimPrefix(child.Original, "FROM ")
fromValue = child.Original[5:]
}

if ignoreStruct.getIgnoreComments(child) {
Expand Down Expand Up @@ -133,7 +133,7 @@ func (p *Parser) GetKind() model.FileKind {

// SupportedExtensions returns Dockerfile extensions
func (p *Parser) SupportedExtensions() []string {
return []string{"Dockerfile", ".dockerfile", ".ubi8", ".debian", "possibleDockerfile"}
return []string{".dockerfile", ".ubi8", ".debian"}
}

// SupportedTypes returns types supported by this parser, which are dockerfile
Expand Down
45 changes: 44 additions & 1 deletion pkg/parser/docker/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestParser_GetKind(t *testing.T) {
// TestParser_SupportedExtensions tests the functions [SupportedExtensions()] and all the methods called by them
func TestParser_SupportedExtensions(t *testing.T) {
p := &Parser{}
require.Equal(t, []string{"Dockerfile", ".dockerfile", ".ubi8", ".debian", "possibleDockerfile"}, p.SupportedExtensions())
require.Equal(t, []string{".dockerfile", ".ubi8", ".debian"}, p.SupportedExtensions())
}

// TestParser_SupportedExtensions tests the functions [SupportedTypes()] and all the methods called by them
Expand Down Expand Up @@ -235,3 +235,46 @@ func TestParser_GetResolvedFiles(t *testing.T) {
})
}
}

// TestParser_Parse_CaseInsensitive tests that the parser handles Dockerfile commands
// in a case-insensitive manner
func TestParser_Parse_CaseInsensitive(t *testing.T) {
p := &Parser{}
// baseline sample
upper := `
FROM alpine:3.18
RUN echo "hello"
`
lower := `
from alpine:3.18
run echo "hello"
`
mixed := `
fRoM alpine:3.18
rUn echo "hello"
`

docUpper, _, err := p.Parse("Dockerfile", []byte(upper))
require.NoError(t, err)
require.Len(t, docUpper, 1)
cmdsUpper := docUpper[0]["command"].(map[string]interface{})["alpine:3.18"].([]interface{})

docLower, _, err := p.Parse("Dockerfile", []byte(lower))
require.NoError(t, err)
require.Len(t, docLower, 1)
cmdsLower := docLower[0]["command"].(map[string]interface{})["alpine:3.18"].([]interface{})
require.Len(t, cmdsUpper, len(cmdsLower))

docMixed, _, err := p.Parse("Dockerfile", []byte(mixed))
require.NoError(t, err)
require.Len(t, docMixed, 1)
cmdsMixed := docMixed[0]["command"].(map[string]interface{})["alpine:3.18"].([]interface{})
require.Len(t, cmdsUpper, len(cmdsMixed))

for i := range cmdsUpper {
require.Equal(t, cmdsUpper[i].(map[string]interface{})["Cmd"], cmdsMixed[i].(map[string]interface{})["Cmd"])
require.Equal(t, cmdsUpper[i].(map[string]interface{})["Value"], cmdsMixed[i].(map[string]interface{})["Value"])
require.Equal(t, cmdsUpper[i].(map[string]interface{})["Cmd"], cmdsLower[i].(map[string]interface{})["Cmd"])
require.Equal(t, cmdsUpper[i].(map[string]interface{})["Value"], cmdsLower[i].(map[string]interface{})["Value"])
}
}
1 change: 0 additions & 1 deletion pkg/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ func TestParser_SupportedExtensions(t *testing.T) {
require.Contains(t, extensions, ".tf")
require.Contains(t, extensions, ".yaml")
require.Contains(t, extensions, ".dockerfile")
require.Contains(t, extensions, "Dockerfile")
}

func initilizeBuilder() []*Parser {
Expand Down
2 changes: 1 addition & 1 deletion pkg/remediation/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func getPayload(filePath string, content []byte, openAPIResolveReferences bool,
var err error

switch ext {
case ".dockerfile", "Dockerfile", "possibleDockerfile", ".ubi8", ".debian":
case ".dockerfile", ".ubi8", ".debian":
p, err = parser.NewBuilder().Add(&dockerParser.Parser{}).Build([]string{""}, []string{""})

case terraformExtension:
Expand Down
71 changes: 45 additions & 26 deletions pkg/utils/get_extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ import (

// GetExtension gets the extension of a file path
func GetExtension(path string) (string, error) {
targets := []string{"Dockerfile", "tfvars"}

// Get file information
extDockerfile := ".dockerfile"
fileInfo, err := os.Stat(path)
if err != nil {
return "", fmt.Errorf("file %s not found", path)
Expand All @@ -26,35 +24,54 @@ func GetExtension(path string) (string, error) {
return "", fmt.Errorf("the path %s is a directory", path)
}

if ext, ok := isDockerfileExtension(path, extDockerfile); ok {
return ext, nil
}

ext := filepath.Ext(path)
if ext == "" {
base := filepath.Base(path)
switch ext {
case ".ubi8", ".debian":
if readPossibleDockerFile(path) {
return extDockerfile, nil
}
case "":
if filepath.Base(path) == "tfvars" {
return ".tfvars", nil
}
isText, err := isTextFile(path)
if err != nil {
return "", err
}
if isText && readPossibleDockerFile(path) {
return extDockerfile, nil
}
}
return ext, nil
}

if Contains(base, targets) {
ext = base
} else {
isText, err := isTextFile(path)
func isDockerfileExtension(path, extDockerfile string) (string, bool) {
base := filepath.Base(path)
d := "dockerfile"

if err != nil {
return "", err
}
lower := strings.ToLower(base)
if lower == d || strings.HasPrefix(lower, "dockerfile.") {
return extDockerfile, true
}

if isText {
if readPossibleDockerFile(path) {
ext = "possibleDockerfile"
}
}
}
if strings.EqualFold(filepath.Ext(path), extDockerfile) {
return extDockerfile, true
}

return ext, nil
dir := strings.ToLower(filepath.Base(filepath.Dir(path)))
if (dir == "docker" || dir == d || dir == "dockerfiles") && readPossibleDockerFile(path) {
return extDockerfile, true
}

return "", false
}

func readPossibleDockerFile(path string) bool {
path = filepath.Clean(path)
if strings.HasSuffix(path, "gitignore") {
return true
}
file, err := os.Open(path)
if err != nil {
return false
Expand All @@ -68,12 +85,14 @@ func readPossibleDockerFile(path string) bool {
scanner := bufio.NewScanner(file)
// Read lines from the file
for scanner.Scan() {
if strings.HasPrefix(scanner.Text(), "FROM") {
return true
} else if strings.HasPrefix(scanner.Text(), "#") {
if strings.HasPrefix(scanner.Text(), "#") || strings.HasPrefix(strings.ToLower(scanner.Text()), "arg") || scanner.Text() == "" {
continue
} else {
return false
if strings.HasPrefix(strings.ToLower(scanner.Text()), "from ") {
return true
} else {
return false
}
}
}
return false
Expand Down
Loading
Loading