# Reverse MCP Security Architecture
**Document Version:** 1.0
**Created:** 2025-10-16
**Status:** Security Design Specification
**Classification:** Internal - Security Sensitive
---
## Security Philosophy
**Defense in Depth**: Multiple independent security layers so that if one fails, others still protect the system.
**Fail Secure**: All security checks must pass. Any failure results in denied access, not degraded security.
**Principle of Least Privilege**: Remote teams can only access the minimum tools and targets required for their function.
**Zero Trust**: Even though traffic flows through SSH tunnels, we don't trust the transport alone. Every request is authenticated and authorized.
---
## Threat Model
### In Scope
**Threats We Defend Against:**
1. **Compromised Remote Host**
- Attacker gains root on remote machine
- Attempts to hijack SSH tunnel
- Tries to impersonate legitimate team
2. **Man-in-the-Middle Attacks**
- Attacker intercepts traffic between remote Claude and Iris
- Attempts to modify or replay requests
3. **Denial of Service**
- Malicious or compromised remote team floods Iris with requests
- Resource exhaustion attacks
4. **Privilege Escalation**
- Remote team attempts to access tools/teams outside their authorization
- Lateral movement between teams
5. **Data Exfiltration**
- Remote team attempts to read sensitive session data
- Cache snooping from other teams
### Out of Scope
**Assumptions:**
1. **Local Machine Security**: We assume the local machine running Iris is trusted and secure
2. **SSH Key Security**: We assume SSH private keys are protected (encrypted, not shared)
3. **Physical Security**: Physical access to either machine is assumed secure
4. **Side Channel Attacks**: Timing attacks, speculative execution vulnerabilities are out of scope for Phase 1
---
## Security Architecture Layers
### Layer 1: Transport Security (HTTPS over SSH)
#### 1.1 HTTPS Requirement
**Requirement**: Iris MUST run HTTPS server, not HTTP, even though traffic is inside SSH tunnel.
**Rationale**: Defense in depth. If SSH tunnel is misconfigured or compromised, HTTPS provides backup encryption.
**Implementation**:
```typescript
// src/mcp_server.ts
const httpsServer = https.createServer({
key: fs.readFileSync('~/.iris/certs/iris-key.pem'),
cert: fs.readFileSync('~/.iris/certs/iris-cert.pem'),
}, app);
```
**Config**:
```json
{
"settings": {
"defaultTransport": "https", // Changed from "http"
"httpsPort": 1615,
"tlsCertPath": "~/.iris/certs/iris-cert.pem",
"tlsKeyPath": "~/.iris/certs/iris-key.pem"
}
}
```
#### 1.2 Certificate Management
**Self-Signed with Fingerprint Pinning**
Generate certificate on first run:
```bash
# Automatically generated by Iris on first start
openssl req -x509 -newkey rsa:4096 -nodes \
-keyout ~/.iris/certs/iris-key.pem \
-out ~/.iris/certs/iris-cert.pem \
-days 365 -subj "/CN=localhost"
# Calculate fingerprint
openssl x509 -in ~/.iris/certs/iris-cert.pem -noout -fingerprint -sha256
```
**Store fingerprint in team config**:
```json
{
"team-inanna": {
"remote": "ssh inanna",
"enableReverseMcp": true,
"reverseMcpCertFingerprint": "sha256:A1:B2:C3:D4:E5:F6:..."
}
}
```
**Remote Claude verifies fingerprint**:
```bash
# In --mcp-config
{
"iris": {
"url": "https://localhost:1615/mcp",
"tlsCertFingerprint": "sha256:A1:B2:C3:D4:E5:F6:..."
}
}
```
**Certificate rotation**: Auto-rotate every 365 days, update fingerprints in config.
#### 1.3 SSH Tunnel Hardening
**Enforce localhost-only binding**:
```bash
ssh -R 1615:127.0.0.1:1615 \ # Explicit 127.0.0.1, not localhost
-o GatewayPorts=no \ # Force no gateway ports
-o ServerAliveInterval=30 \ # Keepalive every 30s
-o ServerAliveCountMax=3 \ # 3 failed = disconnect
-o StrictHostKeyChecking=yes \ # Require known host
user@remote
```
**Verify binding after connection**:
```typescript
// After spawning SSH process, verify tunnel binding
async function verifyTunnelBinding(teamName: string) {
// Execute remote command to check netstat
const result = await execRemoteCommand(
teamName,
'netstat -an | grep :1615'
);
// Verify binding is 127.0.0.1:1615, not 0.0.0.0:1615
if (result.includes('0.0.0.0:1615')) {
throw new SecurityError(
`Tunnel for ${teamName} is exposed! Expected 127.0.0.1, got 0.0.0.0`
);
}
}
```
---
### Layer 2: Authentication
#### 2.1 API Key Authentication
**Generation**:
```typescript
import crypto from 'crypto';
import keytar from 'keytar';
function generateTeamApiKey(teamName: string): string {
// 256-bit random key
const apiKey = crypto.randomBytes(32).toString('hex');
// Store in OS keychain (NOT in config file)
keytar.setPassword('iris-mcp', `team-${teamName}`, apiKey);
logger.info({ teamName }, 'Generated API key for team');
return apiKey;
}
```
**Storage**:
- **Local**: OS Keychain (macOS), Credential Manager (Windows), Secret Service (Linux)
- **Remote**: Environment variable passed to Claude process
- **NOT in config files** (ever!)
**Transmission**:
```bash
# Remote Claude MCP config includes API key
{
"iris": {
"url": "https://localhost:1615/mcp",
"headers": {
"X-Iris-Api-Key": "${IRIS_TEAM_INANNA_API_KEY}"
}
}
}
```
**Validation**:
```typescript
function authenticateReverseMcpRequest(req: McpRequest): boolean {
const providedKey = req.headers['x-iris-api-key'];
const expectedKey = keytar.getPassword('iris-mcp', `team-${req.sourceTeam}`);
if (!providedKey || !expectedKey) {
throw new AuthenticationError('Missing API key');
}
// Constant-time comparison to prevent timing attacks
const providedBuffer = Buffer.from(providedKey);
const expectedBuffer = Buffer.from(expectedKey);
if (!crypto.timingSafeEqual(providedBuffer, expectedBuffer)) {
// Log failed attempt
auditLog.warn({
event: 'auth_failure',
team: req.sourceTeam,
reason: 'invalid_api_key'
});
throw new AuthenticationError('Invalid API key');
}
return true;
}
```
#### 2.2 SSH Session Binding
**Prevent session hijacking**:
```typescript
// When spawning remote team, generate unique session ID
const sshSessionId = crypto.randomUUID();
sessionBindings.set(teamName, sshSessionId);
// Pass to remote Claude via env var
process.env.IRIS_SSH_SESSION_ID = sshSessionId;
// In reverse MCP handler
function validateSshSession(req: McpRequest): boolean {
const providedSessionId = req.headers['x-iris-session-id'];
const expectedSessionId = sessionBindings.get(req.sourceTeam);
if (providedSessionId !== expectedSessionId) {
auditLog.error({
event: 'session_hijack_attempt',
team: req.sourceTeam,
provided: providedSessionId,
expected: expectedSessionId
});
throw new SecurityError('Invalid SSH session');
}
return true;
}
```
---
### Layer 3: Authorization
#### 3.1 Tool-Level Permissions
**Default Deny**: Remote teams have NO access unless explicitly granted.
**Config Schema**:
```json
{
"team-inanna": {
"remote": "ssh inanna",
"enableReverseMcp": true,
"reverseMcpPermissions": {
"allowedTools": [
"team_wake",
"team_status",
"send_message",
"session_fork"
],
"deniedTools": [
"session_delete", // Too dangerous
"session_reboot" // Could lose data
],
"allowedTargets": [
"team-alpha",
"team-beta"
],
"deniedTargets": [
"team-production" // Never allow production access
]
}
}
}
```
**Authorization Check**:
```typescript
function authorizeToolAccess(
sourceTeam: string,
tool: string,
targetTeam?: string
): boolean {
const config = configManager.getIrisConfig(sourceTeam);
const perms = config.reverseMcpPermissions;
if (!perms) {
throw new AuthorizationError(
`Team ${sourceTeam} has no reverse MCP permissions`
);
}
// Check tool allow list
if (perms.allowedTools && !perms.allowedTools.includes(tool)) {
auditLog.warn({
event: 'authorization_failure',
team: sourceTeam,
tool,
reason: 'tool_not_allowed'
});
throw new AuthorizationError(`Tool ${tool} not allowed for ${sourceTeam}`);
}
// Check tool deny list (explicit deny overrides allow)
if (perms.deniedTools && perms.deniedTools.includes(tool)) {
auditLog.warn({
event: 'authorization_failure',
team: sourceTeam,
tool,
reason: 'tool_explicitly_denied'
});
throw new AuthorizationError(`Tool ${tool} explicitly denied for ${sourceTeam}`);
}
// Check target team permissions
if (targetTeam) {
if (perms.allowedTargets && !perms.allowedTargets.includes(targetTeam)) {
auditLog.warn({
event: 'authorization_failure',
team: sourceTeam,
tool,
target: targetTeam,
reason: 'target_not_allowed'
});
throw new AuthorizationError(
`Team ${sourceTeam} cannot access target ${targetTeam}`
);
}
if (perms.deniedTargets && perms.deniedTargets.includes(targetTeam)) {
auditLog.warn({
event: 'authorization_failure',
team: sourceTeam,
tool,
target: targetTeam,
reason: 'target_explicitly_denied'
});
throw new AuthorizationError(
`Team ${sourceTeam} explicitly denied access to ${targetTeam}`
);
}
}
return true;
}
```
#### 3.2 Wildcard Patterns
**Support glob patterns** for flexibility:
```json
{
"reverseMcpPermissions": {
"allowedTools": ["team_*"], // All team_ tools
"allowedTargets": ["team-dev-*"] // All dev teams
}
}
```
---
### Layer 4: Rate Limiting
#### 4.1 Token Bucket Algorithm
**Per-Team Limits**:
```typescript
import { RateLimiterMemory } from 'rate-limiter-flexible';
class ReverseMcpRateLimiter {
private limiters = new Map<string, RateLimiterMemory>();
constructor(
private defaultLimit = {
points: 20, // 20 requests
duration: 60, // Per 60 seconds
blockDuration: 300 // Block for 5 minutes if exceeded
}
) {}
async checkLimit(teamName: string): Promise<void> {
const limiter = this.getOrCreateLimiter(teamName);
try {
await limiter.consume(teamName, 1);
} catch (error) {
// Rate limit exceeded
auditLog.warn({
event: 'rate_limit_exceeded',
team: teamName,
limit: this.defaultLimit
});
throw new RateLimitError(
`Rate limit exceeded for ${teamName}. ` +
`Max ${this.defaultLimit.points} requests per ${this.defaultLimit.duration}s. ` +
`Blocked for ${this.defaultLimit.blockDuration}s.`
);
}
}
private getOrCreateLimiter(teamName: string): RateLimiterMemory {
if (!this.limiters.has(teamName)) {
const config = configManager.getIrisConfig(teamName);
const limit = config.reverseMcpRateLimit || this.defaultLimit;
this.limiters.set(
teamName,
new RateLimiterMemory(limit)
);
}
return this.limiters.get(teamName)!;
}
}
```
**Per-Team Custom Limits**:
```json
{
"team-inanna": {
"reverseMcpRateLimit": {
"points": 50, // Higher limit for trusted team
"duration": 60,
"blockDuration": 60 // Shorter block time
}
}
}
```
#### 4.2 Circuit Breaker Pattern
**Auto-disable after repeated violations**:
```typescript
class CircuitBreaker {
private failures = new Map<string, number>();
private openCircuits = new Set<string>();
recordFailure(teamName: string): void {
const count = (this.failures.get(teamName) || 0) + 1;
this.failures.set(teamName, count);
// Open circuit after 5 failures
if (count >= 5) {
this.openCircuits.add(teamName);
auditLog.error({
event: 'circuit_breaker_open',
team: teamName,
reason: 'repeated_security_violations'
});
// Auto-close after 15 minutes
setTimeout(() => {
this.closeCircuit(teamName);
}, 15 * 60 * 1000);
}
}
isOpen(teamName: string): boolean {
return this.openCircuits.has(teamName);
}
closeCircuit(teamName: string): void {
this.openCircuits.delete(teamName);
this.failures.delete(teamName);
auditLog.info({
event: 'circuit_breaker_closed',
team: teamName
});
}
}
```
---
### Layer 5: Audit Logging
#### 5.1 Structured Audit Log Format
**Every reverse MCP call is logged**:
```typescript
interface AuditLogEntry {
timestamp: string; // ISO 8601
event: string; // Event type
sourceTeam: string; // Remote caller
targetTeam?: string; // Target (if applicable)
tool: string; // MCP tool invoked
result: 'success' | 'failure';
duration_ms: number;
authenticated: boolean;
authorized: boolean;
error?: string; // Error message if failed
requestId: string; // Unique request ID
sshSessionId: string; // SSH session binding
}
```
**Example log entries**:
```json
{
"timestamp": "2025-10-16T15:30:45.123Z",
"event": "reverse_mcp_call",
"sourceTeam": "team-inanna",
"targetTeam": "team-alpha",
"tool": "team_wake",
"result": "success",
"duration_ms": 145,
"authenticated": true,
"authorized": true,
"requestId": "req-abc123",
"sshSessionId": "session-xyz789"
}
```
```json
{
"timestamp": "2025-10-16T15:31:20.456Z",
"event": "reverse_mcp_call",
"sourceTeam": "team-inanna",
"targetTeam": "team-production",
"tool": "session_delete",
"result": "failure",
"duration_ms": 2,
"authenticated": true,
"authorized": false,
"error": "Tool session_delete not allowed for team-inanna",
"requestId": "req-def456",
"sshSessionId": "session-xyz789"
}
```
#### 5.2 Audit Log Storage
**SQLite for queryability**:
```sql
CREATE TABLE reverse_mcp_audit (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp INTEGER NOT NULL,
event TEXT NOT NULL,
source_team TEXT NOT NULL,
target_team TEXT,
tool TEXT NOT NULL,
result TEXT NOT NULL,
duration_ms INTEGER,
authenticated BOOLEAN,
authorized BOOLEAN,
error TEXT,
request_id TEXT UNIQUE NOT NULL,
ssh_session_id TEXT NOT NULL,
INDEX idx_timestamp (timestamp),
INDEX idx_source_team (source_team),
INDEX idx_result (result),
INDEX idx_event (event)
);
```
**Retention**: Keep 90 days, auto-purge older entries.
**Export**: Support export to SIEM systems (JSON, CSV, Splunk HEC).
---
### Layer 6: Monitoring & Alerting
#### 6.1 Security Metrics
**Prometheus metrics**:
```typescript
import { Counter, Histogram, Gauge } from 'prom-client';
// Request counters
const reverseMcpRequests = new Counter({
name: 'iris_reverse_mcp_requests_total',
help: 'Total reverse MCP requests',
labelNames: ['team', 'tool', 'result']
});
// Authentication failures
const authFailures = new Counter({
name: 'iris_reverse_mcp_auth_failures_total',
help: 'Authentication failures',
labelNames: ['team', 'reason']
});
// Authorization failures
const authzFailures = new Counter({
name: 'iris_reverse_mcp_authz_failures_total',
help: 'Authorization failures',
labelNames: ['team', 'tool']
});
// Rate limit violations
const rateLimitViolations = new Counter({
name: 'iris_reverse_mcp_rate_limit_violations_total',
help: 'Rate limit violations',
labelNames: ['team']
});
// Latency histogram
const requestLatency = new Histogram({
name: 'iris_reverse_mcp_latency_seconds',
help: 'Request latency',
labelNames: ['team', 'tool'],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5]
});
// Active tunnels
const activeTunnels = new Gauge({
name: 'iris_reverse_mcp_active_tunnels',
help: 'Number of active reverse MCP tunnels',
labelNames: ['team']
});
```
#### 6.2 Alert Rules
**Prometheus alert rules** (`alerts/reverse-mcp.yml`):
```yaml
groups:
- name: reverse_mcp_security
interval: 30s
rules:
# High authentication failure rate
- alert: ReverseMcpAuthFailureSpike
expr: rate(iris_reverse_mcp_auth_failures_total[5m]) > 0.1
for: 2m
labels:
severity: warning
annotations:
summary: "High auth failure rate for {{ $labels.team }}"
description: "{{ $labels.team }} has {{ $value }} auth failures/sec"
# Repeated authorization failures
- alert: ReverseMcpAuthzViolations
expr: increase(iris_reverse_mcp_authz_failures_total[15m]) > 5
labels:
severity: warning
annotations:
summary: "Repeated authz failures for {{ $labels.team }}"
description: "{{ $labels.team }} attempted {{ $value }} unauthorized actions"
# Rate limit violations
- alert: ReverseMcpRateLimitAbuse
expr: increase(iris_reverse_mcp_rate_limit_violations_total[10m]) > 3
labels:
severity: critical
annotations:
summary: "Rate limit abuse by {{ $labels.team }}"
description: "{{ $labels.team }} violated rate limits {{ $value }} times"
# Tunnel disconnections
- alert: ReverseMcpTunnelDown
expr: iris_reverse_mcp_active_tunnels == 0
for: 5m
labels:
severity: warning
annotations:
summary: "Reverse MCP tunnel down for {{ $labels.team }}"
description: "{{ $labels.team }} tunnel has been down for 5+ minutes"
```
#### 6.3 Alert Channels
**Notification integrations**:
```json
{
"monitoring": {
"alerts": {
"channels": [
{
"type": "slack",
"webhook": "https://hooks.slack.com/services/...",
"severity": ["critical", "warning"]
},
{
"type": "email",
"recipients": ["security@company.com"],
"severity": ["critical"]
},
{
"type": "pagerduty",
"integrationKey": "...",
"severity": ["critical"]
}
]
}
}
}
```
---
### Layer 7: Input Validation
#### 7.1 Request Size Limits
```typescript
const SECURITY_LIMITS = {
MAX_MESSAGE_SIZE: 100 * 1024, // 100KB
MAX_NESTING_DEPTH: 10,
MAX_ARRAY_LENGTH: 1000,
MAX_STRING_LENGTH: 10000,
MAX_REQUEST_RATE: 20, // Per minute
};
function validateRequestSize(req: McpRequest): void {
const size = JSON.stringify(req).length;
if (size > SECURITY_LIMITS.MAX_MESSAGE_SIZE) {
auditLog.warn({
event: 'request_too_large',
team: req.sourceTeam,
size,
limit: SECURITY_LIMITS.MAX_MESSAGE_SIZE
});
throw new ValidationError(
`Request size ${size} exceeds limit ${SECURITY_LIMITS.MAX_MESSAGE_SIZE}`
);
}
}
function validateNestingDepth(obj: any, maxDepth = 10, currentDepth = 0): void {
if (currentDepth > maxDepth) {
throw new ValidationError(`Object nesting depth exceeds ${maxDepth}`);
}
if (typeof obj === 'object' && obj !== null) {
for (const key in obj) {
validateNestingDepth(obj[key], maxDepth, currentDepth + 1);
}
}
}
```
#### 7.2 String Sanitization
```typescript
function sanitizeString(str: string): string {
// Remove null bytes
str = str.replace(/\0/g, '');
// Remove control characters (except newline, tab)
str = str.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, '');
// Limit length
if (str.length > SECURITY_LIMITS.MAX_STRING_LENGTH) {
str = str.substring(0, SECURITY_LIMITS.MAX_STRING_LENGTH);
}
return str;
}
```
---
### Layer 8: Fail-Secure Pattern
**All security checks must pass**:
```typescript
async function handleReverseMcpRequest(req: McpRequest): Promise<McpResponse> {
const requestId = crypto.randomUUID();
const startTime = Date.now();
try {
// Security checkpoint 1: Input validation
validateRequestSize(req);
validateNestingDepth(req);
// Security checkpoint 2: Authentication
await authenticateApiKey(req);
await validateSshSession(req);
// Security checkpoint 3: Authorization
await authorizeToolAccess(req.sourceTeam, req.tool, req.targetTeam);
// Security checkpoint 4: Rate limiting
await rateLimiter.checkLimit(req.sourceTeam);
// Security checkpoint 5: Circuit breaker
if (circuitBreaker.isOpen(req.sourceTeam)) {
throw new SecurityError('Circuit breaker open for team');
}
// Security checkpoint 6: Audit logging (fail if can't log)
await auditLog.log({
timestamp: new Date().toISOString(),
event: 'reverse_mcp_call',
requestId,
sourceTeam: req.sourceTeam,
tool: req.tool,
targetTeam: req.targetTeam
});
// All checks passed - process request
const response = await processToolRequest(req);
// Log success
await auditLog.log({
requestId,
result: 'success',
duration_ms: Date.now() - startTime
});
// Update metrics
reverseMcpRequests.inc({
team: req.sourceTeam,
tool: req.tool,
result: 'success'
});
return response;
} catch (error) {
// Log failure
await auditLog.log({
requestId,
result: 'failure',
error: error.message,
duration_ms: Date.now() - startTime
});
// Update metrics
reverseMcpRequests.inc({
team: req.sourceTeam,
tool: req.tool,
result: 'failure'
});
// Record failure for circuit breaker
if (error instanceof SecurityError) {
circuitBreaker.recordFailure(req.sourceTeam);
}
// Fail secure - deny request
throw error;
}
}
```
---
## Security Checklist
### Setup Checklist
Before enabling reverse MCP for a team:
- [ ] Generate TLS certificate and calculate fingerprint
- [ ] Generate strong API key (256-bit) and store in keychain
- [ ] Configure `reverseMcpPermissions` (allowedTools, allowedTargets)
- [ ] Set appropriate rate limits
- [ ] Enable audit logging
- [ ] Configure monitoring alerts
- [ ] Verify GatewayPorts=no on remote host
- [ ] Test certificate pinning
- [ ] Test authentication with invalid key (should fail)
- [ ] Test authorization with denied tool (should fail)
- [ ] Test rate limiting (should block after threshold)
- [ ] Verify audit logs are being written
- [ ] Review security documentation with team
### Operational Checklist
Regular security maintenance:
- [ ] Review audit logs weekly for anomalies
- [ ] Rotate API keys every 90 days
- [ ] Rotate TLS certificates annually
- [ ] Review and update authorization rules monthly
- [ ] Monitor security metrics in Grafana
- [ ] Test incident response playbook quarterly
- [ ] Update security documentation as needed
---
## Incident Response Playbook
### Suspected Compromise
**If you suspect a team's credentials are compromised:**
1. **Immediate Actions** (within 5 minutes):
```bash
# Revoke API key
iris revoke-key team-inanna
# Disable reverse MCP
iris disable-reverse-mcp team-inanna
# Kill active SSH tunnel
iris kill-tunnel team-inanna
```
2. **Investigation** (within 1 hour):
```bash
# Review audit logs
iris audit-query --team team-inanna --since "24 hours ago"
# Check for unauthorized access attempts
iris audit-query --result failure --team team-inanna
# Review metrics for anomalies
iris metrics --team team-inanna
```
3. **Remediation** (within 4 hours):
```bash
# Rotate all credentials
iris rotate-key team-inanna --force
iris regenerate-cert
# Update fingerprint in config
iris update-cert-fingerprint team-inanna
# Re-enable with stricter permissions
iris enable-reverse-mcp team-inanna --strict
```
4. **Post-Incident Review** (within 1 week):
- Document timeline of events
- Identify security gaps
- Update playbook with lessons learned
- Brief team on incident
---
## Security Testing
### Penetration Testing Scenarios
**Test cases to validate security**:
1. **Authentication Bypass Attempts**
- Invalid API key โ Should fail with 401
- Missing API key โ Should fail with 401
- Reused API key from different team โ Should fail with 403
2. **Authorization Escalation**
- Call denied tool โ Should fail with 403
- Access denied target team โ Should fail with 403
- Wildcard bypass attempt โ Should fail with 403
3. **Rate Limiting**
- Send 30 requests in 60 seconds โ Last 10 should be blocked with 429
- Wait for refill โ Should succeed again
4. **Input Validation**
- Send 200KB payload โ Should fail with 413
- Send deeply nested JSON (20 levels) โ Should fail with 400
- Send null bytes in string โ Should be sanitized
5. **Certificate Validation**
- Connect with wrong cert fingerprint โ Should fail
- Connect without HTTPS โ Should fail
- MITM attack simulation โ Should detect and fail
### Automated Security Scanning
```bash
# Run security test suite
pnpm test:security
# Run with specific scenario
pnpm test:security --scenario auth-bypass
# Generate security report
pnpm security-report
```
---
## Future Enhancements
### Phase 2: Advanced Security
1. **Mutual TLS (mTLS)**
- Client certificates for remote teams
- Stronger authentication than API keys
2. **JWT Tokens with Expiry**
- Short-lived tokens (1 hour)
- Auto-refresh mechanism
- Revocation support
3. **IP Allowlisting**
- Restrict reverse MCP to specific SSH source IPs
- Geographic restrictions
4. **Behavioral Analytics**
- ML-based anomaly detection
- Automatic threat response
5. **Hardware Security Module (HSM)**
- Store keys in HSM instead of OS keychain
- FIPS 140-2 compliance
---
## References
- [OWASP API Security Top 10](https://owasp.org/www-project-api-security/)
- [SSH Tunnel Security Best Practices](https://www.ssh.com/academy/ssh/tunneling)
- [Defense in Depth Strategy](https://www.nist.gov/cybersecurity)
- [Fail-Secure Design Principles](https://en.wikipedia.org/wiki/Fail-safe)
---
**Document Status**: Draft - Pending Security Review
**Next Review Date**: 2025-11-16 (30 days)
**Approvers**:
- Security Team: _____________
- Engineering Lead: _____________
- Product Owner: _____________