risk

package
v0.0.0-...-ccd67f6 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: AGPL-3.0 Imports: 83 Imported by: 0

Documentation

Index

Constants

View Source
const (

	// PolicyBypassTargetKindShadowMCPServer identifies a Shadow MCP server target.
	PolicyBypassTargetKindShadowMCPServer = "shadow_mcp_server"
	// PolicyBypassWholePolicyTargetKey identifies a whole-policy target.
	PolicyBypassWholePolicyTargetKey = "policy"
)

Variables

View Source
var (
	ErrInvalidFingerprintPepperJSON    = errors.New("invalid fingerprint pepper keyring json")
	ErrInvalidFingerprintPepperKeyRing = errors.New("invalid fingerprint pepper keyring")
	ErrFingerprintPepperKeyNotFound    = errors.New("fingerprint pepper key not found")
)

Functions

func Attach

func Attach(mux goahttp.Muxer, service *Service)

func GeneratePolicyAckToken

func GeneratePolicyAckToken(ctx context.Context, c cache.Cache, input PolicyAckTokenInput, ttl time.Duration) (string, time.Time, error)

GeneratePolicyAckToken stores the challenge state in the cache and returns the rpak1 token (prefix + cache id) plus its expiry.

func GeneratePolicyAckURL

func GeneratePolicyAckURL(ctx context.Context, c cache.Cache, siteURL *url.URL, input PolicyAckTokenInput, ttl time.Duration) (string, time.Time, error)

GeneratePolicyAckURL stores the challenge state and returns the dashboard URL that redeems it (token in the fragment, never the query — same rationale as GeneratePolicyBypassRequestURL) plus the link expiry.

func GeneratePolicyBypassRequestToken

func GeneratePolicyBypassRequestToken(ctx context.Context, c cache.Cache, input PolicyBypassRequestTokenInput, ttl time.Duration) (string, time.Time, error)

GeneratePolicyBypassRequestToken stores the request state in the cache and returns a short rpbr2 token (the cache id) plus its expiry. The token is the only reference to the state — it must be stored under the same cache the redeem handler reads from.

func GeneratePolicyBypassRequestURL

func GeneratePolicyBypassRequestURL(ctx context.Context, c cache.Cache, siteURL *url.URL, input PolicyBypassRequestTokenInput, ttl time.Duration) (string, time.Time, error)

func NewObserver

func NewObserver(
	logger *slog.Logger,
	tracerProvider trace.TracerProvider,
	db *pgxpool.Pool,
	signaler RiskAnalysisSignaler,
	auditLogger *audit.Logger,
) chat.MessageObserver

NewObserver creates a lightweight chat.MessageObserver that signals the risk drain workflow when new messages are stored. Use this in contexts (e.g. the worker process) where the full risk Service is not needed.

func RedactMatchAll

func RedactMatchAll(match string, orgID string) string

RedactMatchAll encodes a match value as `<redacted len=N sha=XXXXXXXX>`, redacting every source with no passthrough. An empty match collapses to `<redacted len=0>` without a sha component so the absence of a finding payload is distinguishable from a real hash.

The hash is salted by orgID with a NUL separator so two different orgs holding the same secret produce different fingerprints — defense in depth against any future surface that crosses an org boundary. Within an org the fingerprint stays deterministic so agents can still dedupe.

This is the canonical redaction for the ClickHouse analytics store (match_redacted), where no source may store plaintext. The API-facing redactMatch wraps it for its non-passthrough sources, and the risk_findings backfill (server/cmd/tools/migrations) calls it directly, so the two never drift in salt layout, prefix, or sha truncation.

Types

type FindingCHWriter

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

FindingCHWriter consumes Finding messages off the shared Pub/Sub topic and writes them to the ClickHouse risk_findings table. It never stores the raw matched value: only its length, a redacted display string, and one-way fingerprints. The verbatim value stays in Postgres for the audited unmask path.

func NewFindingCHWriter

func NewFindingCHWriter(logger *slog.Logger, exclusionsDB repo.DBTX, meterProvider metric.MeterProvider, inserter RiskFindingInserter, fingerprinter Fingerprinter) *FindingCHWriter

