detect

package
v0.0.0-...-fe79b6d Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: GPL-3.0 Imports: 25 Imported by: 0

Documentation

Overview

Package detect implements passive vulnerability detection over HTTP traffic already captured by the proxy. It reads proxy.CapturedRequest records and reports findings; it never sends requests of its own.

Two kinds of check run against each captured message:

  • Regex rules (KindRegex), from rules_builtin.go or created by the operator: a pattern plus gates (content type, status, scheme) and named post-filters that validate the captured group in Go.
  • Analyzers (KindAnalyzer), Go functions in analyzers.go, for absence and relational logic a regex cannot express — a missing header, a cookie flag matrix, an Access-Control-Allow-Origin that echoes the request.

Both converge on Engine.newFinding, the only code path that produces a Finding.

Patterns compile with Go's regexp (RE2): no lookahead, no lookbehind, no backreferences. PostFilters is the escape hatch for negative conditions.

Index

Constants

This section is empty.

Variables

View Source
var DefaultOHTTPContentTypes = []string{"message/ohttp-", "application/ohttp-keys"}

DefaultOHTTPContentTypes are the Oblivious HTTP media types never scanned. OHTTP bodies are HPKE-encrypted to a gateway key the proxy never holds, so they are opaque by design rather than a codec gap; skipping them by content type keeps them out of the skippedBinary "unreadable" count. Exported so the project-config backfill adds the same values these defaults ship.

View Source
var ErrBuiltinImmutable = errors.New("built-in rules cannot be edited or deleted")

ErrBuiltinImmutable is returned when an edit or delete targets a built-in rule. Built-ins accept only an enabled toggle and a severity override.

View Source
var ErrRuleNotFound = errors.New("rule not found")

ErrRuleNotFound is returned when no rule has the given ID.

View Source
var ErrScanRunning = errors.New("a scan is already running")

ErrScanRunning is returned when a rescan is already in progress.

Functions

func FindingID

func FindingID(ruleID, host, dim string) string

FindingID is the dedupe identity: it contains no request ID, timestamp or seq, so rescanning reproduces byte-identical IDs. Exported so a caller outside the engine can mint a finding in the same identity space.

func PostFilterNames

func PostFilterNames() []string

PostFilterNames returns the registered post-filter names, for API validation of operator-supplied rules.

func RedactValue

func RedactValue(s string) string

RedactValue exposes the masking helper for the rule tester.

func ShannonEntropy

func ShannonEntropy(s string) float64

ShannonEntropy returns the Shannon entropy of s in bits per character. Used to validate a value a pattern already matched, never as a detector on its own. For calibration: random base64 scores around 6.0, random hex around 4.0, and English prose 4.0 to 4.5.

func ValidateRule

func ValidateRule(r *Rule) error

ValidateRule checks an operator-supplied rule, returning a message suitable for a 400 response. The RE2 compile error is surfaced verbatim.

Types

type Analyzer

type Analyzer func(m *Message, emit func(AnalyzerHit))

Analyzer inspects a whole message and emits zero or more hits, covering the checks a regex cannot express: the absence of a header, a relationship between request and response headers, or a decision requiring a parsed value.

type AnalyzerHit

type AnalyzerHit struct {
	Detail     string
	Evidence   string
	Severity   Severity
	Confidence Confidence
	// GroupExtra adds a dimension to the dedupe key, e.g. a cookie name for a
	// per-cookie rule.
	GroupExtra string
	// Offset is relative to the buffer named by OffsetIn, not to the raw document;
	// newFinding translates it. An unset OffsetIn means no offset.
	Offset   int
	OffsetIn Target
	// OffsetLen is the length of the matched region at Offset, which is not
	// len(Evidence): an analyzer's Evidence is a synthesized description, not the
	// matched bytes. Zero means no usable region, and the finding reports no
	// offset.
	OffsetLen int
	Part      string
	// contains filtered or unexported fields
}

AnalyzerHit is one result emitted by an analyzer. Empty Severity and Confidence inherit the rule's own values; an analyzer sets them only to escalate or downgrade a specific case.

type Category

type Category string

Category groups rules for filtering and for the Rules UI.

const (
	CategorySecrets     Category = "secrets"
	CategoryPII         Category = "pii"
	CategoryCredentials Category = "credentials"
	CategoryAccess      Category = "access"
	CategoryDisclosure  Category = "disclosure"
	CategoryHeaders     Category = "headers"
	CategoryCookies     Category = "cookies"
)

