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

598 lines
15 KiB
Markdown

# Security Audit: Configuration Files and Environment Handling
**Audit Date:** 2026-02-08
**Auditor:** Security Engineer
**Scope:** mcp-server/ configuration files, environment handling, secrets management
**Risk Rating:** HIGH - Multiple insecure defaults and secret exposure risks identified
---
## Executive Summary
This audit identified **11 security issues** across configuration files, with severity ranging from Critical to Low. The primary concerns are insecure default values that could lead to production deployments without proper security hardening, and potential secret exposure in container environments.
### Risk Distribution
- **CRITICAL:** 2 issues
- **HIGH:** 3 issues
- **MEDIUM:** 4 issues
- **LOW:** 2 issues
---
## Critical Issues
### 1. Database SSL Disabled by Default in Compose
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/deploy/podman-compose.yml:144`
**Risk:** CRITICAL
```yaml
- DB_SSLMODE=${DB_SSLMODE:-disable}
```
**Security Risk:**
- Database connections default to unencrypted communication
- Credentials and data transmitted in plaintext
- Susceptible to man-in-the-middle attacks
- Compliance violations (PCI-DSS, HIPAA, SOC2)
**Recommendation:**
```yaml
# Change default to require minimum
- DB_SSLMODE=${DB_SSLMODE:-require}
# For production, use verify-full
- DB_SSLMODE=${DB_SSLMODE:-verify-full}
```
**Remediation Priority:** IMMEDIATE
---
### 2. Redis Authentication Disabled by Default in Compose
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/deploy/podman-compose.yml:149`
**Risk:** CRITICAL
```yaml
- REDIS_USE_TLS=${REDIS_USE_TLS:-false}
```
**Security Risk:**
- Redis connections default to unencrypted communication
- Session data and cached secrets transmitted in plaintext
- Redis password exposed in environment variables
**Recommendation:**
```yaml
# Enable TLS by default
- REDIS_USE_TLS=${REDIS_USE_TLS:-true}
# Add certificate verification
- REDIS_TLS_VERIFY=${REDIS_TLS_VERIFY:-true}
```
**Remediation Priority:** IMMEDIATE
---
## High Issues
### 3. Permissive CORS Default
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/.env.example:39`
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/deploy/podman-compose.yml:174`
**Risk:** HIGH
```bash
# .env.example
CORS_ALLOWED_ORIGINS=*
# podman-compose.yml
CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-*}
```
**Security Risk:**
- Allows cross-origin requests from any domain
- Enables CSRF attacks against the API
- Session hijacking via malicious websites
- Violates principle of least privilege
**Recommendation:**
```bash
# .env.example - No default, must be explicitly configured
CORS_ALLOWED_ORIGINS= # REQUIRED: Set to your domain(s)
# In config.go validation, reject wildcard in production
func (c *Config) Validate() error {
if c.ProductionMode && len(c.CORSAllowedOrigins) == 1 && c.CORSAllowedOrigins[0] == "*" {
return fmt.Errorf("CORS_ALLOWED_ORIGINS cannot be '*' in production mode")
}
// ...
}
```
**Remediation Priority:** HIGH
---
### 4. Redis Password Exposure in Health Check
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/deploy/podman-compose.yml:39`
**Risk:** HIGH
```yaml
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
```
**Security Risk:**
- Password visible in `docker inspect` output
- Password appears in container logs if health check fails
- Process listing may expose command with password
- Container runtime history stores password in plain text
**Recommendation:**
```yaml
# Use Redis ACL file or environment file instead
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
# Configure requirepass in redis.conf, not command line
```
Or use a health check script:
```yaml
healthcheck:
test: ["CMD", "sh", "-c", "redis-cli -a \"$REDIS_PASSWORD\" ping | grep PONG"]
```
**Remediation Priority:** HIGH
---
### 5. Placeholder Secrets Could Be Used in Production
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/.env.example:102-109`
**Risk:** HIGH
```bash
MCP_API_KEY=generate_a_32_byte_random_key_here
IDE_API_KEY=generate_a_different_32_byte_random_key_here
JWT_SECRET=generate_a_64_byte_random_secret_here_for_jwt_signing
```
**Security Risk:**
- Placeholder values may be accidentally used in production
- No runtime validation to detect placeholder secrets
- Weak entropy in placeholder strings
- Predictable secrets enable authentication bypass
**Recommendation:**
Add validation in `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/config/config.go`:
```go
func ValidateAPIKey(key, name string) error {
// Existing validation...
// Check for placeholder patterns
placeholders := []string{
"generate_a_",
"change_me",
"placeholder",
"example",
"test",
"demo",
"secret_here",
}
lowerKey := strings.ToLower(key)
for _, placeholder := range placeholders {
if strings.Contains(lowerKey, placeholder) {
return fmt.Errorf("%s appears to be a placeholder value", name)
}
}
return nil
}
```
**Remediation Priority:** HIGH
---
## Medium Issues
### 6. Production Mode Defaults to False
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/.env.example:182`
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/deploy/podman-compose.yml:130`
**Risk:** MEDIUM
```bash
PRODUCTION_MODE=false
```
**Security Risk:**
- Stricter security checks disabled by default
- Debug features may be enabled unintentionally
- Security headers not enforced
- Rate limiting may be relaxed
**Recommendation:**
Make `PRODUCTION_MODE` required with no default:
```bash
# .env.example
PRODUCTION_MODE= # REQUIRED: Set to 'true' for production, 'false' for development
```
Add validation:
```go
func (c *Config) Validate() error {
if !c.ProductionMode {
slog.Warn("Running in development mode - security features relaxed")
}
// ...
}
```
**Remediation Priority:** MEDIUM
---
### 7. TLS Minimum Version Allows 1.2
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/config/config.go:75`
**Risk:** MEDIUM
```go
TLSMinVersion string `env:"TLS_MIN_VERSION" envDefault:"1.3"`
```
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/config/config.go:214-216`
```go
if c.TLSMinVersion != "1.2" && c.TLSMinVersion != "1.3" {
return fmt.Errorf("TLS_MIN_VERSION must be 1.2 or 1.3, got %s", c.TLSMinVersion)
}
```
**Security Risk:**
- TLS 1.2 has known vulnerabilities (POODLE, BEAST)
- Allows downgrade attacks
- TLS 1.3 should be minimum for production
**Recommendation:**
```go
func (c *Config) Validate() error {
if c.TLSEnabled {
// ...
if c.ProductionMode && c.TLSMinVersion != "1.3" {
return fmt.Errorf("TLS_MIN_VERSION must be 1.3 in production mode")
}
if c.TLSMinVersion != "1.2" && c.TLSMinVersion != "1.3" {
return fmt.Errorf("TLS_MIN_VERSION must be 1.2 or 1.3, got %s", c.TLSMinVersion)
}
}
// ...
}
```
**Remediation Priority:** MEDIUM
---
### 8. Database Password in .env.example Uses Weak Placeholder
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/.env.example:62`
**Risk:** MEDIUM
```bash
DB_PASSWORD=change_me_in_production
```
**Security Risk:**
- Weak placeholder may be used accidentally
- No validation prevents this value in production
- Common pattern that attackers test for
**Recommendation:**
```bash
# Leave empty to force configuration
DB_PASSWORD= # REQUIRED: Generate strong password with: openssl rand -base64 32
```
Add validation in config.go:
```go
func (c *Config) Validate() error {
weakPasswords := []string{
"change_me_in_production",
"password",
"admin",
"123456",
"guardrail",
}
for _, weak := range weakPasswords {
if c.DBPassword == weak {
return fmt.Errorf("DB_PASSWORD is using a weak/placeholder value")
}
}
// ...
}
```
**Remediation Priority:** MEDIUM
---
### 9. JWT Rotation Period Too Long
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/.env.example:112`
**Risk:** MEDIUM
```bash
JWT_ROTATION_HOURS=168 # 7 days
```
**Security Risk:**
- Long rotation period increases exposure window
- Compromised tokens valid for extended period
- No mechanism for emergency rotation
**Recommendation:**
```bash
# Reduce default to 24 hours
JWT_ROTATION_HOURS=24
# Add maximum validation
func ValidateTimeout(name string, value, min, max time.Duration) error {
// Add maximum JWT rotation check
if name == "JWT_ROTATION_HOURS" && value > 168*time.Hour {
return fmt.Errorf("JWT_ROTATION_HOURS should not exceed 168 hours (7 days)")
}
// ...
}
```
**Remediation Priority:** MEDIUM
---
## Low Issues
### 10. Secrets Passed via Environment Variables in Compose
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/deploy/podman-compose.yml:117-179`
**Risk:** LOW
**Security Risk:**
- Secrets visible in `docker inspect` output
- Environment variables may be logged by orchestration tools
- Process listing (`ps e`) exposes environment
**Recommendation:**
Use Docker secrets or external secret management:
```yaml
services:
mcp-server:
secrets:
- db_password
- redis_password
- jwt_secret
- mcp_api_key
- ide_api_key
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password
# ...
secrets:
db_password:
file: ./secrets/db_password.txt
redis_password:
file: ./secrets/redis_password.txt
# Or use external secrets manager
```
Update config.go to support `_FILE` suffix:
```go
func loadSecretFromFile(envVar string) string {
fileVar := envVar + "_FILE"
if path := os.Getenv(fileVar); path != "" {
data, err := os.ReadFile(path)
if err != nil {
slog.Warn("Failed to read secret file", "file", path, "error", err)
return ""
}
return strings.TrimSpace(string(data))
}
return os.Getenv(envVar)
}
```
**Remediation Priority:** LOW
---
### 11. No Cipher Suite Configuration for TLS
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/config/config.go:70-76`
**Risk:** LOW
**Security Risk:**
- Default cipher suites may include weak ciphers
- No protection against SWEET32 or other cipher-based attacks
- Missing forward secrecy enforcement
**Recommendation:**
Add cipher suite configuration:
```go
// Config struct addition
TLSCipherSuites []string `env:"TLS_CIPHER_SUITES" envDefault:"TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_GCM_SHA256"`
// Validation
func (c *Config) Validate() error {
if c.TLSEnabled {
weakCiphers := []string{
"TLS_RSA_WITH_RC4_128_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
}
// Validate no weak ciphers selected
}
// ...
}
```
**Remediation Priority:** LOW
---
## Positive Security Findings
The following security controls are properly implemented:
### 1. Secret Masking in Config
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/internal/config/config.go:391-400`
```go
func (c *Config) Masked() *Config {
masked := *c
masked.DBPassword = "***"
masked.RedisPassword = "***"
masked.MCPAPIKey = "***"
masked.IDEAPIKey = "***"
masked.JWTSecret = "***"
return &masked
}
```
**Status:** GOOD - Proper secret masking for logging
---
### 2. Kubernetes Secrets Usage
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/deploy/k8s-deployment.yaml:64-122`
```yaml
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: guardrail-db-credentials
key: password
```
**Status:** GOOD - Proper Kubernetes secrets integration
---
### 3. Security Context in Kubernetes
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/deploy/k8s-deployment.yaml:29-33,146-151`
```yaml
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
```
**Status:** GOOD - Proper security hardening
---
### 4. Network Policies
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/deploy/k8s-deployment.yaml:218-260`
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
```
**Status:** GOOD - Network segmentation properly configured
---
### 5. Non-root Container User
**File:** `/mnt/ollama/git/agent-guardrails-template/mcp-server/deploy/Dockerfile:31-38`
```dockerfile
FROM gcr.io/distroless/static:nonroot
USER 65532:65532
```
**Status:** GOOD - Principle of least privilege followed
---
## Compliance Mapping
| Issue | PCI-DSS | SOC2 | ISO 27001 | NIST 800-53 |
|-------|---------|------|-----------|-------------|
| Database SSL disabled | 4.1, 4.2 | CC6.7 | A.13.2.1 | SC-8, SC-13 |
| Redis TLS disabled | 4.1, 4.2 | CC6.7 | A.13.2.1 | SC-8, SC-13 |
| Permissive CORS | 6.5.9 | CC6.6 | A.14.1.2 | AC-2, AC-3 |
| Placeholder secrets | 8.2.1 | CC6.1 | A.9.2.1 | IA-5 |
| Password exposure | 8.2.1 | CC6.1 | A.9.4.3 | SC-28 |
---
## Remediation Checklist
- [ ] Change `DB_SSLMODE` default from `disable` to `require` in podman-compose.yml
- [ ] Change `REDIS_USE_TLS` default from `false` to `true` in podman-compose.yml
- [ ] Remove `CORS_ALLOWED_ORIGINS=*` default, make it required
- [ ] Fix Redis health check to not expose password in command
- [ ] Add placeholder detection to config validation
- [ ] Make `PRODUCTION_MODE` required with no default
- [ ] Enforce TLS 1.3 minimum in production mode
- [ ] Add `_FILE` suffix support for Docker secrets
- [ ] Document secret generation procedures
- [ ] Add pre-deployment security validation script
---
## Appendix A: Secure Configuration Template
```bash
# Production-ready .env file template
PRODUCTION_MODE=true
# Server
MCP_PORT=8080
WEB_PORT=8081
LOG_LEVEL=warn
# Database (SSL required)
DB_HOST=postgres.example.com
DB_PORT=5432
DB_NAME=guardrails
DB_USER=guardrail
DB_PASSWORD=<GENERATE_STRONG_PASSWORD>
DB_SSLMODE=verify-full
# Redis (TLS required)
REDIS_HOST=redis.example.com
REDIS_PORT=6379
REDIS_PASSWORD=<GENERATE_STRONG_PASSWORD>
REDIS_USE_TLS=true
# TLS (Required for production)
TLS_ENABLED=true
TLS_CERT_PATH=/etc/ssl/certs/server.crt
TLS_KEY_PATH=/etc/ssl/private/server.key
TLS_CA_PATH=/etc/ssl/certs/ca.crt
TLS_MIN_VERSION=1.3
# Secrets (Generate with: openssl rand -hex 32)
MCP_API_KEY=<64_CHAR_HEX_STRING>
IDE_API_KEY=<64_CHAR_HEX_STRING>
JWT_SECRET=<128_CHAR_HEX_STRING>
# CORS (Explicit origins only)
CORS_ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com
# Security
PPROF_ENABLED=false
RATE_LIMIT_MCP=1000
RATE_LIMIT_IDE=500
```
---
*End of Security Audit Report*