store

package
v1.2.0 Latest Latest
Warning

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

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

Documentation

Overview

Package store persists flow metadata in SQLite and bodies on disk.

Index

Constants

View Source
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.

View Source
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.

View Source
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

View Source
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

func DecodeNotesImagePayload(mime, b64 string) (string, []byte, error)

DecodeNotesImagePayload decodes a base64 image upload body.

func FlowSortValue

func FlowSortValue(fl *Flow, key string) string

FlowSortValue returns the string form of a flow's sort key (for client cursors).

func NormalizeFlowSortKey

func NormalizeFlowSortKey(key string) string

NormalizeFlowSortKey returns a whitelisted sort key (default id).

func NormalizeScope

func NormalizeScope(s string) string

NormalizeScope maps any input to a known scope (default full).

func NormalizeTags

func NormalizeTags(tags []string) []string

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

func SanitizeNotesImageMIME(mime string) string

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).

func (*BodyWriter) Finalize

func (w *BodyWriter) Finalize() (string, int64, error)

Finalize commits the body and returns its sha256 hex hash and byte length. If a body with the same hash already exists it is deduplicated.

func (*BodyWriter) Write

func (w *BodyWriter) Write(p []byte) (int, error)

Write implements io.Writer.

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

type HostStat struct {
	Host  string
	Flows int64
	Bytes int64
}

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

type Rule struct {
	ID      int64
	Ord     int
	Enabled bool
	Type    string
	Match   string
	Replace string
}

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

type SavedView struct {
	ID   int64  `json:"id"`
	Name string `json:"name"`
	Data string `json:"data"`
}

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 Open

func Open(dir string) (*Store, error)

Open creates (or opens) the database and body store under dir.

func (*Store) AddFlowTags

func (s *Store) AddFlowTags(flowID int64, tags []string) ([]string, error)

AddFlowTags adds tags to a flow (union) and returns the flow's full tag set.

func (*Store) AppendNote

func (s *Store) AppendNote(text string) error

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

func (s *Store) AttachFlow(findingID, flowID int64, note string, pos int) error

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

func (s *Store) AttachImage(findingID int64, hash, mime, caption string, pos int) error

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

func (s *Store) AttachTags(flows []*Flow) error

AttachTags populates each flow's Tags field from one batch query.

func (*Store) BackupTo

func (s *Store) BackupTo(destPath string) error

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

func (s *Store) BodiesDir() string

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

func (s *Store) BodyExists(hash string) bool

BodyExists reports whether a content-addressed body file is present on disk.

func (*Store) Close

func (s *Store) Close() error

Close closes the underlying database.

func (*Store) CountFlowsWithFlag

func (s *Store) CountFlowsWithFlag(flag int64) (int64, error)

CountFlowsWithFlag returns how many flows have any of the given flag bits set.

func (*Store) CountSuccessfulHTTPS

func (s *Store) CountSuccessfulHTTPS(hostSubstring string) (int64, error)

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

func (s *Store) CreateFinding(f *Finding) (int64, error)

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

func (s *Store) CreateRule(r *Rule) (int64, error)

CreateRule inserts a rule and returns its id.

func (*Store) CreateScopeRule

func (s *Store) CreateScopeRule(r *ScopeRule) (int64, error)

CreateScopeRule inserts a scope rule and returns its id.

func (*Store) CreateView

func (s *Store) CreateView(v *SavedView) (int64, error)

CreateView stores a named view and returns its id.

func (*Store) DeleteAPIKey

func (s *Store) DeleteAPIKey(id int64) error

DeleteAPIKey revokes a key by id.

func (*Store) DeleteActivity

func (s *Store) DeleteActivity() error

DeleteActivity removes every recorded activity row (the user cleared the feed).

func (*Store) DeleteFinding

func (s *Store) DeleteFinding(id int64) error

DeleteFinding removes a finding and its PoC attachments.

func (*Store) DeleteFlows

func (s *Store) DeleteFlows(ids []int64) (int64, error)

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

func (s *Store) DeleteFlowsByHost(hosts []string, keepOnly bool) (int64, error)

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

func (s *Store) DeleteRule(id int64) error

DeleteRule removes a rule by id.

func (*Store) DeleteScopeRule

func (s *Store) DeleteScopeRule(id int64) error

DeleteScopeRule removes a scope rule by id.

func (*Store) DeleteView

func (s *Store) DeleteView(id int64) error

DeleteView removes a view by id.

func (*Store) DetachFlow

func (s *Store) DetachFlow(findingID, flowID int64) error

DetachFlow removes a PoC flow from a finding's flow table and body.

func (*Store) DistinctTags

func (s *Store) DistinctTags() ([]TagCount, error)

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

func (s *Store) FindingImageHashes() (map[string]struct{}, error)

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

func (s *Store) FindingImageMIME(hash string) string

FindingImageMIME returns the MIME stored on the first finding image block that references hash, or "" if none.

func (*Store) FlowCount

func (s *Store) FlowCount() (int64, error)

FlowCount returns the total number of captured flows.

func (*Store) FlowIDsBodySearch

func (s *Store) FlowIDsBodySearch(f FlowFilter, maxScan int) ([]int64, string, error)

FlowIDsBodySearch returns flow ids whose request or response body contains term (case-insensitive). Other FlowFilter fields apply; Search is the body term.

