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.servecallsp.enricher.EnrichConnection(ctx, cm)once per connection (proxy/pipeline.go:73), buildingConnMetafrom the PROXY-proto TLVs (JA3/JA4/SNI/TLS). Findings are cached on the connection. - Request:
Proxy.analyzemerges the cached connection findings and, if any pluginWantsRequest, callsp.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.