Skip to content

Commit 607ac16

Browse files
jackbatznerCopilot
andcommitted
feat(dotnet): add MCP protocol support with OWASP coverage, multi-target .NET 8/10, ML-DSA post-quantum signing
Add comprehensive MCP (Model Context Protocol) security governance to the .NET SDK with 11/12 OWASP MCP Security Cheat Sheet sections covered. Multi-targets .NET 8.0 (LTS) and .NET 10.0 with post-quantum ML-DSA-65 (NIST FIPS 204) signing on .NET 10+. Core components: - McpGateway: 5-stage pipeline (deny→allow→sanitize→rate-limit→approve) - McpSecurityScanner: 6-threat detection with SHA-256 fingerprinting - McpMessageHandler: JSON-RPC routing with tool-to-ActionType classification - McpResponseScanner: Output validation (injection, credentials, exfiltration) - McpSessionAuthenticator: Crypto session binding with TOCTOU-safe concurrency - McpMessageSigner: HMAC-SHA256 (.NET 8) + ML-DSA-65 post-quantum (.NET 10+) - CredentialRedactor: 10 credential pattern redaction for audit logs - McpSlidingRateLimiter: Per-agent sliding window rate limiting Integration: - ASP.NET Core: AddMcpGovernance(), UseMcpGovernance(), MapMcpGovernance() - IConfiguration binding, ILogger<T>, IHealthCheck, gRPC interceptor - McpToolRegistry with [McpTool] attribute auto-discovery - AgentGovernance.ModelContextProtocol adapter sub-package (official SDK) - OTel metrics: mcp_decisions, mcp_threats_detected, mcp_rate_limit_hits, mcp_scans Tests: 973 passing (0 failures) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c553a66 commit 607ac16

63 files changed

Lines changed: 12766 additions & 419 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
1212
## [Unreleased]
1313

