-
Notifications
You must be signed in to change notification settings - Fork 1
Xeol scanner #244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Xeol scanner #244
Changes from all commits
ff76395
d7cf709
bdc5231
5c212d4
45cd9a8
67813f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,13 +24,46 @@ | |
| RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod \ | ||
| go run ./cmd/docgen | ||
|
|
||
| FROM busybox | ||
| # Download xeol EOL DB at build time (offline at runtime). Listing serves .tar.xz URLs. | ||
| RUN apk add --no-cache curl jq xz && \ | ||
| XEOL_DB_URL=$(curl -sSfL https://data.xeol.io/xeol/databases/listing.json | jq -r '.available["1"] | .[-1] | .url') && \ | ||
| curl -sSfL "$XEOL_DB_URL" -o /tmp/xeol-db.tar.xz && \ | ||
| mkdir -p /src/xeol-db/1 && tar -xJf /tmp/xeol-db.tar.xz -C /src/xeol-db/1 && \ | ||
| rm /tmp/xeol-db.tar.xz | ||
|
|
||
| RUN adduser -u 2004 -D docker | ||
| # Download Trivy vuln DB at build time so slim image can run EOL scan (runner still needs DB to init). | ||
| WORKDIR /src/trivy-cache/db | ||
| RUN ORAS_VER=1.1.0 && \ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 Codacy found a high ErrorProne issue: Note that A && B || C is not if-then-else. C may run when A is true. The issue identified by Hadolint is related to the use of the To fix this issue, we can use a more explicit structure that clearly defines the intended logic. In this case, we can ensure that the commands are grouped correctly to avoid unexpected executions. Here’s the suggested code change: RUN ORAS_VER=1.1.0 && \
curl -sSfL "https://github.com/oras-project/oras/releases/download/v${ORAS_VER}/oras_${ORAS_VER}_linux_amd64.tar.gz" -o /tmp/oras.tar.gz && \
tar -xzf /tmp/oras.tar.gz -C /usr/local/bin oras && rm /tmp/oras.tar.gz && \
mkdir -p /src/trivy-cache/db && cd /src/trivy-cache/db && \
oras pull ghcr.io/aquasecurity/trivy-db:2 && \
(test -f db.tar.gz && tar -xzf db.tar.gz && rm -f db.tar.gz && mv 2/* . && rmdir 2) || trueIn this change, I've combined the This comment was generated by an experimental AI tool. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Codacy found an issue: Use WORKDIR to switch to a directory There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Codacy found an issue: Note that A && B || C is not if-then-else. C may run when A is true. |
||
| curl -sSfL "https://github.com/oras-project/oras/releases/download/v${ORAS_VER}/oras_${ORAS_VER}_linux_amd64.tar.gz" -o /tmp/oras.tar.gz && \ | ||
| tar -xzf /tmp/oras.tar.gz -C /usr/local/bin oras && rm /tmp/oras.tar.gz && \ | ||
| mkdir -p /src/trivy-cache/db && \ | ||
| oras pull ghcr.io/aquasecurity/trivy-db:2 && \ | ||
| ( (test -f db.tar.gz && tar -xzf db.tar.gz && rm -f db.tar.gz) || true ) && \ | ||
| ( (test -d 2 && mv 2/* . 2>/dev/null && rmdir 2 2>/dev/null) || true ) | ||
|
|
||
| # Build eoltest for container verification (optional). | ||
| RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod \ | ||
| go build -o bin/eoltest ./cmd/eoltest | ||
|
|
||
| FROM busybox:1.36 AS full | ||
| RUN adduser -u 2004 -D docker | ||
| COPY --from=builder --chown=docker:docker /src/bin /dist/bin | ||
| COPY --from=builder --chown=docker:docker /src/docs /docs | ||
| COPY --from=builder --chown=docker:docker /src/docs /docs | ||
| COPY --chown=docker:docker cache/ /dist/cache/codacy-trivy | ||
| COPY --chown=docker:docker openssf-malicious-packages/openssf-malicious-packages-index.json.gz /dist/cache/codacy-trivy/openssf-malicious-packages-index.json.gz | ||
| COPY --from=builder --chown=docker:docker /src/xeol-db /dist/cache/xeol/db | ||
| ENV XEOL_DB_CACHE_DIR=/dist/cache/xeol/db | ||
| CMD [ "/dist/bin/codacy-trivy" ] | ||
|
|
||
| # Slim: no host cache/openssf; includes Trivy DB + xeol DB for EOL scan. Use: docker build --target slim -t codacy-trivy:eol . | ||
| FROM busybox:1.36 AS slim | ||
| RUN adduser -u 2004 -D docker | ||
| COPY --from=builder --chown=docker:docker /src/bin /dist/bin | ||
| COPY --from=builder --chown=docker:docker /src/docs /docs | ||
| RUN mkdir -p /dist/cache/codacy-trivy | ||
| COPY --from=builder --chown=docker:docker /src/trivy-cache/db /dist/cache/codacy-trivy/db | ||
| COPY --from=builder --chown=docker:docker /src/xeol-db /dist/cache/xeol/db | ||
| ENV XEOL_DB_CACHE_DIR=/dist/cache/xeol/db | ||
| CMD [ "/dist/bin/codacy-trivy" ] | ||
|
|
||
| FROM full | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # Build and test require Go 1.25+ and GOEXPERIMENT=jsonv2 (for Trivy). | ||
| # CGO_ENABLED=0 avoids linking system libs (e.g. faiss on macOS). | ||
| export GOEXPERIMENT := jsonv2 | ||
| export GOTOOLCHAIN := auto | ||
| export CGO_ENABLED := 0 | ||
|
|
||
| .PHONY: build test | ||
| build: | ||
| go build -o bin/codacy-trivy -ldflags="-s -w" ./cmd/tool | ||
|
|
||
| test: | ||
| go test ./... |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,142 @@ | ||||||
| // Program eoltest runs the codacy-trivy tool against a directory with EOL patterns | ||||||
| // for local testing. EOL scan uses the xeol Go library (no xeol binary needed). | ||||||
| // Requires (for full scan) a valid malicious-packages index; uses an empty index if the default path is missing. | ||||||
| // | ||||||
| // Usage: go run ./cmd/eoltest -dir ./test-eol-project | ||||||
| package main | ||||||
|
|
||||||
| import ( | ||||||
| "compress/gzip" | ||||||
| "context" | ||||||
| "flag" | ||||||
| "fmt" | ||||||
| "io" | ||||||
| "os" | ||||||
| "path/filepath" | ||||||
|
|
||||||
| codacy "github.com/codacy/codacy-engine-golang-seed/v8" | ||||||
| "github.com/codacy/codacy-trivy/internal/tool" | ||||||
| ) | ||||||
|
|
||||||
| func main() { | ||||||
| dir := flag.String("dir", "", "Source directory to scan (e.g. test-eol-project)") | ||||||
| flag.Parse() | ||||||
| if *dir == "" { | ||||||
| fmt.Fprintln(os.Stderr, "usage: eoltest -dir <path>") | ||||||
| os.Exit(1) | ||||||
| } | ||||||
| absDir, err := filepath.Abs(*dir) | ||||||
| if err != nil { | ||||||
| fmt.Fprintf(os.Stderr, "dir: %v\n", err) | ||||||
| os.Exit(1) | ||||||
| } | ||||||
| if _, err := os.Stat(absDir); err != nil { | ||||||
| fmt.Fprintf(os.Stderr, "dir %s: %v\n", absDir, err) | ||||||
| os.Exit(1) | ||||||
| } | ||||||
| indexPath, cleanup, err := resolveIndexPath() | ||||||
| if err != nil { | ||||||
| fmt.Fprintf(os.Stderr, "%v\n", err) | ||||||
| os.Exit(1) | ||||||
| } | ||||||
| defer cleanup() | ||||||
| results, err := runEOLScan(context.Background(), absDir, indexPath) | ||||||
| if err != nil { | ||||||
| fmt.Fprintf(os.Stderr, "%v\n", err) | ||||||
| os.Exit(1) | ||||||
| } | ||||||
| printEOLResults(results) | ||||||
| } | ||||||
|
|
||||||
| // resolveIndexPath returns the malicious-packages index path and a cleanup function. | ||||||
| // If the default path exists it is returned with a no-op cleanup; otherwise a temp empty index is created. | ||||||
| func resolveIndexPath() (path string, cleanup func(), err error) { | ||||||
| return resolveIndexPathWithDefault(tool.MaliciousPackagesIndexPath) | ||||||
| } | ||||||
|
|
||||||
| // resolveIndexPathWithDefault is the testable core; defaultPath is the path to check first (e.g. tool.MaliciousPackagesIndexPath). | ||||||
| func resolveIndexPathWithDefault(defaultPath string) (path string, cleanup func(), err error) { | ||||||
| if _, statErr := os.Stat(defaultPath); statErr == nil { | ||||||
| return defaultPath, func() {}, nil | ||||||
| } | ||||||
| f, err := os.CreateTemp("", "codacy-trivy-malicious-*.json.gz") | ||||||
| if err != nil { | ||||||
| return "", nil, fmt.Errorf("temp index: %w", err) | ||||||
| } | ||||||
| gw := gzip.NewWriter(f) | ||||||
| _, _ = gw.Write([]byte("{}")) | ||||||
| _ = gw.Close() | ||||||
| _ = f.Close() | ||||||
| return f.Name(), func() { os.Remove(f.Name()) }, nil | ||||||
| } | ||||||
|
|
||||||
| // runEOLScan runs the tool with EOL patterns only and returns all results. | ||||||
| func runEOLScan(ctx context.Context, dir, indexPath string) ([]codacy.Result, error) { | ||||||
| trivy, err := tool.New(indexPath) | ||||||
| if err != nil { | ||||||
| return nil, fmt.Errorf("New: %w", err) | ||||||
| } | ||||||
| files := listFiles(dir) | ||||||
| patterns := []codacy.Pattern{ | ||||||
| {ID: "eol_critical"}, | ||||||
| {ID: "eol_high"}, | ||||||
| {ID: "eol_medium"}, | ||||||
| {ID: "eol_minor"}, | ||||||
| } | ||||||
| te := codacy.ToolExecution{SourceDir: dir, Patterns: &patterns, Files: &files} | ||||||
| return trivy.Run(ctx, te) | ||||||
| } | ||||||
|
|
||||||
| // printEOLResults prints EOL issues and file errors from results, then a summary line. | ||||||
| func printEOLResults(results []codacy.Result) { | ||||||
| printEOLResultsTo(os.Stdout, os.Stderr, results) | ||||||
| } | ||||||
|
|
||||||
| func defaultWriter(w, d io.Writer) io.Writer { | ||||||
| if w == nil { | ||||||
| return d | ||||||
| } | ||||||
| return w | ||||||
| } | ||||||
|
|
||||||
| // printEOLResultsTo is the testable core; if stdout or stderr is nil, os.Stdout or os.Stderr is used. | ||||||
| func printEOLResultsTo(stdout, stderr io.Writer, results []codacy.Result) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The issue identified by the Lizard linter pertains to the cyclomatic complexity of the In this case, the complexity of 8 exceeds the recommended limit of 7, primarily due to the multiple conditional checks and the loop that processes the results. To reduce the complexity, one approach is to simplify the handling of the Here’s the code suggestion to address the complexity issue:
Suggested change
This change simplifies the assignment logic into one line, effectively reducing the cyclomatic complexity of the function. This comment was generated by an experimental AI tool. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Codacy found an issue: Method printEOLResultsTo has a cyclomatic complexity of 8 (limit is 7) |
||||||
| stdout = defaultWriter(stdout, os.Stdout) | ||||||
| stderr = defaultWriter(stderr, os.Stderr) | ||||||
| var count int | ||||||
| for _, r := range results { | ||||||
| switch v := r.(type) { | ||||||
| case codacy.Issue: | ||||||
| if isEOL(v.PatternID) { | ||||||
| count++ | ||||||
| fmt.Fprintf(stdout, "%s:%d [%s] %s\n", v.File, v.Line, v.PatternID, v.Message) | ||||||
| } | ||||||
| case codacy.FileError: | ||||||
| if v.File != "" { | ||||||
| fmt.Fprintf(stderr, "file error %s: %s\n", v.File, v.Message) | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| if count == 0 { | ||||||
| fmt.Fprintln(stdout, "No EOL issues found. Ensure the project has EOL deps (e.g. npm install in test-eol-project) and XEOL_DB_CACHE_DIR is set or DB is in default cache.") | ||||||
| } else { | ||||||
| fmt.Fprintf(stdout, "\nTotal EOL issues: %d\n", count) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| func listFiles(dir string) []string { | ||||||
| var out []string | ||||||
| _ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { | ||||||
| if err != nil || info.IsDir() { | ||||||
| return nil | ||||||
| } | ||||||
| rel, _ := filepath.Rel(dir, path) | ||||||
| out = append(out, rel) | ||||||
| return nil | ||||||
| }) | ||||||
| return out | ||||||
| } | ||||||
|
|
||||||
| func isEOL(id string) bool { | ||||||
| return id == "eol_critical" || id == "eol_high" || id == "eol_medium" || id == "eol_minor" | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "os" | ||
| "path/filepath" | ||
| "testing" | ||
|
|
||
| codacy "github.com/codacy/codacy-engine-golang-seed/v8" | ||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestResolveIndexPathWithDefault_WhenPathExists(t *testing.T) { | ||
| dir := t.TempDir() | ||
| existing := filepath.Join(dir, "index.json.gz") | ||
| f, err := os.Create(existing) | ||
| assert.NoError(t, err) | ||
| assert.NoError(t, f.Close()) | ||
|
|
||
| path, cleanup, err := resolveIndexPathWithDefault(existing) | ||
| assert.NoError(t, err) | ||
| defer cleanup() | ||
| assert.Equal(t, existing, path) | ||
| } | ||
|
|
||
| func TestResolveIndexPathWithDefault_WhenPathNotExists(t *testing.T) { | ||
| path, cleanup, err := resolveIndexPathWithDefault(filepath.Join(t.TempDir(), "nonexistent.json.gz")) | ||
| assert.NoError(t, err) | ||
| defer cleanup() | ||
| assert.NotEmpty(t, path) | ||
| _, err = os.Stat(path) | ||
| assert.NoError(t, err) | ||
| cleanup() | ||
| _, err = os.Stat(path) | ||
| assert.True(t, os.IsNotExist(err)) | ||
| } | ||
|
|
||
| func TestPrintEOLResultsTo_Empty(t *testing.T) { | ||
| var buf bytes.Buffer | ||
| printEOLResultsTo(&buf, &buf, nil) | ||
| assert.Contains(t, buf.String(), "No EOL issues found") | ||
| } | ||
|
|
||
| func TestPrintEOLResultsTo_OneIssue(t *testing.T) { | ||
| var buf bytes.Buffer | ||
| printEOLResultsTo(&buf, &buf, []codacy.Result{ | ||
| codacy.Issue{File: "go.mod", Line: 5, PatternID: "eol_critical", Message: "EOL pkg"}, | ||
| }) | ||
| out := buf.String() | ||
| assert.Contains(t, out, "go.mod:5 [eol_critical] EOL pkg") | ||
| assert.Contains(t, out, "Total EOL issues: 1") | ||
| } | ||
|
|
||
| func TestPrintEOLResultsTo_FileError(t *testing.T) { | ||
| var stdout, stderr bytes.Buffer | ||
| printEOLResultsTo(&stdout, &stderr, []codacy.Result{ | ||
| codacy.FileError{File: "bad.txt", Message: "read failed"}, | ||
| }) | ||
| assert.Contains(t, stderr.String(), "file error bad.txt: read failed") | ||
| } | ||
|
|
||
| func TestIsEOL(t *testing.T) { | ||
| assert.True(t, isEOL("eol_critical")) | ||
| assert.True(t, isEOL("eol_high")) | ||
| assert.True(t, isEOL("eol_medium")) | ||
| assert.True(t, isEOL("eol_minor")) | ||
| assert.False(t, isEOL("vulnerability_high")) | ||
| assert.False(t, isEOL("")) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "context" | ||
| "flag" | ||
| "fmt" | ||
| "log" | ||
| "os" | ||
| "path/filepath" | ||
|
|
||
| "github.com/codacy/codacy-trivy/internal/openssfdb" | ||
| git "github.com/go-git/go-git/v5" | ||
| "github.com/go-git/go-git/v5/plumbing" | ||
| ) | ||
|
|
||
| const ( | ||
| defaultRepoURL = "https://github.com/ossf/malicious-packages.git" | ||
| defaultOutput = "/dist/cache/openssf-malicious-packages.json.gz" | ||
| defaultRef = "" | ||
| defaultSourceID = "OpenSSF Malicious Packages DB" | ||
| ) | ||
|
|
||
| func main() { | ||
| repoURL := flag.String("repo", defaultRepoURL, "OpenSSF malicious packages repository URL") | ||
| repoDir := flag.String("repo-dir", "", "Pre-cloned OpenSSF repository to reuse instead of cloning") | ||
| ref := flag.String("ref", defaultRef, "Specific git reference to checkout when cloning the repository") | ||
| output := flag.String("out", defaultOutput, "Destination path for the gzipped JSON payload") | ||
|
|
||
| flag.Parse() | ||
|
|
||
| ctx := context.Background() | ||
|
|
||
| var ( | ||
| sourcePath string | ||
| sourceMeta string | ||
| cleanup func() | ||
| err error | ||
| ) | ||
|
|
||
| switch { | ||
| case *repoDir != "": | ||
| sourcePath, err = filepath.Abs(*repoDir) | ||
| if err != nil { | ||
| log.Fatalf("resolve repo dir: %v", err) | ||
| } | ||
| sourceMeta = fmt.Sprintf("%s (local)", defaultSourceID) | ||
| default: | ||
| sourcePath, cleanup, sourceMeta, err = cloneRepository(ctx, *repoURL, *ref) | ||
| if err != nil { | ||
| log.Fatalf("clone repository: %v", err) | ||
| } | ||
| defer cleanup() | ||
| } | ||
|
|
||
| builder := openssfdb.NewBuilder() | ||
|
|
||
| outputPayload, err := builder.Build(ctx, sourcePath, sourceMeta) | ||
| if err != nil { | ||
| log.Fatalf("build payload: %v", err) | ||
| } | ||
|
|
||
| if err := openssfdb.WriteGzippedJSON(*output, outputPayload); err != nil { | ||
| log.Fatalf("write payload: %v", err) | ||
| } | ||
| } | ||
|
|
||
| func cloneRepository(ctx context.Context, repoURL, ref string) (string, func(), string, error) { | ||
| tmpDir, err := os.MkdirTemp("", "openssf-malicious-*") | ||
| if err != nil { | ||
| return "", nil, "", err | ||
| } | ||
| cleanup := func() { | ||
| _ = os.RemoveAll(tmpDir) | ||
| } | ||
|
|
||
| options := &git.CloneOptions{ | ||
| URL: repoURL, | ||
| Depth: 1, | ||
| } | ||
| if ref != "" { | ||
| options.ReferenceName = plumbing.ReferenceName(ref) | ||
| } | ||
|
|
||
| repo, err := git.PlainCloneContext(ctx, tmpDir, false, options) | ||
| if err != nil { | ||
| cleanup() | ||
| return "", nil, "", err | ||
| } | ||
|
|
||
| sourceMeta := fmt.Sprintf("%s@%s", repoURL, resolveHeadHash(repo)) | ||
|
|
||
| return tmpDir, cleanup, sourceMeta, nil | ||
| } | ||
|
|
||
| func resolveHeadHash(repo *git.Repository) string { | ||
| headRef, err := repo.Head() | ||
| if err != nil { | ||
| return "unknown" | ||
| } | ||
| return headRef.Hash().String() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue identified by the Hadolint linter is related to the use of the
RUNcommand without setting a working directory usingWORKDIR. When you useRUNto execute commands, it's generally a good practice to set a specific working directory withWORKDIRto ensure that the commands are executed in the intended context. This enhances readability and maintainability of the Dockerfile.To fix the issue, you can add a
WORKDIRinstruction before theRUNcommand that sets theORAS_VERvariable. Here's the code suggestion:This comment was generated by an experimental AI tool.