Plugin SDK contract

Internals live

The plugin SDK is the only seam between the open-source core and out-of-process intelligence. It is a gRPC contract (HashiCorp go-plugin) defined in dataplane/security-engine/pkg/plugin. This page is the developer reference for that boundary; the conceptual view is in Intelligence plugins.

Contract types · pkg/plugin/plugin.go

Type Location Role
Contributor plugin.go:77 The plugin interface: Info(), EnrichConnection(ConnContext), AnalyzeRequest(RequestView)
ConnContext plugin.go:44 Per-connection input: IP, hash, JA3, JA4, SNI, TLS version, country, header order
RequestView plugin.go:64 Per-request read-only projection of the canonical request
Enrichment plugin.go:38 Plugin output: Findings, Reputation, Tags
Finding plugin.go:29 Same shape as a core finding; Engine must be ≥ EnginePluginBase (128)

Host · pkg/plugin/host.go

Function Location Role
NewHost host.go:37 Start each -plugins executable over the go-plugin gRPC handshake
Host.start host.go:61 Launch + handshake a single plugin (50 ms default timeout)
Host.EnrichConnection host.go:84 Per-connection fan-out to plugins that want it; fail-open on error
Host.AnalyzeRequest host.go:105 Per-request fan-out
sanitizeFindings host.go:163 Drop any plugin finding claiming Engine < 128 (anti-impersonation)

Engine-side adapter · internal/enrich

The data plane talks to the host through an Enricher interface so the proxy never depends on gRPC directly.

Symbol Location Role
Enricher enrich/enrich.go:45 EnrichConnection / EnrichRequest / WantsRequest
NewPluginEnricher enrich/plugin.go:18 Wrap the plugin host as an Enricher

Where it hooks into the flow

  • Connection: Proxy.serve calls p.enricher.EnrichConnection(ctx, cm) once per connection (proxy/pipeline.go:73), building ConnMeta from the PROXY-proto TLVs (JA3/JA4/SNI/TLS). Findings are cached on the connection.
  • Request: Proxy.analyze merges the cached connection findings and, if any plugin WantsRequest, calls p.enricher.EnrichRequest(...) (proxy/pipeline.go:461), adding the returned findings into the same set before risk assessment — so plugin findings traverse the identical risk → decision → telemetry path as core engines.

Writing a plugin

A plugin is a standalone Go program that implements Contributor and calls the SDK's serve entry point. The essentials:

type myPlugin struct{ plugin.Base }

func (p *myPlugin) Info() plugin.Info {
    return plugin.Info{Name: "my-plugin", Version: "0.1.0", WantsConnection: true, WantsRequest: true}
}

func (p *myPlugin) EnrichConnection(_ context.Context, c plugin.ConnContext) (plugin.Enrichment, error) {
    // classify the TLS stack, look up reputation, …
    return plugin.Enrichment{}, nil
}

func (p *myPlugin) AnalyzeRequest(_ context.Context, r plugin.RequestView) (plugin.Enrichment, error) {
    // contribute findings; Engine must be >= 128
    return plugin.Enrichment{}, nil
}

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

Register it by passing the executable path to the engine's -plugins flag.

Boundary invariants

Calls are timeout-bounded and fail open — a slow or crashing plugin degrades to no enrichment, never to a stalled request. Plugin findings are confined to engine IDs ≥ 128 and sanitized on the way in, so a plugin can contribute risk but never masquerade as a core engine.