claw-code/.guardrails/docs/security/SECURITY_AUDIT_API.md

521 lines
18 KiB
Markdown

# API Security Audit Report
**Repository**: guardrail-mcp
**Component**: mcp-server/internal/web/
**Audit Date**: 2026-02-08
**Auditor**: Security Engineer
## Executive Summary
This audit covers the web layer of the guardrail-mcp server, focusing on authentication, authorization, rate limiting, CORS, input validation, and security headers. **3 Critical, 5 High, 4 Medium, and 3 Low severity issues** were identified.
### Risk Overview
| Severity | Count | Immediate Action Required |
|----------|-------|--------------------------|
| Critical | 3 | Yes |
| High | 5 | Yes |
| Medium | 4 | Recommended |
| Low | 3 | Scheduled |
---
## Critical Issues
### 1. AUTH-BYPASS-001: Path Traversal in Authentication Bypass
**File**: `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/middleware.go:21-28`
**Issue**: The authentication middleware uses `c.Path()` to check if routes should be skipped, but `c.Path()` returns the *route pattern* (e.g., `/health/live`), not the actual request path. A request to `/health/live/../../../api/sensitive` would match the `/health/live` pattern, bypassing authentication.
```go
// VULNERABLE CODE
path := c.Path() // Returns route pattern, not actual URL
if path == "/health/live" || path == "/health/ready" || path == "/metrics" {
return next(c) // Bypasses auth incorrectly
}
```
**Severity**: Critical
**CVSS**: 9.1 (Critical)
**Impact**: Complete authentication bypass, unauthorized API access
**Remediation**:
```go
path := c.Request().URL.Path // Use actual request path
// Normalize path to prevent traversal
path = filepath.Clean(path)
// Use exact matching with proper path handling
if path == "/health/live" || path == "/health/ready" || path == "/metrics" {
return next(c)
}
```
---
### 2. RATE-001: Rate Limiting Bypass via IP Spoofing
**File**: `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/middleware.go:105-108`
**Issue**: When API key hash is not available, the middleware falls back to `c.RealIP()` for rate limiting. Echo's `RealIP()` extracts IP from `X-Forwarded-For`/`X-Real-IP` headers without validation, allowing attackers to spoof different IPs and bypass rate limits.
```go
// VULNERABLE CODE
keyHash, ok := c.Get("api_key_hash").(string)
if !ok {
keyHash = c.RealIP() // Easily spoofed via headers
}
```
**Severity**: Critical
**CVSS**: 8.2 (High)
**Impact**: Rate limit bypass, potential DoS
**Remediation**:
```go
// Extract IP from trusted source only
func getClientIP(c echo.Context, trustProxy bool) string {
if !trustProxy {
// Use direct connection IP
ip := c.Request().RemoteAddr
host, _, err := net.SplitHostPort(ip)
if err == nil {
return host
}
return ip
}
// Only trust proxy headers if behind verified load balancer
return c.RealIP()
}
```
---
### 3. CORS-001: Overly Permissive CORS in Non-Production
**File**: `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/server.go:87-102`
**Issue**: In non-production mode, CORS allows `http://localhost:*` and `https://localhost:*`, which permits any localhost port. This allows malicious websites running on localhost to make authenticated cross-origin requests.
```go
// VULNERABLE CODE - allows any localhost port
corsOrigins = []string{"http://localhost:*", "https://localhost:*"}
```
**Severity**: Critical
**CVSS**: 7.5 (High)
**Impact**: CSRF-style attacks from malicious localhost services
**Remediation**:
```go
// Use specific allowed origins only
corsOrigins = s.cfg.CORSAllowedOrigins
// Validate origins against allowlist
allowedOrigins := map[string]bool{
"http://localhost:3000": true,
"http://localhost:8081": true,
// Add specific allowed origins
}
```
---
## High Severity Issues
### 4. INPUT-001: Insufficient Input Validation on PATCH Endpoints
**File**: `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/handlers.go:283-343`
**Issue**: The `patchRule` handler accepts arbitrary fields in the request body without strict validation. While the struct defines expected fields, Echo's `Bind()` method will ignore unknown fields, potentially allowing injection of unexpected data.
**Severity**: High
**Impact**: Mass assignment vulnerability, data corruption
**Remediation**:
```go
// Use strict validation
var req struct {
Enabled *bool `json:"enabled,omitempty" validate:"omitempty"`
Name *string `json:"name,omitempty" validate:"omitempty,max=255"`
Message *string `json:"message,omitempty" validate:"omitempty,max=1000"`
Pattern *string `json:"pattern,omitempty" validate:"omitempty,max=500"`
Severity *string `json:"severity,omitempty" validate:"omitempty,oneof=low medium high critical"`
}
// Add strict decoding to reject unknown fields
decoder := json.NewDecoder(c.Request().Body)
decoder.DisallowUnknownFields()
if err := decoder.Decode(&req); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid fields in request"})
}
```
---
### 5. INPUT-002: Missing Content-Type Validation
**Files**: All handlers in `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/handlers.go`
**Issue**: No Content-Type validation on requests. Endpoints accepting JSON don't verify `Content-Type: application/json`, allowing CSRF attacks through HTML forms or content type confusion attacks.
**Severity**: High
**Impact**: CSRF attacks, content type confusion
**Remediation**:
```go
// Add middleware for content type validation
func RequireContentType(contentTypes ...string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if c.Request().Method == http.MethodGet || c.Request().Method == http.MethodDelete {
return next(c)
}
contentType := c.Request().Header.Get("Content-Type")
for _, allowed := range contentTypes {
if strings.HasPrefix(contentType, allowed) {
return next(c)
}
}
return echo.NewHTTPError(http.StatusUnsupportedMediaType, "invalid content type")
}
}
}
```
---
### 6. AUTH-002: Weak IDE Endpoint Authorization Logic
**File**: `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/middleware.go:59-62`
**Issue**: The endpoint restriction logic has a logical error. It checks if path starts with `/ide` and requires `ide` key type, but `mcp` key type is also allowed, making the restriction ineffective.
```go
// INEFFECTIVE CODE
if strings.HasPrefix(path, "/ide") && keyType != "ide" && keyType != "mcp" {
return echo.NewHTTPError(http.StatusForbidden, "IDE API key required")
}
```
**Severity**: High
**Impact**: Authorization bypass, privilege escalation
**Remediation**:
```go
// Proper endpoint authorization
switch {
case strings.HasPrefix(path, "/ide"):
if keyType != "ide" {
return echo.NewHTTPError(http.StatusForbidden, "IDE API key required")
}
case strings.HasPrefix(path, "/api/admin"):
if keyType != "admin" { // Add admin key type
return echo.NewHTTPError(http.StatusForbidden, "admin key required")
}
}
```
---
### 7. SEC-001: Missing HSTS Header
**File**: `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/server.go:246-270`
**Issue**: The security headers middleware doesn't include `Strict-Transport-Security` (HSTS), allowing downgrade attacks when TLS is enabled.
**Severity**: High
**Impact**: SSL/TLS downgrade attacks, MITM
**Remediation**:
```go
func securityHeadersMiddleware() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Add HSTS when TLS is enabled
if c.Request().TLS != nil {
c.Response().Header().Set(
"Strict-Transport-Security",
"max-age=31536000; includeSubDomains; preload",
)
}
// ... existing headers ...
return next(c)
}
}
}
```
---
### 8. ERROR-001: Verbose Error Messages in Production
**Files**: Multiple handlers in `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/handlers.go`
**Issue**: Error messages expose internal details (e.g., database errors, file paths) in production. Example: `return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})`
**Severity**: High
**Impact**: Information disclosure, system fingerprinting
**Remediation**:
```go
func handleError(c echo.Context, err error, publicMsg string, isProduction bool) error {
slog.Error("Request failed", "error", err, "path", c.Path())
if isProduction {
return c.JSON(http.StatusInternalServerError, map[string]string{
"error": publicMsg,
"request_id": c.Response().Header().Get("X-Request-ID"),
})
}
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
```
---
## Medium Severity Issues
### 9. INPUT-003: Insufficient Query Parameter Validation
**File**: `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/handlers.go:116-140`
**Issue**: Search query parameter `q` is accepted without length limits or sanitization, potentially enabling ReDoS (Regular Expression Denial of Service) if regex is used in search.
**Severity**: Medium
**Impact**: ReDoS, resource exhaustion
**Remediation**:
```go
const maxQueryLength = 200
func (s *Server) searchDocuments(c echo.Context) error {
query := c.QueryParam("q")
if query == "" {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "query required"})
}
if len(query) > maxQueryLength {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "query too long",
"max_length": strconv.Itoa(maxQueryLength),
})
}
// Sanitize query to prevent regex injection
query = sanitizeSearchQuery(query)
// ... rest of handler
}
```
---
### 10. RATE-002: No Burst Protection for Rate Limiting
**File**: `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/cache/redis.go:282-298`
**Issue**: The rate limiter uses a simple counter without burst bucket or proper sliding window implementation. It resets at window boundaries, allowing burst attacks at minute boundaries.
**Severity**: Medium
**Impact**: Burst attacks at window boundaries
**Remediation**:
```go
// Implement token bucket or proper sliding window
func (dl *DistributedRateLimiter) Allow(ctx context.Context, key string, limit int, burst int) bool {
now := time.Now().UnixMilli()
bucketKey := fmt.Sprintf("ratelimit:bucket:%s", key)
lua := `
local key = KEYS[1]
local now = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local burst = tonumber(ARGV[3])
local window = tonumber(ARGV[4])
local bucket = redis.call('hmget', key, 'tokens', 'last_update')
local tokens = tonumber(bucket[1]) or burst
local last_update = tonumber(bucket[2]) or now
local delta = math.max(0, now - last_update)
tokens = math.min(burst, tokens + (delta * limit / window))
if tokens < 1 then
return 0
end
tokens = tokens - 1
redis.call('hset', key, 'tokens', tokens, 'last_update', now)
redis.call('pexpire', key, window)
return 1
`
result, err := dl.redis.Eval(ctx, lua, []string{bucketKey}, now, limit, burst, 60000).Int()
if err != nil {
slog.Error("Rate limiter error", "error", err)
return false // Fail closed
}
return result == 1
}
```
---
### 11. SEC-002: Version Endpoint Information Disclosure
**File**: `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/server.go:273-280`
**Issue**: The `/version` endpoint exposes version information without authentication, enabling attackers to identify vulnerable versions.
**Severity**: Medium
**Impact**: System fingerprinting, targeted attacks
**Remediation**:
```go
// Require authentication for version endpoint
// Or return minimal information
func (s *Server) versionInfo(c echo.Context) error {
// Only return version to authenticated requests
if c.Get("api_key_type") == nil {
return echo.NewHTTPError(http.StatusUnauthorized, "authentication required")
}
// ... existing code ...
}
```
---
### 12. SEC-003: Missing Request Size Validation for Query Parameters
**File**: `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/server.go:109-110`
**Issue**: While body size is limited to 10MB, there's no validation on URL length or query string size, potentially enabling HTTP request smuggling or buffer overflow attacks.
**Severity**: Medium
**Impact**: HTTP request smuggling, DoS
**Remediation**:
```go
// Add URL length validation middleware
func URLLengthLimit(maxLength int) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if len(c.Request().URL.String()) > maxLength {
return echo.NewHTTPError(http.StatusRequestURITooLong, "URL too long")
}
return next(c)
}
}
}
// Use in setupMiddleware
s.echo.Use(URLLengthLimit(4096))
```
---
## Low Severity Issues
### 13. SEC-004: X-XSS-Protection Deprecated Header
**File**: `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/server.go:264`
**Issue**: The `X-XSS-Protection` header is deprecated and can introduce vulnerabilities in older browsers. CSP is the modern replacement.
**Severity**: Low
**Remediation**:
```go
// Remove X-XSS-Protection header
// CSP already provides better protection
```
---
### 14. LOG-001: Sensitive Information in Logs (API Key Hash)
**File**: `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/middleware.go:66-72`
**Issue**: API key hashes are logged, which could enable offline cracking attempts if logs are compromised.
**Severity**: Low
**Impact**: Information disclosure (requires log access)
**Remediation**:
```go
// Don't log key hashes, only use for rate limiting internally
// Or use HMAC with server secret for log identifiers
```
---
### 15. SEC-005: Race Condition in Cache Invalidation
**File**: `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/handlers.go:334-336`
**Issue**: The `patchRule` handler doesn't use the same error handling pattern as other handlers for cache invalidation, potentially masking failures.
**Severity**: Low
**Impact**: Inconsistent error handling, stale cache
**Remediation**: Standardize cache invalidation error handling across all handlers.
---
## Security Strengths
The following security controls are well-implemented:
1. **Constant-Time API Key Comparison** (`middleware.go:47-50`): Uses `subtle.ConstantTimeCompare` to prevent timing attacks
2. **Secret Scanning** (`handlers.go:90-96`): Documents are scanned for secrets before saving
3. **CSP Implementation** (`server.go:251-261`): Comprehensive Content Security Policy
4. **Panic Recovery** (`server.go:213-244`): Proper panic recovery with stack trace logging
5. **Request Timeout** (`server.go:104-107`): Configurable request timeouts
6. **Body Size Limit** (`server.go:110`): 10MB body limit prevents large payload attacks
7. **Audit Logging** (multiple files): Comprehensive audit logging for changes
8. **Health Check Security** (`server.go:296-313`): Health endpoints don't expose component details
---
## Remediation Priority
### Immediate (Critical & High - Next 7 Days)
1. [ ] Fix AUTH-BYPASS-001: Use actual request path instead of route pattern
2. [ ] Fix RATE-001: Validate proxy headers before using RealIP()
3. [ ] Fix CORS-001: Remove wildcard localhost origins
4. [ ] Fix AUTH-002: Correct IDE endpoint authorization logic
5. [ ] Fix INPUT-001: Add strict PATCH validation
6. [ ] Fix SEC-001: Add HSTS header for TLS connections
### Short-term (High & Medium - Next 30 Days)
7. [ ] Fix INPUT-002: Add Content-Type validation middleware
8. [ ] Fix ERROR-001: Implement production-safe error handling
9. [ ] Fix INPUT-003: Add query parameter length limits
10. [ ] Fix RATE-002: Implement proper token bucket rate limiting
### Long-term (Medium & Low - Next 90 Days)
11. [ ] Fix SEC-002: Protect version endpoint
12. [ ] Fix SEC-003: Add URL length validation
13. [ ] Fix SEC-004: Remove deprecated headers
14. [ ] Fix LOG-001: Reduce sensitive information in logs
15. [ ] Fix SEC-005: Standardize cache invalidation handling
---
## Testing Recommendations
1. **Authentication Bypass Testing**:
```bash
curl "http://localhost:8080/health/live/../../../api/rules" -H "Authorization: Bearer invalid"
```
2. **Rate Limit Bypass Testing**:
```bash
for i in {1..1100}; do
curl -H "X-Forwarded-For: 1.2.3.$i" http://localhost:8080/api/rules
done
```
3. **CORS Testing**:
```bash
curl -H "Origin: http://localhost:9999" -H "Authorization: Bearer $KEY" \
-X OPTIONS http://localhost:8080/api/rules -v
```
4. **Content-Type Testing**:
```bash
curl -X POST http://localhost:8080/api/rules \
-H "Content-Type: text/plain" \
-d "malicious data"
```
---
## Compliance Mapping
| Issue | OWASP Top 10 2021 | NIST 800-53 | CIS Controls |
|-------|------------------|-------------|--------------|
| AUTH-BYPASS-001 | A01:2021-Broken Access Control | AC-3 | 6.1 |
| RATE-001 | A07:2021-Identification | AC-17 | 13.1 |
| CORS-001 | A07:2021-Identification | AC-4 | 13.1 |
| INPUT-001 | A03:2021-Injection | SI-10 | 3.5 |
| SEC-001 | A02:2021-Cryptographic Failures | SC-8 | 14.4 |
---
## Appendix: File Locations
| File | Lines | Purpose |
|------|-------|---------|
| `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/middleware.go` | 1-133 | Authentication & rate limiting |
| `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/handlers.go` | 1-712 | API endpoint handlers |
| `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/web/server.go` | 1-321 | Server setup & middleware |
| `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/config/config.go` | 1-401 | Configuration & validation |
| `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/cache/redis.go` | 1-356 | Rate limiting implementation |