func AllCategories

func AllCategories() []Category

AllCategories lists every category in display order.

func (Category) Valid

func (c Category) Valid() bool

Valid reports whether c is a known category.

type Confidence

type Confidence string

Confidence describes whether a match is what the rule claims it is, independent of Severity.

const (
	ConfidenceHigh   Confidence = "high"
	ConfidenceMedium Confidence = "medium"
	ConfidenceLow    Confidence = "low"
)

func (Confidence) Valid

func (c Confidence) Valid() bool

Valid reports whether c is a known confidence level.

type Config

type Config struct {
	// ScopeOnly limits scanning to in-scope requests. On by default.
	ScopeOnly bool `json:"scopeOnly"`
	// ScanRequests enables request-side targets (Basic auth, credentials in
	// query strings, keys posted by the app).
	ScanRequests bool `json:"scanRequests"`
	// PersistFindings snapshots findings into the project file, preserving
	// false-positive marks and notes. On by default.
	PersistFindings bool `json:"persistFindings"`
	// ClearFindingsWithHistory clears findings when request history is cleared.
	// Off by default.
	ClearFindingsWithHistory bool `json:"clearFindingsWithHistory"`

	MaxBodyScanBytes        int `json:"maxBodyScanBytes"`
	MaxRequestBodyScanBytes int `json:"maxRequestBodyScanBytes"`

	// SkipContentTypes and SkipExtensions are MIME prefixes and URL suffixes
	// never scanned. .js and .css are not in either list.
	SkipContentTypes []string `json:"skipContentTypes"`
	SkipExtensions   []string `json:"skipExtensions"`
	// ExcludeHosts suppresses findings whose host contains any of these
	// substrings.
	ExcludeHosts []string `json:"excludeHosts"`
}

Config holds the tunable engine settings that ride with a project.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the shipped engine configuration.

type Engine

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

Engine holds the rule set and produces findings from captured messages.

Built-in rules are immutable: builtinRules() returns a fresh slice, operator changes are held separately as a disabled-ID set plus a severity-override map, and the API rejects edits and deletes of built-ins.

func NewEngine

func NewEngine() *Engine

NewEngine returns an enabled engine with the built-in library loaded and the default configuration.

func (*Engine) ActiveRuleCount

func (e *Engine) ActiveRuleCount() int

ActiveRuleCount returns how many rules are currently compiled and live.

func (*Engine) AddRule

func (e *Engine) AddRule(r Rule) (Rule, error)

AddRule validates and appends an operator rule, returning the stored copy.

func (*Engine) Config

func (e *Engine) Config() Config

Config returns the current configuration.

func (*Engine) DisabledBuiltins

func (e *Engine) DisabledBuiltins() []string

DisabledBuiltins returns the IDs of built-in rules the operator turned off.

func (*Engine) IsEnabled

func (e *Engine) IsEnabled() bool

IsEnabled reports whether detection is active.

func (*Engine) RemoveRule

func (e *Engine) RemoveRule(id string) error

RemoveRule deletes an operator rule.

func (*Engine) ResetRule

func (e *Engine) ResetRule(id string) error

ResetRule clears an operator's overrides for a built-in rule.

func (*Engine) Rule

func (e *Engine) Rule(id string) (Rule, bool)

Rule returns a single rule by ID.

func (*Engine) RuleEnabledFunc

func (e *Engine) RuleEnabledFunc() func(string) bool

RuleEnabledFunc returns a predicate over rule IDs, snapshotted into a map for callers filtering large finding sets. An unknown ID counts as enabled, so a finding whose custom rule was deleted stays visible.

func (*Engine) Rules

func (e *Engine) Rules() []Rule

Rules returns every rule, built-in and user, as copies with Enabled and Severity resolved into one flat list.

func (*Engine) Scan

func (e *Engine) Scan(r *proxy.CapturedRequest, inScope proxy.ScopeFunc) []Finding

Scan runs every live rule against a captured request and returns the findings. It performs no deduplication; that is Store.Upsert's job.

func (*Engine) SetConfig

func (e *Engine) SetConfig(cfg Config)

SetConfig replaces the configuration, filling zero values with defaults.

