# Trivy Module System vs Plugins: Understanding Wasm Extensions and Binary Plugins

> Explore Trivy modules and plugins. Understand how WebAssembly extensions customize scanning versus binary plugins offering full host access. Enhance your Trivy workflow.

- Repository: [Aqua Security/trivy](https://github.com/aquasecurity/trivy)
- Tags: deep-dive
- Published: 2026-03-23

---

**Trivy modules are sandboxed WebAssembly extensions that run inside the Trivy CLI to customize scanning logic, while plugins are standalone external binaries executed as sub-commands with full host access.**

The **Trivy module system** is an experimental extensibility mechanism in the `aquasecurity/trivy` repository that allows developers to write WebAssembly (Wasm) extensions. These modules integrate directly into the scanning pipeline without requiring recompilation of the core binary, offering a lighter alternative to the traditional plugin architecture.

## What Is the Trivy Module System?

Trivy’s module system enables **Wasm-based extensions** that execute inside the Trivy process using the [wazero](https://github.com/tetratelabs/wazero) runtime. This approach eliminates CGO dependencies while providing near-native performance through zero-copy memory access.

### Wasm Runtime and Discovery

At startup, Trivy walks the directory configured by `--module-dir` (default `$HOME/.trivy/modules`) and loads every `*.wasm` file it finds. In [`pkg/module/module.go`](https://github.com/aquasecurity/trivy/blob/main/pkg/module/module.go), the `loadModules` function handles this discovery process, compiling each binary with wazero and validating compatibility before instantiation.

### API Contract and Entry Points

Each module must expose an `api_version` function that matches the Trivy SDK version (`tapi.Version`). If version numbers differ, the module is silently ignored to prevent runtime errors.

A module implements one or both extension points defined in the `wasmModule` struct:

- **`analyze(filePath string, data []byte) -> json`** – Implements custom file analysis logic
- **`post_scan(results []Result) -> json`** – Mutates scan results after the core scan completes

After instantiation, the `Register()` method adds the module to Trivy’s analyzer registry or post-scan hook system.

### Security Model and State Management

Modules run in a **sandboxed environment** with no direct file-system, network, or OS access unless explicitly exposed through the Trivy SDK. The architecture enforces **stateless execution**—modules cannot persist mutable data across calls, ensuring consistent behavior during concurrent scans.

Installation occurs via `trivy module install`, which pulls Wasm binaries as OCI artifacts (e.g., `ghcr.io/aquasecurity/trivy-module-spring4shell`) into the modules directory.

## What Are Trivy Plugins?

**Trivy plugins** are standalone executables that extend the CLI through external process invocation. Unlike modules, plugins operate outside Trivy’s memory space and communicate via standard input/output streams.

### Binary Execution Model

Located in [`pkg/plugin/plugin.go`](https://github.com/aquasecurity/trivy/blob/main/pkg/plugin/plugin.go), the plugin system uses `exec.Command` to spawn separate processes when users invoke `trivy <plugin-name>` (e.g., `trivy kubectl`). The `Cmd`, `Run`, and `Start` functions manage plugin lifecycle, executing binaries discovered in `$HOME/.trivy/plugins` or the XDG data directory.

Plugins run **without sandboxing**, inheriting the same privileges as the parent Trivy process. This allows unrestricted access to the host environment, including the ability to spawn additional processes or modify system state.

### Interface and Distribution

Plugins implement the **Trivy plugin protocol** by reading JSON from `stdin` and writing results to `stdout`. No specific SDK is required—any language capable of producing an executable and parsing JSON can implement a plugin.

Distribution formats include OCI images, Git repositories, or plain archives. The [`plugin.yaml`](https://github.com/aquasecurity/trivy/blob/main/plugin.yaml) manifest describes metadata and installation requirements. Unlike modules, plugins support **lazy loading**—binaries execute only when their specific sub-command is invoked.

## Key Differences Between Modules and Plugins

The architectural distinction centers on **isolation versus flexibility**:

| Feature | Trivy Modules (Wasm) | Trivy Plugins (Binary) |
|---------|---------------------|------------------------|
| **Runtime** | In-process via wazero | Separate OS process |
| **Isolation** | Sandboxed VM with restricted system access | Full host access, no sandbox |
| **Performance** | Zero-copy memory, no IPC overhead | Process creation and JSON serialization overhead |
| **State** | Stateless (enforced by SDK) | Can maintain state across invocations |
| **Compatibility** | Version enforced via `api_version` function | Manual version management by author |
| **Distribution** | OCI artifact with `*.wasm` layer | OCI, zip, tarball, or plain binary |
| **Loading** | Pre-loaded at startup (all enabled modules) | Lazy-loaded on sub-command invocation |
| **Scope** | File analyzers and post-scan result mutations | CLI sub-commands, output formats, custom reporters |

## When to Use Modules vs Plugins

Choose the extension mechanism based on your security requirements and integration depth:

**Prefer Modules when:**
- You need **sandboxed execution** for untrusted third-party code
- You want to **extend scanning logic** (custom file analyzers or result post-processing)
- You require **minimal runtime overhead** for high-throughput scanning
- You need users to selectively enable extensions via `--enable-modules`

**Prefer Plugins when:**
- You are adding a **new CLI sub-command** (e.g., `trivy kubectl`)
- You need to **reuse existing binary tools** without rewriting in Go
- You require **stateful operations** or external network/file access
- You want to implement **custom output formats** as standalone processors

## Implementation Examples

### Creating a Wasm Module

The following example implements a minimal analyzer that processes `.txt` files:

```go
package main

import (
	"github.com/aquasecurity/trivy/pkg/module/wasm"
	"github.com/aquasecurity/trivy/pkg/module/serialize"
)

const version = 1
const name = "example-analyzer"

var required = []string{`.*\.txt$`}

//export analyze
func analyze(ptrSize uint64) uint64 {
	var res serialize.AnalysisResult
	// Analysis logic here
	return marshalResult(res)
}

//export required
func requiredFiles() uint64 {
	return marshalJSON(required)
}

//export name
func moduleName() uint64 {
	return marshalString(name)
}

//export version
func moduleVersion() int32 {
	return int32(version)
}

//export api_version
func apiVersion() int32 {
	// Must match Trivy's SDK version (see pkg/module/api)
	return int32(1)
}

type Module struct{}

func (m Module) Name() string { return name }
func (m Module) Version() int { return version }

func init() {
	wasm.RegisterModule(Module{})
}

func main() {}

```

Compile the module using:

```bash
GOOS=wasip1 GOARCH=wasm go build -o mod.wasm -buildmode=c-shared

```

Install and run:

```bash
trivy module install ghcr.io/aquasecurity/trivy-module-example
trivy fs ./my-project

```

### Creating a Plugin

This plugin example reads Trivy JSON from stdin and injects a custom vulnerability:

```go
package main

import (
	"encoding/json"
	"io"
	"log"
	"os"

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

func main() {
	var in types.Results
	if err := json.NewDecoder(os.Stdin).Decode(&in); err != nil && err != io.EOF {
		log.Fatalf("decode error: %v", err)
	}

	for i := range in {
		in[i].Vulnerabilities = append(in[i].Vulnerabilities, types.DetectedVulnerability{
			VulnerabilityID: "CUSTOM-001",
			PkgName:         "example",
			Severity:        "MEDIUM",
			Title:           "Example custom finding",
		})
	}

	if err := json.NewEncoder(os.Stdout).Encode(in); err != nil {
		log.Fatalf("encode error: %v", err)
	}
}

```

Install and use as an output plugin:

```bash
go build -o trivy-example-plugin .
trivy plugin install ./trivy-example-plugin
trivy image alpine:latest --format json --output plugin=example-plugin

```

## Summary

- **Trivy modules** are Wasm-based extensions running in [`pkg/module/module.go`](https://github.com/aquasecurity/trivy/blob/main/pkg/module/module.go) that provide sandboxed, in-process analysis and post-scan hooks with enforced statelessness.
- **Trivy plugins** are external binaries managed by [`pkg/plugin/plugin.go`](https://github.com/aquasecurity/trivy/blob/main/pkg/plugin/plugin.go) that execute as sub-commands with full host privileges but higher overhead.
- Modules use the **wazero runtime** for zero-copy performance; plugins use `exec.Command` for flexibility.
- Choose **modules** for security-sensitive scan extensions; choose **plugins** for CLI tooling and system integrations.

## Frequently Asked Questions

### Are Trivy modules production-ready?

No, the Trivy module system is currently **experimental** according to the source code in [`pkg/module/module.go`](https://github.com/aquasecurity/trivy/blob/main/pkg/module/module.go). While functional, the API surface may change between versions, and the `api_version` strictness (requiring exact SDK version matching) means modules must be updated to match newer Trivy releases.

### Can I use languages other than Go for Trivy modules?

While the Wasm standard supports any language, Trivy currently only officially provides a **Go SDK** (`pkg/module/api`, `pkg/module/serialize`). To write modules in other languages (Rust, C++, etc.), you would need to manually implement the ABI contract that the `wasmModule` struct expects, including memory management conventions and JSON serialization formats.

### How do I debug a Wasm module during development?

Since modules run inside wazero, standard debugging tools are limited. The Trivy SDK exposes logging helpers (`logDebug`, `logInfo`, etc.) in [`pkg/module/module.go`](https://github.com/aquasecurity/trivy/blob/main/pkg/module/module.go) that write to Trivy’s log output. Enable debug logging with `--debug` to see module lifecycle messages and analysis traces during execution.

### Can plugins modify scan results like modules can?

Plugins can modify results **only when used as output plugins** (`--output plugin=<name>`), receiving the complete JSON result set via stdin and returning modified JSON via stdout. However, they cannot intervene during the scan phase like modules can. Modules integrate at the `analyze` and `post_scan` hooks defined in the analyzer and extension registries, allowing real-time result mutation during the scanning process.