fix(misconf): handle unsupported experimental flags in Dockerfile (#9769)

Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
This commit is contained in:
Nikita Pivkin
2025-11-11 19:22:34 +06:00
committed by GitHub
parent 09ea608a3b
commit 08d51a8e08
2 changed files with 108 additions and 4 deletions

View File

@@ -2,12 +2,13 @@ package parser
import (
"context"
"fmt"
"io"
"slices"
"strings"
"github.com/moby/buildkit/frontend/dockerfile/instructions"
"github.com/moby/buildkit/frontend/dockerfile/parser"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile"
)
@@ -15,7 +16,7 @@ import (
func Parse(_ context.Context, r io.Reader, path string) ([]*dockerfile.Dockerfile, error) {
parsed, err := parser.Parse(r)
if err != nil {
return nil, fmt.Errorf("dockerfile parse error: %w", err)
return nil, xerrors.Errorf("dockerfile parse error: %w", err)
}
var (
@@ -28,9 +29,9 @@ func Parse(_ context.Context, r io.Reader, path string) ([]*dockerfile.Dockerfil
for _, child := range parsed.AST.Children {
child.Value = strings.ToLower(child.Value)
instr, err := instructions.ParseInstruction(child)
instr, err := parseInstruction(child)
if err != nil {
return nil, fmt.Errorf("parse dockerfile instruction: %w", err)
return nil, xerrors.Errorf("parse dockerfile instruction: %w", err)
}
if _, ok := instr.(*instructions.Stage); ok {
@@ -89,6 +90,39 @@ func Parse(_ context.Context, r io.Reader, path string) ([]*dockerfile.Dockerfil
return []*dockerfile.Dockerfile{&parsedFile}, nil
}
func parseInstruction(child *parser.Node) (any, error) {
for {
instr, err := instructions.ParseInstruction(child)
if err == nil {
return instr, nil
}
flagName := extractUnknownFlag(err.Error())
if flagName == "" {
return nil, xerrors.Errorf("parse instruction %q: %w", child.Value, err)
}
filtered := slices.DeleteFunc(child.Flags, func(flag string) bool {
return strings.HasPrefix(flag, flagName)
})
if len(filtered) == len(child.Flags) {
return nil, xerrors.Errorf("cannot remove unknown flag %q from flags %v", flagName, child.Flags)
}
child.Flags = filtered
}
}
func extractUnknownFlag(errMsg string) string {
after, ok := strings.CutPrefix(errMsg, "unknown flag: ")
if !ok {
return ""
}
flagName, _, _ := strings.Cut(after, " ")
return flagName
}
func originalFromHeredoc(node *parser.Node) string {
var sb strings.Builder
sb.WriteString(node.Original)

View File

@@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile"
"github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile/parser"
)
@@ -57,6 +58,75 @@ CMD python /app/app.py
assert.Equal(t, 4, commands[3].EndLine)
}
func TestParserUnknownFlags(t *testing.T) {
input := `FROM ubuntu:18.04
COPY --foo --chown=1 --bar=./test . /app
RUN --baz --baz --network=host make /app
ONBUILD RUN --foo make /app
CMD python /app/app.py
`
dfs, err := parser.Parse(t.Context(), strings.NewReader(input), "Dockerfile")
require.NoError(t, err)
require.Len(t, dfs, 1)
expected := &dockerfile.Dockerfile{
Stages: []dockerfile.Stage{
{
Name: "ubuntu:18.04",
Commands: []dockerfile.Command{
{
Cmd: "from",
Value: []string{"ubuntu:18.04"},
Flags: []string{},
Original: "FROM ubuntu:18.04",
Path: "Dockerfile",
StartLine: 1,
EndLine: 1,
},
{
Cmd: "copy",
Value: []string{".", "/app"},
Flags: []string{"--chown=1"},
Original: "COPY --foo --chown=1 --bar=./test . /app",
Path: "Dockerfile",
StartLine: 2,
EndLine: 2,
},
{
Cmd: "run",
Value: []string{"make /app"},
Flags: []string{"--network=host"},
Original: "RUN --baz --baz --network=host make /app",
Path: "Dockerfile",
StartLine: 3,
EndLine: 3,
},
{
Cmd: "onbuild",
SubCmd: "RUN",
Value: []string{"make /app"},
Flags: []string{},
Original: "ONBUILD RUN --foo make /app",
Path: "Dockerfile",
StartLine: 4,
EndLine: 4,
},
{
Cmd: "cmd",
Value: []string{"python /app/app.py"},
Flags: []string{},
Original: "CMD python /app/app.py",
Path: "Dockerfile",
StartLine: 5,
EndLine: 5,
},
},
},
},
}
assert.Equal(t, expected, dfs[0])
}
func Test_ParseHeredocs(t *testing.T) {
tests := []struct {
name string