func (*FindingCHWriter) HandleBatch

func (w *FindingCHWriter) HandleBatch(ctx context.Context, messages []*riskv1.Finding, _ []gcp.MessageMetadata) error

type Fingerprinter

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

func ParsePepperKeyRing

func ParsePepperKeyRing(jsonSecret []byte) (Fingerprinter, error)

ParsePepperKeyRing parses a JSON payload containing the pepper keyring for fingerprinting risk findings. The expected format is:

{
  "current": "v2",
  "keys": {
    "v1": "base64-encoded-key-for-v1",
    "v2": "base64-encoded-key-for-v2"
  }
}

func (Fingerprinter) HS256

func (p Fingerprinter) HS256(message []byte) ([]byte, string, error)

func (Fingerprinter) HS256WithVersion

func (p Fingerprinter) HS256WithVersion(version string, message []byte) ([]byte, error)

func (Fingerprinter) TenantedHS256

func (p Fingerprinter) TenantedHS256(tenantID string, message []byte, opts ...TenantedOption) ([]byte, string, error)

TenantedHS256 fingerprints message under a per-tenant key derived from the current pepper version, so the same secret in two different tenants produces unrelated fingerprints (tenant isolation).

func (Fingerprinter) TenantedHS256WithVersion

func (p Fingerprinter) TenantedHS256WithVersion(version string, tenantID string, message []byte, opts ...TenantedOption) ([]byte, error)

TenantedHS256WithVersion is like HS256WithVersion but keys the HMAC with a per-tenant key instead of the raw pepper. See deriveKey for the derivation.

type PolicyAckTokenInput

type PolicyAckTokenInput struct {
	OrganizationID string
	ProjectID      string
	UserID         string
	RiskPolicyID   string
	PolicyName     string
	ToolName       *string
	// CallFingerprint scopes the acknowledgement to the concrete call that was
	// challenged (SHA-256 of the scanned input). Redeeming writes it onto the
	// challenge row so only an identical retry — not any same-tool call — clears.
	CallFingerprint  string
	ChallengeMessage string
	// RememberFor is how long the acknowledgement, once granted, suppresses
	// re-challenging that same call. Zero uses the ack window default.
	RememberFor time.Duration
}

PolicyAckTokenInput is the state a challenge link points at. Deliberately carries the log-safe identity needed to record the ack, plus ChallengeMessage.

ChallengeMessage is the human-facing warning shown on the approval page (the same text rendered to the operator in the terminal). It MAY contain the matched value: this is an ephemeral, token-gated cache record (~10 min TTL) — the same scoped exposure as the terminal display, NOT durable persistence. It must never be copied into ClickHouse, tool_call_blocks, or audit.

type PolicyBypassEvaluation

type PolicyBypassEvaluation struct {
	OrganizationID string
	UserID         string
	PolicyID       string
	Target         *PolicyBypassTarget
}

type PolicyBypassEvaluator

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

func NewPolicyBypassEvaluator

func NewPolicyBypassEvaluator(logger *slog.Logger, db repo.DBTX) *PolicyBypassEvaluator

func (*PolicyBypassEvaluator) CanBypass

type PolicyBypassRequestTokenInput

type PolicyBypassRequestTokenInput struct {
	OrganizationID         string
	ProjectID              string
	RequesterUserID        string
	ObservedName           *string
	ObservedFullURL        *string
	ObservedURLHost        *string
	ObservedServerIdentity *string
	ToolName               *string
	ToolCall               *string
	BlockReason            *string
	RiskPolicyID           string
	RiskResultID           *string
}

type PolicyBypassTarget

type PolicyBypassTarget struct {
	Kind       string
	Label      string
	Key        string
	Dimensions map[string]string
}

PolicyBypassTarget identifies the generic resource a bypass request or runtime bypass check applies to.

func ShadowMCPPolicyBypassTarget

func ShadowMCPPolicyBypassTarget(evidence shadowmcp.AccessEvidence, toolName string) *PolicyBypassTarget

func ShadowMCPServerPolicyBypassTarget

func ShadowMCPServerPolicyBypassTarget(serverURL string, serverIdentity string, label string) PolicyBypassTarget

