Documentation
¶
Index ¶
- Constants
- Variables
- func Attach(mux goahttp.Muxer, service *Service)
- func GeneratePolicyAckToken(ctx context.Context, c cache.Cache, input PolicyAckTokenInput, ...) (string, time.Time, error)
- func GeneratePolicyAckURL(ctx context.Context, c cache.Cache, siteURL *url.URL, ...) (string, time.Time, error)
- func GeneratePolicyBypassRequestToken(ctx context.Context, c cache.Cache, input PolicyBypassRequestTokenInput, ...) (string, time.Time, error)
- func GeneratePolicyBypassRequestURL(ctx context.Context, c cache.Cache, siteURL *url.URL, ...) (string, time.Time, error)
- func NewObserver(logger *slog.Logger, tracerProvider trace.TracerProvider, db *pgxpool.Pool, ...) chat.MessageObserver
- func RedactMatchAll(match string, orgID string) string
- type FindingCHWriter
- type Fingerprinter
- func (p Fingerprinter) HS256(message []byte) ([]byte, string, error)
- func (p Fingerprinter) HS256WithVersion(version string, message []byte) ([]byte, error)
- func (p Fingerprinter) TenantedHS256(tenantID string, message []byte, opts ...TenantedOption) ([]byte, string, error)
- func (p Fingerprinter) TenantedHS256WithVersion(version string, tenantID string, message []byte, opts ...TenantedOption) ([]byte, error)
- type PolicyAckTokenInput
- type PolicyBypassEvaluation
- type PolicyBypassEvaluator
- type PolicyBypassRequestTokenInput
- type PolicyBypassTarget
- type RiskAnalysisSignaler
- type RiskExclusionReconciler
- type RiskFindingInserter
- type RiskPolicyResultsCleaner
- type RiskScanner
- type ScanResult
- type Scanner
- func (s *Scanner) HasAcknowledgedChallenge(ctx context.Context, projectID uuid.UUID, ...) bool
- func (s *Scanner) HasEnabledShadowMCPPolicy(ctx context.Context, projectID uuid.UUID) (bool, error)
- func (s *Scanner) LookupShadowMCPBlockingPolicy(ctx context.Context, organizationID string, projectID uuid.UUID, userID string) (*ShadowMCPPolicy, error)
- func (s *Scanner) RecordPolicyChallenge(ctx context.Context, organizationID string, projectID uuid.UUID, ...)
- func (s *Scanner) ScanForEnforcement(ctx context.Context, organizationID string, projectID uuid.UUID, userID string, ...) (result *ScanResult, retErr error)
- type Service
- func (s *Service) APIKeyAuth(ctx context.Context, key string, schema *security.APIKeyScheme) (context.Context, error)
- func (s *Service) AcknowledgeRiskPolicyChallenge(ctx context.Context, payload *gen.AcknowledgeRiskPolicyChallengePayload) (*gen.AcknowledgeRiskPolicyChallengeResult, error)
- func (s *Service) ApproveRiskPolicyBypassRequest(ctx context.Context, payload *gen.ApproveRiskPolicyBypassRequestPayload) (*gen.RiskPolicyBypassRequest, error)
- func (s *Service) CompileExpr(ctx context.Context, payload *gen.CompileExprPayload) (*gen.ExprCompileResult, error)
- func (s *Service) CreateCustomDetectionRule(ctx context.Context, payload *gen.CreateCustomDetectionRulePayload) (*types.RiskCustomDetectionRule, error)
- func (s *Service) CreateRiskExclusion(ctx context.Context, payload *gen.CreateRiskExclusionPayload) (*types.RiskExclusion, error)
- func (s *Service) CreateRiskPolicy(ctx context.Context, payload *gen.CreateRiskPolicyPayload) (*types.RiskPolicy, error)
- func (s *Service) CreateRiskPolicyBypassRequest(ctx context.Context, payload *gen.CreateRiskPolicyBypassRequestPayload) (*gen.RiskPolicyBypassRequest, error)
- func (s *Service) DeclineRiskPolicyChallenge(ctx context.Context, payload *gen.DeclineRiskPolicyChallengePayload) (*gen.DeclineRiskPolicyChallengeResult, error)
- func (s *Service) DeleteCustomDetectionRule(ctx context.Context, payload *gen.DeleteCustomDetectionRulePayload) error
- func (s *Service) DeleteRiskEvalReview(ctx context.Context, payload *gen.DeleteRiskEvalReviewPayload) error
- func (s *Service) DeleteRiskExclusion(ctx context.Context, payload *gen.DeleteRiskExclusionPayload) error
- func (s *Service) DeleteRiskPolicy(ctx context.Context, payload *gen.DeleteRiskPolicyPayload) error
- func (s *Service) DenyRiskPolicyBypassRequest(ctx context.Context, payload *gen.DenyRiskPolicyBypassRequestPayload) (*gen.RiskPolicyBypassRequest, error)
- func (s *Service) EvaluatePromptGuardrail(ctx context.Context, payload *gen.EvaluatePromptGuardrailPayload) (*gen.PromptGuardrailEvalResult, error)
- func (s *Service) GetCustomDetectionRule(ctx context.Context, payload *gen.GetCustomDetectionRulePayload) (*types.RiskCustomDetectionRule, error)
- func (s *Service) GetRiskBlock(ctx context.Context, payload *gen.GetRiskBlockPayload) (*gen.RiskBlock, error)
- func (s *Service) GetRiskOverview(ctx context.Context, payload *gen.GetRiskOverviewPayload) (*gen.RiskOverviewResult, error)
- func (s *Service) GetRiskPolicy(ctx context.Context, payload *gen.GetRiskPolicyPayload) (*types.RiskPolicy, error)
- func (s *Service) GetRiskPolicyChallenge(ctx context.Context, payload *gen.GetRiskPolicyChallengePayload) (*gen.GetRiskPolicyChallengeResult, error)
- func (s *Service) GetRiskPolicyStatus(ctx context.Context, payload *gen.GetRiskPolicyStatusPayload) (*types.RiskPolicyStatus, error)
- func (s *Service) GetRiskRuleBreakdown(ctx context.Context, payload *gen.GetRiskRuleBreakdownPayload) (*gen.RiskRuleBreakdownResult, error)
- func (s *Service) GetRiskUserBreakdown(ctx context.Context, payload *gen.GetRiskUserBreakdownPayload) (*gen.RiskUserBreakdownResult, error)
- func (s *Service) ListBuiltinExclusions(ctx context.Context, _ *gen.ListBuiltinExclusionsPayload) (*gen.ListBuiltinExclusionsResult, error)
- func (s *Service) ListCustomDetectionRules(ctx context.Context, payload *gen.ListCustomDetectionRulesPayload) (*gen.ListCustomDetectionRulesResult, error)
- func (s *Service) ListRiskCategories(ctx context.Context, payload *gen.ListRiskCategoriesPayload) (*gen.RiskCategoriesResult, error)
- func (s *Service) ListRiskEvalReviews(ctx context.Context, payload *gen.ListRiskEvalReviewsPayload) (*gen.ListRiskEvalReviewsResult, error)
- func (s *Service) ListRiskExclusions(ctx context.Context, payload *gen.ListRiskExclusionsPayload) (*gen.ListRiskExclusionsResult, error)
- func (s *Service) ListRiskPolicies(ctx context.Context, payload *gen.ListRiskPoliciesPayload) (*gen.ListRiskPoliciesResult, error)
- func (s *Service) ListRiskPolicyBypassRequests(ctx context.Context, payload *gen.ListRiskPolicyBypassRequestsPayload) (*gen.ListRiskPolicyBypassRequestsResult, error)
- func (s *Service) ListRiskResults(ctx context.Context, payload *gen.ListRiskResultsPayload) (*gen.ListRiskResultsResult, error)
- func (s *Service) ListRiskResultsByChat(ctx context.Context, payload *gen.ListRiskResultsByChatPayload) (*gen.ListRiskResultsByChatResult, error)
- func (s *Service) ListRiskResultsForAgent(ctx context.Context, payload *gen.ListRiskResultsForAgentPayload) (*gen.ListRiskResultsForAgentResult, error)
- func (s *Service) OnMessagesStored(ctx context.Context, projectID uuid.UUID)
- func (s *Service) RevokeRiskPolicyBypassRequest(ctx context.Context, payload *gen.RevokeRiskPolicyBypassRequestPayload) (*gen.RiskPolicyBypassRequest, error)
- func (s *Service) SaveRiskEvalReview(ctx context.Context, payload *gen.SaveRiskEvalReviewPayload) (*types.RiskPolicyEvalReview, error)
- func (s *Service) SubmitRiskBlockFeedback(ctx context.Context, payload *gen.SubmitRiskBlockFeedbackPayload) (*gen.RiskBlock, error)
- func (s *Service) SuggestCustomDetectionRule(ctx context.Context, payload *gen.SuggestCustomDetectionRulePayload) (*gen.SuggestCustomDetectionRuleResult, error)
- func (s *Service) SuggestExclusion(ctx context.Context, payload *gen.SuggestExclusionPayload) (*gen.SuggestExclusionResult, error)
- func (s *Service) TestDetectionRule(ctx context.Context, payload *gen.TestDetectionRulePayload) (*gen.TestDetectionRuleResult, error)
- func (s *Service) TriggerRiskAnalysis(_ context.Context, _ *gen.TriggerRiskAnalysisPayload) error
- func (s *Service) UnmaskRiskResult(ctx context.Context, payload *gen.UnmaskRiskResultPayload) (*gen.RiskUnmaskResultResult, error)
- func (s *Service) UpdateCustomDetectionRule(ctx context.Context, payload *gen.UpdateCustomDetectionRulePayload) (*types.RiskCustomDetectionRule, error)
- func (s *Service) UpdateRiskExclusion(ctx context.Context, payload *gen.UpdateRiskExclusionPayload) (*types.RiskExclusion, error)
- func (s *Service) UpdateRiskPolicy(ctx context.Context, payload *gen.UpdateRiskPolicyPayload) (*types.RiskPolicy, error)
- type ShadowMCPPolicy
- type TenantedOption
Constants ¶
const ( // PolicyBypassTargetKindShadowMCPServer identifies a Shadow MCP server target. PolicyBypassTargetKindShadowMCPServer = "shadow_mcp_server" // PolicyBypassWholePolicyTargetKey identifies a whole-policy target. PolicyBypassWholePolicyTargetKey = "policy" )
Variables ¶
Functions ¶
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 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 ¶
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) 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 ¶
func (e *PolicyBypassEvaluator) CanBypass(ctx context.Context, input PolicyBypassEvaluation) bool
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 ¶
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 ¶
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 ¶
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.
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 (*Service) AcknowledgeRiskPolicyChallenge ¶
func (s *Service) AcknowledgeRiskPolicyChallenge(ctx context.Context, payload *gen.AcknowledgeRiskPolicyChallengePayload) (*gen.AcknowledgeRiskPolicyChallengeResult, error)
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 (*Service) DeleteRiskEvalReview ¶
func (s *Service) DeleteRiskEvalReview(ctx context.Context, payload *gen.DeleteRiskEvalReviewPayload) error
DeleteRiskEvalReview clears the current reviewer's verdict.
func (*Service) DeleteRiskExclusion ¶
func (*Service) DeleteRiskPolicy ¶
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 ¶
func (s *Service) ListBuiltinExclusions(ctx context.Context, _ *gen.ListBuiltinExclusionsPayload) (*gen.ListBuiltinExclusionsResult, error)
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) ListRiskPolicyBypassRequests ¶
func (s *Service) ListRiskPolicyBypassRequests(ctx context.Context, payload *gen.ListRiskPolicyBypassRequestsPayload) (*gen.ListRiskPolicyBypassRequestsResult, 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 ¶
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 (*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.
Source Files
¶
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. |