diff --git a/pkg/action/diff.go b/pkg/action/diff.go
index 22a2e2279..3daa893ab 100644
--- a/pkg/action/diff.go
+++ b/pkg/action/diff.go
@@ -12,7 +12,6 @@ import (
"path/filepath"
"regexp"
"strings"
- "sync"
"github.com/agext/levenshtein"
"github.com/chainguard-dev/clog"
@@ -21,23 +20,40 @@ import (
"golang.org/x/sync/errgroup"
)
-func relPath(from string, fr *malcontent.FileReport, isArchive bool) (string, error) {
+// displayPath mimics diff(1) output for relative paths.
+func displayPath(base, path string) string {
+ if filepath.IsAbs(path) {
+ rel, err := filepath.Rel(base, path)
+ if err == nil {
+ return rel
+ }
+ }
+ return path
+}
+
+// relPath returns the cleanest possible relative path between a source path and files within said path.
+func relPath(from string, fr *malcontent.FileReport, isArchive bool) (string, string, error) {
+ var base string
var err error
var rel string
switch {
case isArchive:
fromRoot := fr.ArchiveRoot
-
+ base = fr.FullPath
// trim archiveRoot from fullPath
archiveFile := strings.TrimPrefix(fr.FullPath, fr.ArchiveRoot)
rel, err = filepath.Rel(fromRoot, archiveFile)
if err != nil {
- return "", err
+ return "", "", err
}
default:
+ base, err = filepath.Abs(from)
+ if err != nil {
+ return "", "", err
+ }
info, err := os.Stat(from)
if err != nil {
- return "", err
+ return "", "", err
}
dir := filepath.Dir(from)
// Evaluate symlinks to cover edge cases like macOS' /private/tmp -> /tmp symlink
@@ -50,28 +66,29 @@ func relPath(from string, fr *malcontent.FileReport, isArchive bool) (string, er
fromRoot, err = filepath.EvalSymlinks(dir)
}
if err != nil {
- return "", err
+ return "", "", err
}
if fromRoot == "." {
fromRoot = from
}
rel, err = filepath.Rel(fromRoot, fr.Path)
if err != nil {
- return "", err
+ return "", "", err
}
}
- return rel, nil
+ return rel, base, nil
}
-func relFileReport(ctx context.Context, c malcontent.Config, fromPath string) (map[string]*malcontent.FileReport, error) {
+func relFileReport(ctx context.Context, c malcontent.Config, fromPath string) (map[string]*malcontent.FileReport, string, error) {
fromConfig := c
fromConfig.Renderer = nil
fromConfig.ScanPaths = []string{fromPath}
fromReport, err := recursiveScan(ctx, fromConfig)
if err != nil {
- return nil, err
+ return nil, "", err
}
fromRelPath := map[string]*malcontent.FileReport{}
+ var base, rel string
fromReport.Files.Range(func(key, value any) bool {
if key == nil || value == nil {
return true
@@ -81,7 +98,7 @@ func relFileReport(ctx context.Context, c malcontent.Config, fromPath string) (m
if fr.Skipped != "" || fr.Error != "" {
return true
}
- rel, err := relPath(fromPath, fr, isArchive)
+ rel, base, err = relPath(fromPath, fr, isArchive)
if err != nil {
return false
}
@@ -90,7 +107,35 @@ func relFileReport(ctx context.Context, c malcontent.Config, fromPath string) (m
return true
})
- return fromRelPath, nil
+ return fromRelPath, base, nil
+}
+
+// scoreFile returns a boolean to determine how individual files are stored in a diff report.
+func scoreFile(fr, tr *malcontent.FileReport) bool {
+ scoreSrc := false
+ scoreDest := false
+
+ patterns := []string{
+ `^[\w.-]+\.so$`,
+ `^.+-.*-r\d+\.spdx\.json$`,
+ }
+
+ for _, pattern := range patterns {
+ re := regexp.MustCompile(pattern)
+ if re.MatchString(fr.Path) {
+ scoreSrc = true
+ }
+ if re.MatchString(tr.Path) {
+ scoreDest = true
+ }
+ }
+
+ // If both files match patterns, reeturn true to indicate that `inferMoves` should be used
+ // Otherwise, indicate that `handleFile` should be used
+ if scoreSrc && scoreDest {
+ return true
+ }
+ return false
}
func Diff(ctx context.Context, c malcontent.Config) (*malcontent.Report, error) {
@@ -98,13 +143,29 @@ func Diff(ctx context.Context, c malcontent.Config) (*malcontent.Report, error)
return nil, fmt.Errorf("diff mode requires 2 paths, you passed in %d path(s)", len(c.ScanPaths))
}
+ srcPath := c.ScanPaths[0]
+ destPath := c.ScanPaths[1]
+
var g errgroup.Group
var src, dest map[string]*malcontent.FileReport
+ var srcBase, destBase string
srcCh := make(chan map[string]*malcontent.FileReport, 1)
destCh := make(chan map[string]*malcontent.FileReport, 1)
+ srcIsArchive := isSupportedArchive(srcPath)
+ destIsArchive := isSupportedArchive(destPath)
+
+ srcInfo, err := os.Stat(srcPath)
+ if err != nil {
+ return nil, err
+ }
+
+ destInfo, err := os.Stat(destPath)
+ if err != nil {
+ return nil, err
+ }
g.Go(func() error {
- src, err := relFileReport(ctx, c, c.ScanPaths[0])
+ src, srcBase, err = relFileReport(ctx, c, srcPath)
if err != nil {
return err
}
@@ -113,7 +174,7 @@ func Diff(ctx context.Context, c malcontent.Config) (*malcontent.Report, error)
})
g.Go(func() error {
- dest, err := relFileReport(ctx, c, c.ScanPaths[1])
+ dest, destBase, err = relFileReport(ctx, c, destPath)
if err != nil {
return err
}
@@ -137,8 +198,35 @@ func Diff(ctx context.Context, c malcontent.Config) (*malcontent.Report, error)
Modified: orderedmap.New[string, *malcontent.FileReport](),
}
- processSrc(ctx, c, src, dest, d)
- processDest(ctx, c, src, dest, d)
+ // When scanning two directories, compare the files in each directory
+ // and employ add/delete for files that are not the same
+ // When scanning two files, do a 1:1 comparison and
+ // consider the source -> destination as a change rather than an add/delete
+ if (srcInfo.IsDir() && destInfo.IsDir()) || (srcIsArchive && destIsArchive) {
+ handleDir(ctx, c, src, dest, d)
+ } else {
+ var srcFile, destFile *malcontent.FileReport
+ for _, fr := range src {
+ srcFile = fr
+ break
+ }
+ for _, fr := range dest {
+ destFile = fr
+ break
+ }
+ if srcFile != nil && destFile != nil {
+ formatSrc := displayPath(srcBase, srcFile.Path)
+ formatDest := displayPath(destBase, destFile.Path)
+ if scoreFile(srcFile, destFile) {
+ d.Removed.Set(srcFile.Path, srcFile)
+ d.Added.Set(destFile.Path, destFile)
+ inferMoves(ctx, c, d)
+ } else {
+ handleFile(ctx, c, srcFile, destFile, fmt.Sprintf("%s -> %s", formatSrc, formatDest), d)
+ }
+ }
+ }
+
// skip inferring moves if added and removed are empty
if d.Added != nil && d.Removed != nil {
inferMoves(ctx, c, d)
@@ -146,61 +234,51 @@ func Diff(ctx context.Context, c malcontent.Config) (*malcontent.Report, error)
return &malcontent.Report{Diff: d}, nil
}
-func processSrc(ctx context.Context, c malcontent.Config, src, dest map[string]*malcontent.FileReport, d *malcontent.DiffReport) {
- // things that appear in the source
- for relPath, fr := range src {
- tr, exists := dest[relPath]
- if !exists {
- d.Removed.Set(relPath, fr)
- continue
- }
- handleFile(ctx, c, fr, tr, relPath, d)
- }
-}
-
-func processDest(ctx context.Context, c malcontent.Config, from, to map[string]*malcontent.FileReport, d *malcontent.DiffReport) {
- // findings that exist only in the destination
- for relPath, tr := range to {
- fr, exists := from[relPath]
- if !exists {
- d.Added.Set(relPath, tr)
- continue
- }
-
- fileDestination(ctx, c, fr, tr, relPath, d)
- }
-}
+func handleDir(ctx context.Context, c malcontent.Config, src, dest map[string]*malcontent.FileReport, d *malcontent.DiffReport) {
+ srcFiles := make(map[string]*malcontent.FileReport)
+ destFiles := make(map[string]*malcontent.FileReport)
-func fileDestination(ctx context.Context, c malcontent.Config, fr, tr *malcontent.FileReport, relPath string, d *malcontent.DiffReport) {
- // We've now established that this file exists in both source and destination
- if fr.RiskScore < c.MinFileRisk && tr.RiskScore < c.MinFileRisk {
- clog.FromContext(ctx).Info("diff does not meet min trigger level", slog.Any("path", tr.Path))
- return
+ for path, fr := range src {
+ base := filepath.Base(path)
+ srcFiles[base] = fr
}
-
- // Filter files that are marked as added
- if filterDiff(ctx, c, fr, tr) {
- return
+ for path, fr := range dest {
+ base := filepath.Base(path)
+ destFiles[base] = fr
}
- abs := createFileReport(tr, fr)
-
- // if destination behavior is not in the source
- for _, tb := range tr.Behaviors {
- if !behaviorExists(tb, fr.Behaviors) {
- tb.DiffAdded = true
- abs.Behaviors = append(abs.Behaviors, tb)
- continue
+ // Check for files that exist in both the source and destination
+ // Files that exist in both pass to handleFile which considers files as modifications
+ // Otherwise, treat the source file as existing only in the source directory
+ // These files are considered removals from the destination
+ for name, srcFr := range srcFiles {
+ if destFr, exists := destFiles[name]; exists {
+ if !filterDiff(ctx, c, srcFr, destFr) {
+ formatSrc := displayPath(name, srcFr.Path)
+ formatDest := displayPath(name, destFr.Path)
+ if scoreFile(srcFr, destFr) {
+ d.Removed.Set(srcFr.Path, srcFr)
+ d.Added.Set(destFr.Path, destFr)
+ inferMoves(ctx, c, d)
+ } else {
+ handleFile(ctx, c, srcFr, destFr, fmt.Sprintf("%s -> %s", formatSrc, formatDest), d)
+ }
+ }
+ } else {
+ formatSrc := displayPath(name, srcFr.Path)
+ dirPath := filepath.Dir(formatSrc)
+ d.Removed.Set(fmt.Sprintf("%s/%s", dirPath, name), srcFr)
}
}
- // are there already modified behaviors for this file?
- rel, exists := d.Modified.Get(relPath)
- if !exists {
- d.Modified.Set(relPath, abs)
- } else {
- rel.Behaviors = append(rel.Behaviors, abs.Behaviors...)
- d.Modified.Set(relPath, rel)
+ // Check for files that exist only in the destination directory
+ // These files are considered additions to the destination
+ for name, destFr := range destFiles {
+ if _, exists := srcFiles[name]; !exists {
+ formatDest := displayPath(name, destFr.Path)
+ dirPath := filepath.Dir(formatDest)
+ d.Added.Set(fmt.Sprintf("%s/%s", dirPath, name), destFr)
+ }
}
}
@@ -218,15 +296,29 @@ func handleFile(ctx context.Context, c malcontent.Config, fr, tr *malcontent.Fil
rbs := createFileReport(tr, fr)
+ // Findings that exist only in the source
+ // If true, these are considered to be removed from the destination
for _, fb := range fr.Behaviors {
- // findings that exist only in the source
if !behaviorExists(fb, tr.Behaviors) {
fb.DiffRemoved = true
rbs.Behaviors = append(rbs.Behaviors, fb)
continue
}
- // findings that exist in both, for reference
- rbs.Behaviors = append(rbs.Behaviors, fb)
+ }
+
+ // Findings that exist only in the destination
+ // If true, these are considered to be added to the destination
+ // If findings exist in both files, then there is no diff for the given behavior
+ for _, tb := range tr.Behaviors {
+ if !behaviorExists(tb, fr.Behaviors) {
+ tb.DiffAdded = true
+ rbs.Behaviors = append(rbs.Behaviors, tb)
+ continue
+ }
+ if behaviorExists(tb, fr.Behaviors) {
+ rbs.Behaviors = append(rbs.Behaviors, tb)
+ continue
+ }
}
d.Modified.Set(relPath, rbs)
@@ -254,23 +346,11 @@ func behaviorExists(b *malcontent.Behavior, behaviors []*malcontent.Behavior) bo
return false
}
-// filterMap filters orderedmap pairs by checking for matches against a slice of compiled regular expression patterns.
-func filterMap(om *orderedmap.OrderedMap[string, *malcontent.FileReport], ps []*regexp.Regexp, c chan<- *orderedmap.Pair[string, *malcontent.FileReport], wg *sync.WaitGroup) {
- defer wg.Done()
- for pair := om.Oldest(); pair != nil; pair = pair.Next() {
- for _, pattern := range ps {
- if match := pattern.FindString(filepath.Base(pair.Key)); match != "" {
- c <- pair
- }
- }
- }
-}
-
// combine iterates over the removed and added channels to create a diff report to store in the combined channel.
-func combine(removed, added <-chan *orderedmap.Pair[string, *malcontent.FileReport]) []malcontent.CombinedReport {
- combined := make([]malcontent.CombinedReport, 0, len(removed)*len(added))
- for r := range removed {
- for a := range added {
+func combineReports(removed, added *orderedmap.OrderedMap[string, *malcontent.FileReport]) []malcontent.CombinedReport {
+ combined := make([]malcontent.CombinedReport, 0, removed.Len()*added.Len())
+ for r := removed.Oldest(); r != nil; r = r.Next() {
+ for a := added.Oldest(); a != nil; a = a.Next() {
score := levenshtein.Match(r.Key, a.Key, levenshtein.NewParams())
if score < 0.9 {
continue
@@ -287,44 +367,8 @@ func combine(removed, added <-chan *orderedmap.Pair[string, *malcontent.FileRepo
return combined
}
-// combineReports orchestrates the population of the diffs channel with relevant diffReports.
-func combineReports(d *malcontent.DiffReport) []malcontent.CombinedReport {
- var wg sync.WaitGroup
-
- // Patterns we care about when handling diffs
- patterns := []string{
- `^[\w.-]+\.so$`,
- `^.+-.*-r\d+\.spdx\.json$`,
- }
-
- ps := make([]*regexp.Regexp, len(patterns))
- for i, pattern := range patterns {
- ps[i] = regexp.MustCompile(pattern)
- }
-
- // Build two channels with filtered paths to iterate through in the worker pool
- removed := make(chan *orderedmap.Pair[string, *malcontent.FileReport], d.Removed.Len())
- added := make(chan *orderedmap.Pair[string, *malcontent.FileReport], d.Added.Len())
-
- wg.Add(1)
- go func() {
- filterMap(d.Removed, ps, removed, &wg)
- close(removed)
- }()
-
- wg.Add(1)
- go func() {
- filterMap(d.Added, ps, added, &wg)
- close(added)
- }()
-
- wg.Wait()
-
- return combine(removed, added)
-}
-
func inferMoves(ctx context.Context, c malcontent.Config, d *malcontent.DiffReport) {
- for _, cr := range combineReports(d) {
+ for _, cr := range combineReports(d.Removed, d.Added) {
fileMove(ctx, c, cr.RemovedFR, cr.AddedFR, cr.Removed, cr.Added, d, cr.Score)
}
}
@@ -371,6 +415,9 @@ func fileMove(ctx context.Context, c malcontent.Config, fr, tr *malcontent.FileR
fb.DiffRemoved = true
abs.Behaviors = append(abs.Behaviors, fb)
}
+ if behaviorExists(fb, tr.Behaviors) {
+ abs.Behaviors = append(abs.Behaviors, fb)
+ }
}
d.Modified.Set(apath, abs)
diff --git a/pkg/render/markdown.go b/pkg/render/markdown.go
index 2de625db7..566a385ef 100644
--- a/pkg/render/markdown.go
+++ b/pkg/render/markdown.go
@@ -82,6 +82,7 @@ func (r Markdown) Full(ctx context.Context, rep *malcontent.Report) error {
}
added := 0
removed := 0
+ noDiff := 0
for _, b := range modified.Value.Behaviors {
if b.DiffAdded {
added++
@@ -89,24 +90,57 @@ func (r Markdown) Full(ctx context.Context, rep *malcontent.Report) error {
if b.DiffRemoved {
removed++
}
+ if !b.DiffAdded && !b.DiffRemoved {
+ noDiff++
+ }
}
// We split the added/removed up in Markdown to address readability feedback. Unfortunately,
// this means we hide "existing" behaviors, which causes context to suffer. We should evaluate an
// improved rendering, similar to the "terminal" refresh, that includes everything.
+ var count int
+ var qual string
if added > 0 {
+ count = added
+ noun := "behavior"
+ qual = "new"
+ if count > 1 {
+ noun = "behaviors"
+ }
markdownTable(ctx, modified.Value, r.w, tableConfig{
- Title: fmt.Sprintf("### %d new behaviors", added),
+ Title: fmt.Sprintf("### %d %s %s", count, qual, noun),
SkipRemoved: true,
SkipExisting: true,
+ SkipNoDiff: true,
})
}
if removed > 0 {
+ count = removed
+ noun := "behavior"
+ qual = "removed"
+ if count > 1 {
+ noun = "behaviors"
+ }
markdownTable(ctx, modified.Value, r.w, tableConfig{
- Title: fmt.Sprintf("### %d removed behaviors", removed),
+ Title: fmt.Sprintf("### %d %s %s", count, qual, noun),
SkipAdded: true,
SkipExisting: true,
+ SkipNoDiff: true,
+ })
+ }
+
+ if noDiff > 0 {
+ count = noDiff
+ noun := "behavior"
+ qual = "consistent"
+ if count > 1 {
+ noun = "behaviors"
+ }
+ markdownTable(ctx, modified.Value, r.w, tableConfig{
+ Title: fmt.Sprintf("### %d %s %s", count, qual, noun),
+ SkipAdded: true,
+ SkipRemoved: true,
})
}
}
@@ -121,7 +155,6 @@ func markdownTable(_ context.Context, fr *malcontent.FileReport, w io.Writer, rc
}
if fr.Skipped != "" {
- // fmt.Printf("%s - skipped: %s\n", path, fr.Skipped)
return
}
@@ -195,6 +228,11 @@ func markdownTable(_ context.Context, fr *malcontent.FileReport, w io.Writer, rc
}
risk = fmt.Sprintf("-%s", risk)
}
+ if (!k.Behavior.DiffRemoved && !k.Behavior.DiffAdded) || rc.NoDiff {
+ if rc.SkipNoDiff {
+ continue
+ }
+ }
key := fmt.Sprintf("[%s](%s)", k.Key, k.Behavior.RuleURL)
if strings.HasPrefix(risk, "+") {
diff --git a/pkg/render/simple.go b/pkg/render/simple.go
index b0f904a74..fe9f52814 100644
--- a/pkg/render/simple.go
+++ b/pkg/render/simple.go
@@ -104,6 +104,9 @@ func (r Simple) Full(_ context.Context, rep *malcontent.Report) error {
if b.DiffAdded {
fmt.Fprintf(r.w, "+%s\n", b.ID)
}
+ if !b.DiffRemoved && !b.DiffAdded {
+ fmt.Fprintf(r.w, "%s\n", b.ID)
+ }
}
}
diff --git a/pkg/render/terminal.go b/pkg/render/terminal.go
index af3497bd9..714d5f5a0 100644
--- a/pkg/render/terminal.go
+++ b/pkg/render/terminal.go
@@ -26,7 +26,9 @@ type tableConfig struct {
ShowTitle bool
DiffRemoved bool
DiffAdded bool
+ NoDiff bool
SkipAdded bool
+ SkipNoDiff bool
SkipRemoved bool
SkipExisting bool
}
@@ -319,6 +321,13 @@ func renderFileSummary(_ context.Context, fr *malcontent.FileReport, w io.Writer
content = fmt.Sprintf("%s%s%s %s %s", prefix, indent, bullet, rest, desc)
e = ""
}
+
+ if !b.DiffAdded && !b.DiffRemoved {
+ prefix = " "
+ pc = color.New(color.FgHiCyan)
+ content = fmt.Sprintf("%s%s%s %s %s", prefix, indent, bullet, rest, desc)
+ e = ""
+ }
}
// no evidence to give
diff --git a/tests/javascript/2024.lottie-player/lottie-player.min.js.mdiff b/tests/javascript/2024.lottie-player/lottie-player.min.js.mdiff
index 50a5439c3..7d72bbedf 100644
--- a/tests/javascript/2024.lottie-player/lottie-player.min.js.mdiff
+++ b/tests/javascript/2024.lottie-player/lottie-player.min.js.mdiff
@@ -51,3 +51,14 @@
| -MEDIUM | [exec/remote_commands/code_eval](https://github.com/chainguard-dev/malcontent/blob/main/rules/exec/remote_commands/code_eval.yara#eval) | evaluate code dynamically using eval() | [eval("](https://github.com/search?q=eval%28%22&type=code) |
| -MEDIUM | [os/time/clock_sleep](https://github.com/chainguard-dev/malcontent/blob/main/rules/os/time/clock-sleep.yara#setInterval) | uses setInterval to wait | [setInterval(](https://github.com/search?q=setInterval%28&type=code) |
+### 6 consistent behaviors
+
+| RISK | KEY | DESCRIPTION | EVIDENCE |
+|--------|-------------------------------------------------------------------------------------------------------------------------------------|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| MEDIUM | [net/download](https://github.com/chainguard-dev/malcontent/blob/main/rules/net/download/download.yara#download) | download files | [Downloads](https://github.com/search?q=Downloads&type=code)
[downloads-view](https://github.com/search?q=downloads-view&type=code)
[mobile-download-links](https://github.com/search?q=mobile-download-links&type=code) |
+| LOW | [data/encoding/json_decode](https://github.com/chainguard-dev/malcontent/blob/main/rules/data/encoding/json-decode.yara#jsondecode) | Decodes JSON messages | [JSON.parse](https://github.com/search?q=JSON.parse&type=code) |
+| LOW | [data/encoding/json_encode](https://github.com/chainguard-dev/malcontent/blob/main/rules/data/encoding/json-encode.yara#JSONEncode) | encodes JSON | [JSON.stringify](https://github.com/search?q=JSON.stringify&type=code) |
+| LOW | [exec/plugin](https://github.com/chainguard-dev/malcontent/blob/main/rules/exec/plugin/plugin.yara#plugin) | references a 'plugin' | [plugin_relativeTime](https://github.com/search?q=plugin_relativeTime&type=code)
[plugin_updateLocale](https://github.com/search?q=plugin_updateLocale&type=code)
[plugins](https://github.com/search?q=plugins&type=code) |
+| LOW | [net/url/embedded](https://github.com/chainguard-dev/malcontent/blob/main/rules/net/url/embedded.yara#https_url) | contains embedded HTTPS URLs | [https://abitype.dev](https://abitype.dev)
[https://andromeda-explorer.metis.io/api](https://andromeda-explorer.metis.io/api)
[https://andromeda.metis.io/?owner=1088](https://andromeda.metis.io/?owner=1088)
[https://api-era.zksync.network/api](https://api-era.zksync.network/api)
[https://api-moonbeam.moonscan.io/api](https://api-moonbeam.moonscan.io/api)
[https://api-moonriver.moonscan.io/api](https://api-moonriver.moonscan.io/api)
[https://api-optimistic.etherscan.io/api](https://api-optimistic.etherscan.io/api)
[https://api-zkevm.polygonscan.com/api](https://api-zkevm.polygonscan.com/api)
[https://api.arbiscan.io/api](https://api.arbiscan.io/api)
[https://api.avax.network/ext/bc/C/rpc](https://api.avax.network/ext/bc/C/rpc)
[https://api.basescan.org/api](https://api.basescan.org/api)
[https://api.blastscan.io/api](https://api.blastscan.io/api)
[https://api.bscscan.com/api](https://api.bscscan.com/api)
[https://api.celoscan.io/api](https://api.celoscan.io/api)
[https://api.etherscan.io/api](https://api.etherscan.io/api)
[https://api.ftmscan.com/api](https://api.ftmscan.com/api)
[https://api.gnosisscan.io/api](https://api.gnosisscan.io/api)
[https://api.lineascan.build/api](https://api.lineascan.build/api)
[https://api.mantlescan.xyz/api](https://api.mantlescan.xyz/api)
[https://api.polygonscan.com/api](https://api.polygonscan.com/api)
[https://api.roninchain.com/rpc](https://api.roninchain.com/rpc)
[https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan/api](https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan/api)
[https://api.scan.pulsechain.com/api](https://api.scan.pulsechain.com/api)
[https://api.scrollscan.com/api](https://api.scrollscan.com/api)
[https://api.snowtrace.io](https://api.snowtrace.io)
[https://api.wallet.coinbase.com/rpc/v2/desktop/chrome](https://api.wallet.coinbase.com/rpc/v2/desktop/chrome)
[https://api.web3modal.org](https://api.web3modal.org)
[https://app.roninchain.com](https://app.roninchain.com)
[https://arb1.arbitrum.io/rpc](https://arb1.arbitrum.io/rpc)
[https://arbiscan.io](https://arbiscan.io)
[https://arweave.net](https://arweave.net)
[https://aurorascan.dev/api](https://aurorascan.dev/api)
[https://avatar.vercel.sh/andrew.svg?size=50](https://avatar.vercel.sh/andrew.svg?size=50)
[https://basescan.org](https://basescan.org)
[https://blastscan.io](https://blastscan.io)
[https://block-explorer-api.mainnet.zksync.io/api](https://block-explorer-api.mainnet.zksync.io/api)
[https://bobascan.com](https://bobascan.com)
[https://bscscan.com](https://bscscan.com)
[https://build.onbeam.com/rpc](https://build.onbeam.com/rpc)
[https://celoscan.io](https://celoscan.io)
[https://cloudflare-eth.com](https://cloudflare-eth.com)
[https://docs.cloud.coinbase.com/wallet-sdk/docs/errors](https://docs.cloud.coinbase.com/wallet-sdk/docs/errors)
[https://docs.soliditylang.org/en/latest/cheatsheet.html](https://docs.soliditylang.org/en/latest/cheatsheet.html)
[https://echo.walletconnect.com/](https://echo.walletconnect.com/)
[https://era.zksync.network/](https://era.zksync.network/)
[https://ethereum.org/en/developers/docs/networks/](https://ethereum.org/en/developers/docs/networks/)
[https://etherscan.io](https://etherscan.io)
[https://evm.cronos.org](https://evm.cronos.org)
[https://evm.kava.io](https://evm.kava.io)
[https://exchainrpc.okex.org](https://exchainrpc.okex.org)
[https://explorer-api.cronos.org/mainnet/api](https://explorer-api.cronos.org/mainnet/api)
[https://explorer-api.walletconnect.com](https://explorer-api.walletconnect.com)
[https://explorer.cronos.org](https://explorer.cronos.org)
[https://explorer.dogechain.dog/api](https://explorer.dogechain.dog/api)
[https://explorer.fuse.io/api](https://explorer.fuse.io/api)
[https://explorer.harmony.one](https://explorer.harmony.one)
[https://explorer.kcc.io](https://explorer.kcc.io)
[https://explorer.metis.io](https://explorer.metis.io)
[https://explorer.walletconnect.com/?type=wallet](https://explorer.walletconnect.com/?type=wallet)
[https://explorer.zksync.io/](https://explorer.zksync.io/)
[https://fonts.googleapis.com/css2?family=Inter](https://fonts.googleapis.com/css2?family=Inter)
[https://forno.celo.org](https://forno.celo.org)
[https://ftmscan.com](https://ftmscan.com)
[https://gnosisscan.io](https://gnosisscan.io)
[https://go.cb-w.com/dapp?cb_url=](https://go.cb-w.com/dapp?cb_url=)
[https://go.cb-w.com/walletlink](https://go.cb-w.com/walletlink)
[https://kavascan.com/api](https://kavascan.com/api)
[https://kcc-rpc.com](https://kcc-rpc.com)
[https://keys.coinbase.com/connect](https://keys.coinbase.com/connect)
[https://lineascan.build](https://lineascan.build)
[https://links.ethers.org/v5-errors-](https://links.ethers.org/v5-errors-)
[https://mainnet.aurora.dev](https://mainnet.aurora.dev)
[https://mainnet.base.org](https://mainnet.base.org)
[https://mainnet.boba.network](https://mainnet.boba.network)
[https://mainnet.era.zksync.io](https://mainnet.era.zksync.io)
[https://mainnet.optimism.io](https://mainnet.optimism.io)
[https://mantlescan.xyz/](https://mantlescan.xyz/)
[https://moonbeam.public.blastapi.io](https://moonbeam.public.blastapi.io)
[https://moonriver.moonscan.io](https://moonriver.moonscan.io)
[https://moonriver.public.blastapi.io](https://moonriver.public.blastapi.io)
[https://moonscan.io](https://moonscan.io)
[https://npms.io/search?q=ponyfill.](https://npms.io/search?q=ponyfill.)
[https://openchain.xyz/signatures?query=](https://openchain.xyz/signatures?query=)
[https://optimistic.etherscan.io](https://optimistic.etherscan.io)
[https://polygon-rpc.com](https://polygon-rpc.com)
[https://polygonscan.com](https://polygonscan.com)
[https://pulse.walletconnect.org](https://pulse.walletconnect.org)
[https://reactjs.org/docs/error-decoder.html?invariant=](https://reactjs.org/docs/error-decoder.html?invariant=)
[https://rpc.ankr.com/bsc](https://rpc.ankr.com/bsc)
[https://rpc.ankr.com/fantom](https://rpc.ankr.com/fantom)
[https://rpc.ankr.com/harmony](https://rpc.ankr.com/harmony)
[https://rpc.blast.io](https://rpc.blast.io)
[https://rpc.dogechain.dog](https://rpc.dogechain.dog)
[https://rpc.fuse.io](https://rpc.fuse.io)
[https://rpc.gnosischain.com](https://rpc.gnosischain.com)
[https://rpc.linea.build](https://rpc.linea.build)
[https://rpc.mantle.xyz](https://rpc.mantle.xyz)
[https://rpc.pulsechain.com](https://rpc.pulsechain.com)
[https://rpc.scroll.io](https://rpc.scroll.io)
[https://rpc.walletconnect.com/v1/?chainId=eip155](https://rpc.walletconnect.com/v1/?chainId=eip155)
[https://rpc.walletconnect.org](https://rpc.walletconnect.org)
[https://safe-client.safe.global](https://safe-client.safe.global)
[https://scan.pulsechain.com](https://scan.pulsechain.com)
[https://scrollscan.com](https://scrollscan.com)
[https://secure.walletconnect.org/sdk](https://secure.walletconnect.org/sdk)
[https://snowtrace.io](https://snowtrace.io)
[https://subnets.avax.network/beam](https://subnets.avax.network/beam)
[https://verify.walletconnect.com](https://verify.walletconnect.com)
[https://verify.walletconnect.org](https://verify.walletconnect.org)
[https://wagmi.sh/core](https://wagmi.sh/core)
[https://wagmi.sh/react](https://wagmi.sh/react)
[https://walletconnect.com/explorer?type=wallet](https://walletconnect.com/explorer?type=wallet)
[https://walletconnect.com/faq](https://walletconnect.com/faq)
[https://www.jsdelivr.com/using-sri-with-dynamic-files](https://www.jsdelivr.com/using-sri-with-dynamic-files)
[https://www.oklink.com/okc](https://www.oklink.com/okc)
[https://www.walletlink.org](https://www.walletlink.org)
[https://zkevm-rpc.com](https://zkevm-rpc.com)
[https://zkevm.polygonscan.com](https://zkevm.polygonscan.com) |
+| LOW | [net/url/parse](https://github.com/chainguard-dev/malcontent/blob/main/rules/net/url/parse.yara#url_handle) | Handles URL strings | [new URL](https://github.com/search?q=new+URL&type=code) |
+
diff --git a/tests/linux/2023.FreeDownloadManager/freedownloadmanager.sdiff b/tests/linux/2023.FreeDownloadManager/freedownloadmanager.sdiff
index cf673d234..e0327f1fd 100644
--- a/tests/linux/2023.FreeDownloadManager/freedownloadmanager.sdiff
+++ b/tests/linux/2023.FreeDownloadManager/freedownloadmanager.sdiff
@@ -1,12 +1,4 @@
---- missing: freedownloadmanager_clear_postinst
--data/embedded/pgp_key
--exec/install_additional/add_apt_key
--exec/shell/ignore_output
--fs/path/etc
--fs/path/usr_bin
--net/download
--net/url/embedded
-++++ added: freedownloadmanager_infected_postinst
+*** changed: linux/2023.FreeDownloadManager/freedownloadmanager_infected_postinst
+3P/threat_hunting/touch
+anti-static/base64/exec
+anti-static/base64/http_agent
@@ -14,24 +6,24 @@
+data/embedded/base64_elf
+data/embedded/base64_terms
+data/embedded/base64_url
-+data/embedded/pgp_key
+data/embedded/pgp_key
+data/encoding/base64
+evasion/file/location/var_tmp
-+exec/install_additional/add_apt_key
+exec/install_additional/add_apt_key
+exec/shell/exec
-+exec/shell/ignore_output
+exec/shell/ignore_output
+fs/directory/create
+fs/file/delete_forcibly
+fs/file/make_executable
+fs/file/times_set
-+fs/path/etc
+fs/path/etc
+fs/path/tmp
-+fs/path/usr_bin
+fs/path/usr_bin
+fs/path/var
+fs/permission/modify
+impact/remote_access/botnet
-+net/download
-+net/url/embedded
+net/download
+net/url/embedded
+persist/cron/etc_d
+persist/cron/tab
+sus/geopolitics
diff --git a/tests/linux/2024.sbcl.market/sbcl.sdiff b/tests/linux/2024.sbcl.market/sbcl.sdiff
index 2ef508139..6a7704720 100644
--- a/tests/linux/2024.sbcl.market/sbcl.sdiff
+++ b/tests/linux/2024.sbcl.market/sbcl.sdiff
@@ -1,44 +1,24 @@
---- missing: sbcl.clean
--crypto/rc4
--data/compression/zstd
--discover/user/HOME
--discover/user/USER
--evasion/file/location/var_tmp
--exec/dylib/address_check
--exec/dylib/symbol_address
--exec/program
--exec/program/background
--exec/shell/echo
--fs/file/delete
--fs/file/truncate
--fs/link_read
--fs/path/dev
--fs/path/tmp
--fs/path/var
--fs/permission/modify
--fs/proc/self_exe
--fs/symlink_resolve
--net/url/embedded
-++++ added: sbcl.dirty
+*** changed: linux/2024.sbcl.market/sbcl.dirty
+anti-static/elf/entropy
-+data/compression/zstd
+-crypto/rc4
+data/compression/zstd
+data/embedded/zstd
-+discover/user/HOME
-+discover/user/USER
-+evasion/file/location/var_tmp
-+exec/dylib/address_check
-+exec/dylib/symbol_address
-+exec/program
-+exec/program/background
-+exec/shell/echo
-+fs/file/delete
-+fs/file/truncate
-+fs/link_read
-+fs/path/dev
-+fs/path/tmp
-+fs/path/var
-+fs/permission/modify
-+fs/proc/self_exe
-+fs/symlink_resolve
+discover/user/HOME
+discover/user/USER
+evasion/file/location/var_tmp
+exec/dylib/address_check
+exec/dylib/symbol_address
+exec/program
+exec/program/background
+exec/shell/echo
+fs/file/delete
+fs/file/truncate
+fs/link_read
+fs/path/dev
+fs/path/tmp
+fs/path/var
+fs/permission/modify
+fs/proc/self_exe
+fs/symlink_resolve
+net/dns/txt
-+net/url/embedded
+net/url/embedded
diff --git a/tests/linux/clean/aws-c-io/aws-c-io.sdiff b/tests/linux/clean/aws-c-io/aws-c-io.sdiff
index 1a0be2922..b183c2eda 100644
--- a/tests/linux/clean/aws-c-io/aws-c-io.sdiff
+++ b/tests/linux/clean/aws-c-io/aws-c-io.sdiff
@@ -1 +1,3 @@
->>> moved: linux/clean/aws-c-io/aws-c-io-0.14.10-r0.spdx.json -> linux/clean/aws-c-io/aws-c-io-0.14.11-r0.spdx.json (score: 0.979310)
+>>> moved: linux/clean/aws-c-io/aws-c-io-0.14.10-r0.spdx.json -> linux/clean/aws-c-io/aws-c-io-0.14.11-r0.spdx.json (score: 0.988000)
+net/download
+net/url/embedded
diff --git a/tests/macOS/2023.3CX/libffmpeg.change_decrease.mdiff b/tests/macOS/2023.3CX/libffmpeg.change_decrease.mdiff
index bef727d5f..22f124ca8 100644
Binary files a/tests/macOS/2023.3CX/libffmpeg.change_decrease.mdiff and b/tests/macOS/2023.3CX/libffmpeg.change_decrease.mdiff differ
diff --git a/tests/macOS/2023.3CX/libffmpeg.change_increase.mdiff b/tests/macOS/2023.3CX/libffmpeg.change_increase.mdiff
index 8d90c3b3f..7329a7bd6 100644
Binary files a/tests/macOS/2023.3CX/libffmpeg.change_increase.mdiff and b/tests/macOS/2023.3CX/libffmpeg.change_increase.mdiff differ
diff --git a/tests/macOS/2023.3CX/libffmpeg.change_unrelated.mdiff b/tests/macOS/2023.3CX/libffmpeg.change_unrelated.mdiff
index fa19b7c49..24baa8424 100644
--- a/tests/macOS/2023.3CX/libffmpeg.change_unrelated.mdiff
+++ b/tests/macOS/2023.3CX/libffmpeg.change_unrelated.mdiff
@@ -1,24 +1,30 @@
-## Deleted: libffmpeg.dylib [🟡 MEDIUM]
+## Changed: macOS/clean/ls [🟡 MEDIUM → 🔵 LOW]
-| RISK | KEY | DESCRIPTION | EVIDENCE |
-|---------|------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| -MEDIUM | [data/base64/decode](https://github.com/chainguard-dev/malcontent/blob/main/rules/data/base64/base64-decode.yara#py_base64_decode) | decode base64 strings | [base64_decode](https://github.com/search?q=base64_decode&type=code) |
-| -MEDIUM | [fs/path/tmp](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/path/tmp.yara#tmp_path) | path reference within /tmp | [/tmp/%sXXXXXX](https://github.com/search?q=%2Ftmp%2F%25sXXXXXX&type=code) |
-| -MEDIUM | [impact/remote_access/agent](https://github.com/chainguard-dev/malcontent/blob/main/rules/impact/remote_access/agent.yara#agent) | references an 'agent' | [user_agent](https://github.com/search?q=user_agent&type=code) |
-| -MEDIUM | [net/http/post](https://github.com/chainguard-dev/malcontent/blob/main/rules/net/http/post.yara#http_post) | submits content to websites | [HTTP](https://github.com/search?q=HTTP&type=code)
[POST](https://github.com/search?q=POST&type=code)
[http](https://github.com/search?q=http&type=code) |
-| -LOW | [crypto/aes](https://github.com/chainguard-dev/malcontent/blob/main/rules/crypto/aes.yara#crypto_aes) | Supports AES (Advanced Encryption Standard) | [AES](https://github.com/search?q=AES&type=code) |
-| -LOW | [crypto/rc4](https://github.com/chainguard-dev/malcontent/blob/main/rules/crypto/rc4.yara#rc4_ksa) | RC4 key scheduling algorithm, by Thomas Barabosch | $cmp_e_x_256
$cmp_r_x_256 |
-| -LOW | [data/encoding/base64](https://github.com/chainguard-dev/malcontent/blob/main/rules/data/encoding/base64.yara#b64) | Supports base64 encoded strings | [base64](https://github.com/search?q=base64&type=code) |
-| -LOW | [exec/shell/TERM](https://github.com/chainguard-dev/malcontent/blob/main/rules/exec/shell/TERM.yara#TERM) | [Look up or override terminal settings](https://www.gnu.org/software/gettext/manual/html_node/The-TERM-variable.html) | [TERM](https://github.com/search?q=TERM&type=code) |
-| -LOW | [fs/directory/create](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/directory/directory-create.yara#mkdir) | [creates directories](https://man7.org/linux/man-pages/man2/mkdir.2.html) | [mkdir](https://github.com/search?q=mkdir&type=code) |
-| -LOW | [net/url/parse](https://github.com/chainguard-dev/malcontent/blob/main/rules/net/url/parse.yara#url_handle) | Handles URL strings | [URLContext](https://github.com/search?q=URLContext&type=code) |
-| -LOW | [process/multithreaded](https://github.com/chainguard-dev/malcontent/blob/main/rules/process/multithreaded.yara#pthread_create) | [creates pthreads](https://man7.org/linux/man-pages/man3/pthread_create.3.html) | [pthread_create](https://github.com/search?q=pthread_create&type=code) |
+### 2 new behaviors
-## Added: ls [🔵 LOW]
+| RISK | KEY | DESCRIPTION | EVIDENCE |
+|------|------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| +LOW | **[fs/directory/traverse](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/directory/directory-traverse.yara#fts)** | traverse filesystem hierarchy | [_fts_children](https://github.com/search?q=_fts_children&type=code)
[_fts_close](https://github.com/search?q=_fts_close&type=code)
[_fts_open](https://github.com/search?q=_fts_open&type=code)
[_fts_read](https://github.com/search?q=_fts_read&type=code)
[_fts_set](https://github.com/search?q=_fts_set&type=code) |
+| +LOW | **[fs/link_read](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/link-read.yara#readlink)** | [read value of a symbolic link](https://man7.org/linux/man-pages/man2/readlink.2.html) | [readlink](https://github.com/search?q=readlink&type=code) |
-| RISK | KEY | DESCRIPTION | EVIDENCE |
-|------|------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| +LOW | **[exec/shell/TERM](https://github.com/chainguard-dev/malcontent/blob/main/rules/exec/shell/TERM.yara#TERM)** | [Look up or override terminal settings](https://www.gnu.org/software/gettext/manual/html_node/The-TERM-variable.html) | [TERM](https://github.com/search?q=TERM&type=code) |
-| +LOW | **[fs/directory/traverse](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/directory/directory-traverse.yara#fts)** | traverse filesystem hierarchy | [_fts_children](https://github.com/search?q=_fts_children&type=code)
[_fts_close](https://github.com/search?q=_fts_close&type=code)
[_fts_open](https://github.com/search?q=_fts_open&type=code)
[_fts_read](https://github.com/search?q=_fts_read&type=code)
[_fts_set](https://github.com/search?q=_fts_set&type=code) |
-| +LOW | **[fs/link_read](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/link-read.yara#readlink)** | [read value of a symbolic link](https://man7.org/linux/man-pages/man2/readlink.2.html) | [readlink](https://github.com/search?q=readlink&type=code) |
+### 10 removed behaviors
+
+| RISK | KEY | DESCRIPTION | EVIDENCE |
+|---------|------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| -MEDIUM | [data/base64/decode](https://github.com/chainguard-dev/malcontent/blob/main/rules/data/base64/base64-decode.yara#py_base64_decode) | decode base64 strings | [base64_decode](https://github.com/search?q=base64_decode&type=code) |
+| -MEDIUM | [fs/path/tmp](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/path/tmp.yara#tmp_path) | path reference within /tmp | [/tmp/%sXXXXXX](https://github.com/search?q=%2Ftmp%2F%25sXXXXXX&type=code) |
+| -MEDIUM | [impact/remote_access/agent](https://github.com/chainguard-dev/malcontent/blob/main/rules/impact/remote_access/agent.yara#agent) | references an 'agent' | [user_agent](https://github.com/search?q=user_agent&type=code) |
+| -MEDIUM | [net/http/post](https://github.com/chainguard-dev/malcontent/blob/main/rules/net/http/post.yara#http_post) | submits content to websites | [HTTP](https://github.com/search?q=HTTP&type=code)
[POST](https://github.com/search?q=POST&type=code)
[http](https://github.com/search?q=http&type=code) |
+| -LOW | [crypto/aes](https://github.com/chainguard-dev/malcontent/blob/main/rules/crypto/aes.yara#crypto_aes) | Supports AES (Advanced Encryption Standard) | [AES](https://github.com/search?q=AES&type=code) |
+| -LOW | [crypto/rc4](https://github.com/chainguard-dev/malcontent/blob/main/rules/crypto/rc4.yara#rc4_ksa) | RC4 key scheduling algorithm, by Thomas Barabosch | $cmp_e_x_256
$cmp_r_x_256 |
+| -LOW | [data/encoding/base64](https://github.com/chainguard-dev/malcontent/blob/main/rules/data/encoding/base64.yara#b64) | Supports base64 encoded strings | [base64](https://github.com/search?q=base64&type=code) |
+| -LOW | [fs/directory/create](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/directory/directory-create.yara#mkdir) | [creates directories](https://man7.org/linux/man-pages/man2/mkdir.2.html) | [mkdir](https://github.com/search?q=mkdir&type=code) |
+| -LOW | [net/url/parse](https://github.com/chainguard-dev/malcontent/blob/main/rules/net/url/parse.yara#url_handle) | Handles URL strings | [URLContext](https://github.com/search?q=URLContext&type=code) |
+| -LOW | [process/multithreaded](https://github.com/chainguard-dev/malcontent/blob/main/rules/process/multithreaded.yara#pthread_create) | [creates pthreads](https://man7.org/linux/man-pages/man3/pthread_create.3.html) | [pthread_create](https://github.com/search?q=pthread_create&type=code) |
+
+### 1 consistent behavior
+
+| RISK | KEY | DESCRIPTION | EVIDENCE |
+|------|-----------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------|
+| LOW | [exec/shell/TERM](https://github.com/chainguard-dev/malcontent/blob/main/rules/exec/shell/TERM.yara#TERM) | [Look up or override terminal settings](https://www.gnu.org/software/gettext/manual/html_node/The-TERM-variable.html) | [TERM](https://github.com/search?q=TERM&type=code) |
diff --git a/tests/macOS/2023.3CX/libffmpeg.decrease.mdiff b/tests/macOS/2023.3CX/libffmpeg.decrease.mdiff
index bef727d5f..e69de29bb 100644
Binary files a/tests/macOS/2023.3CX/libffmpeg.decrease.mdiff and b/tests/macOS/2023.3CX/libffmpeg.decrease.mdiff differ
diff --git a/tests/macOS/2023.3CX/libffmpeg.dirty.mdiff b/tests/macOS/2023.3CX/libffmpeg.dirty.mdiff
index 8d90c3b3f..7329a7bd6 100644
Binary files a/tests/macOS/2023.3CX/libffmpeg.dirty.mdiff and b/tests/macOS/2023.3CX/libffmpeg.dirty.mdiff differ
diff --git a/tests/macOS/2023.3CX/libffmpeg.increase.mdiff b/tests/macOS/2023.3CX/libffmpeg.increase.mdiff
index 8d90c3b3f..7329a7bd6 100644
Binary files a/tests/macOS/2023.3CX/libffmpeg.increase.mdiff and b/tests/macOS/2023.3CX/libffmpeg.increase.mdiff differ
diff --git a/tests/macOS/2023.3CX/libffmpeg.increase_unrelated.mdiff b/tests/macOS/2023.3CX/libffmpeg.increase_unrelated.mdiff
index c30dcb38e..2fac11c65 100644
--- a/tests/macOS/2023.3CX/libffmpeg.increase_unrelated.mdiff
+++ b/tests/macOS/2023.3CX/libffmpeg.increase_unrelated.mdiff
@@ -1,24 +1,30 @@
-## Deleted: ls [🔵 LOW]
+## Changed: macOS/2023.3CX/libffmpeg.dylib [🔵 LOW → 🟡 MEDIUM]
-| RISK | KEY | DESCRIPTION | EVIDENCE |
-|------|--------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| -LOW | [exec/shell/TERM](https://github.com/chainguard-dev/malcontent/blob/main/rules/exec/shell/TERM.yara#TERM) | [Look up or override terminal settings](https://www.gnu.org/software/gettext/manual/html_node/The-TERM-variable.html) | [TERM](https://github.com/search?q=TERM&type=code) |
-| -LOW | [fs/directory/traverse](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/directory/directory-traverse.yara#fts) | traverse filesystem hierarchy | [_fts_children](https://github.com/search?q=_fts_children&type=code)
[_fts_close](https://github.com/search?q=_fts_close&type=code)
[_fts_open](https://github.com/search?q=_fts_open&type=code)
[_fts_read](https://github.com/search?q=_fts_read&type=code)
[_fts_set](https://github.com/search?q=_fts_set&type=code) |
-| -LOW | [fs/link_read](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/link-read.yara#readlink) | [read value of a symbolic link](https://man7.org/linux/man-pages/man2/readlink.2.html) | [readlink](https://github.com/search?q=readlink&type=code) |
+### 10 new behaviors
-## Added: libffmpeg.dylib [🟡 MEDIUM]
+| RISK | KEY | DESCRIPTION | EVIDENCE |
+|---------|----------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| +MEDIUM | **[data/base64/decode](https://github.com/chainguard-dev/malcontent/blob/main/rules/data/base64/base64-decode.yara#py_base64_decode)** | decode base64 strings | [base64_decode](https://github.com/search?q=base64_decode&type=code) |
+| +MEDIUM | **[fs/path/tmp](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/path/tmp.yara#tmp_path)** | path reference within /tmp | [/tmp/%sXXXXXX](https://github.com/search?q=%2Ftmp%2F%25sXXXXXX&type=code) |
+| +MEDIUM | **[impact/remote_access/agent](https://github.com/chainguard-dev/malcontent/blob/main/rules/impact/remote_access/agent.yara#agent)** | references an 'agent' | [user_agent](https://github.com/search?q=user_agent&type=code) |
+| +MEDIUM | **[net/http/post](https://github.com/chainguard-dev/malcontent/blob/main/rules/net/http/post.yara#http_post)** | submits content to websites | [HTTP](https://github.com/search?q=HTTP&type=code)
[POST](https://github.com/search?q=POST&type=code)
[http](https://github.com/search?q=http&type=code) |
+| +LOW | **[crypto/aes](https://github.com/chainguard-dev/malcontent/blob/main/rules/crypto/aes.yara#crypto_aes)** | Supports AES (Advanced Encryption Standard) | [AES](https://github.com/search?q=AES&type=code) |
+| +LOW | **[crypto/rc4](https://github.com/chainguard-dev/malcontent/blob/main/rules/crypto/rc4.yara#rc4_ksa)** | RC4 key scheduling algorithm, by Thomas Barabosch | $cmp_e_x_256
$cmp_r_x_256 |
+| +LOW | **[data/encoding/base64](https://github.com/chainguard-dev/malcontent/blob/main/rules/data/encoding/base64.yara#b64)** | Supports base64 encoded strings | [base64](https://github.com/search?q=base64&type=code) |
+| +LOW | **[fs/directory/create](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/directory/directory-create.yara#mkdir)** | [creates directories](https://man7.org/linux/man-pages/man2/mkdir.2.html) | [mkdir](https://github.com/search?q=mkdir&type=code) |
+| +LOW | **[net/url/parse](https://github.com/chainguard-dev/malcontent/blob/main/rules/net/url/parse.yara#url_handle)** | Handles URL strings | [URLContext](https://github.com/search?q=URLContext&type=code) |
+| +LOW | **[process/multithreaded](https://github.com/chainguard-dev/malcontent/blob/main/rules/process/multithreaded.yara#pthread_create)** | [creates pthreads](https://man7.org/linux/man-pages/man3/pthread_create.3.html) | [pthread_create](https://github.com/search?q=pthread_create&type=code) |
-| RISK | KEY | DESCRIPTION | EVIDENCE |
-|---------|----------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| +MEDIUM | **[data/base64/decode](https://github.com/chainguard-dev/malcontent/blob/main/rules/data/base64/base64-decode.yara#py_base64_decode)** | decode base64 strings | [base64_decode](https://github.com/search?q=base64_decode&type=code) |
-| +MEDIUM | **[fs/path/tmp](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/path/tmp.yara#tmp_path)** | path reference within /tmp | [/tmp/%sXXXXXX](https://github.com/search?q=%2Ftmp%2F%25sXXXXXX&type=code) |
-| +MEDIUM | **[impact/remote_access/agent](https://github.com/chainguard-dev/malcontent/blob/main/rules/impact/remote_access/agent.yara#agent)** | references an 'agent' | [user_agent](https://github.com/search?q=user_agent&type=code) |
-| +MEDIUM | **[net/http/post](https://github.com/chainguard-dev/malcontent/blob/main/rules/net/http/post.yara#http_post)** | submits content to websites | [HTTP](https://github.com/search?q=HTTP&type=code)
[POST](https://github.com/search?q=POST&type=code)
[http](https://github.com/search?q=http&type=code) |
-| +LOW | **[crypto/aes](https://github.com/chainguard-dev/malcontent/blob/main/rules/crypto/aes.yara#crypto_aes)** | Supports AES (Advanced Encryption Standard) | [AES](https://github.com/search?q=AES&type=code) |
-| +LOW | **[crypto/rc4](https://github.com/chainguard-dev/malcontent/blob/main/rules/crypto/rc4.yara#rc4_ksa)** | RC4 key scheduling algorithm, by Thomas Barabosch | $cmp_e_x_256
$cmp_r_x_256 |
-| +LOW | **[data/encoding/base64](https://github.com/chainguard-dev/malcontent/blob/main/rules/data/encoding/base64.yara#b64)** | Supports base64 encoded strings | [base64](https://github.com/search?q=base64&type=code) |
-| +LOW | **[exec/shell/TERM](https://github.com/chainguard-dev/malcontent/blob/main/rules/exec/shell/TERM.yara#TERM)** | [Look up or override terminal settings](https://www.gnu.org/software/gettext/manual/html_node/The-TERM-variable.html) | [TERM](https://github.com/search?q=TERM&type=code) |
-| +LOW | **[fs/directory/create](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/directory/directory-create.yara#mkdir)** | [creates directories](https://man7.org/linux/man-pages/man2/mkdir.2.html) | [mkdir](https://github.com/search?q=mkdir&type=code) |
-| +LOW | **[net/url/parse](https://github.com/chainguard-dev/malcontent/blob/main/rules/net/url/parse.yara#url_handle)** | Handles URL strings | [URLContext](https://github.com/search?q=URLContext&type=code) |
-| +LOW | **[process/multithreaded](https://github.com/chainguard-dev/malcontent/blob/main/rules/process/multithreaded.yara#pthread_create)** | [creates pthreads](https://man7.org/linux/man-pages/man3/pthread_create.3.html) | [pthread_create](https://github.com/search?q=pthread_create&type=code) |
+### 2 removed behaviors
+
+| RISK | KEY | DESCRIPTION | EVIDENCE |
+|------|--------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| -LOW | [fs/directory/traverse](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/directory/directory-traverse.yara#fts) | traverse filesystem hierarchy | [_fts_children](https://github.com/search?q=_fts_children&type=code)
[_fts_close](https://github.com/search?q=_fts_close&type=code)
[_fts_open](https://github.com/search?q=_fts_open&type=code)
[_fts_read](https://github.com/search?q=_fts_read&type=code)
[_fts_set](https://github.com/search?q=_fts_set&type=code) |
+| -LOW | [fs/link_read](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/link-read.yara#readlink) | [read value of a symbolic link](https://man7.org/linux/man-pages/man2/readlink.2.html) | [readlink](https://github.com/search?q=readlink&type=code) |
+
+### 1 consistent behavior
+
+| RISK | KEY | DESCRIPTION | EVIDENCE |
+|------|-----------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------|
+| LOW | [exec/shell/TERM](https://github.com/chainguard-dev/malcontent/blob/main/rules/exec/shell/TERM.yara#TERM) | [Look up or override terminal settings](https://www.gnu.org/software/gettext/manual/html_node/The-TERM-variable.html) | [TERM](https://github.com/search?q=TERM&type=code) |
diff --git a/tests/macOS/clean/ls.mdiff b/tests/macOS/clean/ls.mdiff
index 523345be4..fd6b24bc3 100644
--- a/tests/macOS/clean/ls.mdiff
+++ b/tests/macOS/clean/ls.mdiff
@@ -1,19 +1,24 @@
-## Deleted: ls.x86_64 [🟡 MEDIUM]
+## Changed: macOS/clean/ls [🟡 MEDIUM → 🔵 LOW]
-| RISK | KEY | DESCRIPTION | EVIDENCE |
-|---------|------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| -MEDIUM | [process/name_set](https://github.com/chainguard-dev/malcontent/blob/main/rules/process/name-set.yara#__progname) | [get or set the current process name](https://stackoverflow.com/questions/273691/using-progname-instead-of-argv0) | [__progname](https://github.com/search?q=__progname&type=code) |
-| -LOW | [data/compression/lzma](https://github.com/chainguard-dev/malcontent/blob/main/rules/data/compression/lzma.yara#gzip) | [works with lzma files](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Markov_chain_algorithm) | [lzma](https://github.com/search?q=lzma&type=code) |
-| -LOW | [discover/system/hostname](https://github.com/chainguard-dev/malcontent/blob/main/rules/discover/system/hostname.yara#gethostname) | [get computer host name](https://man7.org/linux/man-pages/man2/sethostname.2.html) | [gethostname](https://github.com/search?q=gethostname&type=code) |
-| -LOW | [exec/shell/TERM](https://github.com/chainguard-dev/malcontent/blob/main/rules/exec/shell/TERM.yara#TERM) | [Look up or override terminal settings](https://www.gnu.org/software/gettext/manual/html_node/The-TERM-variable.html) | [TERM](https://github.com/search?q=TERM&type=code) |
-| -LOW | [fs/link_read](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/link-read.yara#readlink) | [read value of a symbolic link](https://man7.org/linux/man-pages/man2/readlink.2.html) | [readlink](https://github.com/search?q=readlink&type=code) |
-| -LOW | [net/url/embedded](https://github.com/chainguard-dev/malcontent/blob/main/rules/net/url/embedded.yara#https_url) | contains embedded HTTPS URLs | [https://gnu.org/licenses/gpl.html](https://gnu.org/licenses/gpl.html)
[https://translationproject.org/team/](https://translationproject.org/team/)
[https://wiki.xiph.org/MIME_Types_and_File_Extensions](https://wiki.xiph.org/MIME_Types_and_File_Extensions)
[https://www.gnu.org/software/coreutils/](https://www.gnu.org/software/coreutils/) |
+### 1 new behavior
-## Added: ls [🔵 LOW]
+| RISK | KEY | DESCRIPTION | EVIDENCE |
+|------|------------------------------------------------------------------------------------------------------------------------------------|-------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| +LOW | **[fs/directory/traverse](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/directory/directory-traverse.yara#fts)** | traverse filesystem hierarchy | [_fts_children](https://github.com/search?q=_fts_children&type=code)
[_fts_close](https://github.com/search?q=_fts_close&type=code)
[_fts_open](https://github.com/search?q=_fts_open&type=code)
[_fts_read](https://github.com/search?q=_fts_read&type=code)
[_fts_set](https://github.com/search?q=_fts_set&type=code) |
-| RISK | KEY | DESCRIPTION | EVIDENCE |
-|------|------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| +LOW | **[exec/shell/TERM](https://github.com/chainguard-dev/malcontent/blob/main/rules/exec/shell/TERM.yara#TERM)** | [Look up or override terminal settings](https://www.gnu.org/software/gettext/manual/html_node/The-TERM-variable.html) | [TERM](https://github.com/search?q=TERM&type=code) |
-| +LOW | **[fs/directory/traverse](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/directory/directory-traverse.yara#fts)** | traverse filesystem hierarchy | [_fts_children](https://github.com/search?q=_fts_children&type=code)
[_fts_close](https://github.com/search?q=_fts_close&type=code)
[_fts_open](https://github.com/search?q=_fts_open&type=code)
[_fts_read](https://github.com/search?q=_fts_read&type=code)
[_fts_set](https://github.com/search?q=_fts_set&type=code) |
-| +LOW | **[fs/link_read](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/link-read.yara#readlink)** | [read value of a symbolic link](https://man7.org/linux/man-pages/man2/readlink.2.html) | [readlink](https://github.com/search?q=readlink&type=code) |
+### 4 removed behaviors
+
+| RISK | KEY | DESCRIPTION | EVIDENCE |
+|---------|------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| -MEDIUM | [process/name_set](https://github.com/chainguard-dev/malcontent/blob/main/rules/process/name-set.yara#__progname) | [get or set the current process name](https://stackoverflow.com/questions/273691/using-progname-instead-of-argv0) | [__progname](https://github.com/search?q=__progname&type=code) |
+| -LOW | [data/compression/lzma](https://github.com/chainguard-dev/malcontent/blob/main/rules/data/compression/lzma.yara#gzip) | [works with lzma files](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Markov_chain_algorithm) | [lzma](https://github.com/search?q=lzma&type=code) |
+| -LOW | [discover/system/hostname](https://github.com/chainguard-dev/malcontent/blob/main/rules/discover/system/hostname.yara#gethostname) | [get computer host name](https://man7.org/linux/man-pages/man2/sethostname.2.html) | [gethostname](https://github.com/search?q=gethostname&type=code) |
+| -LOW | [net/url/embedded](https://github.com/chainguard-dev/malcontent/blob/main/rules/net/url/embedded.yara#https_url) | contains embedded HTTPS URLs | [https://gnu.org/licenses/gpl.html](https://gnu.org/licenses/gpl.html)
[https://translationproject.org/team/](https://translationproject.org/team/)
[https://wiki.xiph.org/MIME_Types_and_File_Extensions](https://wiki.xiph.org/MIME_Types_and_File_Extensions)
[https://www.gnu.org/software/coreutils/](https://www.gnu.org/software/coreutils/) |
+
+### 2 consistent behaviors
+
+| RISK | KEY | DESCRIPTION | EVIDENCE |
+|------|-----------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
+| LOW | [exec/shell/TERM](https://github.com/chainguard-dev/malcontent/blob/main/rules/exec/shell/TERM.yara#TERM) | [Look up or override terminal settings](https://www.gnu.org/software/gettext/manual/html_node/The-TERM-variable.html) | [TERM](https://github.com/search?q=TERM&type=code) |
+| LOW | [fs/link_read](https://github.com/chainguard-dev/malcontent/blob/main/rules/fs/link-read.yara#readlink) | [read value of a symbolic link](https://man7.org/linux/man-pages/man2/readlink.2.html) | [readlink](https://github.com/search?q=readlink&type=code) |
diff --git a/tests/macOS/clean/ls.sdiff.level_2 b/tests/macOS/clean/ls.sdiff.level_2
index fa0ab56fa..db7b91c5d 100644
--- a/tests/macOS/clean/ls.sdiff.level_2
+++ b/tests/macOS/clean/ls.sdiff.level_2
@@ -1,3 +1,2 @@
---- missing: ls.x86_64
+*** changed: macOS/clean/ls
-process/name_set
-++++ added: ls
diff --git a/tests/macOS/clean/ls.sdiff.trigger_2 b/tests/macOS/clean/ls.sdiff.trigger_2
index 902593b69..67fb0230f 100644
--- a/tests/macOS/clean/ls.sdiff.trigger_2
+++ b/tests/macOS/clean/ls.sdiff.trigger_2
@@ -1,11 +1,8 @@
---- missing: ls.x86_64
+*** changed: macOS/clean/ls
-data/compression/lzma
-discover/system/hostname
--exec/shell/TERM
--fs/link_read
+exec/shell/TERM
++fs/directory/traverse
+fs/link_read
-net/url/embedded
-process/name_set
-++++ added: ls
-+exec/shell/TERM
-+fs/directory/traverse
-+fs/link_read
diff --git a/tests/macOS/clean/ls.sdiff.trigger_3 b/tests/macOS/clean/ls.sdiff.trigger_3
index 902593b69..e69de29bb 100644
--- a/tests/macOS/clean/ls.sdiff.trigger_3
+++ b/tests/macOS/clean/ls.sdiff.trigger_3
@@ -1,11 +0,0 @@
---- missing: ls.x86_64
--data/compression/lzma
--discover/system/hostname
--exec/shell/TERM
--fs/link_read
--net/url/embedded
--process/name_set
-++++ added: ls
-+exec/shell/TERM
-+fs/directory/traverse
-+fs/link_read
diff --git a/tests/samples_test.go b/tests/samples_test.go
index 6d4a4c295..ac61dca16 100644
--- a/tests/samples_test.go
+++ b/tests/samples_test.go
@@ -306,7 +306,7 @@ func TestDiffFileChange(t *testing.T) {
MinRisk: tc.minResultScore,
Renderer: simple,
RuleFS: []fs.FS{rules.FS, thirdparty.FS},
- ScanPaths: []string{strings.TrimPrefix(tc.src, "../out/samples/"), strings.TrimPrefix(tc.dest, "../out/samples/")},
+ ScanPaths: []string{strings.TrimPrefix(tc.src, "../out/chainguard-dev/malcontent-samples/"), strings.TrimPrefix(tc.dest, "../out/chainguard-dev/malcontent-samples/")},
}
logger := clog.New(slog.Default().Handler()).With("src", tc.src)
@@ -373,7 +373,7 @@ func TestDiffFileIncrease(t *testing.T) {
MinRisk: tc.minResultScore,
Renderer: simple,
RuleFS: []fs.FS{rules.FS, thirdparty.FS},
- ScanPaths: []string{strings.TrimPrefix(tc.src, "../out/samples/"), strings.TrimPrefix(tc.dest, "../out/samples/")},
+ ScanPaths: []string{strings.TrimPrefix(tc.src, "../out/chainguard-dev/malcontent-samples/"), strings.TrimPrefix(tc.dest, "../out/chainguard-dev/malcontent-samples/")},
}
logger := clog.New(slog.Default().Handler()).With("src", tc.src)