ShadowMCPServerPolicyBypassTarget applies to a specific Shadow MCP server.

func WholePolicyBypassTarget

func WholePolicyBypassTarget() PolicyBypassTarget

WholePolicyBypassTarget applies to the policy as a whole.

func (PolicyBypassTarget) IsWholePolicy

func (t PolicyBypassTarget) IsWholePolicy() bool

IsWholePolicy reports whether the target represents a bypass for the entire risk policy rather than a narrower target such as a specific Shadow MCP server. Runtime checks use this to build a dimensionless authz check.

type RiskAnalysisSignaler

type RiskAnalysisSignaler interface {
	Signal(ctx context.Context, projectID uuid.UUID) error
}

RiskAnalysisSignaler signals the per-project risk analysis coordinator workflow.

type RiskExclusionReconciler

type RiskExclusionReconciler interface {
	Reconcile(ctx context.Context, projectID, exclusionID uuid.UUID) error
}

RiskExclusionReconciler triggers the retroactive reconcile sweep for an exclusion (flag/unflag matching findings in risk_results). Best-effort: a failed trigger is logged, not fatal — the reconcile itself is idempotent.

type RiskFindingInserter

type RiskFindingInserter interface {
	InsertRiskFindings(ctx context.Context, rows []chrepo.RiskFindingRow) error
}

RiskFindingInserter writes a batch of findings to ClickHouse. *chrepo.Queries satisfies it; tests supply a fake.

type RiskPolicyResultsCleaner

type RiskPolicyResultsCleaner interface {
	Clean(ctx context.Context, projectID, policyID uuid.UUID) error
}

RiskPolicyResultsCleaner asynchronously deletes risk_results rows for a soft-deleted policy. Best-effort: a failed trigger is logged, not fatal.

type RiskScanner

type RiskScanner interface {
	// ScanForEnforcement scans text against enabled blocking policies that
	// apply to the given user. Everyone-audience policies always apply;
	// targeted policies require a matching risk_policy:evaluate grant.
	// toolName is the tool-call name for tool_request/tool_response messages
	// ("" otherwise); it is surfaced to prompt-based policies.
	ScanForEnforcement(ctx context.Context, organizationID string, projectID uuid.UUID, userID string, text string, messageType message.Type, toolName string) (*ScanResult, error)
	// LookupShadowMCPBlockingPolicy returns the first enabled shadow-MCP
	// policy that applies to the given user. Returns nil when no such policy
	// exists. Used by hooks to gate the realtime deny path.
	LookupShadowMCPBlockingPolicy(ctx context.Context, organizationID string, projectID uuid.UUID, userID string) (*ShadowMCPPolicy, error)
	// HasEnabledShadowMCPPolicy reports whether the project has at least one
	// enabled shadow-MCP policy (any action). Used by the MCP server to
	// decide whether to inject the x-gram-toolset-id constant into tool
	// schemas.
	HasEnabledShadowMCPPolicy(ctx context.Context, projectID uuid.UUID) (bool, error)
	// HasAcknowledgedChallenge reports whether a live acknowledgement exists for
	// a warn (challenge) policy match by this (user, policy, tool, callFingerprint).
	// The hooks layer calls this before denying a warn match: true means the user
	// already acknowledged THIS concrete call and the identical retry should be
	// allowed. Fail-closed.
	HasAcknowledgedChallenge(ctx context.Context, projectID uuid.UUID, userID, policyID, toolName, callFingerprint string) bool
	// RecordPolicyChallenge upserts the challenged-state row for a warn match so
	// the challenge is auditable and linkable. Keyed per concrete call via
	// callFingerprint. Log-safe: never receives the raw matched value. Best-effort.
	RecordPolicyChallenge(ctx context.Context, organizationID string, projectID uuid.UUID, userID, policyID, toolName, policyName, entity, ruleID, callFingerprint string)
}

RiskScanner checks text against blocking risk policies.

type ScanResult

