secscan

package
v1.0.126 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package secscan is the security-scan orchestrator: it ties ClamAV, YARA, the threat feed, the hash cache, and the admin hash allowlist into the single pipeline the proxy hot path calls. Extracted from package main per ADR-0006 Slice 2; the collaborators arrive as constructor-injected interfaces (Deps), so the engines stay decoupled and the decision tree is unit-testable with fakes. package main keeps the free-function glue (safeScanBody, scanBlock*, bodyNeedsBuffering, the status map) and the production adapters (security_scan.go shim).

Pipeline for every proxied request/response:

  1. URL / domain threat-feed check (CheckURL / CheckDomain) Instant, in-memory lookup against URLhaus + OpenPhish data. Applied in handleRequest before the request is forwarded upstream.

  2. Response body scan (ScanBody) a. SHA-256 hash looked up in cache → return cached verdict immediately. b. ClamAV INSTREAM scan (binary + text content). c. YARA rule matching (all loaded *.yar / *.yara rules). d. Result stored in hash cache. Applied in handleHTTP and handleTunnelInspect after reading the body.

All components are optional:

  • ClamAV is skipped when no address is configured or daemon is unreachable.
  • YARA is skipped when no matcher is injected or it reports not loaded.
  • Threat feeds are skipped when no checker is injected or it is disabled.

Index

Constants

View Source
const MaxDecompressBytes = 64 << 20

MaxDecompressBytes limits decompressed data to 64 MB to guard against gzip bombs.

View Source
const ScanBodyTimeout = 10 * time.Second

ScanBodyTimeout caps the total time for all body scanners (ClamAV + YARA). If the combined scan doesn't finish in time, the content is blocked (fail-closed).

Variables

This section is empty.

Functions

func AddRemoteScanFail

func AddRemoteScanFail()

AddRemoteScanFail records a remote scan sidecar failure (incremented by the remote scanner client in package main).

func AddScanSkipped

func AddScanSkipped()

