A Trivy plugin can scan for vulnerabilities that Trivy itself doesn’t natively support.

Let’s see a plugin in action. Imagine we want to scan a custom configuration file format, say myapp.conf, for specific insecure settings. Trivy doesn’t know about myapp.conf, but we can teach it.

First, we’ll create a simple Go plugin. This plugin will implement the trivy.Scanner interface.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/aquasecurity/trivy/pkg/types"
	"github.com/aquasecurity/trivy/pkg/plugin"
)

type MyScanner struct{}

func (s *MyScanner) Name() string {
	return "my-custom-scanner"
}

func (s *MyScanner) Scan(ctx context.Context, target string, opts types.ScanOptions) ([]types.Result, error) {
	var results []types.Result

	err := filepath.Walk(target, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() && filepath.Base(path) == "myapp.conf" {
			// Read the file and check for insecure settings
			content, readErr := os.ReadFile(path)
			if readErr != nil {
				return fmt.Errorf("failed to read %s: %w", path, readErr)
			}

			if string(content) == "insecure_setting=true" {
				results = append(results, types.Result{
					Target: path,
					Vulnerabilities: []types.DetectedVulnerability{
						{
							VulnerabilityID:  "MYAPP-001",
							PkgName:          "myapp.conf",
							InstalledVersion: "N/A",
							FixedVersion:     "N/A",
							Severity:         "HIGH",
							Title:            "Insecure setting found in myapp.conf",
							Description:      "The 'insecure_setting=true' parameter is not recommended for production environments.",
							PrimaryURL:       "https://example.com/docs/MYAPP-001",
						},
					},
				})
			}
		}
		return nil
	})
	if err != nil {
		return nil, fmt.Errorf("error walking directory: %w", err)
	}

	return results, nil
}

func main() {
	plugin.Serve(&MyScanner{})
}

To build this, you’d run go build -o my-custom-scanner my-custom-scanner.go. This creates an executable binary.

Now, let’s create a dummy myapp.conf file for testing:

insecure_setting=true
another_setting=false

Place this file in a directory, say test-dir.

To run Trivy with your plugin, you’d use the --plugin-dir flag:

trivy --plugin-dir . --target test-dir

Trivy will discover the my-custom-scanner executable in the current directory (.) and execute it. If it finds myapp.conf with insecure_setting=true, it will report the vulnerability.

The core idea is that Trivy acts as an orchestrator. When you specify a target, it first checks its built-in scanners (OS packages, dependencies, IaC, secrets, etc.). If you point it to a plugin directory, it also enumerates executables there, identifies them as potential scanners via the plugin.Serve mechanism, and invokes them for the given target. Each plugin is essentially a standalone executable that conforms to a specific interface Trivy understands.

The trivy.Scanner interface is the contract. It has two key methods: Name() which identifies the scanner to Trivy, and Scan(ctx context.Context, target string, opts types.ScanOptions) which is where the actual scanning logic resides. The target is the path Trivy is analyzing, and opts contains various configuration parameters. Your plugin’s Scan method is responsible for exploring the target, identifying relevant files or artifacts, performing its specific checks, and returning findings as a slice of types.Result.

The types.Result struct is crucial. It’s how your plugin communicates findings back to Trivy. It contains the Target (which file or resource was scanned), and a slice of types.DetectedVulnerability (or other finding types like types.Misconfiguration or types.Secret). Each types.DetectedVulnerability has fields like VulnerabilityID, Severity, Title, and Description. This structured output allows Trivy to aggregate findings from all scanners, including custom ones, and present them in a unified report.

The filepath.Walk function in our example is a standard Go way to traverse a directory tree. For each file encountered, we check if it’s our target myapp.conf. If it is, we read its content and perform our custom logic – in this case, a simple string check. If the condition is met, we construct a types.Result object with the details of the detected "vulnerability."

What many don’t realize is that Trivy’s plugin system is designed to be extensible for any kind of analysis. While we’ve used vulnerability detection as an example, a plugin could identify misconfigurations, sensitive data, or even custom compliance checks. The types.Result structure is flexible enough to accommodate various finding types, and plugins can return different types of findings by populating the appropriate fields within the Result. Trivy doesn’t strictly enforce that a plugin must find vulnerabilities; it’s just a common use case.

The next step in mastering Trivy plugins is understanding how to package and distribute them, or how to integrate more complex analysis using Trivy’s internal libraries.

Want structured learning?

Take the full Trivy course →