type ScanResult struct {
	Action      string // "flag" | "block" | "warn"
	PolicyID    string
	PolicyName  string
	Source      string
	MessageType message.Type
	RuleID      string
	Description string
	UserMessage *string // optional override for the rendered block/warn message

	// Sensitive - see the type doc. Only for the ephemeral warn render.
	MatchedValue string
	Entity       string

	// CallFingerprint is a SHA-256 (hex) of the exact scanned input (the tool-
	// call arguments / prompt text). It scopes a warn acknowledgement to THIS
	// concrete call: the challenge row and the ack are keyed on it, so
	// acknowledging one command clears only an identical retry, not every call
	// of the same tool under the same policy. Not sensitive (a one-way digest).
	CallFingerprint string
}

ScanResult describes a match from an enforcing risk policy (block or warn).

The base fields are safe to log, store, or serialize; block messages render PolicyName + Description, never the matched value.

MatchedValue and Entity are the EXCEPTION: MatchedValue is the raw matched substring (the secret/PII itself) and MUST NOT be logged, persisted to ClickHouse traces, written to tool_call_blocks.reason, or included in audit snapshots. They exist solely so the `warn` (challenge) path can render the ephemeral, user-facing warning ("... %{match} identified as %{entity} ..."). MatchedValue is empty for judge-based matches (prompt-based policies) that have no literal substring.

CAVEAT: via %{match} the value does reach the agent's permission prompt (Claude permissionDecisionReason/SystemMessage; Cursor/Codex UserMessage/ AgentMessage) and therefore the local agent transcript. That is by design - the human needs to see what tripped the challenge - but it means the "never leaves the server" invariant is scoped to Gram's own persistence, not the agent host. Any Gram-side ingestion that captures permission-prompt or transcript content (e.g. a future session-replay path) MUST scrub %{match} before persisting, or the invariant breaks silently.

type Scanner

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

Scanner implements RiskScanner using gitleaks and optionally Presidio. It pre-creates a gitleaks detector at construction time to avoid the per-scan mutex+init overhead on the hot path.

func NewScanner

func NewScanner(
	logger *slog.Logger,
	tracerProvider trace.TracerProvider,
	meterProvider metric.MeterProvider,
	db *pgxpool.Pool,
	customRuleScanner *customruleanalyzer.Scanner,
	piiScanner ra.PIIScanner,
	piScanner *promptinjection.Scanner,
	promptPolicy *promptpolicy.Scanner,
	flags feature.Provider,
	celEng *celenv.Engine,
) (*Scanner, error)

NewScanner creates a RiskScanner. piiScanner may be nil if Presidio is not available in the server process. piScanner must be non-nil; a nil classifier fails open through NoopClassifier. Primes the gitleaks detector to avoid per-scan rule compilation on the real-time hook path; returns an error if the detector cannot be built (init relies on viper global state and should never realistically fail, but propagating the error keeps startup honest).

func (*Scanner) HasAcknowledgedChallenge

func (s *Scanner) HasAcknowledgedChallenge(ctx context.Context, projectID uuid.UUID, userID, policyID, toolName, callFingerprint string) bool

HasAcknowledgedChallenge reports whether a live acknowledgement exists for this (user, policy, tool). The agent retries a warn-challenged call with NO token, so this DB lookup — not the ack link's cache token — is what lets the retry through. Fail-closed: any error (including an unreachable DB) returns false so a warn never silently allows on infra failure.

func (*Scanner) HasEnabledShadowMCPPolicy

func (s *Scanner) HasEnabledShadowMCPPolicy(ctx context.Context, projectID uuid.UUID) (bool, error)

HasEnabledShadowMCPPolicy reports whether the project has at least one enabled shadow-MCP policy (flag or block). The MCP server uses this to decide whether to inject the x-gram-toolset-id constant into tool schemas.

func (*Scanner) LookupShadowMCPBlockingPolicy

func (s *Scanner) LookupShadowMCPBlockingPolicy(ctx context.Context, organizationID string, projectID uuid.UUID, userID string) (*ShadowMCPPolicy, error)

LookupShadowMCPBlockingPolicy returns the first enabled shadow-MCP policy for the project whose action is "block". Flag-action policies surface as findings via the batch scanner instead of denying at the hook layer.

func (*Scanner) RecordPolicyChallenge

func (s *Scanner) RecordPolicyChallenge(ctx context.Context, organizationID string, projectID uuid.UUID, userID, policyID, toolName, policyName, entity, ruleID, callFingerprint string)

