Documentation
¶
Overview ¶
Package store persists flow metadata in SQLite and bodies on disk.
Index ¶
- Constants
- Variables
- func DecodeNotesImagePayload(mime, b64 string) (string, []byte, error)
- func FlowSortValue(fl *Flow, key string) string
- func NormalizeFlowSortKey(key string) string
- func NormalizeScope(s string) string
- func NormalizeTags(tags []string) []string
- func SanitizeNotesImageMIME(mime string) string
- type APIKey
- type Activity
- type BodyWriter
- type Endpoint
- type EndpointFilter
- type Finding
- type FindingBlock
- type FindingFlow
- type FindingVerification
- type Flow
- type FlowFilter
- type HostStat
- type Issue
- type MergeStats
- type PentestRun
- type Rule
- type SavedView
- type ScopeRule
- type Store
- func (s *Store) AddFlowTags(flowID int64, tags []string) ([]string, error)
- func (s *Store) AppendNote(text string) error
- func (s *Store) AttachFlow(findingID, flowID int64, note string, pos int) error
- func (s *Store) AttachImage(findingID int64, hash, mime, caption string, pos int) error
- func (s *Store) AttachTags(flows []*Flow) error
- func (s *Store) BackupTo(destPath string) error
- func (s *Store) BodiesDir() string
- func (s *Store) BodyExists(hash string) bool
- func (s *Store) Close() error
- func (s *Store) CountFlowsWithFlag(flag int64) (int64, error)
- func (s *Store) CountSuccessfulHTTPS(hostSubstring string) (int64, error)
- func (s *Store) CreateAPIKey(label, scope string, expires int64) (token string, key APIKey, err error)
- func (s *Store) CreateFinding(f *Finding) (int64, error)
- func (s *Store) CreatePentestRun(r *PentestRun) (int64, error)
- func (s *Store) CreateRule(r *Rule) (int64, error)
- func (s *Store) CreateScopeRule(r *ScopeRule) (int64, error)
- func (s *Store) CreateView(v *SavedView) (int64, error)
- func (s *Store) DeleteAPIKey(id int64) error
- func (s *Store) DeleteActivity() error
- func (s *Store) DeleteFinding(id int64) error
- func (s *Store) DeleteFlows(ids []int64) (int64, error)
- func (s *Store) DeleteFlowsByHost(hosts []string, keepOnly bool) (int64, error)
- func (s *Store) DeleteRule(id int64) error
- func (s *Store) DeleteScopeRule(id int64) error
- func (s *Store) DeleteView(id int64) error
- func (s *Store) DetachFlow(findingID, flowID int64) error
- func (s *Store) DistinctTags() ([]TagCount, error)
- func (s *Store) Endpoints(f EndpointFilter) ([]Endpoint, string, error)
- func (s *Store) FindingImageHashes() (map[string]struct{}, error)
- func (s *Store) FindingImageMIME(hash string) string
- func (s *Store) FlowCount() (int64, error)
- func (s *Store) FlowIDsBodySearch(f FlowFilter, maxScan int) ([]int64, string, error)
- func (s *Store) FlowTags(flowID int64) ([]string, error)
- func (s *Store) GCBodies() (removedFiles int64, freedBytes int64, err error)
- func (s *Store) GCNotesImages(notes string) error
- func (s *Store) GetFinding(id int64) (*Finding, error)
- func (s *Store) GetFindingVerification(findingID int64) (*FindingVerification, error)
- func (s *Store) GetFlow(id int64) (*Flow, error)
- func (s *Store) GetNotesImage(id int64) (mime string, data []byte, err error)
- func (s *Store) GetPentestRun(id int64) (*PentestRun, error)
- func (s *Store) GetSetting(key string) (string, bool, error)
- func (s *Store) HasAPIKeys() (bool, error)
- func (s *Store) HostStats() ([]HostStat, error)
- func (s *Store) InsertActivity(a *Activity) (int64, error)
- func (s *Store) InsertFlow(f *Flow) (int64, error)
- func (s *Store) InsertNotesImage(mime string, data []byte) (int64, error)
- func (s *Store) ListAPIKeys() ([]APIKey, error)
- func (s *Store) ListActivity(limit int) ([]Activity, error)
- func (s *Store) ListFindings(severity, status string) ([]Finding, error)
- func (s *Store) ListIssues() ([]Issue, error)
- func (s *Store) ListPentestRuns() ([]PentestRun, error)
- func (s *Store) ListRules() ([]Rule, error)
- func (s *Store) ListScopeRules() ([]ScopeRule, error)
- func (s *Store) ListViews() ([]SavedView, error)
- func (s *Store) LoadNotes() (string, error)
- func (s *Store) MergeFrom(peerDBPath, peerBodiesDir, label string) (MergeStats, error)
- func (s *Store) NewBodyWriter() (*BodyWriter, error)
- func (s *Store) NormalizeNotesMarkdown(notes string) (string, error)
- func (s *Store) NotesImageExists(id int64) (bool, error)
- func (s *Store) OpenBody(sum string) (io.ReadCloser, error)
- func (s *Store) PersistNotes(notes string) (string, error)
- func (s *Store) PutImageBytes(mime string, data []byte) (hash string, n int64, err error)
- func (s *Store) QueryFlows(limit int) ([]*Flow, error)
- func (s *Store) QueryFlowsFilter(f FlowFilter) ([]*Flow, error)
- func (s *Store) QueryFlowsListFilter(f FlowFilter) ([]*Flow, error)
- func (s *Store) QueryWSFrames(flowID int64, limit int) ([]*WSFrame, error)
- func (s *Store) RemoveFlowTag(flowID int64, tag string) error
- func (s *Store) SaveFindingVerification(v *FindingVerification) (int64, error)
- func (s *Store) SaveIssues(issues []Issue) error
- func (s *Store) SaveWSFrame(f *WSFrame) error
- func (s *Store) SetFlowNote(id int64, note string) error
- func (s *Store) SetFlowTags(flowID int64, tags []string) ([]string, error)
- func (s *Store) SetSetting(key, value string) error
- func (s *Store) SetTagColor(tag, color string) error
- func (s *Store) TagsForFlows(ids []int64) (map[int64][]string, error)
- func (s *Store) UpdateFinding(id int64, ...) error
- func (s *Store) UpdateFlow(f *Flow) error
- func (s *Store) UpdatePentestRun(id int64, status, scope, plan, budget, summary, errText *string) error
- func (s *Store) UpdateRule(r *Rule) error
- func (s *Store) UpdateScopeRule(r *ScopeRule) error
- func (s *Store) VerifyAPIKey(token string) (bool, error)
- func (s *Store) VerifyAPIKeyScope(token string) (ok bool, scope string, err error)
- type TagCount
- type WSFrame
Constants ¶
const ( ScopeFull = "full" ScopeRead = "read" )
API-key scopes. A read key may only read (GET/SSE); a full key may also mutate and drive traffic. Unknown/empty normalizes to full for back-compat.
const ( EndpointSearchPath = "path" // host, path, method (default) EndpointSearchHeaders = "headers" // req/res headers JSON EndpointSearchBody = "body" // req/res body files (bounded scan) EndpointSearchAll = "all" // path + headers + body )
Endpoint search scopes for the attack-surface map.
const ( FlagIntercepted int64 = 1 << iota // request passed through the intercept hold queue FlagEdited // request was edited before forwarding FlagDropped // request was dropped by the user (not forwarded) FlagCaptureError // a body could not be captured; forwarding still succeeded FlagTLSFailed // TLS interception failed for this flow FlagWebSocket // a protocol-upgrade (WebSocket) handshake, tunneled transparently FlagRepeater // a request sent from the Repeater module FlagIntruder // a request sent from the Intruder module FlagImported // a flow imported from a HAR file (not proxied) FlagActiveScan // a probe sent by the active scanner FlagAI // request originated from the AI assistant (over MCP) FlagAuthz // a request replayed by the authorization (access-control) tester FlagDiscovery // an endpoint found by the content-discovery (forced-browse) engine FlagTLSBypassed // CONNECT tunneled raw (no MITM) — host on the TLS-bypass list )
Flow flag bits, OR'd into Flow.Flags.
Variables ¶
var ErrFlowNotFound = errors.New("flow not found")
ErrFlowNotFound is returned by AttachFlow when the referenced flow id has no row in the flows table (typo, purged, or never captured).
Functions ¶
func DecodeNotesImagePayload ¶
DecodeNotesImagePayload decodes a base64 image upload body.
func FlowSortValue ¶
FlowSortValue returns the string form of a flow's sort key (for client cursors).
func NormalizeFlowSortKey ¶
NormalizeFlowSortKey returns a whitelisted sort key (default id).
func NormalizeScope ¶
NormalizeScope maps any input to a known scope (default full).
func NormalizeTags ¶
NormalizeTags cleans, de-duplicates and sorts a tag list, dropping empties and capping the count. Exposed so callers (API/MCP) can normalize before display.
func SanitizeNotesImageMIME ¶
SanitizeNotesImageMIME returns mime when it is an allowlisted raster image type, otherwise "application/octet-stream" (served inert — never as HTML, SVG or script). Applied both on insert and on serve, so already-stored rows with a dangerous MIME are also neutralized.
Types ¶
type APIKey ¶
type APIKey struct {
ID int64 `json:"id"`
Label string `json:"label"`
Prefix string `json:"prefix"`
Created int64 `json:"created"` // unix millis
Scope string `json:"scope"` // "full" | "read"
Expires int64 `json:"expires,omitempty"` // unix millis; 0 = never
}
APIKey is metadata for an issued control-API key. The secret token itself is never stored — only its SHA-256 hash and a short identifying prefix.
type Activity ¶
type Activity struct {
ID int64 `json:"id"`
TS int64 `json:"ts"` // unix millis
Tool string `json:"tool"`
Summary string `json:"summary"`
OK bool `json:"ok"`
Result string `json:"result"`
Ms int64 `json:"ms"`
Intent string `json:"intent,omitempty"` // the AI's stated "why" for a consequential action
}
Activity is one recorded AI (MCP) tool call, persisted per-project so the glass-box feed survives restarts.
type BodyWriter ¶
type BodyWriter struct {
// contains filtered or unexported fields
}
BodyWriter streams bytes to a temp file while hashing them, then commits the file to a content-addressed path on Finalize. Safe for bounded memory: bytes are never buffered whole.
func (*BodyWriter) Abort ¶
func (w *BodyWriter) Abort()
Abort discards an in-progress body (e.g. on error).
type Endpoint ¶
type Endpoint struct {
Host string `json:"host"`
Method string `json:"method"`
Path string `json:"path"`
Scheme string `json:"scheme"`
LastStatus int `json:"lastStatus"` // status of the most recent hit
Statuses []int `json:"statuses"` // every distinct status seen, sorted
Hits int `json:"hits"`
LastFlowID int64 `json:"lastFlowId"` // most recent flow, for click-through
ResBodyHash string `json:"resBodyHash"` // SHA-256 of latest response body (empty if none)
ResLen int64 `json:"resLen"` // length of latest response body
Soft404 bool `json:"soft404"` // 2xx/3xx body matches a "not found" content signature
}
Endpoint is a unique (host, method, path) surface aggregated from flows — the building block of the endpoint map. Repeated hits collapse into one row.
type EndpointFilter ¶
type EndpointFilter struct {
Host string
Search string
SearchScope string // path, headers, body, all — see EndpointSearch* constants
ExcludeFlags int64
Tag string // only endpoints with at least one flow carrying this tag
HideNoiseOnly bool // drop endpoints that only ever returned 403/404 (forced-browse noise)
}
EndpointFilter narrows which flows are aggregated into endpoints.
type Finding ¶
type Finding struct {
ID int64 `json:"id"`
TS int64 `json:"ts"` // created, unix millis
UpdatedTS int64 `json:"updatedTs"` // last modified, unix millis
Severity string `json:"severity"` // Critical | High | Medium | Low | Info
Status string `json:"status"` // open | needs_verification | verified | false_positive | wont_fix | fixed
Source string `json:"source"` // human | ai | scanner
Title string `json:"title"`
Target string `json:"target"`
Detail string `json:"detail"` // legacy / MCP compat: first text block synced here
Evidence string `json:"evidence"` // legacy only
Fix string `json:"fix"` // back-compat: kept but superseded by Impact
Impact string `json:"impact"` // security impact — what an attacker gains / business consequence
Cvss string `json:"cvss,omitempty"` // CVSS score or vector string, e.g. "7.5" or "CVSS:3.1/AV:N/..."
// VerificationInstructions tells a human reviewer exactly what to check when
// Status is needs_verification (e.g. "download X and run file on it").
VerificationInstructions string `json:"verificationInstructions,omitempty"`
Body string `json:"body,omitempty"` // stored JSON blocks (use Blocks for rendering)
Flows []FindingFlow `json:"flows"` // attached flow metadata (for list sidebar count)
Blocks []FindingBlock `json:"blocks"` // ordered narrative body (source of truth for UI)
}
Finding is a curated vulnerability write-up for a project. Unlike a scanner Issue (auto-generated, ephemeral), a Finding is persistent and human/AI-curated: it carries a status the operator manages and has a narrative body — an ordered sequence of text blocks (markdown) and flow-reference blocks (clickable PoC request/response), freely interleaved.
type FindingBlock ¶
type FindingBlock struct {
Type string `json:"type"` // "text", "flow", or "image"
MD string `json:"md,omitempty"` // type=="text": markdown content
FlowID int64 `json:"flowId,omitempty"` // type=="flow": attached flow
Note string `json:"note,omitempty"` // type=="flow": annotation
Hash string `json:"hash,omitempty"` // type=="image": content-addressed sha256
Mime string `json:"mime,omitempty"` // type=="image": sanitized MIME
Caption string `json:"caption,omitempty"` // type=="image": optional caption
// Enriched at read time from the flows JOIN — never stored in the body JSON.
Method string `json:"method,omitempty"`
Host string `json:"host,omitempty"`
Path string `json:"path,omitempty"`
Status int `json:"status,omitempty"`
// URL is set at read time for image blocks (GET /api/findings/images/{hash}).
URL string `json:"url,omitempty"`
// Missing is set when referenced evidence is gone: a purged flow (type=="flow")
// or a missing body blob (type=="image"). The block and annotation/caption are
// preserved; the UI/report surface that the evidence is gone.
Missing bool `json:"missing,omitempty"`
}
FindingBlock is one element in a finding's narrative body.
type FindingFlow ¶
type FindingFlow struct {
FlowID int64 `json:"flowId"`
Ord int `json:"ord"`
Note string `json:"note,omitempty"`
Method string `json:"method,omitempty"`
Host string `json:"host,omitempty"`
Path string `json:"path,omitempty"`
Status int `json:"status,omitempty"`
// Missing is true when the referenced flow row no longer exists in the flows
// table (purged via prune_history / GC). The attachment row and note survive.
Missing bool `json:"missing,omitempty"`
}
FindingFlow is one PoC flow attached to a finding, enriched with a compact flow summary for display (the human selects request/responses to record here).
type FindingVerification ¶
type FindingVerification struct {
ID int64 `json:"id"`
FindingID int64 `json:"findingId"` // FK findings; also reachable from the run
RunID int64 `json:"runId"` // FK pentest_run
VulnClass string `json:"vulnClass"` // e.g. sqli-boolean, ssrf-blind, xss-reflected
Gates string `json:"gates"` // JSON: which gates ran and passed
ReproCount int `json:"reproCount"` // how many times the differential held
OOBToken string `json:"oobToken"` // non-empty for blind classes proven via callback
BaselineFlow int64 `json:"baselineFlow"` // PoC flow ids
PayloadFlow int64 `json:"payloadFlow"`
Confidence int `json:"confidence"` // 0-100 derived from which gates passed
TS int64 `json:"ts"` // unix millis
}
FindingVerification is the machine proof-record behind a `verified` Finding: the concrete evidence the 4-gate verifier produced, distinguishing a machine-proven finding from an operator's hand-set `verified` status. It is 1:1 with a Finding (keyed by finding_id — one proof-record per finding).
type Flow ¶
type Flow struct {
ID int64
TS time.Time
Method string
Scheme string
Host string
Port int
Path string
HTTPVersion string
Status int
ReqHeaders map[string][]string
ResHeaders map[string][]string
ReqBodyHash string
ResBodyHash string
ReqLen int64
ResLen int64
Mime string
DurationMs int64
ClientAddr string
Error string
Flags int64
Note string // free-text annotation an operator (or the AI) attaches to the flow
Tags []string // labels attached to the flow (manual or AI); loaded on demand, not a flows column
}
Flow is one captured request/response exchange. Bodies are referenced by content hash, never embedded.
type FlowFilter ¶
type FlowFilter struct {
Limit int // max rows (defaults to 200 when <= 0)
BeforeID int64 // legacy cursor: id < BeforeID when sorting id DESC
CursorID int64 // keyset cursor flow id (0 = first page)
CursorVal string // sort value at the cursor row (required for non-id sorts)
SortKey string // id|method|host|path|status|size|time|mime
SortDir int // +1 asc, -1 desc; 0 = default for the key
Method string // exact method match
Host string // case-insensitive substring of host
Search string // FTS on host/path/method/note, or exact id when SearchScope=id / #id / id:N
SearchScope string // path (default FTS), body (handled in control), id (exact flow id)
Scheme string // exact scheme match ("http"/"https")
StatusClass int // 1..5 → 1xx..5xx; 0 = any
RequireFlags int64 // only rows with any of these flag bits set
ExcludeFlags int64 // only rows with none of these flag bits set
IncludeFlags int64 // rows with any of these bits are kept even if ExcludeFlags also matches
WithoutFlags int64 // only rows with none of these flag bits set (independent of ExcludeFlags)
// Negative filters — each entry excludes matching rows; multiples are ANDed.
NotMethods []string // exclude these exact methods
NotHosts []string // exclude rows whose host contains any of these
NotPaths []string // exclude rows whose path contains any of these
NotStatuses []int // exclude these exact status codes
FlowIDs []int64 // when set, only these ids (used for body search results)
HasNote bool // only rows with a non-empty note
Tag string // only rows carrying this exact tag
}
FlowFilter selects and pages flows. Zero-valued fields are ignored.
type HostStat ¶
HostStat aggregates flow counts and approximate byte totals for one host. Bytes is SUM(req_len + res_len) across all flows for that host — an approximation, because content-addressed bodies are deduplicated on disk, so flows sharing a body each contribute to the sum even though only one file exists. Use it for a rough UI size-breakdown, not an exact disk-usage figure.
type Issue ¶
type Issue struct {
ID int64 `json:"id"`
FlowID int64 `json:"flowId"`
Severity string `json:"severity"`
Title string `json:"title"`
Target string `json:"target"`
Detail string `json:"detail"`
Evidence string `json:"evidence"`
Fix string `json:"fix"`
}
Issue is one scanner finding. Severity is "High" | "Medium" | "Low" | "Info".
type MergeStats ¶
type MergeStats struct {
FlowsAdded int `json:"flowsAdded"`
FlowsSkipped int `json:"flowsSkipped"`
FindingsAdded int `json:"findingsAdded"`
FindingsSkipped int `json:"findingsSkipped"`
BodiesAdded int `json:"bodiesAdded"`
}
MergeStats reports what a union merge added vs. skipped as already-present.
type PentestRun ¶
type PentestRun struct {
ID int64 `json:"id"`
TS int64 `json:"ts"` // started, unix millis
Status string `json:"status"` // planning | executing | verifying | done | stopped | error
Scope string `json:"scope"` // JSON snapshot of the scope rules the run was bound to
Plan string `json:"plan"` // JSON attack plan (targets × vuln classes × tools)
Budget string `json:"budget"` // JSON {maxRequests,maxTokens,maxWallMs} + consumed counters
Summary string `json:"summary"` // JSON rollup: candidates, verified, filed, rejected
Error string `json:"error,omitempty"`
}
PentestRun is one autonomous-pentest ("Autopilot") run. The scope/plan/budget/ summary columns hold opaque JSON snapshots the engine owns; the store treats them as raw strings and never inspects their shape.
type Rule ¶
Rule is one ordered match-&-replace transform. Type is one of "req-header", "req-body" (response-side types are reserved for a later slice).
type SavedView ¶
SavedView is a named history filter (its Data is an opaque JSON blob the UI understands: scheme/method/status/search/host/inScope).
type ScopeRule ¶
type ScopeRule struct {
ID int64 `json:"id"`
Ord int `json:"ord"`
Enabled bool `json:"enabled"`
Action string `json:"action"`
Host string `json:"host"`
Path string `json:"path"`
Scheme string `json:"scheme"`
Port int `json:"port"`
}
ScopeRule is one target-scope rule. Action is "include" | "exclude". Empty host/path/scheme and port 0 mean "any" for that field.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store owns the SQLite database and the on-disk body directory.
func (*Store) AddFlowTags ¶
AddFlowTags adds tags to a flow (union) and returns the flow's full tag set.
func (*Store) AppendNote ¶
AppendNote atomically appends a markdown block to the project notebook, separated from any existing content by a blank line. Unlike a client-side GET-then-PUT (the old append_notes MCP tool's approach), the read and write happen inside one critical section here, so two concurrent appends (two AI agents, or an agent racing a human editing in the UI) can never silently clobber each other — both appends are guaranteed to land.
func (*Store) AttachFlow ¶
AttachFlow records a flow as a PoC for a finding and inserts (or updates) a flow block in the finding's narrative body. pos is the 0-based block index at which to insert the flow block; pass -1 to append at the end. Idempotent on re-attach — updates the note in both tables and in the body block (position unchanged if the block already exists).
Returns ErrFlowNotFound when flowID has no row in flows — callers must not create orphan PoC attachments that later render as Missing.
func (*Store) AttachImage ¶ added in v1.2.0
AttachImage inserts (or updates) an image block in the finding's narrative body. hash must already be stored via PutImageBytes. pos is the 0-based block index; pass -1 to append. Idempotent on the same hash — updates mime/caption in place.
func (*Store) AttachTags ¶
AttachTags populates each flow's Tags field from one batch query.
func (*Store) BackupTo ¶
BackupTo writes a consistent, compacted snapshot of the database to destPath using SQLite's `VACUUM INTO`. It is safe to call on a live WAL-mode database: the snapshot is a single self-contained file with every committed row folded in (no separate -wal/-shm needed) and free pages reclaimed, so it is both consistent and typically smaller than a raw file copy.
destPath must not already exist. We check explicitly rather than relying on `VACUUM INTO`'s own refusal, since that behavior is driver/platform-specific (observed to differ under the pure-Go modernc.org/sqlite driver across OSes). The filename is a SQL string literal (not a bound parameter), so single quotes in the path are escaped to keep it a single literal.
func (*Store) BodiesDir ¶
BodiesDir returns the absolute directory holding content-addressed body blobs for this store. Callers that archive a whole project (DB + bodies) need it.
func (*Store) BodyExists ¶ added in v1.2.0
BodyExists reports whether a content-addressed body file is present on disk.
func (*Store) CountFlowsWithFlag ¶
CountFlowsWithFlag returns how many flows have any of the given flag bits set.
func (*Store) CountSuccessfulHTTPS ¶
CountSuccessfulHTTPS returns flows where HTTPS MITM completed (non-zero status, not a recorded TLS handshake failure).
func (*Store) CreateAPIKey ¶
func (s *Store) CreateAPIKey(label, scope string, expires int64) (token string, key APIKey, err error)
CreateAPIKey mints a new key, returning the full token (shown to the user once) and its stored metadata. The token is "ick_" + 48 hex chars. scope is normalized to full/read; expires is a unix-millis expiry (0 = never).
func (*Store) CreateFinding ¶
CreateFinding inserts a finding and sets f.ID/f.TS/f.UpdatedTS. Title is required. If Body is empty it is synthesized from Detail + Evidence so new findings are immediately in the interleaved-body format.
func (*Store) CreatePentestRun ¶
func (s *Store) CreatePentestRun(r *PentestRun) (int64, error)
CreatePentestRun inserts a run and sets r.ID (and r.TS if unset). Status defaults to "planning" when empty.
func (*Store) CreateRule ¶
CreateRule inserts a rule and returns its id.
func (*Store) CreateScopeRule ¶
CreateScopeRule inserts a scope rule and returns its id.
func (*Store) CreateView ¶
CreateView stores a named view and returns its id.
func (*Store) DeleteAPIKey ¶
DeleteAPIKey revokes a key by id.
func (*Store) DeleteActivity ¶
DeleteActivity removes every recorded activity row (the user cleared the feed).
func (*Store) DeleteFinding ¶
DeleteFinding removes a finding and its PoC attachments.
func (*Store) DeleteFlows ¶
DeleteFlows removes the given flows and returns how many rows were deleted. An empty id list is a no-op. Content-addressed body files are left in place (they are shared/deduplicated across flows); the metadata rows are what go.
func (*Store) DeleteFlowsByHost ¶
DeleteFlowsByHost removes flows by host pattern and returns how many rows were deleted.
- When keepOnly is false, every flow whose host matches ANY pattern in hosts is deleted. An empty hosts slice is a no-op (returns 0, nil).
- When keepOnly is true, every flow whose host matches NONE of the patterns is deleted (i.e. the listed hosts are kept, everything else is purged). An empty hosts slice with keepOnly=true is rejected with an error to prevent silently wiping all data.
Pattern matching is case-insensitive and supports leading-wildcard patterns (e.g. "*.example.com" matches "example.com" and all subdomains).
func (*Store) DeleteRule ¶
DeleteRule removes a rule by id.
func (*Store) DeleteScopeRule ¶
DeleteScopeRule removes a scope rule by id.
func (*Store) DeleteView ¶
DeleteView removes a view by id.
func (*Store) DetachFlow ¶
DetachFlow removes a PoC flow from a finding's flow table and body.
func (*Store) DistinctTags ¶
DistinctTags lists every tag in use with its flow count and color, most-used first.
func (*Store) Endpoints ¶
func (s *Store) Endpoints(f EndpointFilter) ([]Endpoint, string, error)
Endpoints returns unique endpoints aggregated from flows. When SearchScope is headers, body, or all, Search filters by stored headers and/or body content (body search is bounded — see maxEndpointBodyScanFlows).
func (*Store) FindingImageHashes ¶ added in v1.2.0
FindingImageHashes returns every content hash referenced by image blocks in all findings' body JSON. Used by GCBodies so screenshot evidence is not deleted while still attached to a finding.
func (*Store) FindingImageMIME ¶ added in v1.2.0
FindingImageMIME returns the MIME stored on the first finding image block that references hash, or "" if none.
func (*Store) FlowIDsBodySearch ¶
FlowIDsBodySearch returns flow ids whose request or response body contains term (case-insensitive). Other FlowFilter fields apply; Search is the body term.
func (*Store) GCBodies ¶
GCBodies reclaims body files that are no longer referenced by any flow or finding image block. It walks bodiesDir, collects every file whose name looks like a content hash (a 64-hex-char sha256 stored under a two-level prefix directory), queries flows + finding image hashes, and removes any file whose hash is not among them.
GCBodies returns the number of files removed and the total bytes freed. It is safe to call while the store is in use: it never removes a file that is still referenced, and it never touches files outside bodiesDir or files whose names do not match the content-hash scheme (e.g. ".tmp-*" partials).
func (*Store) GCNotesImages ¶
GCNotesImages deletes notebook images no longer referenced in the markdown.
func (*Store) GetFinding ¶
GetFinding loads one finding with its narrative body blocks and PoC flow list.
func (*Store) GetFindingVerification ¶
func (s *Store) GetFindingVerification(findingID int64) (*FindingVerification, error)
GetFindingVerification loads the proof-record for a finding, or sql.ErrNoRows if the finding has no machine verification.
func (*Store) GetNotesImage ¶
GetNotesImage loads a stored notebook image by id.
func (*Store) GetPentestRun ¶
func (s *Store) GetPentestRun(id int64) (*PentestRun, error)
GetPentestRun loads one run by id.
func (*Store) GetSetting ¶
GetSetting returns the value and whether it was present.
func (*Store) HasAPIKeys ¶
HasAPIKeys reports whether any control-API key exists. Auth is opt-in: while this is false the MCP endpoint stays open (loopback trust); once the operator creates a key, a valid bearer token is required.
func (*Store) HostStats ¶
HostStats returns per-host flow counts and approximate byte totals, sorted descending by bytes. See HostStat for the approximation caveat.
func (*Store) InsertActivity ¶
InsertActivity persists an AI tool-call record (a.TS set by the caller) and sets a.ID. Rows older than the most recent activityKeep are pruned so the log can't grow without bound.
func (*Store) InsertFlow ¶
InsertFlow stores a new flow and sets f.ID to the assigned row id.
func (*Store) InsertNotesImage ¶
InsertNotesImage stores one embedded notebook image and returns its row id.
func (*Store) ListAPIKeys ¶
ListAPIKeys returns all key metadata (never the token or hash), newest first.
func (*Store) ListActivity ¶
ListActivity returns recorded activity newest-first, capped at limit.
func (*Store) ListFindings ¶
ListFindings returns findings ordered by severity (High→Info) then newest, each with its PoC flows and narrative blocks. Empty severity/status means "any".
func (*Store) ListIssues ¶
ListIssues returns all issues ordered by severity (High→Info) then id.
func (*Store) ListPentestRuns ¶
func (s *Store) ListPentestRuns() ([]PentestRun, error)
ListPentestRuns returns runs newest-first, capped at pentestRunKeep.
func (*Store) ListScopeRules ¶
ListScopeRules returns scope rules ordered by ord then id.
func (*Store) LoadNotes ¶
LoadNotes returns project notes, migrating any legacy inline data-URL images.
func (*Store) MergeFrom ¶
func (s *Store) MergeFrom(peerDBPath, peerBodiesDir, label string) (MergeStats, error)
MergeFrom unions another project's flows and findings into this one (additive, non-destructive) — the "pull" of a git-like collaboration. Content-addressed bodies dedupe automatically; flows dedupe by a content signature (so re-merging the same peer is idempotent); findings are appended (remapping their PoC flow references to the new local flow ids) and deduped by a title/target signature. A "peer/<label>" tag is added to every imported flow for provenance.
peerDBPath is a peer project's interceptor.db (opened read-only); peerBodiesDir is its bodies/ directory (may be absent for an empty project).
func (*Store) NewBodyWriter ¶
func (s *Store) NewBodyWriter() (*BodyWriter, error)
NewBodyWriter starts a new body capture.
func (*Store) NormalizeNotesMarkdown ¶
NormalizeNotesMarkdown replaces inline data-URL images with /api/notes/images/{id} references backed by SQLite blobs, so the markdown stays small.
func (*Store) NotesImageExists ¶
NotesImageExists reports whether an image id is present (for tests).
func (*Store) OpenBody ¶
func (s *Store) OpenBody(sum string) (io.ReadCloser, error)
OpenBody returns a reader for the body with the given hash. An empty hash yields an empty reader (no body).
func (*Store) PersistNotes ¶
PersistNotes saves normalized markdown and drops orphaned images.
func (*Store) PutImageBytes ¶ added in v1.2.0
PutImageBytes stores screenshot/evidence bytes in the content-addressed bodies directory (same layout as flow bodies) and returns the sha256 hash. MIME is sanitized to the notes raster allowlist. Max size matches notes images (5 MiB).
func (*Store) QueryFlows ¶
QueryFlows returns up to limit flows, newest first.
func (*Store) QueryFlowsFilter ¶
func (s *Store) QueryFlowsFilter(f FlowFilter) ([]*Flow, error)
QueryFlowsFilter returns flows matching f, newest first. Filtering and paging are pushed down to SQL so large histories never materialize in memory.
func (*Store) QueryFlowsListFilter ¶
func (s *Store) QueryFlowsListFilter(f FlowFilter) ([]*Flow, error)
QueryFlowsListFilter is like QueryFlowsFilter but skips req/res header columns — use for list/summary views that only need flowJSON fields; use QueryFlowsFilter when headers or body hashes are needed downstream.
func (*Store) QueryWSFrames ¶
QueryWSFrames returns up to limit frames for a flow, oldest first.
func (*Store) RemoveFlowTag ¶
RemoveFlowTag detaches a single tag from a flow.
func (*Store) SaveFindingVerification ¶
func (s *Store) SaveFindingVerification(v *FindingVerification) (int64, error)
SaveFindingVerification upserts the proof-record for a finding, keyed by finding_id (one proof-record per finding). On conflict it replaces every proof field so re-verifying a finding overwrites the prior record in place; v.ID is set to the resulting row id.
func (*Store) SaveIssues ¶
SaveIssues upserts issues, deduplicated by (title, target).
func (*Store) SaveWSFrame ¶
SaveWSFrame records a captured frame, trimming the flow to the most recent wsFramesPerFlow frames.
func (*Store) SetFlowNote ¶
SetFlowNote sets (or clears, with "") the free-text note attached to a flow.
func (*Store) SetFlowTags ¶
SetFlowTags replaces a flow's tag set with the normalized `tags` (empty clears).
func (*Store) SetSetting ¶
SetSetting upserts a key/value setting.
func (*Store) SetTagColor ¶
SetTagColor sets (or clears, with "") a tag's display color. The value is stored verbatim; callers validate it's a safe CSS color before persisting.
func (*Store) TagsForFlows ¶
TagsForFlows batch-loads tags for many flows in one query (no N+1), returning a map from flow id to its sorted tag list. Ids with no tags are absent from the map.
func (*Store) UpdateFinding ¶
func (s *Store) UpdateFinding(id int64, severity, status, title, target, detail, evidence, fix, body, impact, cvss, verificationInstructions *string) error
UpdateFinding applies non-nil fields and bumps updated_ts. body, when set, is stored as the new narrative body (already-serialized JSON). When detail is set but body is nil, the first text block in an existing body is updated (MCP backward-compat: AI updates detail → UI sees the change). When body is set, detail is synced from its first text block so MCP list_findings still shows meaningful text.
func (*Store) UpdateFlow ¶
UpdateFlow fills in the response-side (and post-send request) fields of a flow that was first inserted at request time, keyed by f.ID. The immutable request identity (ts, scheme, host, port, version, client) is left untouched.
func (*Store) UpdatePentestRun ¶
func (s *Store) UpdatePentestRun(id int64, status, scope, plan, budget, summary, errText *string) error
UpdatePentestRun applies only the non-nil fields to the run keyed by id. The immutable start time (ts) is never touched here.
func (*Store) UpdateRule ¶
UpdateRule overwrites the rule identified by r.ID.
func (*Store) UpdateScopeRule ¶
UpdateScopeRule overwrites the rule identified by r.ID.
func (*Store) VerifyAPIKey ¶
VerifyAPIKey reports whether token matches a stored, unexpired key. Retained for the MCP opt-in gate and existing callers; new code that needs the scope should call VerifyAPIKeyScope.
type TagCount ¶
type TagCount struct {
Tag string `json:"tag"`
Count int `json:"count"`
Color string `json:"color,omitempty"`
}
TagCount is a tag with how many flows carry it and its (optional) color.
type WSFrame ¶
type WSFrame struct {
ID int64 `json:"id"`
FlowID int64 `json:"flowId"`
TS time.Time `json:"-"`
Dir string `json:"dir"`
Opcode int `json:"opcode"`
Length int64 `json:"length"`
Preview string `json:"preview"`
}
WSFrame is one captured WebSocket frame. Dir is "send" (client→server) or "recv" (server→client). Preview holds a bounded prefix of the (unmasked) payload; Length is the full frame payload length.