AddScanSkipped records a response forwarded unscanned because it exceeded the scan buffer limit (incremented by package main's logScanLimitExceeded).

func DecompressForScan

func DecompressForScan(data []byte, contentEncoding string) []byte

DecompressForScan transparently decompresses a response body based on its Content-Encoding header so that ClamAV/YARA signatures can match the actual content. Supports gzip, deflate, and identity (no-op). Brotli ("br") is attempted as gzip (some servers mislabel); on failure the raw bytes are returned so the scan still runs (defense in depth).

Returned data is limited to MaxDecompressBytes to guard against gzip bombs. If decompression fails, the original data is returned unchanged — the scan still runs on compressed bytes (fail-open for availability, but the signature gap is closed for the common case).

Types

type ClamScanner

type ClamScanner interface {
	Ping() error
	Scan(data []byte) (name string, found bool, err error)
}

ClamScanner is the ClamAV surface ScanBody/ClamAVStatus need.

type CounterSnapshot

type CounterSnapshot struct {
	ClamBlocked       int64
	YARABlocked       int64
	ThreatFeedBlocked int64
	ScanTimeout       int64
	ScanSkipped       int64
	RemoteScanFail    int64
}

CounterSnapshot is a point-in-time copy of the scan counters.

func Counters

func Counters() CounterSnapshot

Counters returns a snapshot of all scan counters.

type Deps

type Deps struct {
	Clam     ClamScanner
	Yara     YARAMatcher
	Feed     ThreatChecker
	Excl     HashExcluder
	Cache    *hashcache.HashCache
	MaxBytes int64
}

Deps carries the injectable collaborators for New. Nil collaborators behave as absent (that engine/check is skipped), so partial injection is fine.

type HashExcluder

type HashExcluder interface{ IsHashExcluded(hash string) bool }

HashExcluder is the admin hash-allowlist surface ScanBody needs.

type RemoteScanner

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

RemoteScanner sends scan requests to a scan microservice.

func (*RemoteScanner) Enabled

func (rs *RemoteScanner) Enabled() bool

Enabled reports whether remote scanning is configured.

func (*RemoteScanner) Health

func (rs *RemoteScanner) Health() error

Health checks the remote scan service liveness.

func (*RemoteScanner) Init

func (rs *RemoteScanner) Init(baseURL string)

Init configures the remote scanner client.

func (*RemoteScanner) ScanBody

func (rs *RemoteScanner) ScanBody(data []byte, contentType string) *Result

ScanBody sends data to the remote scan service and returns the result. Returns nil when the content is clean or the service is unreachable (fail-open on network errors to avoid blocking all traffic when the sidecar is down).

func (*RemoteScanner) Status

func (rs *RemoteScanner) Status() (map[string]interface{}, error)

Status fetches the remote scanner status (mirrors /api/security-scan/status).

func (*RemoteScanner) URL

func (rs *RemoteScanner) URL() string

URL returns the configured base URL.

type Result

type Result struct {
	Blocked bool
	Reason  string // virus name, YARA rule name, or feed source
	Source  string // "clamav", "yara", or "threatfeed"
	Hash    string // SHA-256 hex of scanned content (body scans only)
}

Result describes the outcome of a scan that triggered a block.

type ScanResponse

type ScanResponse struct {
	Clean      bool   `json:"clean"`
	Blocked    bool   `json:"blocked"`
	Reason     string `json:"reason,omitempty"`
	Source     string `json:"source,omitempty"`      // "clamav", "yara", "dpi"
	Hash       string `json:"hash,omitempty"`        // SHA-256 of scanned content
	DPIPattern string `json:"dpi_pattern,omitempty"` // matched DPI pattern
	ElapsedMS  int64  `json:"elapsed_ms"`
}

ScanResponse is the JSON wire type between the scan sidecar and this client. The sidecar server (package main, scan_svc.go) produces it; keep the field set in sync with the server handler.

type Scanner

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

Scanner ties together ClamAV, YARA, the threat feed, and the hash cache into a single, easy-to-use interface for the proxy pipeline.

func New

func New(deps Deps) *Scanner

New builds a scanner from injected collaborators (ADR-0006). The scanner starts DISABLED — call Init to enable it — mirroring the pre-extraction package-main singleton (a struct literal with cache+maxBytes defaults that only Init flipped on). Collaborators are fixed at construction and never mutated afterwards (Init only touches clam/cache/maxBytes/enabled).

func (*Scanner) BodyScanEnabled

func (ss *Scanner) BodyScanEnabled() bool

BodyScanEnabled reports whether body scanning (ClamAV and/or YARA) is active.

func (*Scanner) CacheClear

func (ss *Scanner) CacheClear()

CacheClear empties the scan hash cache.

func (*Scanner) CacheEvict

func (ss *Scanner) CacheEvict(hash string) bool

CacheEvict removes a single hash from the scan cache, reporting whether it was present.

func (*Scanner) CacheGet

func (ss *Scanner) CacheGet(hash string) (hashcache.ScanCacheResult, bool)

CacheGet looks up a scan verdict (test support).

func (*Scanner) CacheReady

func (ss *Scanner) CacheReady() bool

CacheReady reports whether the scan hash cache is initialised.

func (*Scanner) CacheSet

func (ss *Scanner) CacheSet(hash string, r hashcache.ScanCacheResult)

CacheSet stores a scan verdict directly (test support and remote-scan result adoption).

func (*Scanner) CacheStats

func (ss *Scanner) CacheStats() (hits, misses int64, size int)

CacheStats returns hash-cache hit/miss counters and current size.

func (*Scanner) CheckDomain

func (ss *Scanner) CheckDomain(domain string) *Result

CheckDomain checks a bare hostname against the threat feed. Returns nil when no threat is found.

func (*Scanner) CheckURL

func (ss *Scanner) CheckURL(rawURL string) *Result

CheckURL checks a full URL against the threat feed. Returns nil when no threat is found.

func (*Scanner) ClamAVStatus

func (ss *Scanner) ClamAVStatus() string

ClamAVStatus returns a human-readable daemon connectivity string. Tier 2.3: Result is cached for clamStatusTTL to avoid hammering the ClamAV daemon on every admin dashboard poll. Cache is invalidated on Init().

func (*Scanner) ClamAVVersion added in v1.0.22

func (ss *Scanner) ClamAVVersion() (clamav.Version, bool)

ClamAVVersion returns the ClamAV engine + signature database version, so operators can see whether virus definitions are current. Returns ok=false when ClamAV is disabled, the client does not support VERSION, or the daemon is unreachable. Cached for clamVersionTTL (definitions change rarely) and invalidated on Init, mirroring ClamAVStatus.

func (*Scanner) Enabled

func (ss *Scanner) Enabled() bool

Enabled reports whether the scanner has been initialised.

func (*Scanner) Init

func (ss *Scanner) Init(clamAddr string, maxBytes int64, cache *hashcache.HashCache)

Init configures the scanner.

clamAddr — ClamAV address string (see clamav.New); "" disables ClamAV.
maxBytes — maximum bytes to buffer per response (0 = use default 5 MiB).
cache    — hash cache to adopt; nil keeps the current one (ADR-0006: the
           cache is handed over here instead of being poked from outside).

func (*Scanner) MaxBytes

func (ss *Scanner) MaxBytes() int64

MaxBytes returns the buffer limit for body scanning.

func (*Scanner) ScanBody

func (ss *Scanner) ScanBody(data []byte) *Result

ScanBody scans a response body with ClamAV and YARA. Results are cached by SHA-256 to avoid redundant work. The entire scan is bounded by ScanBodyTimeout (fail-closed: blocks on timeout). Returns nil when the content is clean (or no scanner is enabled).

type ThreatChecker

type ThreatChecker interface {
	Enabled() bool
	CheckURL(rawURL string) (bool, string)
	CheckDomain(domain string) (bool, string)
}

ThreatChecker is the threat-feed surface CheckURL/CheckDomain need.

type YARAMatcher

type YARAMatcher interface {
	Loaded() bool
	Enabled() bool
	Match(data []byte) []string
}

YARAMatcher is the YARA surface the scan pipeline needs. Loaded and Enabled are deliberately distinct to preserve pre-ADR-0006 behavior verbatim: BodyScanEnabled keys on rules-present only (Loaded), while the body scan additionally honors the runtime enable toggle (Enabled).

Jump to

Keyboard shortcuts

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