Documentation
¶
Index ¶
- Constants
- Variables
- func Attach(mux goahttp.Muxer, service *Service)
- func CanBypassBatchWithGrants(grants []authz.Grant, inputs []PolicyBypassEvaluation) map[PolicyBypassEvaluation]bool
- func CanBypassWithGrants(grants []authz.Grant, input PolicyBypassEvaluation) bool
- func EncodeFingerprint(sum []byte) string
- 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, string, time.Time, error)
- func MatchingReconstruction(matchLen uint32, candidates [][]byte) (string, bool)
- func NewExclusionMutationCore(logger *slog.Logger, db *pgxpool.Pool, auditLogger *audit.Logger, ...) *exclusioncore.Core
- func NewObserver(logger *slog.Logger, tracerProvider trace.TracerProvider, db *pgxpool.Pool, ...) chat.MessageObserver
- func NewPolicyMutationCore(db *pgxpool.Pool, auditLogger *audit.Logger, ...) *policycore.Core
- func RedactMatchAll(match string, orgID string) string
- func ResolveChatID(row *chrepo.RiskFindingUnmaskRow, anchor RevealAnchor) (uuid.UUID, bool)
- type EnforcementDispatcher
- type FindingCHWriter
- type FindingExclusionResolver
- 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)
- func (p Fingerprinter) Versions() []string
- type InferenceScanOutcome
- type PolicyAckTokenInput
- type PolicyBypassEvaluation
- type PolicyBypassEvaluator
- type PolicyBypassRequestTokenInput
- type PolicyBypassTarget
- type RealtimeScanRequest
- type RevealAnchor
- type RevealMatcher
- func (m *RevealMatcher) Candidates(ctx context.Context, chatID uuid.UUID, row *chrepo.RiskFindingUnmaskRow, ...) [][]byte
- func (m *RevealMatcher) HydratePartContent(ctx context.Context, anchor *RevealAnchor)
- func (m *RevealMatcher) LoadAnchor(ctx context.Context, projectID uuid.UUID, row *chrepo.RiskFindingUnmaskRow) RevealAnchor
- 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, request RealtimeScanRequest) (*ScanResult, error)
- func (s *Scanner) ScanForInferenceEnforcement(ctx context.Context, request RealtimeScanRequest) (*InferenceScanOutcome, error)
- func (s *Scanner) Shutdown(ctx context.Context) 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.PolicyBypassRedemption, 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) GetRiskAnalysisStatus(ctx context.Context, _ *gen.GetRiskAnalysisStatusPayload) (*gen.RiskAnalysisStatusResult, 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) GetRiskSignals(ctx context.Context, payload *gen.GetRiskSignalsPayload) (*gen.RiskSignalsResult, 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) ListDismissedRiskResults(ctx context.Context, payload *gen.ListDismissedRiskResultsPayload) (*gen.ListRiskResultsResult, 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) ListSessionQuarantines(ctx context.Context, _ *gen.ListSessionQuarantinesPayload) (*gen.ListSessionQuarantinesResult, error)
- func (s *Service) MarkRiskResultsFalsePositive(ctx context.Context, payload *gen.MarkRiskResultsFalsePositivePayload) error
- func (s *Service) OnMessagesStored(ctx context.Context, projectID uuid.UUID)
- func (s *Service) ReleaseSessionQuarantine(ctx context.Context, payload *gen.ReleaseSessionQuarantinePayload) (*gen.SessionQuarantine, error)
- 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) UnmarkRiskResultsFalsePositive(ctx context.Context, payload *gen.UnmarkRiskResultsFalsePositivePayload) 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 ShadowMCPApprovalIntake
- type ShadowMCPInventoryURLLookup
- type ShadowMCPPolicy
- type ShadowMCPPolicyURLReconciler
- type TenantedOption
Constants ¶
const ( // PolicyBypassTargetKindShadowMCPServer identifies a Shadow MCP server target. PolicyBypassTargetKindShadowMCPServer = "shadow_mcp_server" // PolicyBypassWholePolicyTargetKey identifies a whole-policy target. PolicyBypassWholePolicyTargetKey = "policy" )
const ( ShadowMCPDispositionBlockAll = shadowmcp.DispositionBlockAll ShadowMCPDispositionAllowAll = shadowmcp.DispositionAllowAll )
Default dispositions for shadow MCP blocking policies, aliased from the shadowmcp package (which enforcement code uses directly). The disposition is immutable after create.
Variables ¶
Functions ¶
func CanBypassBatchWithGrants ¶
func CanBypassBatchWithGrants(grants []authz.Grant, inputs []PolicyBypassEvaluation) map[PolicyBypassEvaluation]bool
CanBypassBatchWithGrants evaluates several bypasses against one already-loaded caller grant snapshot, canonicalizing legacy selectors only once.
func CanBypassWithGrants ¶
func CanBypassWithGrants(grants []authz.Grant, input PolicyBypassEvaluation) bool
CanBypassWithGrants evaluates a bypass against an already-loaded caller grant snapshot. Callers that also need policy-audience checks use this to keep both decisions consistent under the same transaction snapshot.
func EncodeFingerprint ¶
EncodeFingerprint renders a fingerprint sum in the encoding stored on ClickHouse rows (unpadded base64url, matching the ingest writer).
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 MatchingReconstruction ¶
MatchingReconstruction selects the reconstruction to trust: the first candidate whose byte length equals the recorded match_len. The length gate is a plain integrity check, not cryptography — a candidate of the wrong size proves the underlying data no longer lines up with what was scanned (edited content, shifted offsets, changed account email) and using it would misattribute bytes as the finding's match. A zero match_len means the finding never had match content (judge verdicts, dead-lettered scans).
func NewExclusionMutationCore ¶
func NewExclusionMutationCore(logger *slog.Logger, db *pgxpool.Pool, auditLogger *audit.Logger, reconciler RiskExclusionReconciler, redactionKey string) *exclusioncore.Core
NewExclusionMutationCore composes the shared exclusion command for non-Goa adapters with the same audit, reconciliation, and keyed-redaction behavior.
func NewObserver ¶
func NewObserver( logger *slog.Logger, tracerProvider trace.TracerProvider, db *pgxpool.Pool, signaler RiskAnalysisSignaler, auditLogger *audit.Logger, riskRecorder *metering.RiskRecorder, ) 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 NewPolicyMutationCore ¶
func NewPolicyMutationCore(db *pgxpool.Pool, auditLogger *audit.Logger, approvals policycore.ApprovalCoordinator, signaler policycore.PolicySignaler, cacheInvalidator policycore.PolicyCacheInvalidator) *policycore.Core
NewPolicyMutationCore composes the shared risk policy command for non-Goa adapters. It preserves the same audit, approval, URL-grant, signal, and cache dependencies used by the dashboard service.
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.
func ResolveChatID ¶
func ResolveChatID(row *chrepo.RiskFindingUnmaskRow, anchor RevealAnchor) (uuid.UUID, bool)
ResolveChatID resolves the chat a finding's reconstructed content belongs to: the chat id stamped on the ClickHouse row at ingest, falling back to the anchored Postgres row's chat. When neither resolves (attribution never ran and the anchor is gone) it yields the nil UUID, mirroring the Postgres path's NULL chat_id.
The second return is false when the stamped id and the anchor's chat are both present and disagree: the anchored message was re-parented (or the stamp is stale), so the anchor's content is not the stamped chat's to serve and callers must refuse the reveal. The chat id is nil in that case, so a caller that ignores the flag can only widen to the nil-chat gate, never to the wrong chat's.
Types ¶
type EnforcementDispatcher ¶
type EnforcementDispatcher interface {
Dispatch(context.Context, enforcereply.DispatchRequest) (enforcereply.Outcome, error)
}
EnforcementDispatcher is the request-reply seam used by realtime scanning.
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 partial-mask display string (maskdisplay), and one-way fingerprints. The verbatim value stays in Postgres for the audited unmask path.
Delivery contract: at-least-once into ClickHouse. A failed insert or a failed attribution read nacks the whole batch for redelivery; a message the writer can never persist — malformed id or timestamp — nacks only itself so it retries alone without dragging the rest of the batch along. There is no dead-letter queue by design: transient failures self-heal under the subscription's retry backoff, and a poison message (always an internal producer bug — every publisher is ours) redelivers within the subscription's retention window, surfaced by the skipped metric and oldest-unacked-age monitoring so the bug is fixed and the still-retained message then processes. Redelivered duplicates are expected and converge at read time: rows share their deterministic id and the read paths resolve each id to one winning copy.
func NewFindingCHWriter ¶
func NewFindingCHWriter(logger *slog.Logger, db repo.DBTX, meterProvider metric.MeterProvider, inserter RiskFindingInserter, fingerprinter Fingerprinter) *FindingCHWriter
func (*FindingCHWriter) HandleBatchWithResult ¶
func (w *FindingCHWriter) HandleBatchWithResult(ctx context.Context, batch []gcp.BatchMessage[*riskv1.Finding]) error
HandleBatchWithResult adapts ProcessBatch to the streams runner: per-message failures are staged as individual nacks, a batch-level error nacks the whole batch.
func (*FindingCHWriter) ProcessBatch ¶
func (w *FindingCHWriter) ProcessBatch(ctx context.Context, messages []*riskv1.Finding) ([]error, error)
ProcessBatch writes one batch of findings to ClickHouse. The returned slice is parallel to messages: a non-nil entry is a per-message rejection (the message can never be persisted and should redeliver on its own). A non-nil error means the whole batch failed (attribution read or insert) and must be redelivered.
type FindingExclusionResolver ¶
type FindingExclusionResolver struct {
// contains filtered or unexported fields
}
FindingExclusionResolver evaluates findings against the currently enabled project and global exclusions for their policy.
func NewFindingExclusionResolver ¶
func NewFindingExclusionResolver(db repo.DBTX) *FindingExclusionResolver
func (*FindingExclusionResolver) ExcludedBy ¶
func (r *FindingExclusionResolver) ExcludedBy(ctx context.Context, message *riskv1.Finding) (uuid.UUID, bool, error)
ExcludedBy returns the matching exclusion id. Lookup and identifier errors are returned so each caller can apply its own delivery policy.
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.
func (Fingerprinter) Versions ¶
func (p Fingerprinter) Versions() []string
Versions returns every pepper version in the keyring, sorted, for callers that must match fingerprints written under any historical pepper (e.g. the retroactive exclusion reconcile matching rows across rotations). A zero Fingerprinter returns nil, which such callers treat as "fingerprinting unavailable".
type InferenceScanOutcome ¶
type InferenceScanOutcome struct {
Result *ScanResult
Complete bool
}
InferenceScanOutcome separates the legacy enforcement disposition from whether the scan encountered incomplete evaluations. Only a complete, error-free scan with no result establishes a clean verdict under the current configuration.
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
func (*PolicyBypassEvaluator) CanBypassBatch ¶
func (e *PolicyBypassEvaluator) CanBypassBatch(ctx context.Context, inputs []PolicyBypassEvaluation) map[PolicyBypassEvaluation]bool
CanBypassBatch evaluates inputs after loading principals and grants once per organization/user pair. Results are keyed by their complete input so callers do not have to correlate parallel slices. Missing entries deny by default.
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 RealtimeScanRequest ¶
type RealtimeScanRequest struct {
// Provenance carries immutable tenant, request, message, and hook attribution.
Provenance metering.RiskProvenance
// Text is the exact content sent to each applicable scanner.
Text string
// MessageType identifies the semantic message role used by scopes and judges.
MessageType message.Type
// ToolName identifies the invoked tool when MessageType is tool-shaped.
ToolName string
// ToolCallID is the harness-assigned id of the tool call when MessageType
// is a tool request, so the LLM analyzer lane can show the model the id
// the agent used. Empty when the harness supplied none.
ToolCallID string
}
RealtimeScanRequest snapshots one hook call and its immutable attribution.
type RevealAnchor ¶
type RevealAnchor struct {
// ChatID is the anchored Postgres row's chat, exported so the unmask
// handler can authorize against it and detect divergence from the chat id
// stamped on the ClickHouse row.
ChatID uuid.NullUUID
// contains filtered or unexported fields
}
RevealAnchor is the Postgres source material behind one ClickHouse finding: the anchored chat message's recorded content and tool calls, or a content part's asset location, plus the chat id used for authorization. All loads are best-effort — a deleted anchor leaves the relevant fields zeroed and the reveal refuses later for lack of candidates.
type RevealMatcher ¶
type RevealMatcher struct {
// contains filtered or unexported fields
}
RevealMatcher reconstructs a ClickHouse finding's raw match text from the original chat data (Postgres chat rows plus content-part assets), guided by the row's surface metadata. ClickHouse never stores plaintext, so this is the only way to recover a match after the fact. Shared by the audited unmask endpoint and the retroactive exclusion reconcile's regex evaluation — the latter keeps the plaintext strictly in-process, mirroring scan-time matching, so no unmask audit entry is written there.
func NewRevealMatcher ¶
func NewRevealMatcher(logger *slog.Logger, repoQueries *repo.Queries, assetStorage blobio.Reader) *RevealMatcher
NewRevealMatcher assembles a matcher from its dependencies.
func (*RevealMatcher) Candidates ¶
func (m *RevealMatcher) Candidates(ctx context.Context, chatID uuid.UUID, row *chrepo.RiskFindingUnmaskRow, anchor RevealAnchor) [][]byte
Candidates assembles the ordered reconstruction candidates for a finding per its surface metadata. Every candidate still has to pass the match-length gate (MatchingReconstruction) before it is used.
func (*RevealMatcher) HydratePartContent ¶
func (m *RevealMatcher) HydratePartContent(ctx context.Context, anchor *RevealAnchor)
HydratePartContent reads a content-part anchor's asset into the anchor, size-capped like the batch scanner's hydration. Best-effort: a missing or unreadable asset (or a nil asset reader) just leaves the anchor without content and the reveal refuses for lack of candidates.
func (*RevealMatcher) LoadAnchor ¶
func (m *RevealMatcher) LoadAnchor(ctx context.Context, projectID uuid.UUID, row *chrepo.RiskFindingUnmaskRow) RevealAnchor
LoadAnchor resolves the finding's Postgres anchor row (chat message or content part). Content-part asset bytes are NOT read here — that happens after the caller's authorization check, in HydratePartContent.
type RiskAnalysisSignaler ¶
type RiskAnalysisSignaler interface {
Signal(ctx context.Context, projectID uuid.UUID) error
analysisstatus.Describer
}
RiskAnalysisSignaler signals the per-project risk analysis coordinator workflow and reports its run state for the Watchdog "last analysed" badge.
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.
ScanForEnforcement(ctx context.Context, request RealtimeScanRequest) (*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
// DeadLetterReason is non-empty when the "match" is a scanner dead-letter
// sentinel (the analyzer failed after exhausting retries, e.g. rule
// pii.dead_letter) rather than an actual finding. A warn policy must not
// challenge the user over an analyzer outage, so ScanForEnforcement skips
// the challenge for such results; block policies still deny (fail closed).
// A sentinel from the LLM analyzer lane (Source llm_analyzer) is returned
// for every enforcing action, warn included, because that lane fails
// closed: the hooks layer renders it as a plain deny.
DeadLetterReason 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.
func (*ScanResult) AnalysisUnavailable ¶
func (r *ScanResult) AnalysisUnavailable() bool
AnalysisUnavailable reports whether the result is the LLM analyzer lane's fail-closed sentinel: the model could not be consulted, so the policy denies with an "analysis unavailable" reason rather than a finding.
func (*ScanResult) IsWarnChallenge ¶
func (r *ScanResult) IsWarnChallenge() bool
IsWarnChallenge reports whether the result should be delivered as a warn challenge (deny plus an acknowledgement link). A fail-closed sentinel on a warn policy is never challengeable: an analyzer outage is not something the user can acknowledge, so it degrades to a plain deny.
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, riskRecorder *metering.RiskRecorder, ) (*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 NewScannerWithEnforcementDispatcher ¶
func NewScannerWithEnforcementDispatcher( 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, dispatcher EnforcementDispatcher, riskRecorder *metering.RiskRecorder, ) (*Scanner, error)
NewScannerWithEnforcementDispatcher creates a scanner with Pub/Sub enforcement enabled when flagged.
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.
func (*Scanner) ScanForEnforcement ¶
func (s *Scanner) ScanForEnforcement(ctx context.Context, request RealtimeScanRequest) (*ScanResult, error)
func (*Scanner) ScanForInferenceEnforcement ¶
func (s *Scanner) ScanForInferenceEnforcement(ctx context.Context, request RealtimeScanRequest) (*InferenceScanOutcome, error)
ScanForInferenceEnforcement preserves ScanForEnforcement's disposition and error behavior while reporting suppressed failures separately. It scans once.
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, approvalIntake ShadowMCPApprovalIntake, piiScanner ra.PIIScanner, piScanner *promptinjection.Scanner, flags feature.Provider, celEng *celenv.Engine, builtinPresets *presetlib.Library, promptJudge promptpolicy.Evaluator, reconcileShadowMCPPolicyURLs ShadowMCPPolicyURLReconciler, shadowMCPInventoryURLLookup ShadowMCPInventoryURLLookup, findingsCH *chrepo.Queries, assetStorage blobio.Reader, riskRecorder *metering.RiskRecorder, ) *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.PolicyBypassRedemption, 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) GetRiskAnalysisStatus ¶
func (s *Service) GetRiskAnalysisStatus(ctx context.Context, _ *gen.GetRiskAnalysisStatusPayload) (*gen.RiskAnalysisStatusResult, error)
GetRiskAnalysisStatus reports the run state of the project's risk analysis coordinator for the Watchdog "last analyzed" badge. It shares the signals endpoint's gates (org:admin plus the Watchdog flag) because it describes the same surface, and it reads Temporal directly rather than Postgres: the coordinator is signal-driven, so the latest workflow run is the only record of when analysis last happened.
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) GetRiskSignals ¶
func (s *Service) GetRiskSignals(ctx context.Context, payload *gen.GetRiskSignalsPayload) (*gen.RiskSignalsResult, error)
GetRiskSignals serves the Watchdog page: findings clustered by rule into ranked signals, window-level KPI counts with previous-window comparisons, and the exposure-by-category rollup. ClickHouse-only — there is no Postgres fallback, so orgs must be on the ClickHouse findings ingest before the Watchdog UI is enabled for them.
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) ListDismissedRiskResults ¶
func (s *Service) ListDismissedRiskResults(ctx context.Context, payload *gen.ListDismissedRiskResultsPayload) (*gen.ListRiskResultsResult, error)
ListDismissedRiskResults serves the Dismissed tab from ClickHouse: findings whose latest state is a manual or automated dismissal, newest dismissal first. Rows arrive store-side redacted like the Risk Events listing — the raw match never reaches ClickHouse — so results carry MatchRedacted and a nil Match where the Postgres-backed listing returned the raw value.
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) ListSessionQuarantines ¶
func (s *Service) ListSessionQuarantines(ctx context.Context, _ *gen.ListSessionQuarantinesPayload) (*gen.ListSessionQuarantinesResult, error)
func (*Service) MarkRiskResultsFalsePositive ¶
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) ReleaseSessionQuarantine ¶
func (s *Service) ReleaseSessionQuarantine(ctx context.Context, payload *gen.ReleaseSessionQuarantinePayload) (*gen.SessionQuarantine, error)
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) UnmarkRiskResultsFalsePositive ¶
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 ShadowMCPApprovalIntake ¶
type ShadowMCPApprovalIntake interface {
// AdmitBlockedServer records the ask and returns the id and current
// status of the review it landed on — a repeat ask for an
// already-approved server attaches without reopening it. A forbidden
// error means the approval workflow is not enabled for the organization
// and the caller should fall back to the legacy bypass request.
AdmitBlockedServer(ctx context.Context, organizationID string, projectID uuid.UUID, serverURL, requesterUserID, requesterEmail, note string) (requestID string, status string, err error)
// ReconcileStandingDecisionsForPolicy replays the project's recorded
// decisions onto a newly blocking policy, inside the transaction that
// creates or transitions it. Without it, ordering decides what an
// approval means: a policy created after decisions were recorded would
// block servers whose reviews still read approved. A returned shareable
// error (an inexpressible blast radius) aborts the policy write with its
// explanation intact.
ReconcileStandingDecisionsForPolicy(ctx context.Context, tx pgx.Tx, organizationID string, projectID uuid.UUID, policyID uuid.UUID) error
// ReviewShadowMCPPolicyURLEdit names the standing decisions a URL-list
// edit on an already-blocking policy would contradict, plus every URL
// whose grants carry a standing decision (so the reconciler can leave
// retained ones untouched). A nil URL list means that list is not being
// edited.
ReviewShadowMCPPolicyURLEdit(ctx context.Context, tx pgx.Tx, organizationID string, projectID uuid.UUID, policyID uuid.UUID, disposition string, desiredAllowedURLs []string, desiredBlockedURLs []string) (shadowmcp.StandingDecisionReview, error)
// SupersedeShadowMCPDecisions transitions each conflicted request to
// superseded — actor-attributed and audit-logged, decision history and
// rationale intact — in the same transaction as the policy edit that
// displaces it.
SupersedeShadowMCPDecisions(ctx context.Context, tx pgx.Tx, organizationID string, projectID uuid.UUID, conflicts []shadowmcp.StandingDecisionConflict, actor urn.Principal, actorDisplayName *string) error
}
ShadowMCPApprovalIntake admits a blocked shadow-MCP server into the MCP approval workflow: the blocked employee's ask attaches as a requester on the server's single review — evidence gathered, deduplicated by canonical URL — instead of minting a per-user bypass request. It is the seam that makes approval the one flow: the block link redeems into the same review an admin decides, and the decision is what changes enforcement.
Implemented by the mcpapproval service and injected at wiring, so this package never imports it. A nil intake, or an intake reporting the approval feature is unavailable, falls back to the legacy bypass request.
type ShadowMCPInventoryURLLookup ¶
type ShadowMCPInventoryURLLookup func( ctx context.Context, projectID uuid.UUID, canonicalURLs []string, ) ([]string, error)
ShadowMCPInventoryURLLookup returns the requested canonical URLs that were observed in the authenticated project inventory.
type ShadowMCPPolicy ¶
type ShadowMCPPolicy struct {
ID string
Name string
Version int64
UserMessage *string // nil/empty means "render the default message"
// Disposition is the policy's default posture: block_all (deny unless
// allowed — the original behavior) or allow_all (permit unless blocked).
Disposition string
// BlockedURLs is the canonical blocked-URL set of an allow_all policy.
// Always empty under block_all.
BlockedURLs []string
}
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.
func (*ShadowMCPPolicy) IsAllowAll ¶
func (p *ShadowMCPPolicy) IsAllowAll() bool
IsAllowAll reports whether the policy permits servers by default and blocks only the URLs on its blocked list. Bypass grants and fail-closed inventory checks are block_all concepts and do not apply under allow-all.
type ShadowMCPPolicyURLReconciler ¶
type ShadowMCPPolicyURLReconciler func( ctx context.Context, db repo.DBTX, input policybypass.ReconcilePolicyURLsInput, ) error
ShadowMCPPolicyURLReconciler replaces the URL grants owned by one risk policy.
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
¶
- analysis_status.go
- approval_intake.go
- block.go
- exclusion.go
- exclusion_admin.go
- false_positive.go
- finding_ch.go
- finding_exclusions.go
- fingerprint.go
- impl.go
- list_ch.go
- metrics.go
- overview_ch.go
- policy_ack_token.go
- policy_audience.go
- policy_bypass.go
- policy_bypass_evaluator.go
- policy_bypass_token.go
- policy_challenge.go
- policy_goa.go
- policy_mutation.go
- revealmatch.go
- scanner.go
- shadow_mcp_policy_setup.go
- signals.go
- signals_ch.go
- unmask_ch.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package analysisstatus describes the run state of a project's risk analysis coordinator workflow.
|
Package analysisstatus describes the run state of a project's risk analysis coordinator workflow. |
|
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 enforcereply binds the generic Redis inbox (internal/redisinbox) to risk enforcement replies and return addresses.
|
Package enforcereply binds the generic Redis inbox (internal/redisinbox) to risk enforcement replies and return addresses. |
|
Package maskdisplay produces the partial-mask display form of a risk finding's matched value — the string stored in the ClickHouse risk_findings.match_redacted column and rendered as the match by default in listings.
|
Package maskdisplay produces the partial-mask display form of a risk finding's matched value — the string stored in the ClickHouse risk_findings.match_redacted column and rendered as the match by default in listings. |
|
Package policycatalog owns the release-pinned detector values that Platform policy administration may expose.
|
Package policycatalog owns the release-pinned detector values that Platform policy administration may expose. |
|
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. |