RecordPolicyChallenge upserts the challenged-state row for a warn match. Log-safe only: it never receives or stores the raw matched value. Best-effort — a failure to record must not change the enforcement decision.

func (*Scanner) ScanForEnforcement

func (s *Scanner) ScanForEnforcement(
	ctx context.Context,
	organizationID string,
	projectID uuid.UUID,
	userID string,
	text string,
	messageType message.Type,
	toolName string,
) (result *ScanResult, retErr error)

type Service

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

func NewService

func NewService(
	logger *slog.Logger,
	tracerProvider trace.TracerProvider,
	db *pgxpool.Pool,
	sessions *sessions.Manager,
	authzEngine *authz.Engine,
	signaler RiskAnalysisSignaler,
	reconciler RiskExclusionReconciler,
	resultsCleaner RiskPolicyResultsCleaner,
	completionClient openrouter.CompletionClient,
	shadowMCPClient *shadowmcp.Client,
	auditLogger *audit.Logger,
	cacheImpl cache.Cache,
	jwtSecret string,
	piiScanner ra.PIIScanner,
	piScanner *promptinjection.Scanner,
	flags feature.Provider,
	celEng *celenv.Engine,
	builtinPresets *presetlib.Library,
	promptJudge promptpolicy.Evaluator,
) *Service

func (*Service) APIKeyAuth

func (s *Service) APIKeyAuth(ctx context.Context, key string, schema *security.APIKeyScheme) (context.Context, error)

func (*Service) AcknowledgeRiskPolicyChallenge

AcknowledgeRiskPolicyChallenge redeems a warn/challenge ack link. Self-service: the same user who was warned confirms and the challenge is recorded as acknowledged, so the retried action proceeds. No admin approval, no RBAC grant.

func (*Service) ApproveRiskPolicyBypassRequest

func (s *Service) ApproveRiskPolicyBypassRequest(ctx context.Context, payload *gen.ApproveRiskPolicyBypassRequestPayload) (*gen.RiskPolicyBypassRequest, error)

func (*Service) CompileExpr

func (s *Service) CompileExpr(ctx context.Context, payload *gen.CompileExprPayload) (*gen.ExprCompileResult, error)

CompileExpr compiles a single CEL expression without evaluating it, so the editor can validate as the author types. It mirrors the save-time gate (celenv.Compile via the shared engine) so an expression that compiles here also saves. An empty expression is valid.

func (*Service) CreateCustomDetectionRule

func (s *Service) CreateCustomDetectionRule(ctx context.Context, payload *gen.CreateCustomDetectionRulePayload) (*types.RiskCustomDetectionRule, error)

func (*Service) CreateRiskExclusion

func (s *Service) CreateRiskExclusion(ctx context.Context, payload *gen.CreateRiskExclusionPayload) (*types.RiskExclusion, error)

func (*Service) CreateRiskPolicy

func (s *Service) CreateRiskPolicy(ctx context.Context, payload *gen.CreateRiskPolicyPayload) (*types.RiskPolicy, error)

func (*Service) CreateRiskPolicyBypassRequest

func (s *Service) CreateRiskPolicyBypassRequest(ctx context.Context, payload *gen.CreateRiskPolicyBypassRequestPayload) (*gen.RiskPolicyBypassRequest, error)

func (*Service) DeclineRiskPolicyChallenge

func (s *Service) DeclineRiskPolicyChallenge(ctx context.Context, payload *gen.DeclineRiskPolicyChallengePayload) (*gen.DeclineRiskPolicyChallengeResult, error)

DeclineRiskPolicyChallenge marks a warn/challenge declined and invalidates the link so it can't later be approved. The blocked action stays blocked.

func (*Service) DeleteCustomDetectionRule

func (s *Service) DeleteCustomDetectionRule(ctx context.Context, payload *gen.DeleteCustomDetectionRulePayload) error

func (*Service) DeleteRiskEvalReview

func (s *Service) DeleteRiskEvalReview(ctx context.Context, payload *gen.DeleteRiskEvalReviewPayload) error

DeleteRiskEvalReview clears the current reviewer's verdict.