func (*Engine) SetDisabledBuiltins

func (e *Engine) SetDisabledBuiltins(ids []string)

SetDisabledBuiltins replaces the disabled-rule set (project load).

func (*Engine) SetEnabled

func (e *Engine) SetEnabled(v bool)

SetEnabled turns detection on or off.

func (*Engine) SetRuleEnabled

func (e *Engine) SetRuleEnabled(id string, enabled bool) error

SetRuleEnabled toggles any rule, built-in or user.

func (*Engine) SetRuleSeverity

func (e *Engine) SetRuleSeverity(id string, sev Severity) error

SetRuleSeverity overrides a rule's severity. Allowed on built-ins.

func (*Engine) SetSeverityOverrides

func (e *Engine) SetSeverityOverrides(in map[string]string)

SetSeverityOverrides replaces the severity override map (project load).

func (*Engine) SetUserRules

func (e *Engine) SetUserRules(rules []Rule)

SetUserRules replaces all operator rules (project load).

func (*Engine) SeverityOverrides

func (e *Engine) SeverityOverrides() map[string]string

SeverityOverrides returns the operator's per-rule severity overrides.

func (*Engine) UpdateRule

func (e *Engine) UpdateRule(id string, r Rule) (Rule, error)

UpdateRule replaces an operator rule in place. The ID must be preserved: Finding.RuleID references it and Finding.ID derives from it.

func (*Engine) UserRules

func (e *Engine) UserRules() []Rule

UserRules returns a copy of the operator-defined rules.

type Finding

type Finding struct {
	ID         string     `json:"id"`
	RuleID     string     `json:"ruleId"`
	RuleName   string     `json:"ruleName"`
	Category   Category   `json:"category"`
	Severity   Severity   `json:"severity"`
	Confidence Confidence `json:"confidence"`
	Target     Target     `json:"target"`

	Host      string `json:"host"`
	Method    string `json:"method"`
	URL       string `json:"url"`
	RequestID string `json:"requestId"`

	// Detail is a short human-readable qualifier, e.g. the cookie name for a
	// cookie rule or the matched product for a fingerprint rule.
	Detail string `json:"detail,omitempty"`
	// Evidence is the redacted, truncated, control-character-escaped snippet
	// rendered by every list and table.
	Evidence string `json:"evidence"`
	// RawEvidence is the unmasked matched value, stored verbatim and populated
	// only for rules that redact. Redaction is a display control; the operator
	// can reveal this per finding.
	RawEvidence    string `json:"rawEvidence,omitempty"`
	EvidenceOffset int    `json:"evidenceOffset"`
	EvidenceLength int    `json:"evidenceLength"`
	EvidencePart   string `json:"evidencePart,omitempty"`

	FirstSeen   time.Time    `json:"firstSeen"`
	LastSeen    time.Time    `json:"lastSeen"`
	Count       int          `json:"count"`
	Occurrences []Occurrence `json:"occurrences,omitempty"`

	FalsePositive bool   `json:"falsePositive"`
	Notes         string `json:"notes,omitempty"`
	// Truncated marks a finding produced from a body that hit a scan size cap:
	// the scan was not exhaustive for that response.
	Truncated bool `json:"truncated,omitempty"`
	// SeverityOverridden records that an operator changed Severity by hand. A
	// rescan leaves it alone.
	SeverityOverridden bool `json:"severityOverridden,omitempty"`
	// contains filtered or unexported fields
}

Finding is one deduplicated detection result.

ID is a deterministic group hash:

hex(sha256(ruleID \x00 host \x00 groupDim))[:32]

It carries no request ID, timestamp, or sequence number, so rescanning the same traffic reproduces byte-identical IDs. Rescan is therefore idempotent, live merges are a map upsert, and persisted findings reload without ID remapping.

type FindingFilter

type FindingFilter struct {
	Severities  []string
	MinSeverity string
	Categories  []string
	RuleID      string
	Host        string
	Search      string
	Confidence  string
	// FP selects false-positive handling: "false" (default) hides them, "true"
	// shows only them, "all" shows everything.
	FP string
	// IncludeDisabled includes findings whose rule is currently switched off.
	// Disabling a rule retains its findings; this toggle controls visibility.
	IncludeDisabled bool
	Sort            string // "severity" (default) | "lastSeen" | "firstSeen" | "count"
	Dir             string // "desc" (default) | "asc"
	Offset          int
	Limit           int
}