func (*Store) FlowTags

func (s *Store) FlowTags(flowID int64) ([]string, error)

FlowTags returns one flow's tags, sorted.

func (*Store) GCBodies

func (s *Store) GCBodies() (removedFiles int64, freedBytes int64, err error)

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

func (s *Store) GCNotesImages(notes string) error

GCNotesImages deletes notebook images no longer referenced in the markdown.

func (*Store) GetFinding

func (s *Store) GetFinding(id int64) (*Finding, error)

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) GetFlow

func (s *Store) GetFlow(id int64) (*Flow, error)

GetFlow loads a single flow by id.

func (*Store) GetNotesImage

func (s *Store) GetNotesImage(id int64) (mime string, data []byte, err error)

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

func (s *Store) GetSetting(key string) (string, bool, error)

GetSetting returns the value and whether it was present.

func (*Store) HasAPIKeys

func (s *Store) HasAPIKeys() (bool, error)

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

func (s *Store) HostStats() ([]HostStat, error)

HostStats returns per-host flow counts and approximate byte totals, sorted descending by bytes. See HostStat for the approximation caveat.

func (*Store) InsertActivity

func (s *Store) InsertActivity(a *Activity) (int64, error)

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

func (s *Store) InsertFlow(f *Flow) (int64, error)

InsertFlow stores a new flow and sets f.ID to the assigned row id.

func (*Store) InsertNotesImage

func (s *Store) InsertNotesImage(mime string, data []byte) (int64, error)

InsertNotesImage stores one embedded notebook image and returns its row id.

func (*Store) ListAPIKeys

func (s *Store) ListAPIKeys() ([]APIKey, error)

ListAPIKeys returns all key metadata (never the token or hash), newest first.

func (*Store) ListActivity

func (s *Store) ListActivity(limit int) ([]Activity, error)

ListActivity returns recorded activity newest-first, capped at limit.

func (*Store) ListFindings

func (s *Store) ListFindings(severity, status string) ([]Finding, error)

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

func (s *Store) ListIssues() ([]Issue, error)

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) ListRules

func (s *Store) ListRules() ([]Rule, error)

ListRules returns all rules ordered by ord then id.

func (*Store) ListScopeRules

func (s *Store) ListScopeRules() ([]ScopeRule, error)

ListScopeRules returns scope rules ordered by ord then id.

func (*Store) ListViews

func (s *Store) ListViews() ([]SavedView, error)

ListViews returns saved views, newest first.

func (*Store) LoadNotes

func (s *Store) LoadNotes() (string, error)

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

func (s *Store) NormalizeNotesMarkdown(notes string) (string, error)

NormalizeNotesMarkdown replaces inline data-URL images with /api/notes/images/{id} references backed by SQLite blobs, so the markdown stays small.

func (*Store) NotesImageExists

func (s *Store) NotesImageExists(id int64) (bool, error)

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

func (s *Store) PersistNotes(notes string) (string, error)

PersistNotes saves normalized markdown and drops orphaned images.

func (*Store) PutImageBytes added in v1.2.0

func (s *Store) PutImageBytes(mime string, data []byte) (hash string, n int64, err error)

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

func (s *Store) QueryFlows(limit int) ([]*Flow, error)

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

func (s *Store) QueryWSFrames(flowID int64, limit int) ([]*WSFrame, error)

QueryWSFrames returns up to limit frames for a flow, oldest first.

func (*Store) RemoveFlowTag

func (s *Store) RemoveFlowTag(flowID int64, tag string) error

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

func (s *Store) SaveIssues(issues []Issue) error

SaveIssues upserts issues, deduplicated by (title, target).

func (*Store) SaveWSFrame

func (s *Store) SaveWSFrame(f *WSFrame) error

SaveWSFrame records a captured frame, trimming the flow to the most recent wsFramesPerFlow frames.

func (*Store) SetFlowNote

func (s *Store) SetFlowNote(id int64, note string) error

SetFlowNote sets (or clears, with "") the free-text note attached to a flow.

func (*Store) SetFlowTags

func (s *Store) SetFlowTags(flowID int64, tags []string) ([]string, error)

SetFlowTags replaces a flow's tag set with the normalized `tags` (empty clears).

func (*Store) SetSetting

func (s *Store) SetSetting(key, value string) error

SetSetting upserts a key/value setting.

func (*Store) SetTagColor

func (s *Store) SetTagColor(tag, color string) error

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

func (s *Store) TagsForFlows(ids []int64) (map[int64][]string, error)

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

func (s *Store) UpdateFlow(f *Flow) error

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

func (s *Store) UpdateRule(r *Rule) error

UpdateRule overwrites the rule identified by r.ID.

func (*Store) UpdateScopeRule

func (s *Store) UpdateScopeRule(r *ScopeRule) error

UpdateScopeRule overwrites the rule identified by r.ID.

func (*Store) VerifyAPIKey

func (s *Store) VerifyAPIKey(token string) (bool, error)

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.

func (*Store) VerifyAPIKeyScope

func (s *Store) VerifyAPIKeyScope(token string) (ok bool, scope string, err error)

VerifyAPIKeyScope reports whether token matches a stored key, and if so its scope. An expired key (expires != 0 && expires <= now) verifies as false.

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.

Jump to

Keyboard shortcuts

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