Documentation
¶
Overview ¶
Package plugin provides a simple SDK for building Deputy extractor plugins.
Plugins are standalone executables that Deputy invokes via pluginrpc to extract package inventory from custom file formats. This SDK provides a high-level interface that handles all the pluginrpc boilerplate.
Architecture ¶
┌─────────────────────────────────────────────────────────────────┐
│ Deputy Process │
│ ┌───────────┐ ┌──────────────┐ ┌───────────────────┐ │
│ │ scan │───▶│ inventory │───▶│ plugin.Client │ │
│ │ command │ │ extraction │ │ (invokes plugin) │ │
│ └───────────┘ └──────────────┘ └─────────┬─────────┘ │
│ │ │
└─────────────────────────────────────────────────│──────────────┘
│
┌────────────────┬──────┴───────┐
│ stdin (proto) │ spawn │
▼ ▼ │
┌─────────────────────────────────────────────────────────│──────┐
│ Plugin Process │ │
│ ┌───────────────────┐ ┌───────────────┐ ┌───────┴────┐ │
│ │ pluginrpc.Server │◀───│ Extractor │◀───│ sdk/plugin│ │
│ │ (protocol logic) │ │ (your impl) │ │ (helpers) │ │
│ └─────────┬─────────┘ └───────────────┘ └────────────┘ │
│ │ │
│ ▼ stdout (proto) │
└────────────│───────────────────────────────────────────────────┘
│
└────▶ back to Deputy
Quick Start ¶
Create a plugin in a single main.go file:
package main
import (
"github.com/temporalio/deputy/sdk/plugin"
)
func main() {
plugin.Main(&myExtractor{})
}
type myExtractor struct{}
func (e *myExtractor) Name() string { return "custom/myformat" }
func (e *myExtractor) DisplayName() string { return "My Custom Format" }
func (e *myExtractor) Ecosystem() string { return "custom" }
func (e *myExtractor) Version() int { return 1 }
func (e *myExtractor) Description() string { return "Extracts packages from .myformat files" }
func (e *myExtractor) FilePatterns() []string { return []string{"*.myformat"} }
func (e *myExtractor) FileRequired(path string, isDir bool, mode uint32, size int64) bool {
return strings.HasSuffix(path, ".myformat")
}
func (e *myExtractor) Extract(path string, contents []byte, root string) ([]*plugin.Package, error) {
// Parse contents and return packages
return []*plugin.Package{
{Name: "example-pkg", Version: "1.0.0", Ecosystem: "custom"},
}, nil
}
Building the Plugin ¶
go build -o deputy-extractor-myformat ./cmd/myformat-plugin
Registering with Deputy ¶
Plugins can be registered via configuration or discovered from PATH:
# .deputy.yaml
plugins:
extractors:
- path: /usr/local/bin/deputy-extractor-myformat
- name: deputy-extractor-gemspec # searches PATH
Or programmatically via the SDK:
client, _ := sdk.NewClient(ctx) client.RegisterExtractor(ctx, "deputy-extractor-myformat")
Distributed Tracing ¶
Plugins automatically participate in distributed traces when the OTEL_EXPORTER_OTLP_ENDPOINT environment variable is set. The SDK extracts trace context from requests and creates child spans.
Deputy Scan (trace-id: abc123) │ ├── inventory.Extract │ │ │ ├── plugin.client.FileRequired ──────────────────┐ │ │ │ │ TraceContext │ │ └──[spawn]──▶ plugin.FileRequired (abc123) ◀─┘ in request │ │ │ └── plugin.client.Extract ───────────────────────┐ │ │ │ TraceContext │ └──[spawn]──▶ plugin.Extract (abc123) ◀──────┘ in request │ └── vulnerability.Lookup
The TraceContext field in FileRequiredRequest and ExtractRequest carries the W3C traceparent header value, enabling end-to-end tracing.
Plugin Interface ¶
Implement the Extractor interface to create a plugin. The SDK handles:
- Pluginrpc protocol negotiation (--protocol, --spec flags)
- Request/response serialization
- OpenTelemetry trace context propagation
- Error handling and exit codes
See the Extractor interface for detailed documentation.
Distributed tracing support for Deputy extractor plugins.
This file provides OpenTelemetry trace context propagation, enabling plugins to participate in distributed traces with Deputy. When OTEL_EXPORTER_OTLP_ENDPOINT is set, traces flow seamlessly across process boundaries.
Trace Context Flow ¶
Deputy Process Plugin Process +-----------------------+ +------------------------+ | | | | | ctx with span | | extractTraceContext() | | | | | | | | v | | v | | injectTraceContext() | | ctx with linked span | | | | | | | | v | TraceContext | v | | "00-abc-def-01" -----|---- field ------->| startSpan() | | | in request | | | | | | v | | | | child span created | +-----------------------+ +------------------------+
The TraceContext field in FileRequiredRequest and ExtractRequest carries the W3C traceparent header value (e.g., "00-traceid-spanid-01").
Index ¶
- Constants
- func Main(extractor Extractor)
- func MainAdvisorySource(source AdvisorySource)
- type Advisory
- type AdvisorySource
- type AdvisorySourceInfo
- type ArtifactKind
- type Extractor
- type ExtractorInfo
- type Finding
- type FindingKind
- type Package
- type PackageBuilder
- func (b *PackageBuilder) Build() *Package
- func (b *PackageBuilder) WithDirect(direct bool) *PackageBuilder
- func (b *PackageBuilder) WithLicenses(licenses ...string) *PackageBuilder
- func (b *PackageBuilder) WithLocations(locations ...string) *PackageBuilder
- func (b *PackageBuilder) WithManifestRef(path, manager string, groups ...string) *PackageBuilder
- func (b *PackageBuilder) WithPURL(purl string) *PackageBuilder
- type SourceCapabilities
Constants ¶
const ( ArtifactKindPackage = vulnerabilityv1.ArtifactKind_ARTIFACT_KIND_PACKAGE ArtifactKindOSPackage = vulnerabilityv1.ArtifactKind_ARTIFACT_KIND_OS_PACKAGE ArtifactKindContainerImageRef = vulnerabilityv1.ArtifactKind_ARTIFACT_KIND_CONTAINER_IMAGE_REF ArtifactKindGitHubAction = vulnerabilityv1.ArtifactKind_ARTIFACT_KIND_GITHUB_ACTION FindingKindVulnerability = vulnerabilityv1.FindingKind_FINDING_KIND_VULNERABILITY FindingKindMalware = vulnerabilityv1.FindingKind_FINDING_KIND_MALWARE )
Enum value constants re-exported so plugin authors declare capabilities and finding kinds without importing the generated proto packages.
const ( // TracerName is the tracer name for plugin spans. // All spans created by plugin SDK use this tracer for identification. TracerName = "github.com/temporalio/deputy/plugin" )
Variables ¶
This section is empty.
Functions ¶
func Main ¶
func Main(extractor Extractor)
Main is the entry point for extractor plugins. Call this from your main() function with your Extractor implementation.
Example:
func main() {
plugin.Main(&myExtractor{})
}
func MainAdvisorySource ¶
func MainAdvisorySource(source AdvisorySource)
MainAdvisorySource is the entry point for advisory-source plugins. Call it from main() with your AdvisorySource implementation:
func main() {
plugin.MainAdvisorySource(&mySource{})
}
Types ¶
type AdvisorySource ¶
type AdvisorySource interface {
// Info returns the source's identity and declared coverage.
Info() *AdvisorySourceInfo
// Query returns the advisories affecting the supplied packages, as findings
// plus the full advisory records keyed by advisory ID.
Query(ctx context.Context, packages []*Package) (findings []*Finding, advisories map[string]*Advisory, err error)
}
AdvisorySource is the interface advisory-source plugins implement to provide vulnerability/malware intelligence for a set of packages. Deputy invokes it via pluginrpc (subprocess) and aggregates its results with other sources (union-with-provenance). Declare accurate Capabilities: Deputy routes only packages a source covers, and a source must ignore anything outside its declared coverage rather than erroring.
type AdvisorySourceInfo ¶
type AdvisorySourceInfo = pluginv1.AdvisorySourceInfo
AdvisorySourceInfo describes a source's identity and declared coverage.
type ArtifactKind ¶
type ArtifactKind = vulnerabilityv1.ArtifactKind
ArtifactKind classifies what a source is asked about (package, os package…).
type Extractor ¶
type Extractor interface {
// Name returns the extractor identifier.
// Convention: "<ecosystem>/<format>" (e.g., "ruby/gemspec", "custom/myformat").
Name() string
// DisplayName returns a human-readable name for UI display.
DisplayName() string
// Ecosystem returns the package ecosystem this extractor supports.
// Use standard ecosystem names: go, npm, pypi, maven, rubygems, cargo, etc.
Ecosystem() string
// Version returns the extractor version (increment on behavior changes).
Version() int
// Description returns context about what this extractor does.
Description() string
// FilePatterns returns glob patterns for files this extractor handles.
// Examples: ["Gemfile.lock", "*.gemspec", "vendor/*/Gemfile"]
FilePatterns() []string
// FileRequired checks if this extractor should process a file.
// Return true to receive an Extract call for this file.
// This is called for every file in the scan - keep it fast (no I/O).
//
// Parameters:
// - path: File path relative to scan root
// - isDir: Whether the path is a directory
// - mode: Unix file permission mode
// - size: File size in bytes
FileRequired(path string, isDir bool, mode uint32, size int64) bool
// Extract extracts packages from a file's contents.
// Called only for files where FileRequired returned true.
//
// Parameters:
// - path: File path relative to scan root
// - contents: Raw file bytes
// - root: Absolute path to scan root (for resolving relative paths)
//
// Returns a slice of discovered packages and any error.
Extract(path string, contents []byte, root string) ([]*Package, error)
}
Extractor is the interface that plugins must implement.
The interface is designed to be simple while matching the semantics of OSV-SCALIBR extractors for consistency across the ecosystem.
type ExtractorInfo ¶
type ExtractorInfo = pluginv1.ExtractorInfo
ExtractorInfo is an alias for the proto ExtractorInfo type.
type FindingKind ¶
type FindingKind = vulnerabilityv1.FindingKind
FindingKind distinguishes vulnerabilities from malware.
type Package ¶
type Package = dependencyv1.Package
Package is an alias for the proto Package type for convenience. This allows plugin authors to use plugin.Package instead of importing the dependency proto package directly.
func NewPackage ¶
NewPackage creates a new Package with the given name, version, and ecosystem. This is a convenience function for plugin authors.
Example:
pkg := plugin.NewPackage("lodash", "4.17.21", "npm")
type PackageBuilder ¶
type PackageBuilder struct {
// contains filtered or unexported fields
}
PackageBuilder provides a fluent interface for building Package instances.
Example:
pkg := plugin.NewPackageBuilder("lodash", "4.17.21", "npm").
WithPURL("pkg:npm/lodash@4.17.21").
WithLicenses("MIT").
WithDirect(true).
Build()
func NewPackageBuilder ¶
func NewPackageBuilder(name, version, ecosystem string) *PackageBuilder
NewPackageBuilder creates a new PackageBuilder with required fields.
func (*PackageBuilder) Build ¶
func (b *PackageBuilder) Build() *Package
Build returns the constructed Package.
func (*PackageBuilder) WithDirect ¶
func (b *PackageBuilder) WithDirect(direct bool) *PackageBuilder
WithDirect marks the package as a direct dependency.
func (*PackageBuilder) WithLicenses ¶
func (b *PackageBuilder) WithLicenses(licenses ...string) *PackageBuilder
WithLicenses sets the SPDX license identifiers.
func (*PackageBuilder) WithLocations ¶
func (b *PackageBuilder) WithLocations(locations ...string) *PackageBuilder
WithLocations sets the file paths where this package was found.
func (*PackageBuilder) WithManifestRef ¶
func (b *PackageBuilder) WithManifestRef(path, manager string, groups ...string) *PackageBuilder
WithManifestRef adds a manifest reference describing where the dependency is declared.
func (*PackageBuilder) WithPURL ¶
func (b *PackageBuilder) WithPURL(purl string) *PackageBuilder
WithPURL sets the Package URL (PURL).
type SourceCapabilities ¶
type SourceCapabilities = pluginv1.SourceCapabilities
SourceCapabilities declares the ecosystems, artifacts, and finding kinds a source can answer for.