14+
### Added
15+
- **.NET MCP Protocol Support** — Full Model Context Protocol governance layer multi-targeting .NET 8.0 and .NET 10.0 with 11/12 OWASP MCP Security Cheat Sheet coverage
16+
- `McpGateway`: 5-stage pipeline (deny-list → allow-list → sanitization → rate-limiting → human approval)
17+
- `McpSecurityScanner`: 6-threat detection (tool poisoning, rug-pull, cross-server, description injection, schema abuse, protocol attacks)
18+
- `McpSessionAuthenticator`: Cryptographic session binding with TTL and TOCTOU-safe concurrency
19+
- `McpMessageSigner`: HMAC-SHA256 message integrity + ML-DSA-65 post-quantum signing on .NET 10+ (NIST FIPS 204)
20+
- `McpResponseScanner`: Output validation (HTML tags, imperatives, credential leakage, data exfiltration)
21+
- `CredentialRedactor`: 10 credential pattern redaction (API keys, tokens, PEM, connection strings)
22+
- `McpSlidingRateLimiter`: Per-agent sliding window rate limiting
23+
- ASP.NET Core integration: `AddMcpGovernance()`, `UseMcpGovernance()`, `MapMcpGovernance()`
24+
- `IConfiguration` binding, `ILogger<T>` structured logging, `IHealthCheck` implementation
25+
- gRPC server interceptor (all 4 handler types)
26+
- `[McpTool]` attribute for auto-discovery with `McpToolRegistry`
27+
- OpenTelemetry: 4 MCP-specific counters (decision, threat, rate-limit, scan)
28+
- `AgentGovernance.ModelContextProtocol` adapter sub-package for official MCP SDK integration
29+
- 2 sample apps: ASP.NET Core full-stack and Official MCP SDK integration
30+
- K8s MCP server hardening guide (`docs/deployment/mcp-server-hardening.md`)
31+
1432
### Security
1533
- **Hardened CLI Error Handling** — standardized sanitized JSON error output across all 7 ecosystem tools to prevent internal information disclosure (CWE-209).
1634
- **Audit Log Whitelisting** — implemented strict key-whitelisting in `agentmesh audit` JSON output to prevent accidental leakage of sensitive agent internal state.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ Still have questions? File a [GitHub issue](https://github.com/microsoft/agent-g
9797
- [Agent SRE](packages/agent-sre/) | [Observability integrations](packages/agent-hypervisor/src/hypervisor/observability/)
9898
- **MCP Security Scanner**: Detect tool poisoning, typosquatting, hidden instructions, and rug-pull attacks in MCP tool definitions
9999
- [MCP Scanner](packages/agent-os/src/agentos/mcp_security.py) | [CLI](packages/agent-os/src/agentos/cli/mcp_scan.py)
100+
- **.NET MCP Protocol Support**: Full governance pipeline for .NET 8.0 — 5-stage gateway, 6-threat scanner, session auth, message signing, credential redaction (11/12 OWASP MCP sections)
101+
- [.NET MCP SDK](packages/agent-governance-dotnet/) | [Official MCP SDK Adapter](packages/agent-governance-dotnet/src/AgentGovernance.ModelContextProtocol/)
100102
- **Trust Report CLI**: `agentmesh trust report` — visualize trust scores, task success/failure, and agent activity
101103
- [Trust CLI](packages/agent-mesh/src/agentmesh/cli/trust_cli.py)
102104
- **Secret Scanning & Fuzzing**: Gitleaks workflow, 7 fuzz targets covering policy, injection, sandbox, trust, and MCP
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# MCP Server Hardening Guide
2+
3+
Deployment guidance for running MCP tool servers securely, aligned with
4+
[OWASP MCP Security Cheat Sheet §3 — Sandbox & Isolate MCP Servers](https://cheatsheetseries.owasp.org/cheatsheets/MCP_Security_Cheat_Sheet.html).
5+
6+
## Transport: prefer stdio over HTTP
7+
8+
When the MCP server runs on the same host as the agent, use **stdio** transport
9+
rather than HTTP/SSE. This eliminates the network attack surface entirely —
10+
no open ports, no TLS configuration, no SSRF vectors.
11+
12+
```yaml
13+
# docker-compose.yml — stdio transport
14+
services:
15+
mcp-server:
16+
image: myregistry/mcp-tools:1.2.3@sha256:abc...
17+
stdin_open: true
18+
read_only: true
19+
security_opt: ["no-new-privileges"]
20+
```
21+
22+
For HTTP transport, require mTLS between agent and server (see §6).
23+
24+
## Kubernetes: securityContext
25+
26+
Every MCP server pod should run as a non-root user with a read-only root
27+
filesystem and all capabilities dropped:
28+
29+
```yaml
30+
apiVersion: v1
31+
kind: Pod
32+
metadata:
33+
name: mcp-server
34+
spec:
35+
securityContext:
36+
runAsNonRoot: true
37+
runAsUser: 65534 # nobody
38+
runAsGroup: 65534
39+
fsGroup: 65534
40+
seccompProfile:
41+
type: RuntimeDefault
42+
containers:
43+
- name: mcp-tools
44+
image: myregistry/mcp-tools:1.2.3@sha256:abc...
45+
securityContext:
46+
allowPrivilegeEscalation: false
47+
readOnlyRootFilesystem: true
48+
capabilities:
49+
drop: ["ALL"]
50+
resources:
51+
limits:
52+
cpu: "500m"
53+
memory: "256Mi"
54+
volumeMounts:
55+
- name: tmp
56+
mountPath: /tmp
57+
volumes:
58+
- name: tmp
59+
emptyDir:
60+
sizeLimit: 50Mi
61+
```
62+
63+
## Network Isolation: NetworkPolicy
64+
65+
Restrict MCP servers so they can **only** communicate with the agent
66+
orchestrator and required backends (database, blob storage). Block all
67+
egress to the public internet and to the cloud metadata service:
68+
69+
```yaml
70+
apiVersion: networking.k8s.io/v1
71+
kind: NetworkPolicy
72+
metadata:
73+
name: mcp-server-policy
74+
spec:
75+
podSelector:
76+
matchLabels:
77+
app: mcp-server
78+
policyTypes: [Ingress, Egress]
79+
ingress:
80+
- from:
81+
- podSelector:
82+
matchLabels:
83+
app: agent-orchestrator
84+
ports:
85+
- port: 8080
86+
protocol: TCP
87+
egress:
88+
# Allow DNS
89+
- to:
90+
- namespaceSelector: {}
91+
ports:
92+
- port: 53
93+
protocol: UDP
94+
# Allow specific backends
95+
- to:
96+
- podSelector:
97+
matchLabels:
98+
app: postgres
99+
ports:
100+
- port: 5432
101+
protocol: TCP
102+
# Block cloud metadata (SSRF protection)
103+
# Azure IMDS: 169.254.169.254
104+
# AWS IMDS: 169.254.169.254
105+
# GCP metadata: metadata.google.internal (100.100.100.200)
106+
# These are blocked by default when no egress rule matches.
107+
```
108+
109+
## gVisor / Kata Containers for Untrusted Servers
110+
111+
For MCP servers that execute arbitrary code (code interpreters, shell tools),
112+
use a sandbox runtime like [gVisor](https://gvisor.dev/) or
113+
[Kata Containers](https://katacontainers.io/):
114+
115+
```yaml
116+
# AKS with gVisor runtime class
117+
apiVersion: node.k8s.io/v1
118+
kind: RuntimeClass
119+
metadata:
120+
name: gvisor
121+
handler: runsc
122+
---
123+
apiVersion: v1
124+
kind: Pod
125+
metadata:
126+
name: mcp-code-interpreter
127+
spec:
128+
runtimeClassName: gvisor
129+
containers:
130+
- name: interpreter
131+
image: myregistry/code-interpreter:1.0@sha256:def...
132+
securityContext:
133+
allowPrivilegeEscalation: false
134+
readOnlyRootFilesystem: true
135+
capabilities:
136+
drop: ["ALL"]
137+
```
138+
139+
On **Azure Kubernetes Service (AKS)**:
140+
- Enable the [Kata Container node pool](https://learn.microsoft.com/azure/aks/use-katacontainers) for VM-level isolation.
141+
- Use [Azure Container Instances (ACI)](https://learn.microsoft.com/azure/container-instances/) with Hyper-V isolation for per-tool ephemeral sandboxes.
142+
143+
## File System Restrictions
144+
145+
MCP tools should only access explicitly mounted paths:
146+
147+
```yaml
148+
volumeMounts:
149+
- name: workspace
150+
mountPath: /workspace
151+
readOnly: false # only if tool needs write
152+
- name: config
153+
mountPath: /config
154+
readOnly: true
155+
```
156+
157+
Combine with the `.NET SDK path traversal sanitization pattern`
158+
(`SanitizationDefaults.AllPatterns` detects `../` sequences) to prevent
159+
escape even if mounts are misconfigured.
160+
161+
## Resource Limits
162+
163+
Prevent a compromised tool from consuming cluster resources:
164+
165+
| Resource | Recommendation |
166+
|----------|---------------|
167+
| CPU | 500m limit per tool pod |
168+
| Memory | 256Mi limit (512Mi for code interpreters) |
169+
| Ephemeral storage | 50Mi via emptyDir sizeLimit |
170+
| Process count | `pids-limit` cgroup (64 for simple tools) |
171+
| Network bandwidth | Use Cilium/Calico bandwidth annotations |
172+
173+
## Checklist
174+
175+
- [ ] Non-root user (`runAsNonRoot: true`)
176+
- [ ] Read-only root filesystem
177+
- [ ] All capabilities dropped
178+
- [ ] seccomp profile enabled (`RuntimeDefault`)
179+
- [ ] NetworkPolicy restricts ingress + egress
180+
- [ ] Cloud metadata IPs blocked (169.254.169.254)
181+
- [ ] Resource limits set (CPU, memory, storage)
182+
- [ ] gVisor/Kata for code execution tools
183+
- [ ] stdio transport where possible
184+
- [ ] Container images use SHA digest tags
185+
- [ ] `.NET SDK McpGateway` sanitization + response scanning enabled
186+
187+
## Related
188+
189+
- [McpGateway](../../packages/agent-governance-dotnet/README.md#mcp-protocol-support) — 5-stage governance pipeline
190+
- [McpSecurityScanner](../../packages/agent-governance-dotnet/README.md#mcp-protocol-support) — tool definition scanning
191+
- [OWASP MCP Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/MCP_Security_Cheat_Sheet.html)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Agent Governance .NET SDK — Coding Agent Instructions
2+
3+
## Project Overview
4+
5+
The .NET SDK provides **governance-as-code for AI agents** targeting .NET 8.0+. It integrates with ASP.NET Core, gRPC, and the official ModelContextProtocol C# SDK to enforce policy, security scanning, and audit logging at the MCP protocol layer.
6+
7+
**Architecture:** GovernanceKernel (policy engine) + MCP governance stack
8+
9+
- **GovernanceKernel:** Deterministic policy evaluation, action classification, middleware pipeline
10+
- **MCP Gateway:** 5-stage pipeline (deny-list → allow-list → sanitization → rate-limiting → human approval)
11+
- **MCP Security Scanner:** 6-threat detection with SHA-256 fingerprinting
12+
- **Extensions:** ASP.NET DI, middleware, health checks, IConfiguration, gRPC interceptor
13+
14+
## Build & Test Commands
15+
16+
```bash
17+
# Build the solution (all projects)
18+
cd packages/agent-governance-dotnet
19+
dotnet build
20+
21+
# Run all tests
22+
dotnet test
23+
24+
# Run tests with verbosity
25+
dotnet test --verbosity normal
26+
27+
# Build samples
28+
dotnet build samples/McpGovernance.AspNetCore/McpGovernance.AspNetCore.csproj
29+
dotnet build samples/McpGovernance.OfficialSdk/McpGovernance.OfficialSdk.csproj
30+
```
31+
32+
## Project Structure
33+
34+
```
35+
packages/agent-governance-dotnet/
36+
├── AgentGovernance.sln
37+
├── src/
38+
│ ├── AgentGovernance/ # Core library (no MCP SDK dependency)
39+
│ │ ├── AgentGovernance.csproj
40+
│ │ ├── Core/ # GovernanceKernel, middleware, policy
41+
│ │ ├── Mcp/ # MCP protocol components
42+
│ │ ├── Extensions/ # ASP.NET, DI, config, gRPC, health
43+
│ │ └── Telemetry/ # OpenTelemetry metrics
44+
│ └── AgentGovernance.ModelContextProtocol/ # Adapter sub-package
45+
│ ├── AgentGovernance.ModelContextProtocol.csproj
46+
│ └── McpSdkGovernanceExtensions.cs
47+
├── tests/
48+
│ └── AgentGovernance.Tests/
49+
└── samples/
50+
├── McpGovernance.AspNetCore/
51+
└── McpGovernance.OfficialSdk/
52+
```
53+
54+
## Coding Conventions
55+
56+
- **Target:** .NET 8.0, C# 12
57+
- **Test framework:** xUnit 2.9.3 with `[Fact]` and `[Theory]`
58+
- **JSON:** `System.Text.Json` (never Newtonsoft)
59+
- **Crypto:** `System.Security.Cryptography` (HMAC-SHA256, SHA-256)
60+
- **Logging:** `ILogger<T>` via settable property (not constructor injection), matching existing `Metrics` pattern
61+
- **Telemetry:** `System.Diagnostics.Metrics` counters via `GovernanceMetrics`
62+
- **DI pattern:** `IServiceCollection` extensions returning the collection for chaining
63+
- **Fail-closed:** Any exception in governance pipeline → deny (never silent pass-through)
64+
- **Regex safety:** All compiled regexes must have `matchTimeout: TimeSpan.FromMilliseconds(200)` for ReDoS prevention
65+
- **Constant-time comparison:** Use `CryptographicOperations.FixedTimeEquals` for all secret comparison
66+
67+
## Key Design Decisions
68+
69+
1. **Core has no ModelContextProtocol NuGet dependency** — the adapter lives in `AgentGovernance.ModelContextProtocol` sub-package (Serilog/MediatR pattern)
70+
2. **HMAC-SHA256** instead of Ed25519 — .NET 8 lacks Ed25519 support
71+
3. **SortedDictionary** for schema hashing — ensures deterministic SHA-256 fingerprints
72+
4. **Nonce cache capped at 10,000** with oldest eviction to prevent memory exhaustion
73+
5. **Session limit checked under lock** — TOCTOU-safe concurrency for `McpSessionAuthenticator`
74+
6. **Properties use `set` not `init`** on `McpGovernanceOptions` — required for `IConfiguration` binding
75+
76+
## OWASP MCP Security Coverage
77+
78+
11 of 12 OWASP MCP Security Cheat Sheet sections covered. §11 (Consent UI) is client-side and out of scope for a server SDK.
Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1+
22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 17
44
VisualStudioVersion = 17.0.31903.59
@@ -7,19 +7,61 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AgentGovernance", "src\Agen
77
EndProject
88
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AgentGovernance.Tests", "tests\AgentGovernance.Tests\AgentGovernance.Tests.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
99
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AgentGovernance.ModelContextProtocol", "src\AgentGovernance.ModelContextProtocol\AgentGovernance.ModelContextProtocol.csproj", "{9D9175D5-F566-43BF-AE50-1F8C4AA1F042}"
13+
EndProject
1014
Global
1115
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1216
Debug|Any CPU = Debug|Any CPU
17+
Debug|x64 = Debug|x64
18+
Debug|x86 = Debug|x86
1319
Release|Any CPU = Release|Any CPU
20+
Release|x64 = Release|x64
21+
Release|x86 = Release|x86
1422
EndGlobalSection
1523
GlobalSection(ProjectConfigurationPlatforms) = postSolution
1624
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1725
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
26+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|Any CPU
27+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|Any CPU
28+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.ActiveCfg = Debug|Any CPU
29+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.Build.0 = Debug|Any CPU
1830
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
1931
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
32+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|Any CPU
33+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU
34+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU
35+
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU
2036
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
2137
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 = Debug|Any CPU
38+
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|x64.ActiveCfg = Debug|Any CPU
39+
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|x64.Build.0 = Debug|Any CPU
40+
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|x86.ActiveCfg = Debug|Any CPU
41+
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|x86.Build.0 = Debug|Any CPU
2242
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.ActiveCfg = Release|Any CPU
2343
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.Build.0 = Release|Any CPU
44+
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|x64.ActiveCfg = Release|Any CPU
45+
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|x64.Build.0 = Release|Any CPU
46+
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|x86.ActiveCfg = Release|Any CPU
47+
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|x86.Build.0 = Release|Any CPU
48+
{9D9175D5-F566-43BF-AE50-1F8C4AA1F042}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49+
{9D9175D5-F566-43BF-AE50-1F8C4AA1F042}.Debug|Any CPU.Build.0 = Debug|Any CPU
50+
{9D9175D5-F566-43BF-AE50-1F8C4AA1F042}.Debug|x64.ActiveCfg = Debug|Any CPU
51+
{9D9175D5-F566-43BF-AE50-1F8C4AA1F042}.Debug|x64.Build.0 = Debug|Any CPU
52+
{9D9175D5-F566-43BF-AE50-1F8C4AA1F042}.Debug|x86.ActiveCfg = Debug|Any CPU
53+
{9D9175D5-F566-43BF-AE50-1F8C4AA1F042}.Debug|x86.Build.0 = Debug|Any CPU
54+
{9D9175D5-F566-43BF-AE50-1F8C4AA1F042}.Release|Any CPU.ActiveCfg = Release|Any CPU
55+
{9D9175D5-F566-43BF-AE50-1F8C4AA1F042}.Release|Any CPU.Build.0 = Release|Any CPU
56+
{9D9175D5-F566-43BF-AE50-1F8C4AA1F042}.Release|x64.ActiveCfg = Release|Any CPU
57+
{9D9175D5-F566-43BF-AE50-1F8C4AA1F042}.Release|x64.Build.0 = Release|Any CPU
58+
{9D9175D5-F566-43BF-AE50-1F8C4AA1F042}.Release|x86.ActiveCfg = Release|Any CPU
59+
{9D9175D5-F566-43BF-AE50-1F8C4AA1F042}.Release|x86.Build.0 = Release|Any CPU
60+
EndGlobalSection
61+
GlobalSection(SolutionProperties) = preSolution
62+
HideSolutionNode = FALSE
63+
EndGlobalSection
64+
GlobalSection(NestedProjects) = preSolution
65+
{9D9175D5-F566-43BF-AE50-1F8C4AA1F042} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
2466
EndGlobalSection
2567
EndGlobal

0 commit comments

Comments
 (0)