18 KiB
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.
// 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:
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.
// 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:
// 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.
// 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:
// 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:
// 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:
// 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.
// 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:
// 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:
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:
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:
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:
// 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:
// 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:
// 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:
// 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:
// 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:
- Constant-Time API Key Comparison (
middleware.go:47-50): Usessubtle.ConstantTimeCompareto prevent timing attacks - Secret Scanning (
handlers.go:90-96): Documents are scanned for secrets before saving - CSP Implementation (
server.go:251-261): Comprehensive Content Security Policy - Panic Recovery (
server.go:213-244): Proper panic recovery with stack trace logging - Request Timeout (
server.go:104-107): Configurable request timeouts - Body Size Limit (
server.go:110): 10MB body limit prevents large payload attacks - Audit Logging (multiple files): Comprehensive audit logging for changes
- Health Check Security (
server.go:296-313): Health endpoints don't expose component details
Remediation Priority
Immediate (Critical & High - Next 7 Days)
- Fix AUTH-BYPASS-001: Use actual request path instead of route pattern
- Fix RATE-001: Validate proxy headers before using RealIP()
- Fix CORS-001: Remove wildcard localhost origins
- Fix AUTH-002: Correct IDE endpoint authorization logic
- Fix INPUT-001: Add strict PATCH validation
- Fix SEC-001: Add HSTS header for TLS connections
Short-term (High & Medium - Next 30 Days)
- Fix INPUT-002: Add Content-Type validation middleware
- Fix ERROR-001: Implement production-safe error handling
- Fix INPUT-003: Add query parameter length limits
- Fix RATE-002: Implement proper token bucket rate limiting
Long-term (Medium & Low - Next 90 Days)
- Fix SEC-002: Protect version endpoint
- Fix SEC-003: Add URL length validation
- Fix SEC-004: Remove deprecated headers
- Fix LOG-001: Reduce sensitive information in logs
- Fix SEC-005: Standardize cache invalidation handling
Testing Recommendations
-
Authentication Bypass Testing:
curl "http://localhost:8080/health/live/../../../api/rules" -H "Authorization: Bearer invalid" -
Rate Limit Bypass Testing:
for i in {1..1100}; do curl -H "X-Forwarded-For: 1.2.3.$i" http://localhost:8080/api/rules done -
CORS Testing:
curl -H "Origin: http://localhost:9999" -H "Authorization: Bearer $KEY" \ -X OPTIONS http://localhost:8080/api/rules -v -
Content-Type Testing:
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 |