FindingFilter holds the criteria for listing findings.

type FindingSummary

type FindingSummary struct {
	ID             string     `json:"id"`
	RuleID         string     `json:"ruleId"`
	RuleName       string     `json:"ruleName"`
	Category       Category   `json:"category"`
	Severity       Severity   `json:"severity"`
	Confidence     Confidence `json:"confidence"`
	Target         Target     `json:"target"`
	Host           string     `json:"host"`
	Method         string     `json:"method"`
	URL            string     `json:"url"`
	RequestID      string     `json:"requestId"`
	Detail         string     `json:"detail,omitempty"`
	Evidence       string     `json:"evidence"`
	RawEvidence    string     `json:"rawEvidence,omitempty"`
	EvidenceOffset int        `json:"evidenceOffset"`
	EvidenceLength int        `json:"evidenceLength"`
	EvidencePart   string     `json:"evidencePart,omitempty"`
	Count          int        `json:"count"`
	FirstSeen      time.Time  `json:"firstSeen"`
	LastSeen       time.Time  `json:"lastSeen"`
	FalsePositive  bool       `json:"falsePositive"`
	HasNotes       bool       `json:"hasNotes"`
	Truncated      bool       `json:"truncated,omitempty"`
}

FindingSummary is the row shape sent to the UI and returned by list endpoints. It omits occurrences and the internal dedupe bookkeeping.

func Summaries

func Summaries(in []Finding) []FindingSummary

Summaries projects a slice of findings.

type GroupBy

type GroupBy string

GroupBy selects how repeated matches collapse into a single finding.

const (
	// GroupByEvidence: one finding per distinct matched value on a host. Used by
	// secrets and PII rules.
	GroupByEvidence GroupBy = "evidence"
	// GroupByURL: one finding per path on a host. Used by panels, directory
	// listings, and interesting files.
	GroupByURL GroupBy = "url"
	// GroupByHost: one finding per host. Used by header, cookie, and
	// fingerprint rules.
	GroupByHost GroupBy = "host"
)

func (GroupBy) Valid

func (g GroupBy) Valid() bool

Valid reports whether g is a known grouping mode.

type Message

type Message struct {
	Req *proxy.CapturedRequest

	URL    *url.URL
	Scheme string
	Host   string
	Path   string

	RespStatus int
	RespHeader http.Header
	RespRawHdr []byte
	RespBody   []byte

	ReqHeader http.Header
	ReqRawHdr []byte
	ReqBody   []byte

	ContentType string

	// BodyScannable reports whether RespBody is worth matching against.
	BodyScannable bool
	// SkipReason explains why not: "binary", "encoding:br", "content-type",
	// "extension", "empty", or "no-response".
	SkipReason string
	// Truncated marks that a body hit a scan size cap, so findings from this
	// message cannot claim to be exhaustive.
	Truncated bool

	// RespBodyStart and ReqBodyStart are where each body begins inside the
	// corresponding raw document, used to turn a body-relative match offset into
	// an offset into the document the UI renders.
	RespBodyStart int
	ReqBodyStart  int
	// RespBodyDecoded records that the body was decompressed for scanning. RespBody
	// and RespRaw then share no coordinate system, so no body offset is reported.
	RespBodyDecoded bool
	// contains filtered or unexported fields
}

Message is a captured request/response pair decomposed into the buffers rules are matched against. Parse handles the raw-byte quirks so rules do not have to:

  • RespRaw is Content-Length framed with a dechunked body (every capture helper in internal/proxy clears TransferEncoding), but proxy hook plugins can substitute their own bytes, so an LF-only header terminator is also accepted.
  • Bodies may arrive gzip- or deflate-encoded with Content-Encoding intact; Parse decompresses them.
  • Brotli and zstd bodies are marked unscannable and counted.

func Parse

func Parse(r *proxy.CapturedRequest, cfg Config) *Message

Parse decomposes a captured request into a Message, applying the false-positive and cost gates from cfg. It never returns nil.

func (*Message) IsHTMLDocument

func (m *Message) IsHTMLDocument() bool

IsHTMLDocument reports whether the response is an HTML page. Document-level header analyzers (CSP, frame-options) gate on this.

func (*Message) SetCookies

