Documentation
¶
Overview ¶
Package checks defines the Check interface, the result types, and the shared registry/catalog used by both the scanner orchestrator and the HTTP API.
Concrete check implementations live in subpackages of internal/scanner (e.g. internal/scanner/wellknown, internal/scanner/tls, ...). They satisfy the Check interface defined here and self-register at init.
Index ¶
- Constants
- func Register(c Check)
- type Check
- type CheckMeta
- type Describer
- type Family
- type Finding
- type FindingStatus
- type Registry
- type Resolver
- type Severity
- type Target
- func (t *Target) CacheValue(key string, factory func() (any, error)) (any, error)
- func (t *Target) Client() *http.Client
- func (t *Target) DialAddress(port string) string
- func (t *Target) FirstPinnedIP() net.IP
- func (t *Target) Resolve(ctx context.Context, host string) ([]net.IP, error)
- func (t *Target) UA() string
Constants ¶
const DefaultUserAgent = "WebSec101/0.1.0 (+https://websec0.example/about; passive-scan)"
DefaultUserAgent is used when a Target is constructed without one set.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Check ¶
type Check interface {
ID() string
Family() Family
DefaultSeverity() Severity
Run(ctx context.Context, target *Target) (*Finding, error)
}
Check is the contract every individual security check fulfils. ID is the stable SCREAMING-KEBAB-CASE identifier (e.g. TLS-PROTOCOL-LEGACY-SSL3).
Run must:
- return a *Finding describing the outcome (one Finding per check, even for "pass" status — pass results power the catalog/coverage UI),
- or return a non-nil error if the check itself failed to execute (the orchestrator turns that into a synthetic Finding with status=error).
type CheckMeta ¶
type CheckMeta struct {
ID string `json:"id"`
Family Family `json:"family"`
DefaultSeverity Severity `json:"default_severity"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
RFCRefs []string `json:"rfc_refs,omitempty"`
}
CheckMeta is the static metadata exposed via GET /api/v1/checks. It must remain serializable independently of any in-flight scan state.
type Describer ¶
Describer is an optional interface that checks can implement to enrich their catalog entry with human-facing documentation.
type Family ¶
type Family string
Family groups checks by domain. Open-ended on purpose: third-party plugins (post-MVP) may introduce their own families.
type Finding ¶
type Finding struct {
ID string `json:"id"`
Family Family `json:"family"`
Severity Severity `json:"severity"`
Status FindingStatus `json:"status"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Evidence map[string]any `json:"evidence,omitempty"`
Remediation map[string]any `json:"remediation,omitempty"`
}
Finding is the unit of output produced by a Check. JSON tags align with the OpenAPI Finding schema for direct serialization on the SSE wire.
type FindingStatus ¶
type FindingStatus string
FindingStatus is the per-check outcome.
const ( StatusPass FindingStatus = "pass" StatusFail FindingStatus = "fail" StatusWarn FindingStatus = "warn" StatusError FindingStatus = "error" StatusSkipped FindingStatus = "skipped" )
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is the in-memory catalogue of available checks. A process-wide default Registry exposes Default() for self-registration from check packages' init() functions.
func Default ¶
func Default() *Registry
Default returns the process-wide registry used by self-registering check packages.
func (*Registry) Catalog ¶
Catalog returns the static metadata for every registered Check. The HTTP layer renders the result as JSON on GET /api/v1/checks.
Checks may optionally implement Describer to enrich the catalog entry. When they don't, only the four interface-mandated fields are populated.
type Resolver ¶
Resolver mirrors the small subset of net.Resolver we depend on. It exists so tests can inject a stub without spinning up a real DNS server.
type Severity ¶
type Severity string
Severity grades the impact of a finding. Values match the OpenAPI Severity enum; conversion between checks.Severity and client.Severity is a no-op string cast.
type Target ¶
type Target struct {
// Hostname is the canonical lowercase hostname (no scheme, no port,
// no path). It is the value scoped by the SSRF/blocklist filters.
Hostname string
// Host is Hostname plus an optional `:port` suffix, used when
// composing URLs (so tests against httptest.NewServer can target
// 127.0.0.1:NNNN). For real scans Host == Hostname.
Host string
// Original is the unmodified user-supplied string (URL or hostname).
Original string
// UserAgent is the User-Agent header used by HTTP-based checks.
// Defaults to DefaultUserAgent when empty.
UserAgent string
// HTTPClient is the HTTP client used by HTTP-based checks. nil means
// http.DefaultClient.
HTTPClient *http.Client
// DNSResolverAddr is the host:port of the resolver consulted by
// internal/scanner/dns. Empty means a sensible package default
// (Cloudflare 1.1.1.1:53). Tests inject mock servers here.
DNSResolverAddr string
// PinnedIPs is the immutable set of IPs the SSRF gatekeeper resolved
// at admission. All outbound connections from the scanner must use
// one of these — see internal/scanner/safety. Empty in tests / CLI
// when running without a Policy.
PinnedIPs []net.IP
// contains filtered or unexported fields
}
Target is the input handed to every Check during a scan. It holds the canonical hostname, the original input string, and a per-scan DNS cache that all checks share to avoid redundant lookups (cf. SPECIFICATIONS.md §4.5).
func NewTarget ¶
NewTarget validates the input and returns a Target with an empty DNS cache. resolver may be nil to use net.DefaultResolver.
func (*Target) CacheValue ¶
CacheValue runs factory at most once per key for the lifetime of the Target — concurrent callers either join the in-flight call or hit the cached result. Safe for use as a per-scan, per-target memo.
func (*Target) DialAddress ¶
DialAddress returns the "host:port" string that outbound TCP dials should use. When PinnedIPs is set, the hostname is replaced by the pinned IP; otherwise the original hostname is used.
func (*Target) FirstPinnedIP ¶
FirstPinnedIP returns the first IP captured at admission, or nil when no policy was applied (tests / standalone CLI without --strict).