|
6 | 6 | "math" |
7 | 7 | "os" |
8 | 8 | "path/filepath" |
| 9 | + "sort" |
9 | 10 | "strconv" |
10 | 11 | "strings" |
11 | 12 | "time" |
@@ -514,6 +515,89 @@ func renderMCPToolUsageTable(mcpData *MCPToolUsageData) { |
514 | 515 |
|
515 | 516 | fmt.Fprint(os.Stderr, console.RenderTable(toolConfig)) |
516 | 517 | } |
| 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 | + } |
517 | 601 | } |
518 | 602 |
|
519 | 603 | // renderFirewallAnalysis renders firewall analysis with summary and domain breakdown |
|
0 commit comments