func (*Service) DeleteRiskExclusion

func (s *Service) DeleteRiskExclusion(ctx context.Context, payload *gen.DeleteRiskExclusionPayload) error

func (*Service) DeleteRiskPolicy

func (s *Service) DeleteRiskPolicy(ctx context.Context, payload *gen.DeleteRiskPolicyPayload) error

func (*Service) DenyRiskPolicyBypassRequest

func (s *Service) DenyRiskPolicyBypassRequest(ctx context.Context, payload *gen.DenyRiskPolicyBypassRequestPayload) (*gen.RiskPolicyBypassRequest, error)

func (*Service) EvaluatePromptGuardrail

func (s *Service) EvaluatePromptGuardrail(ctx context.Context, payload *gen.EvaluatePromptGuardrailPayload) (*gen.PromptGuardrailEvalResult, error)

EvaluatePromptGuardrail replays an inline guardrail without persisting findings.

func (*Service) GetCustomDetectionRule

func (s *Service) GetCustomDetectionRule(ctx context.Context, payload *gen.GetCustomDetectionRulePayload) (*types.RiskCustomDetectionRule, error)

func (*Service) GetRiskBlock

func (s *Service) GetRiskBlock(ctx context.Context, payload *gen.GetRiskBlockPayload) (*gen.RiskBlock, error)

GetRiskBlock returns a durable tool call block by its ID. Blocks are recorded at hook-time deny into tool_call_blocks, carrying the exact reason shown to the agent. The block page is opened from the link an agent embeds in its deny message, so access is intentionally NOT gated on org-admin — the person whose agent was blocked is usually a regular member.

Org MEMBERSHIP is the floor (see the query below): a block is loadable by any signed-in member of the owning org, regardless of their active org. authorizeBlockView then tightens that when the block records an owner: only that owner, or a project admin (project:write), may view it. A block with no recorded owner (empty user_id) stays readable by any member.

func (*Service) GetRiskOverview

func (s *Service) GetRiskOverview(ctx context.Context, payload *gen.GetRiskOverviewPayload) (*gen.RiskOverviewResult, error)

func (*Service) GetRiskPolicy

func (s *Service) GetRiskPolicy(ctx context.Context, payload *gen.GetRiskPolicyPayload) (*types.RiskPolicy, error)

func (*Service) GetRiskPolicyChallenge

func (s *Service) GetRiskPolicyChallenge(ctx context.Context, payload *gen.GetRiskPolicyChallengePayload) (*gen.GetRiskPolicyChallengeResult, error)

GetRiskPolicyChallenge returns a warn/challenge's details from its ack link WITHOUT redeeming it, so the approval page can show what was flagged before the operator chooses Approve or Deny. Binds to the session org+user like the redeem path — a leaked link reveals nothing to anyone else.

func (*Service) GetRiskPolicyStatus

func (s *Service) GetRiskPolicyStatus(ctx context.Context, payload *gen.GetRiskPolicyStatusPayload) (*types.RiskPolicyStatus, error)

func (*Service) GetRiskRuleBreakdown

func (s *Service) GetRiskRuleBreakdown(ctx context.Context, payload *gen.GetRiskRuleBreakdownPayload) (*gen.RiskRuleBreakdownResult, error)

func (*Service) GetRiskUserBreakdown

func (s *Service) GetRiskUserBreakdown(ctx context.Context, payload *gen.GetRiskUserBreakdownPayload) (*gen.RiskUserBreakdownResult, error)

func (*Service) ListBuiltinExclusions

ListBuiltinExclusions returns the built-in exclusion library grouped by category. The catalog is static, embedded reference data (see presetlib), so this is a read gated by org admin with no project data access.

func (*Service) ListCustomDetectionRules

func (s *Service) ListCustomDetectionRules(ctx context.Context, payload *gen.ListCustomDetectionRulesPayload) (*gen.ListCustomDetectionRulesResult, error)

func (*Service) ListRiskCategories

func (s *Service) ListRiskCategories(ctx context.Context, payload *gen.ListRiskCategoriesPayload) (*gen.RiskCategoriesResult, error)

func (*Service) ListRiskEvalReviews

