sdk-go

module
v0.17.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2026 License: GPL-3.0

README

OpenCTEM SDK

Go SDK for building integrations with the OpenCTEM security platform.

License Go Go Reference

Overview

OpenCTEM SDK provides Go packages for:

  • API client for interacting with OpenCTEM API
  • Scanner integrations: SAST/SCA/secrets (Semgrep, CodeQL, Trivy, Betterleaks), DAST (Nuclei, including a validation executor), and recon (subfinder, dnsx, naabu, httpx, katana)
  • Output formatters (SARIF, JSON)
  • Common utilities and helpers

Installation

go get github.com/openctemio/sdk-go

Upgrading from v0.6 (agent → sensor)

v0.7.0 renames the agent vocabulary to sensor (core.BaseAgent → core.BaseSensor, client.WithAgentID → client.WithSensorID, …). Upgrade your module with the codemod, which rewrites only SDK identifiers, type-safely, then moves you to the new SDK:

go run github.com/openctemio/sdk-go/cmd/sensor-migrate@v0.7.0 -dry-run   # review
go run github.com/openctemio/sdk-go/cmd/sensor-migrate@v0.7.0            # apply

The wire to the platform (protocol v1) is unchanged, and the SDK migrates a sensor's saved credentials and old AGENT_* settings by itself. See CHANGELOG.md for the full list.

Quick Start

API Client
package main

import (
    "context"
    "github.com/openctemio/sdk-go/pkg/client"
)

func main() {
    // Create client
    c := client.New(
        client.WithBaseURL("http://localhost:8080"),
        client.WithAPIKey("your-api-key"),
    )

    // List assets
    assets, err := c.Assets().List(context.Background())
    if err != nil {
        panic(err)
    }

    // Create finding
    finding := &client.Finding{
        Title:    "SQL Injection",
        Severity: "HIGH",
        // ...
    }
    err = c.Findings().Create(context.Background(), finding)
}

Build a sensor in 30 lines

pkg/sensorkit is the whole sensor runtime in one call. A sensor implements its tools; the kit reads the settings, connects (protocol v2 negotiated on hello, v1 for what an older platform lacks), reports the tools on every heartbeat, waits while the key is rejected, follows the heartbeat doorbell, claims commands into resource-sized slots, delivers results through the durable outbox, renews the API key and drains on SIGTERM.

package main

import (
	"context"
	"time"

	"github.com/openctemio/sdk-go/pkg/core"
	"github.com/openctemio/sdk-go/pkg/sensorkit"
)

var version = "dev"

func main() {
	tool := core.NewBaseScanner(&core.BaseScannerConfig{ // your tool's binary
		Name: "mytool", Binary: "mytool", Capabilities: []string{"sast"},
		DefaultArgs: []string{"scan", "--sarif", "{target}"}, Timeout: 30 * time.Minute,
	})
	kit, err := sensorkit.New(sensorkit.Options{Name: "my-sensor", Version: version})
	sensorkit.Exit(err)    // a clear message and exit code for a bad setting
	kit.AddScanner(tool)   // reported (installed? version?), dispatched, scheduled
	sensorkit.Exit(kit.Run(context.Background())) // blocks; SIGINT/SIGTERM drain
}

Run it with API_URL and API_KEY (a sensor key from Settings > Sensors). examples/minimal-sensor is a complete one with an in-process tool.

Keep the state directory. A key renewal retires the key the sensor was started with; the renewed key is saved in sensor-credentials.json in the state directory (0600) and used on the next start instead of API_KEY. In a container, mount a volume at /var/lib/openctem/state (-v my-sensor-state:/var/lib/openctem/state): without one the kit does not renew, because a recreated container would come back with the retired key. When an administrator regenerates the key and API_KEY is set to the new one, the new key wins. Every heartbeat also carries the process's instance_id, so the platform flags one key running in two places.

