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
41 changes: 41 additions & 0 deletions pkg/action/diff_scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import (

"github.com/chainguard-dev/malcontent/pkg/file"
"github.com/chainguard-dev/malcontent/pkg/malcontent"
"github.com/chainguard-dev/malcontent/pkg/render"
"github.com/chainguard-dev/malcontent/rules"
thirdparty "github.com/chainguard-dev/malcontent/third_party"
"github.com/minio/sha256-simd"
"github.com/puzpuzpuz/xsync/v4"

yarax "github.com/VirusTotal/yara-x/go"
)
Expand Down Expand Up @@ -707,6 +709,45 @@ func TestDiffReports(t *testing.T) {
}
}

func TestDiffYAMLReports(t *testing.T) {
t.Parallel()
root := diffTestTempDir(t)
write := func(name string, frs map[string]*malcontent.FileReport) string {
files := xsync.NewMap[string, *malcontent.FileReport]()
for k, fr := range frs {
files.Store(k, fr)
}
var out bytes.Buffer
r, err := render.New("yaml", &out)
if err != nil {
t.Fatalf("render.New: %v", err)
}
if err := r.Full(t.Context(), nil, &malcontent.Report{Files: files}); err != nil {
t.Fatalf("Full: %v", err)
}
path := filepath.Join(root, name)
diffTestWriteFile(t, path, out.String())
return path
}
src := write("src.yaml", map[string]*malcontent.FileReport{
"/app/same": {Path: "/app/same", RiskScore: 1, Behaviors: []*malcontent.Behavior{{ID: "net/socket/listen"}}},
"/app/gone": {Path: "/app/gone", RiskScore: 1},
})
dest := write("dest.yaml", map[string]*malcontent.FileReport{
"/app/same": {Path: "/app/same", RiskScore: 3, Behaviors: []*malcontent.Behavior{{ID: "net/socket/listen"}, {ID: "exec/shell/command"}}},
"/app/new": {Path: "/app/new", RiskScore: 2},
})

res, err := Diff(t.Context(), malcontent.Config{Report: true, ScanPaths: []string{src, dest}}, nil)
if err != nil {
t.Fatalf("Diff: got error = %v, want nil", err)
}
if res == nil || res.Diff == nil {
t.Fatalf("Diff: got = %v, want a report with a diff", res)
}
diffTestAssertKeys(t, res.Diff, []string{"/app/gone"}, []string{"/app/new"}, []string{"/app/same"})
}

func TestDiffReportErrors(t *testing.T) {
t.Parallel()
root := diffTestTempDir(t)
Expand Down
20 changes: 18 additions & 2 deletions pkg/report/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,39 @@
package report

import (
"bytes"
"encoding/json"
"regexp"
"strings"

"github.com/chainguard-dev/malcontent/pkg/malcontent"
"gopkg.in/yaml.v3"
)

// tempDirPattern matches the temporary directory root at the start of a path
// extracted under macOS or /tmp.
var tempDirPattern = regexp.MustCompile(`^/(?:var/folders|tmp|private/var/folders|private/tmp)/[^/]+/[^/]+/T/[^/]+`)

// Load parses a report written by the JSON or YAML renderer. Input that is
// neither keeps the JSON error.
func Load(data []byte) (malcontent.ScanResult, error) {
var report malcontent.ScanResult
if err := json.Unmarshal(data, &report); err != nil {
err := json.Unmarshal(data, &report)
if err == nil {
return report, nil
}
// A JSON report is an object, so only other input can be a YAML one,
// which the YAML renderer writes with its file reports under "files".
if trimmed := bytes.TrimSpace(data); len(trimmed) == 0 || trimmed[0] == '{' {
return report, err
}
var yr struct {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: ScanResult's yaml:"Files" tag is unused elsewhere; changing it to files would drop this struct.

Files map[string]*malcontent.FileReport `yaml:"files"`
}
if yaml.Unmarshal(data, &yr) != nil || yr.Files == nil {
return report, err
}
return report, nil
return malcontent.ScanResult{FileReports: yr.Files}, nil
}

// ExtractImageURI extracts the image URI from paths in a report.
Expand Down
36 changes: 36 additions & 0 deletions pkg/report/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,42 @@ func TestLoad(t *testing.T) {
}
}

func TestLoadYAML(t *testing.T) {
t.Parallel()
// The shape the YAML renderer writes: lowercase keys, files under "files".
data := []byte(`files:
/usr/bin/curl:
path: /usr/bin/curl
riskscore: 2
risklevel: MEDIUM
behaviors:
- description: Makes HTTP requests
riskscore: 2
id: net/http
`)
got, err := Load(data)
if err != nil {
t.Fatalf("Load() error = %v", err)
}
fr, ok := got.FileReports["/usr/bin/curl"]
if !ok {
t.Fatalf("Load() files = %v, want /usr/bin/curl", got.FileReports)
}
if fr.Path != "/usr/bin/curl" || fr.RiskScore != 2 || fr.RiskLevel != "MEDIUM" {
t.Errorf("Load() file = %+v, want path /usr/bin/curl, risk 2 MEDIUM", fr)
}
if len(fr.Behaviors) != 1 || fr.Behaviors[0].ID != "net/http" || fr.Behaviors[0].RiskScore != 2 {
t.Errorf("Load() behaviors = %+v, want one net/http behavior with risk 2", fr.Behaviors)
}

// YAML that holds no file reports is not a report.
for _, data := range []string{"this is not a report", "files:\n", "stats:\n total: 1\n", "- a\n- b\n"} {
if _, err := Load([]byte(data)); err == nil {
t.Errorf("Load(%q) error = nil, want an error", data)
}
}
}

func TestExtractImageURI(t *testing.T) {
t.Parallel()
tests := []struct {
Expand Down
Loading