Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/SDK-FEATURE-MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ governed agents in each ecosystem.
| **Identity & Auth** | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Trust Scoring** | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Audit Logging** | ✅ | ✅ | ✅ | ✅ | ✅ |
| **MCP Security** | ✅ | ✅ | — | ✅ | ✅ |
| **MCP Security** | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Execution Rings** | ✅ | — | ✅ | ✅ | ✅ |
| **SRE / SLOs** | ✅ | — | ✅ | — | — |
| **Kill Switch** | ✅ | — | ✅ | — | — |
Expand Down Expand Up @@ -100,8 +100,9 @@ governance stack for enterprise deployments:
| `Integration` | `GovernanceMiddleware` for ASP.NET / Agent Framework |
| `RateLimiting` | Token bucket rate limiter |
| `Telemetry` | OpenTelemetry integration |
| `Mcp` | `McpSecurityScanner` (poisoning, typosquatting, hidden instructions, rug pull, schema abuse, cross-server), `McpResponseSanitizer`, `McpCredentialRedactor`, `McpGateway` |

**Roadmap:** MCP security, full lifecycle persistence.
**Roadmap:** Full lifecycle persistence.

### Rust SDK

Expand Down
24 changes: 15 additions & 9 deletions docs/compliance/soc2-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ The Agent Governance Toolkit provides runtime governance infrastructure that add

| Criteria | Coverage | Key Controls Addressed | Primary Gaps |
|----------|----------|----------------------|--------------|
| **Security** (CC1–CC9) | ⚠️ Partial | Policy engine, RBAC, DID identity, execution rings, audit logging, MCP security scanning | Kill switch placeholder, detection modules unwired from enforcement |
| **Security** (CC1–CC9) | ⚠️ Partial | Policy engine, RBAC, DID identity, execution rings, audit logging, MCP security scanning, kill switch | Detection modules unwired from enforcement |
| **Availability** (A1) | ⚠️ Partial | Circuit breakers, SLO/error budgets, chaos testing framework, sub-millisecond enforcement | Chaos engine framework-only, no health check endpoints, rate limiter unwired |
| **Processing Integrity** (PI1) | ⚠️ Partial | Merkle audit chain, policy validation, input sanitization, drift detection | 3 of 4 audit chain implementations have integrity defects, `post_execute()` never blocks |
| **Processing Integrity** (PI1) | ⚠️ Partial | Merkle audit chain, Delta audit chain (SHA-256 verified), policy validation, input sanitization, drift detection | 2 of 4 audit chain implementations have integrity defects, `post_execute()` never blocks |
| **Confidentiality** (C1) | ⚠️ Partial | Ed25519 identity, HMAC-SHA256 signing, egress policy, PII/secret detection, credential redaction in audit logs | Symmetric HMAC keys, no at-rest encryption, non-credential PII not redacted in audit entries |
| **Privacy** (P1–P8) | ❌ Gap | 2 PII regex patterns, blocked patterns, retention_days schema field | No consent management, no DSAR, no data minimization, retention not enforced |