Setting Environment (an Options field wins) Default
Platform API_URL, API_KEY, SENSOR_ID required to run commands (exit 2 with what is missing)
Name SENSOR_NAME sensor-<hostname>
Protocol SENSOR_PROTOCOL = auto, v1, v2 auto
Concurrency cap SENSOR_MAX_JOBS (1-100) none: slots follow CPU, memory and tool cost
Tool allowlist SENSOR_TOOLS (comma-separated) none
Drain grace SENSOR_DRAIN_GRACE (1s-1h) 30s
Scanner priority SENSOR_SCANNER_PRIORITY = low, normal low: scanners at nice +10, lowest best-effort I/O, oom_score_adj 500 (Linux)
Sensor OOM protection SENSOR_PROTECT_FROM_OOM=true|false: the sensor sets its own oom_score_adj to -500 (Linux; needs CAP_SYS_RESOURCE, e.g. docker run --cap-add SYS_RESOURCE; without it, one warning and the sensor runs unprotected). Scanners never inherit it off
Outbox SENSOR_OUTBOX, SENSOR_OUTBOX_DIR, _MAX_BYTES, _MAX_AGE, _KEY_FILE on, /var/lib/openctem/outbox
State (the renewed API key, tool cost history) SENSOR_STATE_DIR /var/lib/openctem/state when writable, else ~/.openctem: mount a persistent volume there
Private CA SENSOR_CA_CERT_FILE (PEM, added to the system roots; trusted for the platform and content downloads, so a TLS-inspecting proxy works) system roots
Template-signing keys SENSOR_TEMPLATE_SIGNING_KEYS (base64 Ed25519 public keys, comma-separated, the tenant's keys from the platform's GET /api/v1/scanner-templates/signing-key): custom templates in a scan run only with a signature one of them verifies none: commands carrying custom templates fail
Proxy to the platform SENSOR_CONTROL_PROXY (URL or direct), else HTTPS_PROXY, HTTP_PROXY, NO_PROXY none
Proxy for content and feeds SENSOR_CONTENT_PROXY (URL or direct), else the platform setting the platform setting
Proxy variables for scanners SENSOR_SCAN_PROXY: inherit (scanners get the sensor's HTTP(S)_PROXY; warned at start) or direct inherit
Key renewal PLATFORM_KEY_AUTORENEW=true|false, Options.KeyAutoRenew / NoKeyAutoRenew, Options.CredentialsFile on when the state directory persists (outside a container; inside one, a mounted, non-tmpfs volume), else off
Pre-rename names AGENT_ID, AGENT_NAME, AGENT_ALLOW_PRIVATE_TARGETS, -agent-id applied with a warning; both names set differently: exit 2

Plug your own parts in: kit.AddCollector, kit.AddParser (your tool's output format), kit.HandleCommand("type", executor), kit.UseCommandMiddleware(mw, "type") (wrap every command), Options.Content (managed scanner content on the heartbeat), Options.ScanTargetPolicy, Options.AssetResolver, kit.Tools() (more tools or capabilities). The resolvers (ResolveMaxJobs, ResolveOutbox, ResolveProtocol, CheckCredentials, MigrateSettings) are exported for a sensor with its own flags or configuration file. pkg/core stays available for anything the kit does not cover; the sections below describe it.

Writing a sensor: you implement executors; the SDK owns queue, slots, leases and reporting

The platform's queue is authoritative (api RFC-030). A sensor only says what it can run; core.CommandPoller does the rest:

The SDK does How
Slots resource.Manager sizes them from what the sensor may use: CPU cores and memory read from cgroup v2/v1 (quota, cpuset, memory limit) or the host, divided by the learned per-job cost of the sensor's tools (defaults per tool, refined from finished jobs, saved to a small JSON file). An AIMD window halves them after an OOM kill, a timeout or CPU throttling and grows back one slot per window of successes. A configured cap (ManagerConfig.Cap) is an upper bound, never a fixed number.
Claiming Takes a free slot first, polls with limit = free slots, claims nothing while all are busy: no prefetch, so the platform keeps work for sensors that can run it now.
Local queue Orders a batch by class (verify > interactive > scheduled > background) then priority (critical > high > normal > low); at most limits.per_host_concurrency (default 1) held commands touch one host (core.HostKey); a command over it is left pending, not claimed.
Leases Every heartbeat lists the held command ids (running) for the platform to renew.
Cancel A heartbeat's cancel_command_ids cancels those commands' contexts; they are released, not reported.
Drain Stop or a canceled context stops claiming, gives running commands DrainGrace (30 s) and then cancels them; unstarted and aborted commands are released (POST …/commands/{id}/release; failed with "released: …" on a platform without it) so the platform re-queues them at once. A refused start is never executed.
Reporting The heartbeat carries active_jobs, max_concurrent_jobs (the cap), resources, capacity (slots and per-tool cost) and queue.
poller := core.NewCommandPoller(apiClient, executor, &core.CommandPollerConfig{PollInterval: 30 * time.Second})
poller.SetResourceManager(resource.NewManager(resource.ManagerConfig{
	Cap:       0, // no operator cap: slots follow the resources (up to 64)
	Tools:     []string{"nuclei", "trivy"},
	StateFile: "/var/lib/openctem/tool-costs.json",
	Prober:    &resource.Prober{WorkDir: "/var/lib/openctem"},
}))
poller.SetDoorbell(doorbell)
sensor.SetLoadReporter(poller) // heartbeat: load, resources, capacity, queue, running
go poller.Start(ctx)

An executor that starts processes itself calls core.RecordProcessState(ctx, cmd.ProcessState) after Wait so its CPU time and peak memory teach the cost history (the SDK's exec helpers already do).

Tools: register them, the SDK reports them

The platform does not need to know a sensor's tools: the sensor registers what it runs when it starts, and every heartbeat reports it (api RFC-029 §4.3.1). The platform dispatches by that report, and its administrator can only narrow it.

s := core.NewBaseSensor(cfg, apiClient)
s.AddScanner(myScanner)                 // registered: probed with IsInstalled
executor.SetToolRegistry(s.Tools())     // the executor's scanners and collectors too
s.Tools().Register(core.ToolSpec{       // any other tool, with an optional probe
	Name: "zap", Version: "2.15.0", Capabilities: []string{"dast"},
	Probe: func(ctx context.Context) (bool, string, error) { return zapVersion(ctx) },
	Cost:  &resource.ToolCostHint{Cores: 2, MemBytes: 2 << 30, SecondsPerTarget: 60},
})
s.Tools().AddCapabilities("validate")   // served whatever the tools
s.Tools().Limit(strings.Split(os.Getenv("MY_TOOLS"), ",")...) // optional operator allowlist
  • A tool whose probe fails is reported as not installed and gets no jobs. Installed tools report their version.
  • Probes are cached for 10 minutes (SetProbeTTL, Refresh).
  • Feed the slot sizer the same list: resource.ManagerConfig{Tools: s.Tools().Names(), CostHints: s.Tools().CostHints()}.
  • SetCapabilityReporter replaces the registry with your own reporter.

Packages

What the SDK promises, what is stable, what is moving to the sensor, and how it grows without breaking sensors: docs/STABILITY.md.

Package Description
pkg/client API client for OpenCTEM API
pkg/scanners/tenable Nessus API client and .nessus converter (the other tool wrappers live in the sensor)
pkg/sensorkit The sensor runtime in one call: settings, connection, heartbeat, commands, outbox, key renewal, drain
pkg/core Core types and interfaces
pkg/errors Error types and handling
pkg/retry Retry utilities
pkg/metrics Prometheus metrics
pkg/health Health check utilities
pkg/transport HTTP/gRPC transport
pkg/httpsec Hardened HTTP client (SafeHTTPClient) with SSRF protection
pkg/credentials Credential management
pkg/connectors SCM connectors (GitHub, GitLab)
pkg/enrichers Data enrichment (CVE, NVD)
pkg/audit Structured audit logging for sensor operations
pkg/platform Components for running sensors in platform mode
pkg/sensorproto/legacyv1 Protocol v1 wire vocabulary (frozen) and migration of pre-sensor settings
pkg/ctis Common Threat Intelligence Schema (CTIS) types

Examples

See examples/ for complete examples:

  • A complete sensor on pkg/sensorkit (minimal-sensor)
  • Basic API client usage
  • Scanner integration
  • CI/CD pipeline integration
  • Custom scanner development

Building

# Run tests
go test ./...

# Generate proto files
make proto

# Lint
make lint

Contributing

We welcome contributions! Please see CONTRIBUTING.md.

  • openctemio/openctem - the platform: API (api/) and web console (web/), formerly openctemio/api and openctemio/ui
  • openctemio/sensor - the OpenCTEM sensor (binary openctemio-sensor, image ghcr.io/openctemio/sensor)

Enterprise Edition

For advanced features and enterprise support, see OpenCTEM Enterprise.

License

Apache License 2.0 - see LICENSE.

Directories

Path Synopsis
cmd
sensor-migrate command
Command sensor-migrate upgrades a Go module from the SDK's pre-sensor API (sdk-go v0.6 and older: core.BaseAgent, client.WithAgentID, platform.AgentCredentials, ...) to the sensor API (RFC-023 §9.5).
Command sensor-migrate upgrades a Go module from the SDK's pre-sensor API (sdk-go v0.6 and older: core.BaseAgent, client.WithAgentID, platform.AgentCredentials, ...) to the sensor API (RFC-023 §9.5).
examples
custom-adapter command
Example: Using the SARIF Adapter
Example: Using the SARIF Adapter
custom-connector command
Example: Building a Custom Connector
Example: Building a Custom Connector
custom-provider command
Example: Using the GitHub Provider
Example: Using the GitHub Provider
custom-scanner command
Example: Custom Scanner Implementation
Example: Custom Scanner Implementation
integration-test command
Package main provides an integration test example for the OpenCTEM SDK.
Package main provides an integration test example for the OpenCTEM SDK.
minimal-sensor command
Example: a complete OpenCTEM sensor in one file.
Example: a complete OpenCTEM sensor in one file.
internal
sensorrename
Package sensorrename is the type-aware "agent" -> "sensor" rename shared by the SDK's own rename script (scripts/rename/sensorrename) and the codemod third parties run on their code (cmd/sensor-migrate).
Package sensorrename is the type-aware "agent" -> "sensor" rename shared by the SDK's own rename script (scripts/rename/sensorrename) and the codemod third parties run on their code (cmd/sensor-migrate).
pkg
adapters
Package adapters provides a registry for scanner output adapters.
Package adapters provides a registry for scanner output adapters.
adapters/betterleaks
Package betterleaks provides an adapter to convert Betterleaks (v1) JSON reports to CTIS.
Package betterleaks provides an adapter to convert Betterleaks (v1) JSON reports to CTIS.
adapters/nuclei
Package nuclei provides an adapter to convert Nuclei JSONL output to CTIS.
Package nuclei provides an adapter to convert Nuclei JSONL output to CTIS.
adapters/sarif
Package sarif provides an adapter to convert SARIF format to CTIS.
Package sarif provides an adapter to convert SARIF format to CTIS.
adapters/semgrep
Package semgrep provides an adapter to convert Semgrep JSON output to CTIS.
Package semgrep provides an adapter to convert Semgrep JSON output to CTIS.
adapters/trivy
Package trivy provides an adapter to convert Trivy JSON output to CTIS.
Package trivy provides an adapter to convert Trivy JSON output to CTIS.
adapters/vuls
Package vuls provides an adapter to convert Vuls JSON output to CTIS.
Package vuls provides an adapter to convert Vuls JSON output to CTIS.
audit
Package audit provides structured audit logging for sensor operations.
Package audit provides structured audit logging for sensor operations.
chunk
Package chunk provides chunked upload functionality for large scan reports.
Package chunk provides chunked upload functionality for large scan reports.
client
Package client provides the OpenCTEM API client.
Package client provides the OpenCTEM API client.
compress
Package compress provides compression utilities for large payloads.
Package compress provides compression utilities for large payloads.
conformance
Package conformance is the sensor protocol conformance suite (RFC-026 WP-S3, RFC-023 D23), starting with results ingest.
Package conformance is the sensor protocol conformance suite (RFC-026 WP-S3, RFC-023 D23), starting with results ingest.
connectors
Package connectors provides base implementations and utilities for external system connectors.
Package connectors provides base implementations and utilities for external system connectors.
connectors/github
Package github provides a GitHub connector for the OpenCTEM SDK.
Package github provides a GitHub connector for the OpenCTEM SDK.
core
Package core provides the core interfaces and base implementations for the OpenCTEM Scanner SDK.
Package core provides the core interfaces and base implementations for the OpenCTEM Scanner SDK.
credentials
Package credentials provides credential management for the OpenCTEM SDK.
Package credentials provides credential management for the OpenCTEM SDK.
enrichers/epss
Package epss provides EPSS (Exploit Prediction Scoring System) enrichment for CVEs.
Package epss provides EPSS (Exploit Prediction Scoring System) enrichment for CVEs.
enrichers/kev
Package kev provides KEV (Known Exploited Vulnerabilities) enrichment.
Package kev provides KEV (Known Exploited Vulnerabilities) enrichment.
errors
Package errors provides custom error types for the OpenCTEM SDK.
Package errors provides custom error types for the OpenCTEM SDK.
gitenv
Package gitenv provides auto-detection and abstraction for CI/CD environments.
Package gitenv provides auto-detection and abstraction for CI/CD environments.
health
Package health provides health check endpoints for the OpenCTEM SDK.
Package health provides health check endpoints for the OpenCTEM SDK.
httpsec
Package httpsec provides SSRF-safe URL validation and HTTP client construction for the OpenCTEM SDK.
Package httpsec provides SSRF-safe URL validation and HTTP client construction for the OpenCTEM SDK.
internal/assetctx
Package assetctx gives the SDK's converters the asset their findings belong to.
Package assetctx gives the SDK's converters the asset their findings belong to.
internal/cirepo
Package cirepo reads the repository a CI job is building from the CI environment (GitHub Actions, GitLab CI), for converters that must name the repository their findings belong to when the caller did not.
Package cirepo reads the repository a CI job is building from the CI environment (GitHub Actions, GitLab CI), for converters that must name the repository their findings belong to when the caller did not.
metrics
Package metrics provides metrics collection and reporting for the OpenCTEM SDK.
Package metrics provides metrics collection and reporting for the OpenCTEM SDK.
mocks
Package mocks provides mock implementations for testing.
Package mocks provides mock implementations for testing.
options
Package options provides functional options pattern for SDK configuration.
Package options provides functional options pattern for SDK configuration.
outbox
Package outbox is the sensor's durable, write-ahead queue of results waiting for the platform.
Package outbox is the sensor's durable, write-ahead queue of results waiting for the platform.
pipeline
Package pipeline provides async pipeline for separating scan and upload.
Package pipeline provides async pipeline for separating scan and upload.
platform
Package platform provides components for running sensors in platform mode.
Package platform provides components for running sensors in platform mode.
providers/github
Package github provides a GitHub provider for the OpenCTEM SDK.
Package github provides a GitHub provider for the OpenCTEM SDK.
resource
Package resource provides system resource monitoring and control.
Package resource provides system resource monitoring and control.
retry
Package retry provides persistent retry queue functionality for failed API operations.
Package retry provides persistent retry queue functionality for failed API operations.
scanners/tenable
Package tenable converts Tenable Nessus / Tenable.sc ".nessus" XML exports into a CTIS report and provides a REST client for launching/exporting scans.
Package tenable converts Tenable Nessus / Tenable.sc ".nessus" XML exports into a CTIS report and provides a REST client for launching/exporting scans.
sdk
Package sdk identifies this SDK: its name and release version.
Package sdk identifies this SDK: its name and release version.
sensorkit
Package sensorkit is everything a sensor needs to work with the OpenCTEM platform, in one call: a sensor implements its tools, the kit runs the rest.
Package sensorkit is everything a sensor needs to work with the OpenCTEM platform, in one call: a sensor implements its tools, the kit runs the rest.
sensorproto/legacyv1
Package legacyv1 is the only place the SDK still speaks the pre-sensor "agent" vocabulary (RFC-023 §9.5).
Package legacyv1 is the only place the SDK still speaks the pre-sensor "agent" vocabulary (RFC-023 §9.5).
sensorproto/v2
Package v2 is the client side of sensor protocol v2 results ingest (RFC-026, api docs/rfcs/RFC-026-sensor-results-ingest.md): the paths, media type, headers, problem types, status resource, hello document and the Content-Digest the SDK sends.
Package v2 is the client side of sensor protocol v2 results ingest (RFC-026, api docs/rfcs/RFC-026-sensor-results-ingest.md): the paths, media type, headers, problem types, status resource, hello document and the Content-Digest the SDK sends.
shared/fingerprint
Package fingerprint provides unified fingerprint generation algorithms for deduplication of security findings across SDK and Backend.
Package fingerprint provides unified fingerprint generation algorithms for deduplication of security findings across SDK and Backend.
shared/severity
Package severity provides unified severity level definitions and mappings for security findings across SDK and Backend.
Package severity provides unified severity level definitions and mappings for security findings across SDK and Backend.
transport/grpc
Package grpc provides a gRPC transport layer for the OpenCTEM SDK client.
Package grpc provides a gRPC transport layer for the OpenCTEM SDK client.
useragent
Package useragent builds the User-Agent the SDK sends to the platform and to the services its collectors and enrichers call:
Package useragent builds the User-Agent the SDK sends to the platform and to the services its collectors and enrichers call:
politeness module
scripts
rename/sensorrename command
Command sensorrename performs the type-aware "agent" -> "sensor" rename of the SDK's own Go code (RFC-023 §9.5).
Command sensorrename performs the type-aware "agent" -> "sensor" rename of the SDK's own Go code (RFC-023 §9.5).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL