core

package
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Oct 2, 2026 License: GPL-3.0 Imports: 34 Imported by: 0

Documentation

Overview

Package core provides the core interfaces and base implementations for the OpenCTEM Scanner SDK. Tenants can implement these interfaces to create custom scanners, collectors, and sensors.

Package core provides the core interfaces and base implementations for the OpenCTEM Scanner SDK.

Index

Constants

View Source
const (
	// DefaultAuthBackoffBase is the delay before the first re-check after a
	// rejected heartbeat. It doubles on every further rejection.
	DefaultAuthBackoffBase = 30 * time.Second
	// DefaultAuthBackoffMax caps the delay between re-checks.
	DefaultAuthBackoffMax = 10 * time.Minute
)

Backoff after the platform rejects the sensor's API key. A rejected key does not start working by itself: the operator has to re-activate the sensor or give it a new key. Until then the sensor checks back rarely instead of sending a heartbeat and a poll every interval.

View Source
const (
	MinDoorbellHeartbeatInterval = 5 * time.Second
	MaxDoorbellHeartbeatInterval = 5 * time.Minute
	// MaxDoorbellPendingJobs is the most pending_jobs the server reports.
	MaxDoorbellPendingJobs = 100
	// DefaultDoorbellSafetyPoll is how often a doorbell-driven poller still
	// polls on its own, in case a heartbeat answer that would have rung the
	// doorbell was lost.
	DefaultDoorbellSafetyPoll = 5 * time.Minute
)

Client-side bounds on the heartbeat delay the platform advises. The server already clamps its advice; these only protect the sensor (and the platform) from a broken or hostile answer.

View Source
const (
	// EnvScanRoots is a filepath.ListSeparator-separated list (':' on Unix)
	// of directories filesystem scan targets must live under.
	EnvScanRoots = "OPENCTEM_SDK_SCAN_ROOTS"
	// EnvAllowPrivateTargets=1 permits RFC1918 / IPv6 ULA network targets.
	EnvAllowPrivateTargets = "OPENCTEM_SDK_ALLOW_PRIVATE_TARGETS"
	// EnvSensorAllowPrivateTargets is the OpenCTEM sensor's switch for the
	// same posture; honored so an on-prem sensor keeps one knob. Its
	// pre-rename name AGENT_ALLOW_PRIVATE_TARGETS still works (with a
	// deprecation warning); setting both to different values refuses every
	// target (see DefaultScanTargetPolicy).
	EnvSensorAllowPrivateTargets = "SENSOR_ALLOW_PRIVATE_TARGETS"
)

Environment variables read by DefaultScanTargetPolicy.

View Source
const MaxScanTargets = 10000

