Skip to content

Commit 1544fae

Browse files
authored
feat: surface MCP Gateway guard policy events in gh aw audit (#22962)
1 parent ef4c1d6 commit 1544fae

5 files changed

Lines changed: 546 additions & 23 deletions

File tree

.changeset/patch-surface-audit-guard-policy-events.md

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/cli/audit_report.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,11 @@ type ToolUsageInfo struct {
149149

150150
// MCPToolUsageData contains detailed MCP tool usage statistics and individual call records
151151
type MCPToolUsageData struct {
152-
Summary []MCPToolSummary `json:"summary"` // Aggregated statistics per tool
153-
ToolCalls []MCPToolCall `json:"tool_calls"` // Individual tool call records
154-
Servers []MCPServerStats `json:"servers,omitempty"` // Server-level statistics
155-
FilteredEvents []DifcFilteredEvent `json:"filtered_events,omitempty"` // DIFC filtered events
152+
Summary []MCPToolSummary `json:"summary"` // Aggregated statistics per tool
153+
ToolCalls []MCPToolCall `json:"tool_calls"` // Individual tool call records
154+
Servers []MCPServerStats `json:"servers,omitempty"` // Server-level statistics
155+
FilteredEvents []DifcFilteredEvent `json:"filtered_events,omitempty"` // DIFC filtered events
156+
GuardPolicySummary *GuardPolicySummary `json:"guard_policy_summary,omitempty"` // Guard policy enforcement summary
156157
}
157158

158159
// MCPToolSummary contains aggregated statistics for a single MCP tool
@@ -193,6 +194,22 @@ type MCPServerStats struct {
193194
ErrorCount int `json:"error_count,omitempty" console:"header:Errors,omitempty"`
194195
}
195196

197+
// GuardPolicySummary contains summary statistics for guard policy enforcement.
198+
// Guard policies control which tool calls the MCP Gateway allows based on
199+
// repository scope (repos) and content integrity level (min-integrity).
200+
type GuardPolicySummary struct {
201+
TotalBlocked int `json:"total_blocked"`
202+
IntegrityBlocked int `json:"integrity_blocked"` // Blocked by min-integrity (-32006)
203+
RepoScopeBlocked int `json:"repo_scope_blocked"` // Blocked by repos scope (-32002)
204+
AccessDenied int `json:"access_denied"` // General access denied (-32001)
205+
BlockedUserDenied int `json:"blocked_user_denied,omitempty"` // Content from blocked user (-32005)
206+
PermissionDenied int `json:"permission_denied,omitempty"` // Insufficient permissions (-32003)
207+
PrivateRepoDenied int `json:"private_repo_denied,omitempty"` // Private repository denied (-32004)
208+
Events []GuardPolicyEvent `json:"events"`
209+
BlockedToolCounts map[string]int `json:"blocked_tool_counts,omitempty"` // tool name -> blocked count
210+
BlockedServerCounts map[string]int `json:"blocked_server_counts,omitempty"` // server ID -> blocked count
211+
}
212+
196213
// PolicySummaryDisplay is a display-optimized version of PolicyAnalysis for console rendering
197214
type PolicySummaryDisplay struct {
198215
Policy string `console:"header:Policy"`

pkg/cli/audit_report_render.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"math"
77
"os"
88
"path/filepath"
9+
"sort"
910
"strconv"
1011
"strings"
1112
"time"
@@ -514,6 +515,89 @@ func renderMCPToolUsageTable(mcpData *MCPToolUsageData) {
514515

515516
fmt.Fprint(os.Stderr, console.RenderTable(toolConfig))
516517
}
518+
519+
// Render guard policy summary
520+
if mcpData.GuardPolicySummary != nil && mcpData.GuardPolicySummary.TotalBlocked > 0 {
521+
renderGuardPolicySummary(mcpData.GuardPolicySummary)
522+
}
523+
}
524+
525+
// renderGuardPolicySummary renders the guard policy enforcement summary
526+
func renderGuardPolicySummary(summary *GuardPolicySummary) {
527+
auditReportLog.Printf("Rendering guard policy summary: %d total blocked", summary.TotalBlocked)
528+
529+
fmt.Fprintln(os.Stderr)
530+
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(
531+
fmt.Sprintf("Guard Policy: %d tool call(s) blocked", summary.TotalBlocked)))
532+
fmt.Fprintln(os.Stderr)
533+
534+
// Breakdown by reason
535+
fmt.Fprintln(os.Stderr, " Block Reasons:")
536+
if summary.IntegrityBlocked > 0 {
537+
fmt.Fprintf(os.Stderr, " Integrity below minimum : %d\n", summary.IntegrityBlocked)
538+
}
539+
if summary.RepoScopeBlocked > 0 {
540+
fmt.Fprintf(os.Stderr, " Repository not allowed : %d\n", summary.RepoScopeBlocked)
541+
}
542+
if summary.AccessDenied > 0 {
543+
fmt.Fprintf(os.Stderr, " Access denied : %d\n", summary.AccessDenied)
544+
}
545+
if summary.BlockedUserDenied > 0 {
546+
fmt.Fprintf(os.Stderr, " Blocked user : %d\n", summary.BlockedUserDenied)
547+
}
548+
if summary.PermissionDenied > 0 {
549+
fmt.Fprintf(os.Stderr, " Insufficient permissions: %d\n", summary.PermissionDenied)
550+
}
551+
if summary.PrivateRepoDenied > 0 {
552+
fmt.Fprintf(os.Stderr, " Private repo denied : %d\n", summary.PrivateRepoDenied)
553+
}
554+
fmt.Fprintln(os.Stderr)
555+
556+
// Most frequently blocked tools
557+
if len(summary.BlockedToolCounts) > 0 {
558+
toolNames := sliceutil.MapToSlice(summary.BlockedToolCounts)
559+
sort.Slice(toolNames, func(i, j int) bool {
560+
return summary.BlockedToolCounts[toolNames[i]] > summary.BlockedToolCounts[toolNames[j]]
561+
})
562+
563+
toolRows := make([][]string, 0, len(toolNames))
564+
for _, name := range toolNames {
565+
toolRows = append(toolRows, []string{name, strconv.Itoa(summary.BlockedToolCounts[name])})
566+
}
567+
fmt.Fprint(os.Stderr, console.RenderTable(console.TableConfig{
568+
Title: "Most Blocked Tools",
569+
Headers: []string{"Tool", "Blocked"},
570+
Rows: toolRows,
571+
}))
572+
}
573+
574+
// Guard policy event details
575+
if len(summary.Events) > 0 {
576+
fmt.Fprintln(os.Stderr)
577+
eventRows := make([][]string, 0, len(summary.Events))
578+
for _, evt := range summary.Events {
579+
message := evt.Message
580+
if len(message) > 60 {
581+
message = message[:57] + "..."
582+
}
583+
repo := evt.Repository
584+
if repo == "" {
585+
repo = "-"
586+
}
587+
eventRows = append(eventRows, []string{
588+
stringutil.Truncate(evt.ServerID, 20),
589+
stringutil.Truncate(evt.ToolName, 25),
590+
evt.Reason,
591+
message,
592+
repo,
593+
})
594+
}
595+
fmt.Fprint(os.Stderr, console.RenderTable(console.TableConfig{
596+
Title: "Guard Policy Events",
597+
Headers: []string{"Server", "Tool", "Reason", "Message", "Repository"},
598+
Rows: eventRows,
599+
}))
600+
}
517601
}
518602

519603
// renderFirewallAnalysis renders firewall analysis with summary and domain breakdown

0 commit comments

Comments
 (0)