func (s *Service) ListRiskEvalReviews(ctx context.Context, payload *gen.ListRiskEvalReviewsPayload) (*gen.ListRiskEvalReviewsResult, error)

ListRiskEvalReviews returns the active regression set for a policy: every reviewer's current verdicts.

func (*Service) ListRiskExclusions

func (s *Service) ListRiskExclusions(ctx context.Context, payload *gen.ListRiskExclusionsPayload) (*gen.ListRiskExclusionsResult, error)

func (*Service) ListRiskPolicies

func (s *Service) ListRiskPolicies(ctx context.Context, payload *gen.ListRiskPoliciesPayload) (*gen.ListRiskPoliciesResult, error)

func (*Service) ListRiskResults

func (s *Service) ListRiskResults(ctx context.Context, payload *gen.ListRiskResultsPayload) (*gen.ListRiskResultsResult, error)

ListRiskResults serves the dashboard's default risk results listing. It always requires org:admin, but only returns raw `match`/`spans` content when the request is scoped to a single chat_id AND the caller separately holds chat:read for that exact chat — the same condition that already lets them load the chat's full transcript (which contains the same secret embedded in message content) via chat.LoadChat. That is a soft check (FindMatched, not Require): missing chat:read doesn't fail the request, it just falls back to a redacted response. Every other call — notably the Risk Events page, which lists across many chats with no single chat_id filter — gets match_redacted instead of the raw secret.

func (*Service) ListRiskResultsByChat

func (s *Service) ListRiskResultsByChat(ctx context.Context, payload *gen.ListRiskResultsByChatPayload) (*gen.ListRiskResultsByChatResult, error)

func (*Service) ListRiskResultsForAgent

func (s *Service) ListRiskResultsForAgent(ctx context.Context, payload *gen.ListRiskResultsForAgentPayload) (*gen.ListRiskResultsForAgentResult, error)

ListRiskResultsForAgent serves the same data as ListRiskResults but strips raw `match` content from non-shadow_mcp findings before returning, so the agent / MCP surface never holds secret values in model context. Shadow-MCP findings pass `match` through verbatim because the value is a server URL or stdio command identifier the dashboard already exposes unmasked.

func (*Service) OnMessagesStored

func (s *Service) OnMessagesStored(ctx context.Context, projectID uuid.UUID)

OnMessagesStored implements chat.MessageObserver. The caller (notifyObservers) already dispatches this in a goroutine with a detached context, so this method can safely perform I/O.

func (*Service) RevokeRiskPolicyBypassRequest

func (s *Service) RevokeRiskPolicyBypassRequest(ctx context.Context, payload *gen.RevokeRiskPolicyBypassRequestPayload) (*gen.RiskPolicyBypassRequest, error)

func (*Service) SaveRiskEvalReview

func (s *Service) SaveRiskEvalReview(ctx context.Context, payload *gen.SaveRiskEvalReviewPayload) (*types.RiskPolicyEvalReview, error)

SaveRiskEvalReview records one review verdict in the policy regression set.

func (*Service) SubmitRiskBlockFeedback

func (s *Service) SubmitRiskBlockFeedback(ctx context.Context, payload *gen.SubmitRiskBlockFeedbackPayload) (*gen.RiskBlock, error)

SubmitRiskBlockFeedback records 👍/👎 feedback on a tool call block and returns the refreshed block so the page reflects the vote.

func (*Service) SuggestCustomDetectionRule

func (s *Service) SuggestCustomDetectionRule(ctx context.Context, payload *gen.SuggestCustomDetectionRulePayload) (*gen.SuggestCustomDetectionRuleResult, error)

SuggestCustomDetectionRule turns a natural-language description ("what do you want to detect?") into a structured custom-rule suggestion. The response is intentionally minimal — the dashboard prefills its create form with these values and the operator edits before saving.

func (*Service) SuggestExclusion

func (s *Service) SuggestExclusion(ctx context.Context, payload *gen.SuggestExclusionPayload) (*gen.SuggestExclusionResult, error)