Expand Down Expand Up @@ -130,7 +130,7 @@ audit.log_decision(

### Security Gaps

- [ ] **Kill switch is placeholder** (CC7.4): `KillSwitch.kill()` at `kill_switch.py:86` returns structured `KillResult` objects but does not terminate agent processes. `handoff_success_count` is hardcoded to 0. All in-flight saga steps are auto-marked COMPENSATED without actual compensation.
- [x] ~~**Kill switch is placeholder**~~ (CC7.4): **Resolved.** `KillSwitch` now registers agents and substitutes, creates `StepHandoff` records, and increments `handoff_success_count` during saga orchestration. See `kill_switch.py:69-178`.
- [ ] **Detection modules not wired to enforcement** (CC6.8): `PromptInjectionDetector`, `RateLimiter`, `BoundedSemaphore`, `ScopeGuard`, `SupplyChainGuard`, and `MCPSecurityScanner` exist as standalone utilities but are not auto-wired into the `BaseIntegration` enforcement lifecycle. 6 of 10 OWASP risks share this structural gap.
- [ ] **MCP scanner acknowledges incompleteness** (CC7.3): Line 287 of `mcp_security.py` warns it "uses built-in sample rules that may not cover all MCP tool poisoning techniques."
- [ ] **Regex-only prompt injection detection** (CC6.8): No semantic or multilingual detection. English-only regex patterns can be bypassed via paraphrasing.
Expand Down Expand Up @@ -260,7 +260,7 @@ assert entry.previous_hash != "" or log.entries.index(entry) == 0

### Processing Integrity Gaps

- [ ] **DeltaEngine chain verification is a stub** (PI1.5): `verify_chain()` at `packages/agent-hypervisor/src/hypervisor/audit/delta.py:99` always returns `True` with comment "Public Preview: no chain verification." The hypervisor's entire audit trail has zero tamper evidence.
- [x] ~~**DeltaEngine chain verification is a stub**~~ (PI1.5): **Resolved.** `verify_chain()` now computes SHA-256 hashes and verifies parent linkage across entries. See `delta.py:67-127`.
- [ ] **FlightRecorder hash covers INSERT-time state** (PI1.5): Hash is computed at insert time with `policy_verdict='pending'`, but the verdict is later updated to `'allowed'`/`'blocked'`. Tampering of the verdict field is undetectable by integrity verification.
- [ ] **Anomaly detections outside tamper-evident chain** (PI1.5): `RogueAgentDetector` stores assessments in an in-memory list, not in the integrity-protected audit chain.
- [ ] **`post_execute()` never blocks** (PI1.3): `base.py:977-1038` computes drift scores and emits `DRIFT_DETECTED` events but always returns `(True, None)` — advisory only, no enforcement on output integrity.
Expand All @@ -269,7 +269,7 @@ assert entry.previous_hash != "" or log.entries.index(entry) == 0

### Recommended Controls

1. **Fix DeltaEngine `verify_chain()` stub** — replace with real SHA-256 chain verification (same algorithm as `MerkleAuditChain`).
1. ~~**Fix DeltaEngine `verify_chain()` stub**~~ — **Done.** Now performs real SHA-256 chain verification.
2. **Fix FlightRecorder hash** — compute hash over final state including resolved verdict, not INSERT-time state.
3. Wire anomaly detections into the tamper-evident audit chain.
4. Add `GovernancePolicy.block_on_drift` flag to enable enforcement in `post_execute()`.
Expand Down Expand Up @@ -409,8 +409,8 @@ All file paths referenced in this document, organized by package:
| File | Evidence For |
|------|-------------|
| `src/hypervisor/models.py:46-69` | CC6.7 — Execution rings (Ring 0–3) |
| `src/hypervisor/security/kill_switch.py:64-136` | CC7.4 — Kill switch (placeholder handoff) |
| `src/hypervisor/audit/delta.py:59-110` | PI1.5 — Delta audit engine (`verify_chain()` stub) |
| `src/hypervisor/security/kill_switch.py:64-178` | CC7.4 — Kill switch with saga handoff |
| `src/hypervisor/audit/delta.py:59-127` | PI1.5 — Delta audit engine with SHA-256 chain verification |
| `src/hypervisor/rings/breach_detector.py:1-60` | CC9.1 — Ring breach detection |

### Agent SRE (`packages/agent-sre/`)
Expand Down Expand Up @@ -446,15 +446,21 @@ All gaps consolidated and rated by severity for remediation prioritization.
| Gap | Criteria | Impact | Location |
|-----|----------|--------|----------|
| **Non-credential PII not redacted in audit logs** | C1.1, P6 | Credential-like secrets are redacted via `CredentialRedactor`, but non-credential PII (email, phone, addresses) in tool parameters is still stored verbatim | `MCPGateway.intercept_tool_call()` |
| **DeltaEngine `verify_chain()` is a stub** | PI1.5 | Returns `True` always — hypervisor audit trail has zero tamper evidence | `delta.py:99` |
| **No consent management** | P2 | Fundamental Privacy criteria requirement not addressed | — |
| **No data subject access request support** | P5 | Required for Privacy criteria compliance | — |

### Resolved (formerly Critical/High)

| Gap | Criteria | Resolution |
|-----|----------|------------|
| ~~DeltaEngine `verify_chain()` stub~~ | PI1.5 | Now performs SHA-256 chain verification (`delta.py:67-127`) |
| ~~Kill switch placeholder~~ | CC7.4 | Now implements saga handoff with `handoff_success_count` tracking (`kill_switch.py:69-178`) |
| ~~Audit logs store unredacted parameters~~ | C1.1 | Credential-like secrets now redacted via `CredentialRedactor` before audit persistence |

### High

| Gap | Criteria | Impact | Location |
|-----|----------|--------|----------|
| **Kill switch placeholder** | CC7.4 | Does not terminate agent processes; `handoff_success_count` hardcoded to 0 | `kill_switch.py:86` |
| **Detection modules unwired** | CC6.8 | 6 detection modules exist but none are integrated into enforcement lifecycle | `base.py` (multiple) |
| **FlightRecorder hash gap** | PI1.5 | Hash covers INSERT-time state, not final verdict — tampering undetectable | `flight_recorder.py` |
| **HMAC symmetric key risk** | C1.2 | Insider with the key can forge the entire audit chain | `audit_backends.py:61-87` |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.

using System.Text.RegularExpressions;

namespace AgentGovernance.Mcp;

/// <summary>
/// Categorical credential types that may be redacted.
/// </summary>
public enum CredentialKind
{
/// <summary>API key pattern (e.g., api_key=..., x-api-key: ...).</summary>
ApiKey,
/// <summary>Bearer token (e.g., Authorization: Bearer ...).</summary>
BearerToken,
/// <summary>Connection string with password or shared access key.</summary>
ConnectionString,
/// <summary>Generic secret assignment (password=, secret=, token=).</summary>
SecretAssignment
}

/// <summary>
/// Result of credential redaction.
/// </summary>
public sealed class RedactionResult
{
/// <summary>The sanitized text with credentials replaced by placeholders.</summary>
public required string Sanitized { get; init; }

/// <summary>Credential types that were detected and redacted.</summary>
public required IReadOnlyList<CredentialKind> Detected { get; init; }

/// <summary>Whether any credentials were redacted.</summary>
public bool Modified => Detected.Count > 0;
}

/// <summary>
/// Redacts credentials from text and structured data.
/// Detects API keys, bearer tokens, connection strings, and generic secret assignments.
/// Thread-safe.
/// </summary>
public sealed class McpCredentialRedactor
{
private static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(200);

private static readonly (CredentialKind Kind, Regex Pattern, string Placeholder)[] Patterns =
[
(CredentialKind.BearerToken,
new Regex(@"(?i)\bbearer\s+[a-z0-9._~+/=-]{8,}", RegexOptions.Compiled, RegexTimeout),
"[REDACTED_BEARER_TOKEN]"),

(CredentialKind.ApiKey,
new Regex(@"(?i)(?:api[_\-]?key|x-api-key)\s*[:=]\s*[""']?[a-z0-9_\-]{8,}[""']?", RegexOptions.Compiled, RegexTimeout),
"[REDACTED_API_KEY]"),

(CredentialKind.ConnectionString,
new Regex(@"(?i)\b(?:server|host|endpoint)=[^;]+;[^;\n]*(?:password|sharedaccesskey)=[^;\n]+", RegexOptions.Compiled, RegexTimeout),
"[REDACTED_CONNECTION_STRING]"),

(CredentialKind.SecretAssignment,
new Regex(@"(?i)\b(?:password|secret|token)\s*[:=]\s*[""']?[^\s""';,]{4,}[""']?", RegexOptions.Compiled, RegexTimeout),
"[REDACTED_SECRET]")
];

private static readonly Dictionary<string, CredentialKind> KeyHints = new(StringComparer.OrdinalIgnoreCase)
{
["authorization"] = CredentialKind.BearerToken,
["bearer"] = CredentialKind.BearerToken,
["api_key"] = CredentialKind.ApiKey,
["apikey"] = CredentialKind.ApiKey,
["x-api-key"] = CredentialKind.ApiKey,
["token"] = CredentialKind.SecretAssignment,
["secret"] = CredentialKind.SecretAssignment,
["password"] = CredentialKind.SecretAssignment,
["credential"] = CredentialKind.SecretAssignment,
["connection_string"] = CredentialKind.ConnectionString,
["connectionstring"] = CredentialKind.ConnectionString
};

/// <summary>
/// Redacts credentials from a text string.
/// </summary>
public RedactionResult Redact(string input)
{
ArgumentNullException.ThrowIfNull(input);
var sanitized = input;
var detected = new List<CredentialKind>();

foreach (var (kind, pattern, placeholder) in Patterns)
{
if (pattern.IsMatch(sanitized))
{
if (!detected.Contains(kind))
detected.Add(kind);
sanitized = pattern.Replace(sanitized, placeholder);
}
}

return new RedactionResult
{
Sanitized = sanitized,
Detected = detected.AsReadOnly()
};
}

/// <summary>
/// Returns the placeholder string for a credential kind.
/// </summary>
public static string PlaceholderFor(CredentialKind kind) => kind switch
{
CredentialKind.ApiKey => "[REDACTED_API_KEY]",
CredentialKind.BearerToken => "[REDACTED_BEARER_TOKEN]",
CredentialKind.ConnectionString => "[REDACTED_CONNECTION_STRING]",
CredentialKind.SecretAssignment => "[REDACTED_SECRET]",
_ => "[REDACTED]"
};

/// <summary>
/// Infers a credential kind from a dictionary key name (e.g., "x-api-key" → ApiKey).
/// Returns null if the key doesn't match any known credential pattern.
/// </summary>
public static CredentialKind? InferKindFromKey(string key)
{
if (string.IsNullOrEmpty(key)) return null;
var lower = key.ToLowerInvariant();
foreach (var (hint, kind) in KeyHints)
{
if (lower.Contains(hint))
return kind;
}
return null;
}
}
Loading
Loading