func (m *Message) SetCookies() []string

SetCookies returns the raw Set-Cookie header values.

type Occurrence

type Occurrence struct {
	RequestID  string    `json:"requestId"`
	Seq        int       `json:"seq"`
	Method     string    `json:"method"`
	URL        string    `json:"url"`
	StatusCode int       `json:"statusCode"`
	Timestamp  time.Time `json:"timestamp"`
	// Offset is the byte offset of the match within the scanned part, used to
	// highlight the evidence in the response viewer.
	Offset int `json:"offset"`
	// Part names which buffer Offset indexes into, so the UI knows whether to
	// highlight in the request or the response.
	Part string `json:"part"`
}

Occurrence records one sighting of a finding.

type RescanRequest

type RescanRequest struct {
	// Scope is "all" (every captured request) or "host".
	Scope string `json:"scope"`
	Host  string `json:"host,omitempty"`
	// Purge drops findings this pass does not re-confirm, except triaged ones.
	Purge bool `json:"purge,omitempty"`
}

RescanRequest describes an on-demand scan.

type Rule

type Rule struct {
	ID          string     `json:"id"`
	Name        string     `json:"name"`
	Description string     `json:"description,omitempty"`
	Remediation string     `json:"remediation,omitempty"`
	Kind        RuleKind   `json:"kind"`
	Category    Category   `json:"category"`
	Severity    Severity   `json:"severity"`
	Confidence  Confidence `json:"confidence"`
	Target      Target     `json:"target"`

	// Pattern is the RE2 source for KindRegex rules.
	Pattern string `json:"pattern,omitempty"`
	// Literal is a case-insensitive substring prescreen: when set, the regex only
	// runs if the haystack contains it. Must appear in every string the pattern
	// can match, or the rule silently never fires.
	Literal string `json:"literal,omitempty"`
	// Literals is an any-of prescreen for alternation rules: when set, the regex
	// only runs if the haystack contains at least one of these case-insensitive
	// substrings. Every branch of the pattern must be covered by one of them, or
	// matches from an uncovered branch are silently missed. Used by the WAF
	// fingerprint rules, whose broad alternations have no single common Literal.
	// Literal and Literals are independent; a rule with both must satisfy both.
	Literals []string `json:"literals,omitempty"`
	// CaptureGroup selects which submatch becomes the evidence (0 = whole match).
	CaptureGroup int `json:"captureGroup,omitempty"`
	// PostFilters name validators in the postfilters.go registry, run in order
	// against the captured group.
	PostFilters []string `json:"postFilters,omitempty"`
	// Analyzer names a function in the analyzers.go registry (KindAnalyzer only).
	Analyzer string `json:"analyzer,omitempty"`

	// GroupBy selects the dedupe collapse mode. Empty means GroupByEvidence.
	GroupBy GroupBy `json:"groupBy,omitempty"`

	// ContentTypes gates body rules to simplified content-type keywords
	// ("json", "html", "xml", "csv", "plain", "js", "css"). Empty means any
	// scannable body.
	ContentTypes []string `json:"contentTypes,omitempty"`
	// ExcludeContentTypes is the inverse of ContentTypes and takes precedence
	// over it: the rule never runs against these keywords. Rules that look for
	// data rather than a format use this instead of ContentTypes.
	ExcludeContentTypes []string `json:"excludeContentTypes,omitempty"`
	// StatusCodes gates the rule with a proxy status expression, e.g.
	// "200,301,302,401,403". Empty means any status.
	StatusCodes string `json:"statusCodes,omitempty"`
	// Scheme gates the rule to "http" or "https". Empty means either.
	Scheme string `json:"scheme,omitempty"`
	// MinLength rejects captured groups shorter than this.
	MinLength int `json:"minLength,omitempty"`
	// MinEntropy rejects captured groups below this Shannon entropy in bits per
	// character. 0 disables the check.
	MinEntropy float64 `json:"minEntropy,omitempty"`
	// MaxPerResponse caps findings per message (0 means defaultMaxPerResponse).
	MaxPerResponse int `json:"maxPerResponse,omitempty"`
	// RedactEvidence masks the middle of the captured value before storing it.
	RedactEvidence bool `json:"redactEvidence,omitempty"`

	Builtin bool `json:"builtin"`
	Enabled bool `json:"enabled"`
	// contains filtered or unexported fields
}