SuggestExclusion turns a natural-language description of findings an operator wants to stop flagging into a structured exclusion suggestion (match_type, match_value, filters), validated with the same gate the create/update exclusion handlers use (RE2 compile, 512-char cap). The exclusion form serializes the result into its criteria expression via the existing client-side mapping. Falls back to an editable exact-match prefill when the LLM is unavailable, mirroring SuggestCustomDetectionRule's heuristic fallback.

func (*Service) TestDetectionRule

func (s *Service) TestDetectionRule(ctx context.Context, payload *gen.TestDetectionRulePayload) (*gen.TestDetectionRuleResult, error)

TestDetectionRule runs a single detection rule against pasted sample text and returns its matches. The handler dispatches to the same scanners the worker uses during chat-message analysis (gitleaks for secrets.*, the configured PIIScanner for pii.*, the prompt-injection scanner for prompt_injection.*, and a regex matcher for custom.*) so the playground output mirrors what would be recorded as a risk_result in production.

shadow_mcp.* and destructive_tool.* are inherently tool-call shaped — they have no text-only detector — so the handler returns supported:false for them rather than fabricating a match.

func (*Service) TriggerRiskAnalysis

func (s *Service) TriggerRiskAnalysis(_ context.Context, _ *gen.TriggerRiskAnalysisPayload) error

func (*Service) UnmaskRiskResult

func (s *Service) UnmaskRiskResult(ctx context.Context, payload *gen.UnmaskRiskResultPayload) (*gen.RiskUnmaskResultResult, error)

UnmaskRiskResult returns the plaintext match for a single risk result, on demand. Unlike ListRiskResults it is gated solely on chat:read for the result's chat — not org:admin — so a reveal is a discrete, audited access event distinct from browsing the redacted list.

func (*Service) UpdateCustomDetectionRule

func (s *Service) UpdateCustomDetectionRule(ctx context.Context, payload *gen.UpdateCustomDetectionRulePayload) (*types.RiskCustomDetectionRule, error)

func (*Service) UpdateRiskExclusion

func (s *Service) UpdateRiskExclusion(ctx context.Context, payload *gen.UpdateRiskExclusionPayload) (*types.RiskExclusion, error)

func (*Service) UpdateRiskPolicy

func (s *Service) UpdateRiskPolicy(ctx context.Context, payload *gen.UpdateRiskPolicyPayload) (*types.RiskPolicy, error)

type ShadowMCPPolicy

type ShadowMCPPolicy struct {
	ID          string
	Name        string
	Version     int64
	UserMessage *string // nil/empty means "render the default message"
}

ShadowMCPPolicy is the minimal policy view the hooks layer needs to render a deny message that follows the same `matched policy %q (...)` format as gitleaks/presidio enforcement.

type TenantedOption

type TenantedOption func(*tenantedOptions)

TenantedOption customizes the behaviour of TenantedHS256 and TenantedHS256WithVersion.

func WithKeyCache

func WithKeyCache(cache map[string][]byte) TenantedOption

WithKeyCache supplies a cache for per-tenant derived keys so that repeated fingerprinting under the same (version, tenant) pair does not re-run HKDF. The cache is keyed by version and tenant ID and is read from and written to by the tenanted fingerprinting methods. The caller owns the map and is responsible for its lifetime and any concurrency control; a fresh map scoped to a single batch is the typical usage.

Directories

Path Synopsis
Package categories is the single source of truth for the (source, rule_id) → risk category mapping shown across the dashboard.
Package categories is the single source of truth for the (source, rule_id) → risk category mapping shown across the dashboard.
Package celenv defines the single CEL environment for risk rule expressions.
Package celenv defines the single CEL environment for risk rule expressions.
Package presetlib classifies risk findings from ANY detection source against a git-versioned catalog of known-benign values (test credit cards, example API keys/tokens, module hashes, placeholder emails).
Package presetlib classifies risk findings from ANY detection source against a git-versioned catalog of known-benign values (test credit cards, example API keys/tokens, module hashes, placeholder emails).
Package presidiofp classifies Presidio PII findings as false positives.
Package presidiofp classifies Presidio PII findings as false positives.
Package recommendedscopes contains the centrally-maintained detection scope registry for built-in risk categories.
Package recommendedscopes contains the centrally-maintained detection scope registry for built-in risk categories.

Jump to

Keyboard shortcuts

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