MaxScanTargets bounds the targets one scan command may carry (the platform's per-run limit).

View Source
const MaxTemplateNameLength = 128

MaxTemplateNameLength is the maximum allowed length for template names.

View Source
const MaxTemplateSize = 1024 * 1024

MaxTemplateSize is the maximum allowed size for a single template (1MB).

View Source
const MaxTemplatesPerCommand = 50

MaxTemplatesPerCommand is the maximum number of templates allowed per command.

View Source
const ScannerBetterleaks = "betterleaks"

ScannerBetterleaks is the name of the secret scanner (betterleaks), as the sensor reports it in CTIS tool.name and as the platform dispatches it.

Variables

CVSSPriority defines the priority order for CVSS sources. Higher priority sources are preferred.

View Source
var PresetScanners = map[string]*BaseScannerConfig{
	"semgrep": {
		Name:         "semgrep",
		Binary:       "semgrep",
		DefaultArgs:  []string{"scan", "--sarif", "--config", "auto", "{target}"},
		Timeout:      30 * time.Minute,
		OKExitCodes:  []int{0, 1},
		Capabilities: []string{"sast", "secret"},
	},
	"trivy-fs": {
		Name:         "trivy",
		Binary:       "trivy",
		DefaultArgs:  []string{"fs", "--format", "sarif", "{target}"},
		Timeout:      15 * time.Minute,
		OKExitCodes:  []int{0},
		Capabilities: []string{"sca", "vulnerability"},
	},
	"trivy-config": {
		Name:         "trivy",
		Binary:       "trivy",
		DefaultArgs:  []string{"config", "--format", "sarif", "{target}"},
		Timeout:      15 * time.Minute,
		OKExitCodes:  []int{0},
		Capabilities: []string{"iac", "misconfiguration"},
	},
	"betterleaks": {
		Name:         "betterleaks",
		Binary:       "betterleaks",
		DefaultArgs:  []string{"dir", "{target}", "--report-format", "sarif", "--report-path", "-", "--exit-code", "0", "--no-banner"},
		Timeout:      15 * time.Minute,
		OKExitCodes:  []int{0, 1},
		Capabilities: []string{"secret"},
	},
	"slither": {
		Name:         "slither",
		Binary:       "slither",
		DefaultArgs:  []string{"{target}", "--sarif", "/dev/stdout"},
		Timeout:      30 * time.Minute,
		OKExitCodes:  []int{0, 1, 255},
		Capabilities: []string{"web3", "smart-contract"},
	},
	"checkov": {
		Name:         "checkov",
		Binary:       "checkov",
		DefaultArgs:  []string{"-d", "{target}", "-o", "sarif", "--output-file-path", "/dev/stdout"},
		Timeout:      15 * time.Minute,
		OKExitCodes:  []int{0, 1},
		Capabilities: []string{"iac", "misconfiguration"},
	},
	"bandit": {
		Name:         "bandit",
		Binary:       "bandit",
		DefaultArgs:  []string{"-r", "{target}", "-f", "sarif", "-o", "/dev/stdout"},
		Timeout:      15 * time.Minute,
		OKExitCodes:  []int{0, 1},
		Capabilities: []string{"sast"},
	},
	"gosec": {
		Name:         "gosec",
		Binary:       "gosec",
		DefaultArgs:  []string{"-fmt", "sarif", "-out", "/dev/stdout", "{target}/..."},
		Timeout:      15 * time.Minute,
		OKExitCodes:  []int{0, 1},
		Capabilities: []string{"sast"},
	},
	"snyk": {
		Name:         "snyk",
		Binary:       "snyk",
		DefaultArgs:  []string{"code", "test", "{target}", "--sarif"},
		Timeout:      30 * time.Minute,
		OKExitCodes:  []int{0, 1},
		Capabilities: []string{"sast", "sca"},
	},
	"codeql": {
		Name:         "codeql",
		Binary:       "codeql",
		DefaultArgs:  []string{"database", "analyze", "{target}", "--format=sarif-latest", "--output=/dev/stdout"},
		Timeout:      60 * time.Minute,
		OKExitCodes:  []int{0},
		Capabilities: []string{"sast"},
	},
}

PresetScanners contains configurations for popular security tools.

View Source
var ValidTemplateTypes = map[string]bool{
	"nuclei":           true,
	"semgrep":          true,
	ScannerBetterleaks: true,
}

ValidTemplateTypes defines allowed template types for security validation. A retired type ("gitleaks") is accepted through CanonicalScannerName.

Functions

func APIKeyHint added in v0.8.0

func APIKeyHint(key string) string

APIKeyHint returns the non-secret prefix of an API key for log lines: at most 8 characters (e.g. "rda_" and 4 more) and never more than half of the key, followed by an ellipsis.

func AuthFailureAdvice added in v0.8.0

func AuthFailureAdvice(err error, keyHint string) string

AuthFailureAdvice explains a rejected request (AuthFailureStatus(err) != 0) in one actionable sentence: the HTTP status, the key's non-secret prefix (keyHint, see APIKeyHint) and what to do. A 401 saying "API key required" although the sensor sent its key means the key never reached the API: API_URL points at the web UI, or at a proxy that strips the Authorization header, rather than at the API.

func AuthFailureStatus added in v0.8.0

func AuthFailureStatus(err error) int

AuthFailureStatus returns the HTTP status (401 or 403) when err, or an error it wraps, says the platform rejected the sensor's credentials, and 0 otherwise.

func CanonicalScannerName added in v0.8.0

func CanonicalScannerName(name string) string

CanonicalScannerName returns the current name for a scanner or template type: a retired name maps to its replacement, anything else is returned unchanged.

func CheckBinaryInstalled

func CheckBinaryInstalled(ctx context.Context, binary string, versionArgs ...string) (bool, string, error)

CheckBinaryInstalled checks if a binary is installed and returns its version.

func CheckEnv added in v0.7.0

func CheckEnv() error

CheckEnv reports a configuration error in the environment variables the default scan target policy reads: SENSOR_ALLOW_PRIVATE_TARGETS and its pre-rename name set to different values, or an allow-private switch set to anything but "1" (allow) or "0"/empty (refuse). Only "1" enables the switch, so "true" or "yes" would otherwise be silently ignored and every private target refused. A sensor calls it at startup to refuse to start instead of failing jobs later.

func CommandIDFromContext added in v0.8.0

func CommandIDFromContext(ctx context.Context) string

CommandIDFromContext returns the command id set by WithCommandID, or "".

func GenerateSastFingerprint

func GenerateSastFingerprint(file, ruleID string, startLine int) string

GenerateSastFingerprint creates a fingerprint for SAST/Secret findings. Deprecated: Use fingerprint.GenerateSAST from pkg/shared/fingerprint instead.

func GenerateScaFingerprint

func GenerateScaFingerprint(pkgName, pkgVersion, vulnID string) string

GenerateScaFingerprint creates a fingerprint for SCA vulnerabilities. Deprecated: Use fingerprint.GenerateSCA from pkg/shared/fingerprint instead.

func GenerateSecretFingerprint

func GenerateSecretFingerprint(file, ruleID string, startLine int, secretValue string) string

GenerateSecretFingerprint creates a fingerprint for secret findings. Deprecated: Use fingerprint.GenerateSecret from pkg/shared/fingerprint instead.

func ListPresetScanners

func ListPresetScanners() []string

ListPresetScanners returns all available preset scanner names.

func MaskAPIKey

func MaskAPIKey(key string) string

MaskAPIKey masks an API key.

func MaskSecret

func MaskSecret(secret string) string

MaskSecret masks a secret value, showing only first and last few characters.

func MaskSecretInText added in v0.7.0

func MaskSecretInText(text, secret string) string

MaskSecretInText returns text with every occurrence of secret replaced by MaskSecret(secret). It is for context strings such as a scanner's matched line, which embed the raw secret.

It fails safe: when secret is empty or does not occur in text (the scanner trimmed or re-encoded it), the whole text is masked, because it cannot be shown that the text is free of the secret.

func NormalizeSeverity

func NormalizeSeverity(sev string) string

NormalizeSeverity normalizes severity strings from different scanners. Deprecated: Use severity.FromString from pkg/shared/severity instead.

func ScannerEnviron added in v0.7.0

func ScannerEnviron(extra ...map[string]string) []string

ScannerEnviron returns the environment for a scanner child process: the allowlisted subset of the current environment followed by extra (which wins on conflict because later entries take precedence in exec).

func SetDefaultLogger

func SetDefaultLogger(logger Logger)

SetDefaultLogger sets the global default logger.

func SetScannerEnvAllowlist added in v0.7.0

func SetScannerEnvAllowlist(names []string)

SetScannerEnvAllowlist adds variable names to the scanner environment allowlist, replacing any previous call's list (the env var list is replaced too). A name ending in '*' is treated as a prefix.

func SetScannerInheritEnv added in v0.7.0

func SetScannerInheritEnv(inherit bool)

SetScannerInheritEnv makes scanner processes inherit the full sensor environment when inherit is true (the pre-hardening behavior). Only for setups where a scanner needs variables that cannot be enumerated.

func SeverityFromCVSS

func SeverityFromCVSS(score float64) string

SeverityFromCVSS converts a CVSS score to severity level. Deprecated: Use severity.FromCVSS from pkg/shared/severity instead.

func ValidateBaseScannerConfig

func ValidateBaseScannerConfig(cfg *BaseScannerConfig) error

ValidateBaseScannerConfig validates a BaseScannerConfig.

func ValidateBaseSensorConfig added in v0.7.0

func ValidateBaseSensorConfig(cfg *BaseSensorConfig) error

ValidateBaseSensorConfig validates a BaseSensorConfig.

func ValidateCommandPollerConfig

func ValidateCommandPollerConfig(cfg *CommandPollerConfig) error

ValidateCommandPollerConfig validates a CommandPollerConfig.

func ValidateScanOptions

func ValidateScanOptions(opts *ScanOptions) error

ValidateScanOptions validates ScanOptions.

func ValidateTemplate

func ValidateTemplate(tpl *EmbeddedTemplate) error

ValidateTemplate validates an embedded template for security issues. It checks for path traversal, valid template types, and size limits.

func WithCommandID added in v0.8.0

func WithCommandID(ctx context.Context, cmdID string) context.Context

WithCommandID returns a context that says the work done under it belongs to the platform command cmdID. The CommandPoller sets it around every command, so a Pusher can bind the results it pushes to that command (protocol v2's command-bound resource) and report the command only after them.

Types

type APIKeyHinter added in v0.8.0

type APIKeyHinter interface {
	APIKeyHint() string
}

APIKeyHinter is implemented by a Pusher that can name its API key in log lines without revealing it (*client.Client does).

type Adapter

type Adapter interface {
	// Name returns the adapter name (e.g., "sarif", "cyclonedx")
	Name() string

	// InputFormats returns supported input formats
	InputFormats() []string

	// OutputFormat returns the output format (usually "ctis")
	OutputFormat() string

	// CanConvert checks if the input can be converted
	CanConvert(input []byte) bool

	// Convert transforms input to CTIS Report
	Convert(ctx context.Context, input []byte, opts *AdapterOptions) (*ctis.Report, error)
}

Adapter translates between different data formats and CTIS.

type AdapterOptions

type AdapterOptions struct {
	// Source information
	SourceName string `yaml:"source_name" json:"source_name"`
	SourceType string `yaml:"source_type" json:"source_type"`

	// Target repository/asset info
	Repository string `yaml:"repository" json:"repository"`
	Branch     string `yaml:"branch" json:"branch"`
	CommitSHA  string `yaml:"commit_sha" json:"commit_sha"`

	// Filtering
	MinSeverity string `yaml:"min_severity" json:"min_severity"`

	// Debug
	Verbose bool `yaml:"verbose" json:"verbose"`
}

AdapterOptions configures adapter behavior.

type AssetResolver added in v0.7.0

type AssetResolver func(scanner, target string) (ctis.AssetType, string)

AssetResolver returns the asset a scan of target (already validated) should report its findings on, or an empty value to let the parser decide.

type AuthGate added in v0.8.0

type AuthGate struct {
	// contains filtered or unexported fields
}

AuthGate tracks whether the platform accepts the sensor's heartbeats. The heartbeat loop reports every outcome to it (Observe). While the key is rejected the gate turns the heartbeat interval into a capped exponential backoff and tells the command poller to stop polling (Rejected); the first accepted heartbeat lifts it. It logs without verbose mode, but only once per backoff step or change of state, never once per request. Safe for concurrent use.

func NewAuthGate added in v0.8.0

func NewAuthGate(cfg *AuthGateConfig) *AuthGate

NewAuthGate creates an AuthGate. A nil config uses the defaults.

func (*AuthGate) MarkRejected added in v0.8.0

func (g *AuthGate) MarkRejected(err error, keyHint string)

MarkRejected records an authentication failure seen outside the heartbeat (a command poll). It stops polling until the next accepted heartbeat but leaves the backoff to the heartbeat loop. It logs only on the change of state.

func (*AuthGate) Observe added in v0.8.0

func (g *AuthGate) Observe(err error, keyHint string) time.Duration

Observe records the outcome of one heartbeat (or, for a poller without a heartbeat to share a gate with, one poll). keyHint names the key in the log line (see APIKeyHint). It returns the delay before the next heartbeat when the gate overrides the normal interval (a rejected key), or 0 when the caller keeps its own interval.

func (*AuthGate) Rejected added in v0.8.0

func (g *AuthGate) Rejected() bool

Rejected reports whether the last heartbeat (or poll) was rejected for authentication. The command poller does not poll while it is true.

type AuthGateConfig added in v0.8.0

type AuthGateConfig struct {
	// BackoffBase is the first re-check delay after a rejection.
	BackoffBase time.Duration
	// BackoffMax caps the re-check delay.
	BackoffMax time.Duration
	// Logger receives the state changes. Nil logs to stderr with timestamps.
	Logger *log.Logger
}

AuthGateConfig configures an AuthGate. Zero values use the defaults.

type BaseCollector

type BaseCollector struct {
	// contains filtered or unexported fields
}

BaseCollector provides a base implementation for collectors. Embed this in your custom collector to get common functionality.

func NewBaseCollector

func NewBaseCollector(cfg *BaseCollectorConfig) *BaseCollector

NewBaseCollector creates a new base collector.

func (*BaseCollector) Collect

func (c *BaseCollector) Collect(ctx context.Context, opts *CollectOptions) (*CollectResult, error)

Collect is the base collect method - override in your implementation.

func (*BaseCollector) FetchJSON

func (c *BaseCollector) FetchJSON(ctx context.Context, path string, query map[string]string, result interface{}) error

FetchJSON makes an HTTP GET request and decodes JSON response.

func (*BaseCollector) Name

func (c *BaseCollector) Name() string

Name returns the collector name.

func (*BaseCollector) SetVerbose

func (c *BaseCollector) SetVerbose(v bool)

SetVerbose sets verbose mode.

func (*BaseCollector) TestConnection

func (c *BaseCollector) TestConnection(ctx context.Context) error

TestConnection tests the connection to the source.

func (*BaseCollector) Type

func (c *BaseCollector) Type() string

Type returns the source type.

type BaseCollectorConfig

type BaseCollectorConfig struct {
	Name       string            `yaml:"name" json:"name"`
	SourceType string            `yaml:"source_type" json:"source_type"`
	BaseURL    string            `yaml:"base_url" json:"base_url"`
	APIKey     string            `yaml:"api_key" json:"api_key"`
	Headers    map[string]string `yaml:"headers" json:"headers"`
	Timeout    time.Duration     `yaml:"timeout" json:"timeout"`
	Verbose    bool              `yaml:"verbose" json:"verbose"`
}

BaseCollectorConfig configures a BaseCollector.

type BaseParser

type BaseParser struct {
	// contains filtered or unexported fields
}

BaseParser provides a base implementation for custom parsers. Embed this in your custom parser for common functionality.

func NewBaseParser

func NewBaseParser(name string, formats []string) *BaseParser

NewBaseParser creates a new base parser.

func (*BaseParser) CanParse

func (p *BaseParser) CanParse(data []byte) bool

CanParse default implementation - override in your parser.

func (*BaseParser) CreateFinding

func (p *BaseParser) CreateFinding(id, title string, severity ctis.Severity) ctis.Finding

CreateFinding is a helper to create a finding with common fields set.

func (*BaseParser) CreateReport

func (p *BaseParser) CreateReport(toolName, toolVersion string) *ctis.Report

CreateReport is a helper to create a new report.

func (*BaseParser) Name

func (p *BaseParser) Name() string

Name returns the parser name.

func (*BaseParser) Parse

func (p *BaseParser) Parse(ctx context.Context, data []byte, opts *ParseOptions) (*ctis.Report, error)

Parse default implementation - override in your parser.

func (*BaseParser) SupportedFormats

func (p *BaseParser) SupportedFormats() []string

SupportedFormats returns supported formats.

type BaseProcessor

type BaseProcessor struct {
	// contains filtered or unexported fields
}

BaseProcessor provides a default implementation of the Processor interface.

func NewBaseProcessor

func NewBaseProcessor(pusher Pusher) *BaseProcessor

NewBaseProcessor creates a new processor.

func (*BaseProcessor) AddParser

func (p *BaseProcessor) AddParser(parser Parser)

AddParser adds a custom parser.

func (*BaseProcessor) Process

func (p *BaseProcessor) Process(ctx context.Context, scanner Scanner, opts *ProcessOptions) (*ProcessResult, error)

Process runs a complete scan workflow: scan -> parse -> push.

func (*BaseProcessor) ProcessBatch

func (p *BaseProcessor) ProcessBatch(ctx context.Context, scanners []Scanner, opts *ProcessOptions) ([]*ProcessResult, error)

ProcessBatch runs multiple scanners in parallel.

func (*BaseProcessor) SetVerbose

func (p *BaseProcessor) SetVerbose(v bool)

SetVerbose sets verbose mode.

type BaseScanner

type BaseScanner struct {
	// contains filtered or unexported fields
}

BaseScanner provides a base implementation for scanners. Embed this in your custom scanner to get common functionality.

func NewBaseScanner

func NewBaseScanner(cfg *BaseScannerConfig) *BaseScanner

NewBaseScanner creates a new base scanner with the given config.

func NewPresetScanner

func NewPresetScanner(name string) (*BaseScanner, error)

NewPresetScanner creates a scanner from a preset configuration.

func (*BaseScanner) BuildArgs

func (s *BaseScanner) BuildArgs(target string, opts *ScanOptions) []string

BuildArgs builds command arguments. Override this in your scanner for custom argument handling.

func (*BaseScanner) Capabilities

func (s *BaseScanner) Capabilities() []string

Capabilities returns what the scanner can detect.

func (*BaseScanner) IsInstalled

func (s *BaseScanner) IsInstalled(ctx context.Context) (bool, string, error)

IsInstalled checks if the scanner binary is available.

func (*BaseScanner) Name

func (s *BaseScanner) Name() string

Name returns the scanner name.

func (*BaseScanner) Scan

func (s *BaseScanner) Scan(ctx context.Context, target string, opts *ScanOptions) (*ScanResult, error)

Scan executes the scanner with the given options. This is the base implementation - override BuildArgs in your scanner for customization.

func (*BaseScanner) SetVerbose

func (s *BaseScanner) SetVerbose(v bool)

SetVerbose sets verbose mode.

func (*BaseScanner) Version

func (s *BaseScanner) Version() string

Version returns the scanner version.

type BaseScannerConfig

type BaseScannerConfig struct {
	Name         string            `yaml:"name" json:"name"`
	Version      string            `yaml:"version" json:"version"`
	Binary       string            `yaml:"binary" json:"binary"`
	DefaultArgs  []string          `yaml:"default_args" json:"default_args"`
	Timeout      time.Duration     `yaml:"timeout" json:"timeout"`
	OKExitCodes  []int             `yaml:"ok_exit_codes" json:"ok_exit_codes"`
	Capabilities []string          `yaml:"capabilities" json:"capabilities"`
	WorkDir      string            `yaml:"work_dir" json:"work_dir"`
	Env          map[string]string `yaml:"env" json:"env"`
	Verbose      bool              `yaml:"verbose" json:"verbose"`
}

BaseScannerConfig configures a BaseScanner.

type BaseSensor added in v0.7.0

type BaseSensor struct {
	// contains filtered or unexported fields
}

BaseSensor provides a base implementation for sensors. Embed this in your custom sensor to get common functionality.

func NewBaseSensor added in v0.7.0

func NewBaseSensor(cfg *BaseSensorConfig, pusher Pusher) *BaseSensor

NewBaseSensor creates a new base sensor.

func (*BaseSensor) AddCollector added in v0.7.0

func (a *BaseSensor) AddCollector(collector Collector) error

AddCollector adds a collector to the sensor.

func (*BaseSensor) AddParser added in v0.7.0

func (a *BaseSensor) AddParser(parser Parser)

AddParser adds a custom parser to the sensor.

func (*BaseSensor) AddScanner added in v0.7.0

func (a *BaseSensor) AddScanner(scanner Scanner) error

AddScanner adds a scanner to the sensor.

func (*BaseSensor) AuthGate added in v0.8.0

func (a *BaseSensor) AuthGate() *AuthGate

AuthGate returns the gate that tracks whether the platform accepts this sensor's heartbeats. Share it with a CommandPoller (SetAuthGate) so the poller stops polling while the key is rejected; a poller sharing the sensor's doorbell gets it automatically.

func (*BaseSensor) FirstHeartbeat added in v0.8.0

func (a *BaseSensor) FirstHeartbeat(ctx context.Context) (time.Duration, error)

FirstHeartbeat sends the sensor's first heartbeat now, before Start, and returns the delay before the next one and the heartbeat's error. It is the daemon's connection check: it goes through the doorbell and the AuthGate exactly like the heartbeat loop, so a rejected key is logged and backed off the same way (AuthFailureStatus(err) != 0; wait the returned delay before trying again). The next Start does not send another heartbeat at once: its first one follows the returned delay. Without this, a daemon that checked the connection with Pusher.TestConnection sent two heartbeats back to back.

func (*BaseSensor) Name added in v0.7.0

func (a *BaseSensor) Name() string

Name returns the sensor name.

func (*BaseSensor) RemoveCollector added in v0.7.0

func (a *BaseSensor) RemoveCollector(name string) error

RemoveCollector removes a collector from the sensor.

func (*BaseSensor) RemoveScanner added in v0.7.0

func (a *BaseSensor) RemoveScanner(name string) error

RemoveScanner removes a scanner from the sensor.

func (*BaseSensor) SetAssetResolver added in v0.8.0

func (a *BaseSensor) SetAssetResolver(r AssetResolver)

SetAssetResolver names the asset a scheduled scan's findings are filed on (for example the repository a directory belongs to). Without it a parser may emit findings without an asset, which protocol v2 rejects (RFC-026).

func (*BaseSensor) SetAuthGate added in v0.8.0

func (a *BaseSensor) SetAuthGate(g *AuthGate)

SetAuthGate replaces the sensor's AuthGate (nil is ignored). Call before Start.

func (*BaseSensor) SetDoorbell added in v0.7.1

func (a *BaseSensor) SetDoorbell(d *Doorbell)

SetDoorbell makes the sensor act on the heartbeat doorbell: heartbeats announce the feature, their hints go to d, the advised interval replaces the configured one, and scheduled scans and collections are skipped while the platform has paused or drained the sensor. Share d with the CommandPoller (CommandPoller.SetDoorbell) so it polls when the doorbell rings. The pusher must implement DoorbellPusher (*client.Client does); otherwise heartbeats stay plain. Call before Start.

func (*BaseSensor) SetVerbose added in v0.7.0

func (a *BaseSensor) SetVerbose(v bool)

SetVerbose sets verbose mode.

func (*BaseSensor) Start added in v0.7.0

func (a *BaseSensor) Start(ctx context.Context) error

Start starts the sensor.

func (*BaseSensor) Status added in v0.7.0

func (a *BaseSensor) Status() *SensorStatus

Status returns the current sensor status.

func (*BaseSensor) Stop added in v0.7.0

func (a *BaseSensor) Stop(ctx context.Context) error

Stop stops the sensor gracefully.

type BaseSensorConfig added in v0.7.0

type BaseSensorConfig struct {
	Name              string        `yaml:"name" json:"name"`
	Version           string        `yaml:"version" json:"version"`
	Region            string        `yaml:"region" json:"region"` // Deployment region (e.g., "us-east-1", "ap-southeast-1")
	ScanInterval      time.Duration `yaml:"scan_interval" json:"scan_interval"`
	CollectInterval   time.Duration `yaml:"collect_interval" json:"collect_interval"`
	HeartbeatInterval time.Duration `yaml:"heartbeat_interval" json:"heartbeat_interval"`
	Targets           []string      `yaml:"targets" json:"targets"`
	Verbose           bool          `yaml:"verbose" json:"verbose"`
}

BaseSensorConfig configures a BaseSensor.

type CVSSData

type CVSSData struct {
	Source CVSSSource `json:"source"`
	Score  float64    `json:"score"`
	Vector string     `json:"vector"`
}

CVSSData holds CVSS information from various sources.

func SelectBestCVSS

func SelectBestCVSS(cvssMap map[CVSSSource]CVSSData) *CVSSData

SelectBestCVSS selects the best CVSS data from multiple sources. Uses priority order: NVD > GHSA > RedHat > Bitnami

type CVSSSource

type CVSSSource string

CVSSSource represents the source of CVSS data.

const (
	CVSSSourceNVD     CVSSSource = "nvd"     // National Vulnerability Database
	CVSSSourceGHSA    CVSSSource = "ghsa"    // GitHub Security Advisory
	CVSSSourceRedHat  CVSSSource = "redhat"  // Red Hat
	CVSSSourceBitnami CVSSSource = "bitnami" // Bitnami
)

type CacheStats

type CacheStats struct {
	TemplateCount int            `json:"template_count"`
	TotalSize     int64          `json:"total_size"`
	TenantCount   map[string]int `json:"tenant_count"`
	TypeCount     map[string]int `json:"type_count"`
	OldestEntry   time.Time      `json:"oldest_entry"`
	NewestEntry   time.Time      `json:"newest_entry"`
}

CacheStats contains cache statistics.

type CachedTemplateMetadata

type CachedTemplateMetadata struct {
	ID           string    `json:"id"`
	Name         string    `json:"name"`
	TemplateType string    `json:"template_type"`
	ContentHash  string    `json:"content_hash"`
	TenantID     string    `json:"tenant_id"`
	FilePath     string    `json:"file_path"`
	Size         int64     `json:"size"`
	CachedAt     time.Time `json:"cached_at"`
	LastUsedAt   time.Time `json:"last_used_at"`
}

CachedTemplateMetadata stores metadata about a cached template.

type CollectCommandPayload

type CollectCommandPayload struct {
	Collector    string                 `json:"collector"`
	SourceConfig map[string]interface{} `json:"source_config,omitempty"`
}

CollectCommandPayload is the payload for collect commands.

type CollectOptions

type CollectOptions struct {
	// Source configuration
	SourceURL string            `yaml:"source_url" json:"source_url"`
	APIKey    string            `yaml:"api_key" json:"api_key"`
	Headers   map[string]string `yaml:"headers" json:"headers"`
	Query     map[string]string `yaml:"query" json:"query"`

	// Filtering
	Since      int64    `yaml:"since" json:"since"` // Unix timestamp
	Repository string   `yaml:"repository" json:"repository"`
	Branches   []string `yaml:"branches" json:"branches"`

	// Pagination
	PageSize int `yaml:"page_size" json:"page_size"`
	MaxPages int `yaml:"max_pages" json:"max_pages"`
}

CollectOptions configures data collection.

type CollectResult

type CollectResult struct {
	// Source info
	SourceName string `json:"source_name"`
	SourceType string `json:"source_type"`

	// Collection timing
	CollectedAt int64 `json:"collected_at"`
	DurationMs  int64 `json:"duration_ms"`

	// Results
	Reports    []*ctis.Report `json:"reports"`
	TotalItems int            `json:"total_items"`
	ErrorItems int            `json:"error_items"`

	// Cursor for pagination
	NextCursor string `json:"next_cursor,omitempty"`
	HasMore    bool   `json:"has_more"`
}

CollectResult holds collected data.

type Collector

type Collector interface {
	// Name returns the collector name
	Name() string

	// Type returns the source type (e.g., "github", "gitlab", "api")
	Type() string

	// Collect pulls data from the external source
	Collect(ctx context.Context, opts *CollectOptions) (*CollectResult, error)

	// TestConnection verifies the connection to the external source
	TestConnection(ctx context.Context) error
}

Collector pulls security data from external sources (GitHub, GitLab, etc.). Implement this interface to create a custom collector.

type Command

type Command struct {
	ID        string          `json:"id"`
	Type      string          `json:"type"`
	Priority  string          `json:"priority"`
	Payload   json.RawMessage `json:"payload"`
	CreatedAt time.Time       `json:"created_at"`
	ExpiresAt time.Time       `json:"expires_at"`
}

Command represents a server command.

type CommandClient

type CommandClient interface {
	GetCommands(ctx context.Context) (*GetCommandsResponse, error)
	AcknowledgeCommand(ctx context.Context, cmdID string) error
	StartCommand(ctx context.Context, cmdID string) error
	ReportCommandResult(ctx context.Context, cmdID string, result *CommandResult) error
	ReportCommandProgress(ctx context.Context, cmdID string, progress int, message string) error
}

CommandClient interface for command-related API operations.

type CommandExecutionResult

type CommandExecutionResult struct {
	DurationMs    int64
	FindingsCount int
	ExitCode      int
	Metadata      map[string]interface{}
}

CommandExecutionResult is the result of command execution.

type CommandExecutor

type CommandExecutor interface {
	Execute(ctx context.Context, cmd *Command) (*CommandExecutionResult, error)
}

CommandExecutor executes commands.

type CommandPoller

type CommandPoller struct {
	// contains filtered or unexported fields
}

CommandPoller polls the server for pending commands.

func NewCommandPoller

func NewCommandPoller(client CommandClient, executor CommandExecutor, cfg *CommandPollerConfig) *CommandPoller

NewCommandPoller creates a new command poller.

func (*CommandPoller) SetAuthGate added in v0.8.0

func (p *CommandPoller) SetAuthGate(g *AuthGate)

SetAuthGate shares the heartbeat's AuthGate (BaseSensor.AuthGate) with the poller: it does not poll while the platform rejects the key, and resumes with the first accepted heartbeat. A poller sharing the sensor's doorbell already gets it. Call before Start.

func (*CommandPoller) SetDoorbell added in v0.7.1

func (p *CommandPoller) SetDoorbell(d *Doorbell)

SetDoorbell makes the poller follow the heartbeat doorbell: it polls when the doorbell rings, and while the server sends hints it drops the fixed interval for a long safety poll (CommandPollerConfig.DoorbellSafetyPoll). While the platform has paused or drained the sensor it claims nothing. Share d with the heartbeat (BaseSensor.SetDoorbell). Call before Start.

func (*CommandPoller) SetVerbose

func (p *CommandPoller) SetVerbose(v bool)

SetVerbose sets verbose mode.

func (*CommandPoller) Start

func (p *CommandPoller) Start(ctx context.Context) error

Start starts the command poller.

func (*CommandPoller) Stop

func (p *CommandPoller) Stop()

Stop stops the command poller.

type CommandPollerConfig

type CommandPollerConfig struct {
	PollInterval  time.Duration `yaml:"poll_interval" json:"poll_interval"`
	MaxConcurrent int           `yaml:"max_concurrent" json:"max_concurrent"`
	AllowedTypes  []string      `yaml:"allowed_types" json:"allowed_types"`
	Verbose       bool          `yaml:"verbose" json:"verbose"`
	// DoorbellSafetyPoll is how often the poller still polls on its own
	// while the heartbeat doorbell is active. Default 5m.
	DoorbellSafetyPoll time.Duration `yaml:"doorbell_safety_poll" json:"doorbell_safety_poll"`
}

CommandPollerConfig configures a CommandPoller.

func DefaultCommandPollerConfig

func DefaultCommandPollerConfig() *CommandPollerConfig

DefaultCommandPollerConfig returns default config.

type CommandResult

type CommandResult struct {
	Status        string                 `json:"status"`
	CompletedAt   time.Time              `json:"completed_at"`
	DurationMs    int64                  `json:"duration_ms"`
	ExitCode      int                    `json:"exit_code"`
	FindingsCount int                    `json:"findings_count"`
	Error         string                 `json:"error,omitempty"`
	Metadata      map[string]interface{} `json:"metadata,omitempty"`
}

CommandResult represents the result of command execution.

type Connector

type Connector interface {
	// Name returns the connector name (e.g., "github", "gitlab")
	Name() string

	// Type returns the connector type (e.g., "scm", "cloud", "ticketing")
	Type() string

	// Connect establishes connection to the external system
	Connect(ctx context.Context) error

	// Close closes the connection
	Close() error

	// IsConnected returns true if connected
	IsConnected() bool

	// TestConnection verifies the connection is working
	TestConnection(ctx context.Context) error

	// HTTPClient returns the configured HTTP client (with auth headers)
	HTTPClient() *http.Client

	// RateLimited returns true if rate limiting is enabled
	RateLimited() bool

	// WaitForRateLimit blocks until rate limit allows next request
	WaitForRateLimit(ctx context.Context) error
}

Connector manages connection to an external system with authentication, rate limiting, and connection pooling.

type ConnectorConfig

type ConnectorConfig struct {
	// Authentication
	APIKey      string       `yaml:"api_key" json:"api_key"`
	Token       string       `yaml:"token" json:"token"`
	Username    string       `yaml:"username" json:"username"`
	Password    string       `yaml:"password" json:"password"`
	OAuthConfig *OAuthConfig `yaml:"oauth" json:"oauth,omitempty"`

	// Connection
	BaseURL string        `yaml:"base_url" json:"base_url"`
	Timeout time.Duration `yaml:"timeout" json:"timeout"`

	// Rate limiting
	RateLimit  int `yaml:"rate_limit" json:"rate_limit"`   // requests per hour
	BurstLimit int `yaml:"burst_limit" json:"burst_limit"` // burst size

	// Retry
	MaxRetries int           `yaml:"max_retries" json:"max_retries"`
	RetryDelay time.Duration `yaml:"retry_delay" json:"retry_delay"`

	// Debug
	Verbose bool `yaml:"verbose" json:"verbose"`
}

ConnectorConfig holds common configuration for connectors.

type DNSRecord

type DNSRecord struct {
	Host       string   `json:"host"`                  // Hostname
	RecordType string   `json:"record_type"`           // A, AAAA, CNAME, MX, NS, TXT, SOA
	Values     []string `json:"values"`                // Record values
	TTL        int      `json:"ttl,omitempty"`         // Time to live
	Resolver   string   `json:"resolver,omitempty"`    // Resolver used
	StatusCode string   `json:"status_code,omitempty"` // NOERROR, NXDOMAIN, etc.
}

DNSRecord represents a DNS record.

type DefaultCommandExecutor

type DefaultCommandExecutor struct {
	// contains filtered or unexported fields
}

DefaultCommandExecutor provides default command execution.

func NewDefaultCommandExecutor

func NewDefaultCommandExecutor(pusher Pusher) *DefaultCommandExecutor

NewDefaultCommandExecutor creates a new default executor.

Scan targets are validated with DefaultScanTargetPolicy() (SSRF blocklist, filesystem confinement, flag-injection guard); override with SetScanTargetPolicy.

func (*DefaultCommandExecutor) AddCollector

func (e *DefaultCommandExecutor) AddCollector(collector Collector)

AddCollector adds a collector.

func (*DefaultCommandExecutor) AddScanner

func (e *DefaultCommandExecutor) AddScanner(scanner Scanner)

AddScanner adds a scanner.

func (*DefaultCommandExecutor) Execute

Execute executes a command.

func (*DefaultCommandExecutor) ScanTargetPolicy added in v0.7.0

func (e *DefaultCommandExecutor) ScanTargetPolicy() *ScanTargetPolicy

ScanTargetPolicy returns the policy currently applied to scan targets.

func (*DefaultCommandExecutor) SetAssetResolver added in v0.7.0

func (e *DefaultCommandExecutor) SetAssetResolver(r AssetResolver)

SetAssetResolver sets how executeScan names the scanned asset. Without one, parsers that cannot tell the asset from the scanner output (a filesystem scan by betterleaks, for one) send findings without an asset, which the platform files under a placeholder. Set it before the poller starts.

func (*DefaultCommandExecutor) SetParserRegistry added in v0.5.1

func (e *DefaultCommandExecutor) SetParserRegistry(r *ParserRegistry)

SetParserRegistry supplies the registry used to convert a scanner's raw output into a CTIS report. executeScan uses the parser registered under the scanner's name, else the first parser that recognizes the content (see ParserRegistry.ForScanner). Without a registry only SARIF output is understood. Output no parser recognizes fails the command instead of being reported as 0 findings — register a parser for every scanner that does not emit SARIF (betterleaks, semgrep, trivy, nuclei).

func (*DefaultCommandExecutor) SetScanTargetPolicy added in v0.7.0

func (e *DefaultCommandExecutor) SetScanTargetPolicy(p *ScanTargetPolicy)

SetScanTargetPolicy replaces the policy used to validate server-supplied scan targets (allowed filesystem roots, private-range opt-in). A nil policy restores DefaultScanTargetPolicy(). Safe to call concurrently with Execute.

func (*DefaultCommandExecutor) SetVerbose

func (e *DefaultCommandExecutor) SetVerbose(v bool)

SetVerbose sets verbose mode.

type DefaultLogger

type DefaultLogger struct {
	// contains filtered or unexported fields
}

DefaultLogger is the default logger implementation using standard library.

func NewDefaultLogger

func NewDefaultLogger(prefix string, level LogLevel) *DefaultLogger

NewDefaultLogger creates a new default logger.

func (*DefaultLogger) Debug

func (l *DefaultLogger) Debug(format string, args ...interface{})

Debug logs a debug message.

func (*DefaultLogger) Error

func (l *DefaultLogger) Error(format string, args ...interface{})

Error logs an error message.

func (*DefaultLogger) Info

func (l *DefaultLogger) Info(format string, args ...interface{})

Info logs an info message.

func (*DefaultLogger) SetLevel

func (l *DefaultLogger) SetLevel(level LogLevel)

SetLevel sets the log level.

func (*DefaultLogger) SetOutput

func (l *DefaultLogger) SetOutput(w io.Writer)

SetOutput sets the output writer.

func (*DefaultLogger) Warn

func (l *DefaultLogger) Warn(format string, args ...interface{})

Warn logs a warning message.

type DiscoveredURL

type DiscoveredURL struct {
	URL        string `json:"url"`              // Full URL
	Method     string `json:"method,omitempty"` // HTTP method (GET, POST, etc.)
	Source     string `json:"source,omitempty"` // Discovery source (crawl, js, archive)
	StatusCode int    `json:"status_code,omitempty"`
	Depth      int    `json:"depth,omitempty"`     // Crawl depth
	Parent     string `json:"parent,omitempty"`    // Parent URL
	Type       string `json:"type,omitempty"`      // endpoint, form, api, static
	Extension  string `json:"extension,omitempty"` // File extension
}

DiscoveredURL represents a discovered URL/endpoint.

type Doorbell added in v0.7.1

type Doorbell struct {
	// contains filtered or unexported fields
}

Doorbell holds what the platform said on the last heartbeat and turns it into sensor behavior: it wakes the command poller when work is waiting, pauses and resumes job intake, and triggers key renewal. One Doorbell is shared by the heartbeat loop (which calls Handle) and the poller (which reads Paused, HintsActive and Wake). Safe for concurrent use.

func NewDoorbell added in v0.7.1

func NewDoorbell(cfg *DoorbellConfig) *Doorbell

NewDoorbell creates a Doorbell. A nil config uses defaults.

func (*Doorbell) ConfigVersion added in v0.7.1

func (d *Doorbell) ConfigVersion() string

ConfigVersion returns the last config version the platform sent, or "".

func (*Doorbell) Draining added in v0.7.1

func (d *Doorbell) Draining() bool

Draining reports whether the platform drained this sensor.

func (*Doorbell) Handle added in v0.7.1

func (d *Doorbell) Handle(h *HeartbeatHints)

Handle applies one heartbeat answer. A nil hints value (or one with Present=false) means the server does not speak the doorbell: the poller falls back to its fixed interval.

func (*Doorbell) HeartbeatFailed added in v0.7.1

func (d *Doorbell) HeartbeatFailed()

HeartbeatFailed records that a heartbeat got no answer. The doorbell can no longer ring, so the poller returns to its fixed interval until the next answered heartbeat. Pause and drain are kept.

func (*Doorbell) HintsActive added in v0.7.1

func (d *Doorbell) HintsActive() bool

HintsActive reports whether the last heartbeat answer carried hints, i.e. the poller may rely on the doorbell instead of polling on a fixed interval.

func (*Doorbell) Paused added in v0.7.1

func (d *Doorbell) Paused() bool

Paused reports whether the sensor must not claim or start new jobs (pause or drain).

func (*Doorbell) PendingJobs added in v0.7.1

func (d *Doorbell) PendingJobs() int

PendingJobs returns pending_jobs from the last heartbeat answer.

func (*Doorbell) State added in v0.7.1

func (d *Doorbell) State() string

State is a one-line, human-readable job-intake state for status output.

func (*Doorbell) Wake added in v0.7.1

func (d *Doorbell) Wake() <-chan struct{}

Wake fires when the platform reported claimable work.

type DoorbellConfig added in v0.7.1

type DoorbellConfig struct {
	// OnRotateKey is called (in its own goroutine) when the platform sends
	// rotate_key, e.g. platform.KeyRenewManager.RenewNow. Nil: rotate_key is
	// logged and otherwise ignored.
	OnRotateKey func()
	// Logger receives the doorbell's state changes. Nil logs to stderr with
	// timestamps.
	Logger *log.Logger
	// Verbose also logs every heartbeat's hints.
	Verbose bool
}

DoorbellConfig configures a Doorbell.

type DoorbellPusher added in v0.7.1

type DoorbellPusher interface {
	SendHeartbeatWithHints(ctx context.Context, status *SensorStatus) (*HeartbeatHints, error)
}

DoorbellPusher is a Pusher whose heartbeat announces the doorbell and returns the hints. *client.Client implements it.

type EmbeddedTemplate

type EmbeddedTemplate struct {
	ID           string `json:"id"`
	Name         string `json:"name"`
	TemplateType string `json:"template_type"` // nuclei, semgrep, betterleaks
	Content      string `json:"content"`       // Base64-encoded template content (YAML/TOML)
	ContentHash  string `json:"content_hash"`  // SHA256 hash of decoded content for verification
}

EmbeddedTemplate is a custom template embedded in scan command payload. Templates are sent from the platform and written to temp dir before scan.

type Enricher

type Enricher interface {
	// Name returns the enricher name (e.g., "epss", "kev", "nvd")
	Name() string

	// Enrich adds threat intel to a single finding
	Enrich(ctx context.Context, finding *ctis.Finding) (*ctis.Finding, error)

	// EnrichBatch adds threat intel to multiple findings
	EnrichBatch(ctx context.Context, findings []ctis.Finding) ([]ctis.Finding, error)
}

Enricher adds threat intelligence data to findings.

type EnricherConfig

type EnricherConfig struct {
	// API endpoint (optional, for custom sources)
	Endpoint string `yaml:"endpoint" json:"endpoint"`

	// Cache duration
	CacheTTL time.Duration `yaml:"cache_ttl" json:"cache_ttl"`

	// Rate limiting
	RateLimit int `yaml:"rate_limit" json:"rate_limit"`

	// Debug
	Verbose bool `yaml:"verbose" json:"verbose"`
}

EnricherConfig holds enricher configuration.

type ExecConfig

type ExecConfig struct {
	Binary  string            // Scanner binary path
	Args    []string          // Command arguments
	WorkDir string            // Working directory
	Env     map[string]string // Extra environment variables (added to the allowlisted base, see ScannerEnviron)
	Timeout time.Duration     // Execution timeout
	Verbose bool              // Stream output to logs
}

ExecConfig configures scanner execution.

type ExecResult

type ExecResult struct {
	ExitCode   int
	Stdout     []byte
	Stderr     []byte
	DurationMs int64
	Error      error
}

ExecResult holds the result of scanner execution.

func ExecuteScanner

func ExecuteScanner(ctx context.Context, cfg *ExecConfig) (*ExecResult, error)

ExecuteScanner runs a scanner binary with real-time output streaming.

func StreamScanner

func StreamScanner(ctx context.Context, cfg *ExecConfig, handler OutputHandler) (*ExecResult, error)

StreamScanner runs a scanner with real-time output handling.

type GetCommandsResponse

type GetCommandsResponse struct {
	Commands            []*Command `json:"commands"`
	PollIntervalSeconds int        `json:"poll_interval_seconds,omitempty"`
}

GetCommandsResponse is the response from GetCommands.

type GitHubAlertInstance

type GitHubAlertInstance struct {
	Ref      string              `json:"ref"`
	State    string              `json:"state"`
	Location GitHubAlertLocation `json:"location"`
	Message  GitHubAlertMessage  `json:"message"`
}

GitHubAlertInstance represents an instance of the alert.

type GitHubAlertLocation

type GitHubAlertLocation struct {
	Path        string `json:"path"`
	StartLine   int    `json:"start_line"`
	EndLine     int    `json:"end_line"`
	StartColumn int    `json:"start_column"`
	EndColumn   int    `json:"end_column"`
}

GitHubAlertLocation represents the location of the alert.

type GitHubAlertMessage

type GitHubAlertMessage struct {
	Text string `json:"text"`
}

GitHubAlertMessage represents the alert message.

type GitHubAlertRule

type GitHubAlertRule struct {
	ID                    string   `json:"id"`
	Severity              string   `json:"severity"`
	SecuritySeverityLevel string   `json:"security_severity_level"`
	Description           string   `json:"description"`
	Tags                  []string `json:"tags"`
}

GitHubAlertRule represents the rule that triggered the alert.

type GitHubAlertTool

type GitHubAlertTool struct {
	Name    string `json:"name"`
	Version string `json:"version"`
}

GitHubAlertTool represents the tool that found the alert.

type GitHubCodeScanningAlert

type GitHubCodeScanningAlert struct {
	Number             int                 `json:"number"`
	State              string              `json:"state"`
	HTMLURL            string              `json:"html_url"`
	Rule               GitHubAlertRule     `json:"rule"`
	Tool               GitHubAlertTool     `json:"tool"`
	MostRecentInstance GitHubAlertInstance `json:"most_recent_instance"`
	CreatedAt          string              `json:"created_at"`
}

GitHubCodeScanningAlert represents a GitHub code scanning alert.

type GitHubCollector

type GitHubCollector struct {
	*BaseCollector
	// contains filtered or unexported fields
}

GitHubCollector pulls security alerts from GitHub Advanced Security.

func NewGitHubCollector

func NewGitHubCollector(cfg *GitHubCollectorConfig) *GitHubCollector

NewGitHubCollector creates a new GitHub collector.

func (*GitHubCollector) Collect

func (c *GitHubCollector) Collect(ctx context.Context, opts *CollectOptions) (*CollectResult, error)

Collect pulls security alerts from GitHub.

type GitHubCollectorConfig

type GitHubCollectorConfig struct {
	Token   string `yaml:"token" json:"token"`
	Owner   string `yaml:"owner" json:"owner"`
	Repo    string `yaml:"repo" json:"repo"`
	Verbose bool   `yaml:"verbose" json:"verbose"`
}

GitHubCollectorConfig configures a GitHub collector.

type HeartbeatAction added in v0.7.1

type HeartbeatAction string

HeartbeatAction is a typed directive the platform may send on a heartbeat.

const (
	// HeartbeatActionPause: stop claiming and starting new jobs; running jobs
	// finish and heartbeats continue. Lifted by the first heartbeat answer
	// without pause (or by resume).
	HeartbeatActionPause HeartbeatAction = "pause"
	// HeartbeatActionResume lifts a pause explicitly.
	HeartbeatActionResume HeartbeatAction = "resume"
	// HeartbeatActionDrain is pause that is not lifted for the rest of the
	// process lifetime: the sensor finishes its running jobs and takes no
	// more until it is restarted.
	HeartbeatActionDrain HeartbeatAction = "drain"
	// HeartbeatActionRotateKey: the presented API key is inside its renewal
	// window; renew it now.
	HeartbeatActionRotateKey HeartbeatAction = "rotate_key"
	// HeartbeatActionUpdate: a newer sensor build is available. Only logged:
	// the SDK never downloads or executes anything because of it.
	HeartbeatActionUpdate HeartbeatAction = "update"
)

The closed set of heartbeat actions. Any other value is ignored.

type HeartbeatHints added in v0.7.1

type HeartbeatHints struct {
	// Present is true when the response carried at least one doorbell field.
	// A server that knows the doorbell answers every heartbeat of a
	// doorbell-aware sensor with at least next_heartbeat_seconds.
	Present bool
	// PendingJobs is the number of jobs this sensor could claim now (0..100).
	PendingJobs int
	// NextHeartbeat is the advised delay before the next heartbeat, already
	// clamped to [MinDoorbellHeartbeatInterval, MaxDoorbellHeartbeatInterval].
	// Zero when not advised.
	NextHeartbeat time.Duration
	// Actions are the actions the server sent, unfiltered. Doorbell.Handle
	// acts only on the known ones.
	Actions []HeartbeatAction
	// ConfigVersion is the opaque version of what the platform governs about
	// this sensor, or "" when absent or malformed.
	ConfigVersion string
}

HeartbeatHints is the doorbell part of a heartbeat response. A server without the doorbell sends none of these fields; Present is then false.

func ParseHeartbeatHints added in v0.7.1

func ParseHeartbeatHints(body []byte) *HeartbeatHints

ParseHeartbeatHints reads the doorbell hints from a heartbeat response body. Fields outside their documented range are dropped, never trusted. A body that is not a JSON object yields no hints (Present=false).

type JSONParser

type JSONParser struct{}

JSONParser parses generic JSON output that follows CTIS schema.

func (*JSONParser) CanParse

func (p *JSONParser) CanParse(data []byte) bool

CanParse checks if this parser can handle the data.

func (*JSONParser) Name

func (p *JSONParser) Name() string

Name returns the parser name.

func (*JSONParser) Parse

func (p *JSONParser) Parse(ctx context.Context, data []byte, opts *ParseOptions) (*ctis.Report, error)

Parse converts JSON to CTIS format.

func (*JSONParser) SupportedFormats

func (p *JSONParser) SupportedFormats() []string

SupportedFormats returns supported formats.

type LiveHost

type LiveHost struct {
	URL           string   `json:"url"`                      // Full URL (https://example.com)
	Host          string   `json:"host"`                     // Hostname
	IP            string   `json:"ip,omitempty"`             // Resolved IP
	Port          int      `json:"port,omitempty"`           // Port number
	Scheme        string   `json:"scheme"`                   // http or https
	StatusCode    int      `json:"status_code"`              // HTTP status code
	ContentLength int64    `json:"content_length,omitempty"` // Response size
	Title         string   `json:"title,omitempty"`          // Page title
	WebServer     string   `json:"web_server,omitempty"`     // Server header
	ContentType   string   `json:"content_type,omitempty"`   // Content-Type header
	Technologies  []string `json:"technologies,omitempty"`   // Detected technologies
	CDN           string   `json:"cdn,omitempty"`            // CDN provider
	TLSVersion    string   `json:"tls_version,omitempty"`    // TLS version
	Redirect      string   `json:"redirect,omitempty"`       // Final redirect URL
	ResponseTime  int64    `json:"response_time_ms"`         // Response time in ms
}

LiveHost represents an HTTP/HTTPS live host.

type LogLevel

type LogLevel int

LogLevel represents the logging level.

const (
	LogLevelDebug LogLevel = iota
	LogLevelInfo
	LogLevelWarn
	LogLevelError
	LogLevelSilent
)

type Logger

type Logger interface {
	// Debug logs a debug message
	Debug(format string, args ...interface{})

	// Info logs an info message
	Info(format string, args ...interface{})

	// Warn logs a warning message
	Warn(format string, args ...interface{})

	// Error logs an error message
	Error(format string, args ...interface{})
}

Logger is the interface for logging in the SDK. Implement this interface to use a custom logger (e.g., logrus, zap).

func GetDefaultLogger

func GetDefaultLogger() Logger

GetDefaultLogger returns the global default logger.

func LoggerFromVerbose

func LoggerFromVerbose(prefix string, verbose bool) Logger

LoggerFromVerbose creates a logger based on verbose flag. If verbose is true, returns a PrintfLogger, otherwise returns NopLogger.

type MultiTargetScanner added in v0.7.0

type MultiTargetScanner interface {
	Scanner
	ScanTargets(ctx context.Context, targets []string, opts *ScanOptions) (*ScanResult, error)
}

MultiTargetScanner is a Scanner that can scan a list of targets in one run (nuclei with -l). The command executor uses it when a scan command carries several targets.

type NopLogger

type NopLogger struct{}

NopLogger is a no-op logger that discards all messages.

func (*NopLogger) Debug

func (l *NopLogger) Debug(format string, args ...interface{})

func (*NopLogger) Error

func (l *NopLogger) Error(format string, args ...interface{})

func (*NopLogger) Info

func (l *NopLogger) Info(format string, args ...interface{})

func (*NopLogger) Warn

func (l *NopLogger) Warn(format string, args ...interface{})

type OAuthConfig

type OAuthConfig struct {
	ClientID     string   `yaml:"client_id" json:"client_id"`
	ClientSecret string   `yaml:"client_secret" json:"client_secret"`
	TokenURL     string   `yaml:"token_url" json:"token_url"`
	Scopes       []string `yaml:"scopes" json:"scopes"`
}

OAuthConfig holds OAuth configuration.

type OpenPort

type OpenPort struct {
	Host     string `json:"host"`               // IP or hostname
	IP       string `json:"ip,omitempty"`       // IP address
	Port     int    `json:"port"`               // Port number
	Protocol string `json:"protocol,omitempty"` // tcp, udp
	Service  string `json:"service,omitempty"`  // Detected service name
	Version  string `json:"version,omitempty"`  // Service version
	Banner   string `json:"banner,omitempty"`   // Service banner
}

OpenPort represents an open port on a host.

type OutputHandler

type OutputHandler func(line string, isError bool)

OutputHandler processes scanner output in real-time.

type Package

type Package struct {
	ID       string   `json:"id"`       // Package URL or unique identifier
	Name     string   `json:"name"`     // Package name
	Version  string   `json:"version"`  // Installed version
	Type     string   `json:"type"`     // npm, maven, pip, etc.
	Licenses []string `json:"licenses"` // License identifiers
	PURL     string   `json:"purl"`     // Package URL (purl spec)
}

Package represents a software package.

type PackageDependency

type PackageDependency struct {
	PackageID    string   `json:"package_id"`   // Parent package
	Dependencies []string `json:"dependencies"` // Child package IDs
}

PackageDependency represents a dependency relationship.

type PackageType

type PackageType string

PackageType represents the package ecosystem.

const (
	PackageTypeMaven    PackageType = "maven"
	PackageTypeNPM      PackageType = "npm"
	PackageTypePyPI     PackageType = "pip"
	PackageTypeGo       PackageType = "gomod"
	PackageTypeCargo    PackageType = "cargo"
	PackageTypeNuGet    PackageType = "nuget"
	PackageTypeGem      PackageType = "gem"
	PackageTypeComposer PackageType = "composer"
)

func DetectPackageType

func DetectPackageType(filename string) PackageType

DetectPackageType detects the package type from a manifest file.

type ParseOptions

type ParseOptions struct {
	// Tool information
	ToolName string `json:"tool_name"`
	ToolType string `json:"tool_type"` // sast, sca, secret, iac, web3

	// Asset information
	AssetType  ctis.AssetType `json:"asset_type"`
	AssetValue string         `json:"asset_value"`
	AssetID    string         `json:"asset_id"`

	// Git information (legacy - use BranchInfo for full context)
	Branch    string `json:"branch"`
	CommitSHA string `json:"commit_sha"`

	// Branch information for branch-aware finding lifecycle
	// Provides full CI/CD context for auto-resolve and expiry features
	BranchInfo *ctis.BranchInfo `json:"branch_info,omitempty"`

	// BasePath is the root directory of the scanned codebase.
	// Used to resolve relative file paths when reading snippets from source files.
	BasePath string `json:"base_path,omitempty"`

	// Defaults
	DefaultConfidence int `json:"default_confidence"`
}

ParseOptions configures parsing.

type Parser

type Parser interface {
	// Name returns the parser name (e.g., "sarif", "json", "custom-trivy")
	Name() string

	// SupportedFormats returns the output formats this parser can handle
	SupportedFormats() []string

	// Parse converts raw output to CTIS report
	Parse(ctx context.Context, data []byte, opts *ParseOptions) (*ctis.Report, error)

	// CanParse checks if the parser can handle the given data
	CanParse(data []byte) bool
}

Parser converts scanner output to CTIS format. Implement this interface to support custom output formats.

type ParserRegistry

type ParserRegistry struct {
	// contains filtered or unexported fields
}

ParserRegistry manages registered parsers.

func NewParserRegistry

func NewParserRegistry() *ParserRegistry

NewParserRegistry creates a new parser registry with built-in parsers.

func (*ParserRegistry) FindParser

func (r *ParserRegistry) FindParser(data []byte) Parser

FindParser finds a parser that can handle the given data. Parsers are probed in registration order, after the generic SARIF and JSON parsers that NewParserRegistry registers, so a tool-specific parser registered later wins only when the generic formats do not match.

func (*ParserRegistry) ForScanner added in v0.7.0

func (r *ParserRegistry) ForScanner(scannerName string, data []byte) (Parser, error)

ForScanner returns the parser for a scanner's raw output, or an error when no parser recognizes it.

Selection order: the parser registered under the scanner's own name (when it accepts the data), then content detection, then nothing. Output that no parser recognizes is an error, never "0 findings": a scanner whose results cannot be read must fail its command, or real findings vanish while the scan reports success. Callers decide what empty output means before calling.

func (*ParserRegistry) Get

func (r *ParserRegistry) Get(name string) Parser

Get returns a parser by name.

func (*ParserRegistry) List

func (r *ParserRegistry) List() []string

List returns all registered parser names.

func (*ParserRegistry) Register

func (r *ParserRegistry) Register(parser Parser)

Register adds a parser to the registry. Registering a name again replaces that parser and keeps its original position.

type PrintfLogger

type PrintfLogger struct {
	// contains filtered or unexported fields
}

PrintfLogger is a simple logger that uses fmt.Printf. This is what the verbose mode was using before.

func NewPrintfLogger

func NewPrintfLogger(prefix string) *PrintfLogger

NewPrintfLogger creates a new printf logger.

func (*PrintfLogger) Debug

func (l *PrintfLogger) Debug(format string, args ...interface{})

func (*PrintfLogger) Error

func (l *PrintfLogger) Error(format string, args ...interface{})

func (*PrintfLogger) Info

func (l *PrintfLogger) Info(format string, args ...interface{})

func (*PrintfLogger) Warn

func (l *PrintfLogger) Warn(format string, args ...interface{})

type ProcessOptions

type ProcessOptions struct {
	// Scan options
	ScanOptions *ScanOptions `yaml:"scan" json:"scan"`

	// Parse options
	ParseOptions *ParseOptions `yaml:"parse" json:"parse"`

	// Push options
	Push      bool   `yaml:"push" json:"push"`
	SaveLocal bool   `yaml:"save_local" json:"save_local"`
	OutputDir string `yaml:"output_dir" json:"output_dir"`

	// Retry
	MaxRetries int `yaml:"max_retries" json:"max_retries"`
	RetryDelay int `yaml:"retry_delay_seconds" json:"retry_delay_seconds"`
}

ProcessOptions configures the processing workflow.

type ProcessResult

type ProcessResult struct {
	// Scanner info
	ScannerName string `json:"scanner_name"`

	// Scan result
	ScanResult *ScanResult `json:"scan_result"`

	// Parsed report
	Report *ctis.Report `json:"report,omitempty"`

	// Push result
	PushResult *PushResult `json:"push_result,omitempty"`

	// Local file if saved
	LocalFile string `json:"local_file,omitempty"`

	// Error
	Error string `json:"error,omitempty"`
}

ProcessResult holds the result of a complete processing workflow.

type Processor

type Processor interface {
	// Process runs a complete scan workflow
	Process(ctx context.Context, scanner Scanner, opts *ProcessOptions) (*ProcessResult, error)

	// ProcessBatch runs multiple scanners in parallel
	ProcessBatch(ctx context.Context, scanners []Scanner, opts *ProcessOptions) ([]*ProcessResult, error)
}

Processor orchestrates the complete workflow: scan -> parse -> push.

type Provider

type Provider interface {
	// Name returns the provider name (e.g., "github", "aws")
	Name() string

	// Connector returns the underlying connector
	Connector() Connector

	// ListCollectors returns all available collectors
	ListCollectors() []Collector

	// GetCollector returns a specific collector by name
	GetCollector(name string) (Collector, error)

	// Initialize sets up the provider with configuration
	Initialize(ctx context.Context, config *ProviderConfig) error

	// TestConnection tests the provider connection
	TestConnection(ctx context.Context) error

	// Close closes the provider and all collectors
	Close() error
}

Provider bundles a Connector with multiple Collectors for complete integration.

type ProviderConfig

type ProviderConfig struct {
	// Connector config
	Connector ConnectorConfig `yaml:"connector" json:"connector"`

	// Provider-specific settings
	Settings map[string]any `yaml:"settings" json:"settings"`

	// Which collectors to enable (empty = all)
	EnabledCollectors []string `yaml:"enabled_collectors" json:"enabled_collectors"`
}

ProviderConfig holds provider configuration.

type PushResult

type PushResult struct {
	Success         bool   `json:"success"`
	Message         string `json:"message,omitempty"`
	FindingsCreated int    `json:"findings_created"`
	FindingsUpdated int    `json:"findings_updated"`
	AssetsCreated   int    `json:"assets_created"`
	AssetsUpdated   int    `json:"assets_updated"`

	// Queued: the results are durably stored in the outbox and will be
	// delivered when the platform is reachable; the counts above are not
	// known yet.
	Queued bool `json:"queued,omitempty"`
	// ReportID is the protocol v2 report id the results travel under ("" on
	// v1).
	ReportID string `json:"report_id,omitempty"`
}

PushResult holds the result of a push operation.

type Pusher

type Pusher interface {
	// PushFindings sends findings to OpenCTEM
	PushFindings(ctx context.Context, report *ctis.Report) (*PushResult, error)

	// PushAssets sends assets to OpenCTEM
	PushAssets(ctx context.Context, report *ctis.Report) (*PushResult, error)

	// SendHeartbeat sends a heartbeat to OpenCTEM
	SendHeartbeat(ctx context.Context, status *SensorStatus) error

	// TestConnection tests the API connection
	TestConnection(ctx context.Context) error
}

Pusher sends data to the OpenCTEM API.

type ReconOptions

type ReconOptions struct {
	// Target configuration
	Target     string   `yaml:"target" json:"target"`           // Domain, IP, or CIDR
	InputFile  string   `yaml:"input_file" json:"input_file"`   // File with multiple targets
	OutputFile string   `yaml:"output_file" json:"output_file"` // Output file path
	ExtraArgs  []string `yaml:"extra_args" json:"extra_args"`   // Additional CLI args

	// Performance
	Threads   int           `yaml:"threads" json:"threads"`       // Concurrency
	RateLimit int           `yaml:"rate_limit" json:"rate_limit"` // Rate limit (requests/second)
	Timeout   time.Duration `yaml:"timeout" json:"timeout"`       // Scan timeout

	// DNS configuration
	Resolvers []string `yaml:"resolvers" json:"resolvers"` // Custom DNS resolvers

	// Environment
	Env map[string]string `yaml:"env" json:"env"` // Environment variables

	// Output
	Verbose bool `yaml:"verbose" json:"verbose"`
}

ReconOptions configures a reconnaissance scan.

type ReconResult

type ReconResult struct {
	// Scanner info
	ScannerName    string    `json:"scanner_name"`
	ScannerVersion string    `json:"scanner_version"`
	ReconType      ReconType `json:"recon_type"`

	// Target
	Target string `json:"target"`

	// Timing
	StartedAt  int64 `json:"started_at"`
	FinishedAt int64 `json:"finished_at"`
	DurationMs int64 `json:"duration_ms"`

	// Results (populated based on ReconType)
	Subdomains   []Subdomain     `json:"subdomains,omitempty"`
	DNSRecords   []DNSRecord     `json:"dns_records,omitempty"`
	OpenPorts    []OpenPort      `json:"open_ports,omitempty"`
	LiveHosts    []LiveHost      `json:"live_hosts,omitempty"`
	URLs         []DiscoveredURL `json:"urls,omitempty"`
	Technologies []Technology    `json:"technologies,omitempty"`

	// Raw output
	RawOutput []byte `json:"raw_output,omitempty"`
	ExitCode  int    `json:"exit_code"`
	Error     string `json:"error,omitempty"`
}

ReconResult holds the reconnaissance scan result.

type ReconScanner

type ReconScanner interface {
	// Name returns the scanner name (e.g., "subfinder", "naabu")
	Name() string

	// Version returns the scanner version
	Version() string

	// Type returns the recon type this scanner performs
	Type() ReconType

	// Scan performs reconnaissance on the target and returns results
	Scan(ctx context.Context, target string, opts *ReconOptions) (*ReconResult, error)

	// IsInstalled checks if the underlying tool is available
	IsInstalled(ctx context.Context) (bool, string, error)
}

ReconScanner performs reconnaissance and asset discovery. Implement this interface to create a custom recon scanner.

type ReconType

type ReconType string

ReconType represents the type of reconnaissance scan.

const (
	ReconTypeSubdomain  ReconType = "subdomain"   // Subdomain enumeration
	ReconTypeDNS        ReconType = "dns"         // DNS resolution and records
	ReconTypePort       ReconType = "port"        // Port scanning
	ReconTypeHTTPProbe  ReconType = "http_probe"  // HTTP/HTTPS probing
	ReconTypeURLCrawl   ReconType = "url_crawl"   // URL and endpoint crawling
	ReconTypeTechDetect ReconType = "tech_detect" // Technology fingerprinting
)

type SARIFParser

type SARIFParser struct{}

SARIFParser parses SARIF format output.

func (*SARIFParser) CanParse

func (p *SARIFParser) CanParse(data []byte) bool

CanParse checks if this parser can handle the data: a single JSON object with a "runs" array, the one member every SARIF log must have. Substring checks are not enough: a JSON Lines stream (nuclei) whose response bodies mention "version" and "runs" passed them, and was then read as an empty SARIF log.

func (*SARIFParser) Name

func (p *SARIFParser) Name() string

Name returns the parser name.

func (*SARIFParser) Parse

func (p *SARIFParser) Parse(ctx context.Context, data []byte, opts *ParseOptions) (*ctis.Report, error)

Parse converts SARIF to CTIS format. Data that is not a SARIF log is an error: decoding arbitrary JSON into the SARIF structure succeeds with no runs, which used to turn a scanner's real results into "0 findings".

func (*SARIFParser) SupportedFormats

func (p *SARIFParser) SupportedFormats() []string

SupportedFormats returns supported formats.

type ScaResult

type ScaResult struct {
	// Packages discovered
	Packages []Package `json:"packages"`

	// Dependency relationships
	PackageDependencies []PackageDependency `json:"package_dependencies"`

	// Vulnerabilities found
	Vulnerabilities []Vulnerability `json:"vulnerabilities"`

	// Timing
	DurationMs int64 `json:"duration_ms"`
}

ScaResult holds SCA scan results.

type ScaScanOptions

type ScaScanOptions struct {
	// Target
	TargetDir string `yaml:"target_dir" json:"target_dir"`

	// Scan configuration
	SkipDBUpdate   bool `yaml:"skip_db_update" json:"skip_db_update"`
	IgnoreUnfixed  bool `yaml:"ignore_unfixed" json:"ignore_unfixed"`
	IncludeDevDeps bool `yaml:"include_dev_deps" json:"include_dev_deps"`

	// Output
	GenerateSBOM bool   `yaml:"generate_sbom" json:"generate_sbom"`
	SBOMFormat   string `yaml:"sbom_format" json:"sbom_format"` // cyclonedx, spdx

	// Verbose
	Verbose bool `yaml:"verbose" json:"verbose"`
}

ScaScanOptions configures an SCA scan.

type ScaScanner

type ScaScanner interface {
	// Name returns the scanner name (e.g., "trivy", "snyk")
	Name() string

	// Type returns the scanner type
	Type() ScannerType

	// Scan performs SCA scan and returns results
	Scan(ctx context.Context, target string, opts *ScaScanOptions) (*ScaResult, error)

	// IsInstalled checks if the scanner is available
	IsInstalled(ctx context.Context) (bool, string, error)
}

ScaScanner performs Software Composition Analysis (dependency scanning). It generates SBOM and identifies vulnerabilities in dependencies.

type ScanCommandPayload

type ScanCommandPayload struct {
	Scanner string `json:"scanner"`
	// Target is the single scan target. The platform omits it when a job has
	// several targets for a list-capable scanner (nuclei) and sends only
	// Targets; when both are set, Target is the job.
	Target string `json:"target"`
	// Targets is the full target list (protocol v1, additive).
	Targets         []string               `json:"targets,omitempty"`
	Config          map[string]interface{} `json:"config,omitempty"`
	TimeoutSeconds  int                    `json:"timeout_seconds,omitempty"`
	ReportProgress  bool                   `json:"report_progress,omitempty"`
	CustomTemplates []EmbeddedTemplate     `json:"custom_templates,omitempty"`
}

ScanCommandPayload is the payload for scan commands.

type ScanOptions

type ScanOptions struct {
	// Target configuration
	TargetDir  string            `yaml:"target_dir" json:"target_dir"`
	Include    []string          `yaml:"include" json:"include"`
	Exclude    []string          `yaml:"exclude" json:"exclude"`
	ConfigFile string            `yaml:"config_file" json:"config_file"`
	ExtraArgs  []string          `yaml:"extra_args" json:"extra_args"`
	Env        map[string]string `yaml:"env" json:"env"`

	// Custom templates directory (written from embedded templates)
	// Used by Nuclei (-t), Semgrep (--config), Betterleaks (--config)
	CustomTemplateDir string `yaml:"custom_template_dir" json:"custom_template_dir"`

	// Asset information for linking findings
	RepoURL   string `yaml:"repo_url" json:"repo_url"`
	Branch    string `yaml:"branch" json:"branch"`
	CommitSHA string `yaml:"commit_sha" json:"commit_sha"`

	// Output
	Verbose bool `yaml:"verbose" json:"verbose"`
}

ScanOptions configures a scan.

type ScanResult

type ScanResult struct {
	// Scanner info
	ScannerName    string `json:"scanner_name"`
	ScannerVersion string `json:"scanner_version"`

	// Timing
	StartedAt  int64 `json:"started_at"`
	FinishedAt int64 `json:"finished_at"`
	DurationMs int64 `json:"duration_ms"`

	// Output
	ExitCode  int    `json:"exit_code"`
	RawOutput []byte `json:"raw_output,omitempty"`
	Stderr    string `json:"stderr,omitempty"`

	// Error (if scan failed)
	Error string `json:"error,omitempty"`
}

ScanResult holds the raw scan result before conversion.

type ScanTargetPolicy added in v0.7.0

type ScanTargetPolicy struct {
	// AllowedRoots confines filesystem targets. When non-empty, a path target
	// must resolve (after following symlinks) to one of these directories or
	// a descendant. When empty, filesystem targets are allowed anywhere
	// except the filesystem root, sensitive system directories (/etc, /proc,
	// /root, ...) and credential directories in the user's home (~/.ssh,
	// ~/.aws, ...). Configuring roots is strongly recommended.
	AllowedRoots []string

	// AllowPrivate permits network targets in RFC1918 (10/8, 172.16/12,
	// 192.168/16) and IPv6 ULA (fc00::/7) space, for sensors that scan an
	// internal network. Loopback, link-local (incl. 169.254.169.254 IMDS),
	// CGNAT, multicast, unspecified and reserved ranges stay blocked
	// regardless.
	AllowPrivate bool

	// AllowLoopback permits 127.0.0.0/8 and ::1. Test-only; mirrors
	// httpsec.AllowLoopback.
	AllowLoopback bool

	// Disabled turns validation off entirely. Only for callers that validate
	// targets themselves before the executor sees the command.
	Disabled bool

	// LookupIP resolves a hostname; nil uses net.DefaultResolver. Exposed
	// for tests.
	LookupIP func(ctx context.Context, host string) ([]net.IP, error)
	// contains filtered or unexported fields
}

ScanTargetPolicy decides whether a server-supplied scan target may be scanned. The zero value is NOT the secure default; use DefaultScanTargetPolicy and adjust.

func DefaultScanTargetPolicy added in v0.7.0

func DefaultScanTargetPolicy() *ScanTargetPolicy

DefaultScanTargetPolicy returns the policy DefaultCommandExecutor uses unless SetScanTargetPolicy is called:

  • AllowedRoots from OPENCTEM_SDK_SCAN_ROOTS (unset = sensitive-path denylist only);
  • AllowPrivate when OPENCTEM_SDK_ALLOW_PRIVATE_TARGETS=1, the sensor's SENSOR_ALLOW_PRIVATE_TARGETS=1 (or its pre-rename name AGENT_ALLOW_PRIVATE_TARGETS=1), or httpsec's allow-private switch (OPENCTEM_SDK_HTTPSEC_ALLOW_PRIVATE=1) is set;
  • AllowLoopback follows httpsec.AllowLoopback (test harnesses only).

When SENSOR_ALLOW_PRIVATE_TARGETS and AGENT_ALLOW_PRIVATE_TARGETS are both set to different values the intended posture is unknown, so the policy fails closed: Validate refuses every target with an error naming both.

func (*ScanTargetPolicy) Validate added in v0.7.0

func (p *ScanTargetPolicy) Validate(ctx context.Context, target string) (string, error)

Validate checks target and returns the value to hand to the scanner: for filesystem targets the cleaned, symlink-resolved absolute path (so the scanner reads exactly what was checked); otherwise the target unchanged.

type Scanner

type Scanner interface {
	// Name returns the scanner name (e.g., "semgrep", "trivy")
	Name() string

	// Version returns the scanner version
	Version() string

	// Capabilities returns what the scanner can detect
	Capabilities() []string

	// Scan performs a scan on the target and returns raw output
	Scan(ctx context.Context, target string, opts *ScanOptions) (*ScanResult, error)

	// IsInstalled checks if the underlying tool is available
	IsInstalled(ctx context.Context) (bool, string, error)
}

Scanner is the main interface for security scanning tools. Implement this interface to create a custom scanner.

type ScannerType

type ScannerType string

ScannerType represents the type of security scanner.

const (
	ScannerTypeSAST            ScannerType = "sast"             // Static Application Security Testing
	ScannerTypeDependency      ScannerType = "dependency"       // Software Composition Analysis
	ScannerTypeSecretDetection ScannerType = "secret_detection" // Secret/Credential Detection
	ScannerTypeIaC             ScannerType = "iac"              // Infrastructure as Code
	ScannerTypeContainer       ScannerType = "container"        // Container Image Scanning
	ScannerTypeWeb3            ScannerType = "web3"             // Smart Contract Analysis
)

type SecretFinding

type SecretFinding struct {
	// Identity
	RuleID      string `json:"rule_id"`     // Detection rule ID
	Fingerprint string `json:"fingerprint"` // For deduplication

	// Classification
	SecretType string `json:"secret_type"` // api_key, password, token, etc.
	Service    string `json:"service"`     // AWS, GitHub, Stripe, etc.

	// Location
	File        string `json:"file"`
	StartLine   int    `json:"start_line"`
	EndLine     int    `json:"end_line"`
	StartColumn int    `json:"start_column"`
	EndColumn   int    `json:"end_column"`

	// Content
	Match       string `json:"match"`        // Full matched line
	MaskedValue string `json:"masked_value"` // Redacted secret

	// Metadata
	Entropy float64 `json:"entropy,omitempty"` // Entropy score
	Valid   *bool   `json:"valid,omitempty"`   // If verified
	Author  string  `json:"author,omitempty"`  // Git author
	Commit  string  `json:"commit,omitempty"`  // Git commit
	Date    string  `json:"date,omitempty"`    // Commit date
}

SecretFinding represents a detected secret.

type SecretResult

type SecretResult struct {
	// Secrets found
	Secrets []SecretFinding `json:"secrets"`

	// Timing
	DurationMs int64 `json:"duration_ms"`
}

SecretResult holds secret scan results.

type SecretScanOptions

type SecretScanOptions struct {
	// Target
	TargetDir string `yaml:"target_dir" json:"target_dir"`

	// Scan configuration
	ConfigFile string   `yaml:"config_file" json:"config_file"` // Custom rules file
	Exclude    []string `yaml:"exclude" json:"exclude"`         // Paths to exclude
	NoGit      bool     `yaml:"no_git" json:"no_git"`           // Don't use git history

	// Verification
	Verify bool `yaml:"verify" json:"verify"` // Verify secrets are valid

	// Verbose
	Verbose bool `yaml:"verbose" json:"verbose"`
}

SecretScanOptions configures a secret scan.

type SecretScanner

type SecretScanner interface {
	// Name returns the scanner name (e.g., "betterleaks", "trufflehog")
	Name() string

	// Type returns the scanner type
	Type() ScannerType

	// Scan performs secret detection and returns results
	Scan(ctx context.Context, target string, opts *SecretScanOptions) (*SecretResult, error)

	// IsInstalled checks if the scanner is available
	IsInstalled(ctx context.Context) (bool, string, error)
}

SecretScanner detects secrets and credentials in code.

type Sensor added in v0.7.0

type Sensor interface {
	// Name returns the sensor name
	Name() string

	// Start starts the sensor
	Start(ctx context.Context) error

	// Stop gracefully stops the sensor
	Stop(ctx context.Context) error

	// Status returns the current sensor status
	Status() *SensorStatus

	// AddScanner adds a scanner to the sensor
	AddScanner(scanner Scanner) error

	// AddCollector adds a collector to the sensor
	AddCollector(collector Collector) error

	// RemoveScanner removes a scanner by name
	RemoveScanner(name string) error

	// RemoveCollector removes a collector by name
	RemoveCollector(name string) error
}

Sensor is a long-running service that manages scanners and collectors. Implement this interface to create a custom sensor.

type SensorState added in v0.7.0

type SensorState string

SensorState represents the state of a sensor.

const (
	SensorStateRunning  SensorState = "running"
	SensorStateStopped  SensorState = "stopped"
	SensorStateStopping SensorState = "stopping"
	SensorStateError    SensorState = "error"
)

type SensorStatus added in v0.7.0

type SensorStatus struct {
	Name          string      `json:"name"`
	Status        SensorState `json:"status"`
	StartedAt     int64       `json:"started_at,omitempty"`
	Uptime        int64       `json:"uptime_seconds,omitempty"`
	Scanners      []string    `json:"scanners"`
	Collectors    []string    `json:"collectors"`
	LastScan      int64       `json:"last_scan,omitempty"`
	LastCollect   int64       `json:"last_collect,omitempty"`
	TotalScans    int64       `json:"total_scans"`
	TotalFindings int64       `json:"total_findings"`
	Errors        int64       `json:"errors"`
	Message       string      `json:"message,omitempty"`

	// System Metrics (collected from sensor)
	CPUPercent    float64 `json:"cpu_percent,omitempty"`
	MemoryPercent float64 `json:"memory_percent,omitempty"`
	ActiveJobs    int     `json:"active_jobs,omitempty"`
	Region        string  `json:"region,omitempty"`

	// Version and Hostname identify the running sensor to the platform (shown
	// on the Sensors page). Both are sent with every heartbeat.
	Version  string `json:"version,omitempty"`
	Hostname string `json:"hostname,omitempty"`
}

SensorStatus represents the sensor's current state.

type Subdomain

type Subdomain struct {
	Host   string   `json:"host"`             // Full subdomain (e.g., api.example.com)
	Domain string   `json:"domain"`           // Root domain (e.g., example.com)
	Source string   `json:"source,omitempty"` // Discovery source (crtsh, hackertarget, etc.)
	IPs    []string `json:"ips,omitempty"`    // Resolved IP addresses
}

Subdomain represents a discovered subdomain.

type Technology

type Technology struct {
	Name       string   `json:"name"`                 // Technology name
	Version    string   `json:"version,omitempty"`    // Version if detected
	Categories []string `json:"categories,omitempty"` // Category (framework, cms, etc.)
	Confidence int      `json:"confidence,omitempty"` // Detection confidence (0-100)
	Website    string   `json:"website,omitempty"`    // Technology website
}

Technology represents a detected technology.

type TemplateCache

type TemplateCache struct {
	// contains filtered or unexported fields
}

TemplateCache provides persistent caching of custom templates. Templates are organized by: {cache_dir}/{tenant_id}/{template_type}/{template_name}

func NewTemplateCache

func NewTemplateCache(cfg *TemplateCacheConfig) (*TemplateCache, error)

NewTemplateCache creates a new template cache.

func (*TemplateCache) Cleanup

func (c *TemplateCache) Cleanup() error

Cleanup removes old and excess cached templates.

func (*TemplateCache) Clear

func (c *TemplateCache) Clear(tenantID string) error

Clear removes all cached templates for a tenant.

func (*TemplateCache) Get deprecated

func (c *TemplateCache) Get(contentHash string) (string, bool)

Get retrieves a template from cache by content hash, regardless of the tenant or template type it was cached for.

Deprecated: Get is not tenant-scoped; a hit may be another tenant's file. Use GetFor.

func (*TemplateCache) GetFor added in v0.7.0

func (c *TemplateCache) GetFor(tenantID, templateType, contentHash string) (string, bool)

GetFor retrieves a cached template for one tenant and template type. Entries cached for other tenants are never returned.

func (*TemplateCache) GetOrPut

func (c *TemplateCache) GetOrPut(tenantID string, template *EmbeddedTemplate) (string, error)

GetOrPut returns cached template path or caches the template. The lookup is scoped to tenantID and the template's type.

func (*TemplateCache) GetTemplateDir

func (c *TemplateCache) GetTemplateDir(tenantID, templateType string, templates []EmbeddedTemplate) (string, error)

GetTemplateDir returns the directory holding templates of templateType for tenantID, caching any that are missing. Every template must be of templateType (an empty TemplateType is treated as templateType), so all files land in the single returned directory: {cache_dir}/{tenant_id}/{template_type}. Returns empty string if templates slice is empty.

func (*TemplateCache) Put

func (c *TemplateCache) Put(tenantID string, template *EmbeddedTemplate) (string, error)

Put stores a template in the cache. Returns the file path where the template was written.

tenantID must be a UUID and template.TemplateType one of ValidTemplateTypes; the file is written under {cache_dir}/{tenant_id}/{template_type}/ and the resolved path is checked to stay inside the cache directory.

func (*TemplateCache) Remove

func (c *TemplateCache) Remove(contentHash string) error

Remove removes every cached copy of a template (all tenants) by content hash.

func (*TemplateCache) Stats

func (c *TemplateCache) Stats() *CacheStats

Stats returns cache statistics.

type TemplateCacheConfig

type TemplateCacheConfig struct {
	// CacheDir is the base directory for cached templates.
	// Default: ~/.openctem/templates
	CacheDir string

	// MaxCacheAge is the maximum age of cached templates before cleanup.
	// Default: 7 days
	MaxCacheAge time.Duration

	// MaxCacheSize is the maximum total size of the cache in bytes.
	// Default: 100MB
	MaxCacheSize int64

	// CleanupInterval is how often to run cleanup.
	// Default: 1 hour
	CleanupInterval time.Duration

	// Verbose enables verbose logging.
	Verbose bool
}

TemplateCacheConfig configures the template cache.

func DefaultTemplateCacheConfig

func DefaultTemplateCacheConfig() *TemplateCacheConfig

DefaultTemplateCacheConfig returns default cache configuration.

type ValidationError

type ValidationError struct {
	Field   string
	Message string
}

ValidationError represents a configuration validation error.

func (*ValidationError) Error

func (e *ValidationError) Error() string

type ValidationErrors

type ValidationErrors []ValidationError

ValidationErrors is a collection of validation errors.

func (*ValidationErrors) Add

func (e *ValidationErrors) Add(field, message string)

Add adds a validation error.

func (ValidationErrors) Error

func (e ValidationErrors) Error() string

func (ValidationErrors) HasErrors

func (e ValidationErrors) HasErrors() bool

HasErrors returns true if there are any errors.

type Validator

type Validator struct {
	// contains filtered or unexported fields
}

Validator provides validation methods for configurations.

func NewValidator

func NewValidator() *Validator

NewValidator creates a new validator.

func (*Validator) APIKey

func (v *Validator) APIKey(field, value string) *Validator

APIKey validates an API key format.

func (*Validator) Custom

func (v *Validator) Custom(field string, check func() bool, message string) *Validator

Custom adds a custom validation check.

func (*Validator) DirectoryExists

func (v *Validator) DirectoryExists(field, path string) *Validator

DirectoryExists validates that a directory exists.

func (*Validator) Errors

func (v *Validator) Errors() ValidationErrors

Errors returns all validation errors.

func (*Validator) FileExists

func (v *Validator) FileExists(field, path string) *Validator

FileExists validates that a file exists.

func (*Validator) Max

func (v *Validator) Max(field string, value, max int) *Validator

Max validates that an integer is at most the maximum.

func (*Validator) MaxDuration

func (v *Validator) MaxDuration(field string, value, max time.Duration) *Validator

MaxDuration validates that a duration is at most the maximum.

func (*Validator) Min

func (v *Validator) Min(field string, value, min int) *Validator

Min validates that an integer is at least the minimum.

func (*Validator) MinDuration

func (v *Validator) MinDuration(field string, value, min time.Duration) *Validator

MinDuration validates that a duration is at least the minimum.

func (*Validator) OneOf

func (v *Validator) OneOf(field, value string, allowed []string) *Validator

OneOf validates that a value is one of the allowed values.

func (*Validator) Required

func (v *Validator) Required(field, value string) *Validator

Required validates that a field is not empty.

func (*Validator) SourceID

func (v *Validator) SourceID(field, value string) *Validator

SourceID validates a source ID format.

func (*Validator) URL

func (v *Validator) URL(field, value string) *Validator

URL validates that a field is a valid URL.

func (*Validator) Validate

func (v *Validator) Validate() error

Validate returns an error if there are validation errors.

type Vulnerability

type Vulnerability struct {
	// Identity
	ID          string `json:"id"`          // CVE ID or advisory ID
	Fingerprint string `json:"fingerprint"` // For deduplication

	// Package info
	PkgID      string `json:"pkg_id"`      // Affected package ID
	PkgName    string `json:"pkg_name"`    // Package name
	PkgVersion string `json:"pkg_version"` // Installed version

	// Vulnerability info
	Name         string `json:"name"`          // Short title
	Description  string `json:"description"`   // Full description
	Severity     string `json:"severity"`      // critical, high, medium, low
	FixedVersion string `json:"fixed_version"` // Version with fix

	// Metadata
	Metadata *VulnerabilityMetadata `json:"metadata,omitempty"`
}

Vulnerability represents a security vulnerability in a package.

type VulnerabilityMetadata

type VulnerabilityMetadata struct {
	CWEs       []string `json:"cwes,omitempty"`        // CWE identifiers
	References []string `json:"references,omitempty"`  // Advisory URLs
	CVSSScore  float64  `json:"cvss_score,omitempty"`  // CVSS score
	CVSSVector string   `json:"cvss_vector,omitempty"` // CVSS vector string
	Source     string   `json:"source,omitempty"`      // NVD, GHSA, etc.
}

VulnerabilityMetadata contains additional vulnerability details.

type WebhookCollector

type WebhookCollector struct {
	*BaseCollector
	// contains filtered or unexported fields
}

WebhookCollector receives data via HTTP webhooks.

func NewWebhookCollector

func NewWebhookCollector(cfg *WebhookCollectorConfig) *WebhookCollector

NewWebhookCollector creates a new webhook collector.

func (*WebhookCollector) Collect

Collect waits for webhook data (blocking).

func (*WebhookCollector) Start

func (c *WebhookCollector) Start(ctx context.Context) error

Start starts the webhook server.

func (*WebhookCollector) Stop

func (c *WebhookCollector) Stop(ctx context.Context) error

Stop stops the webhook server.

type WebhookCollectorConfig

type WebhookCollectorConfig struct {
	ListenAddr string `yaml:"listen_addr" json:"listen_addr"`
	Secret     string `yaml:"secret" json:"secret"`
	Verbose    bool   `yaml:"verbose" json:"verbose"`
}

WebhookCollectorConfig configures a webhook collector.

Jump to

Keyboard shortcuts

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