Rule is a single detection check. Built-in rules use stable string IDs, which operator toggles, severity overrides, and Finding.RuleID reference across restarts and project reloads; user rules get a generated ID.

type RuleKind

type RuleKind string

RuleKind distinguishes a declarative pattern from a Go analyzer function. Operators may only create KindRegex rules; KindAnalyzer rules are built in, because their behavior lives in code rather than in the rule record.

const (
	KindRegex    RuleKind = "regex"
	KindAnalyzer RuleKind = "analyzer"
)

type ScanStatus

type ScanStatus struct {
	Running     bool      `json:"running"`
	JobID       string    `json:"jobId,omitempty"`
	Kind        string    `json:"kind,omitempty"`
	Scanned     int       `json:"scanned"`
	Total       int       `json:"total"`
	FindingsNew int       `json:"findingsNew"`
	StartedAt   time.Time `json:"startedAt,omitempty"`
	FinishedAt  time.Time `json:"finishedAt,omitempty"`
	Status      string    `json:"status,omitempty"` // "running" | "complete" | "stopped"
}

ScanStatus reports rescan progress.

type Scanner

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

Scanner drives detection over the proxy's capture store. It pulls on an interval rather than being called from the proxy's request path, so detection never runs on the goroutine serving the browser, and a rescan is the same code path with a different starting cursor.

func NewScanner

func NewScanner(engine *Engine, findings *Store, store *proxy.Store, scope *proxy.Scope, broadcast chan<- any) *Scanner

NewScanner wires a scanner. scope may be nil (no scope gating available).

func (*Scanner) Cancel

func (s *Scanner) Cancel()

Cancel stops a running rescan.

func (*Scanner) Cursor

func (s *Scanner) Cursor() int

Cursor returns the current watermark.

func (*Scanner) ResetCursor

func (s *Scanner) ResetCursor(seq int)

ResetCursor sets the live-scan watermark. Must be called wherever the proxy store rewrites its sequence numbering (Store.Clear, Store.LoadItems); a stale high cursor stops the loop from seeing any further request.

func (*Scanner) Run

func (s *Scanner) Run(ctx context.Context)

Run drives the live scan loop until ctx is cancelled. The work is in scanOnce, which is synchronous and has no timing dependency.

func (*Scanner) StartRescan

func (s *Scanner) StartRescan(ctx context.Context, req RescanRequest) (ScanStatus, error)

StartRescan runs the rule set over already-captured traffic in the background. Findings are not cleared first; Store.Upsert merges the results and keeps counts stable.

func (*Scanner) Status

func (s *Scanner) Status() ScanStatus

Status returns a copy of the current scan status.

func (*Scanner) Wake

func (s *Scanner) Wake()

Wake requests an immediate scan pass without blocking.

type Severity

type Severity string

Severity ranks how much a finding matters. The rubric for assigning it to a new rule:

  • Info: not directly exploitable, and discloses nothing beyond a surface or an identity — exposed panels, missing headers, software and version fingerprints, analytics keys, identifiers that are not credentials.
  • Low: a real if minor weakness, or disclosure of something more than an identity — an exposed configuration file, a verbose error or debug page, a directory listing, a disclosed filesystem path, a CORS or redirect misconfiguration.
  • Medium: anything not covered by the other bands.
  • High: account credentials, sensitive API keys, database connection strings, low-level PII (phone number, date of birth).
  • Critical: high-grade PII (a national identity number or equivalent, or a payment card), or anything that alone leads to severe compromise — remote code execution, authentication bypass, a served database dump.

Every credential is High; none is Critical. A rule that detects a surface (an admin console, an absent header) is Info.

The Info/Low line runs through what the match itself gives away, which matters most for error pages: one that leaks source paths, stack frames, or settings is Low, while one that only names the framework or an exception class is Info.

Severity is orthogonal to Confidence: a low-confidence match on a national ID is still Critical, a certain match on a Server header is still Info.

const (
	SeverityCritical Severity = "critical"
	SeverityHigh     Severity = "high"
	SeverityMedium   Severity = "medium"
	SeverityLow      Severity = "low"
	SeverityInfo     Severity = "info"
)

func (Severity) Rank

func (s Severity) Rank() int

Rank returns the sort weight of a severity (Critical highest, 0 if unknown).

func (Severity) Valid

func (s Severity) Valid() bool

Valid reports whether s is a known severity.

type Store

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

Store holds deduplicated findings. Every finding, from live scanning or from a rescan, enters through Upsert and is keyed on the engine's deterministic Finding.ID, so rescanning the same traffic merges rather than duplicates.

func NewStore

func NewStore(maxItems int) *Store

NewStore returns an empty findings store. maxItems <= 0 uses the default.

func (*Store) All

func (s *Store) All() []Finding

All returns copies of every finding, newest first.

func (*Store) Clear

func (s *Store) Clear() int

Clear removes every finding.

func (*Store) Count

func (s *Store) Count() int

Count returns the number of findings held.

func (*Store) Delete

func (s *Store) Delete(id string) bool

Delete removes one finding.

func (*Store) DeleteFalsePositives

func (s *Store) DeleteFalsePositives() int

DeleteFalsePositives removes only findings marked as false positives.

func (*Store) Generation

func (s *Store) Generation() uint64

Generation returns the current scan generation.

func (*Store) Get

func (s *Store) Get(id string) (Finding, bool)

Get returns a copy of one finding.

func (*Store) List

func (s *Store) List(f FindingFilter, ruleEnabled func(string) bool) ([]Finding, int)

List applies a filter and returns a page plus the total number of matches.

func (*Store) Load

func (s *Store) Load(findings []Finding)

Load replaces the store contents (project load). Findings keep the IDs they were persisted with; the ID is the dedupe identity.

func (*Store) NextGeneration

func (s *Store) NextGeneration() uint64

NextGeneration advances the scan generation and returns the new value.

func (*Store) NoteScanned

func (s *Store) NoteScanned(n int)

NoteScanned records that a message was scanned.

func (*Store) NoteSkipped

func (s *Store) NoteSkipped(reason string)

NoteSkipped records a message the scanner could not read.

func (*Store) PurgeBelowGeneration

func (s *Store) PurgeBelowGeneration(gen uint64) int

PurgeBelowGeneration drops findings a scan pass did not re-confirm. Findings marked false-positive or carrying notes are always kept.

func (*Store) Revision

func (s *Store) Revision() uint64

Revision returns the mutation counter.

func (*Store) Summary

func (s *Store) Summary(ruleEnabled func(string) bool) Summary

Summary aggregates the store for the header and the summary event. It takes the same rule-enabled predicate as List so the counts match the table.

func (*Store) Update

func (s *Store) Update(id string, falsePositive *bool, notes *string, severity *Severity) (Finding, bool)

Update applies operator edits to a finding. Passing nil for a field leaves it unchanged, so a PUT can carry any subset.

func (*Store) Upsert

func (s *Store) Upsert(f Finding) (*Finding, bool)

Upsert merges a finding into the store, returning the stored finding and whether it was newly created.

On a hit it bumps LastSeen, appends the occurrence, and increments Count only for an occurrence not already in occSeen, which keeps a rescan idempotent. Operator state (false-positive mark, notes, severity override) is preserved.

type Summary

type Summary struct {
	Total          int            `json:"total"`
	BySeverity     map[string]int `json:"bySeverity"`
	ByCategory     map[string]int `json:"byCategory"`
	FalsePositives int            `json:"falsePositives"`
	// HiddenByDisabledRule counts findings whose rule is currently switched off.
	// The findings table hides them by default.
	HiddenByDisabledRule int `json:"hiddenByDisabledRule"`
	SkippedEncoded       int `json:"skippedEncoded"`
	SkippedBinary        int `json:"skippedBinary"`
	Scanned              int `json:"scanned"`
}

Summary aggregates the findings store for the dashboard header and for the detect.summary WebSocket event.

type Target

type Target string

Target names the part of the message a rule is matched against.

const (
	TargetResponseBody   Target = "response_body"
	TargetResponseHeader Target = "response_header"
	TargetRequestBody    Target = "request_body"
	TargetRequestHeader  Target = "request_header"
	TargetURL            Target = "url"
	// TargetMessage is used by analyzers, which receive the whole *Message and
	// decide for themselves what to inspect.
	TargetMessage Target = "message"
)

func (Target) Valid

func (t Target) Valid() bool

Valid reports whether t is a known target.

Jump to

Keyboard shortcuts

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