Technical Specification: Security Hardening ("Defense in Depth")
Overview
cliproxyapi++ implements a comprehensive "Defense in Depth" security philosophy with multiple layers of protection: CI-enforced code integrity, hardened container images, device fingerprinting, and secure credential management.
Security Architecture
Defense Layers
Layer 1: Code Integrity
├── Path Guard (CI enforcement)
├── Signed releases
└── Multi-arch builds
Layer 2: Container Hardening
├── Minimal base image (Alpine 3.22.0)
├── Non-root user
├── Read-only filesystem
└── Seccomp profiles
Layer 3: Credential Security
├── Encrypted storage
├── Secure file permissions
├── Token refresh isolation
└── Device fingerprinting
Layer 4: Network Security
├── TLS only
├── Request validation
├── Rate limiting
└── IP allowlisting
Layer 5: Operational Security
├── Audit logging
├── Secret scanning
├── Dependency scanning
└── Vulnerability managementLayer 1: Code Integrity
Path Guard CI Enforcement
Purpose: Prevent unauthorized changes to critical translation logic during pull requests.
Implementation (.github/workflows/pr-path-guard.yml):
name: Path Guard
on:
pull_request:
paths:
- 'pkg/llmproxy/translator/**'
- 'pkg/llmproxy/auth/**'
jobs:
guard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check path protection
run: |
# Only allow changes from trusted maintainers
if ! git log --format="%an" ${{ github.event.pull_request.base.sha }}..${{ github.sha }} | grep -q "KooshaPari"; then
echo "::error::Unauthorized changes to protected paths"
exit 1
fi
- name: Verify no translator logic changes
run: |
# Ensure core translation logic hasn't been tampered
if git diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }} --name-only | grep -q "pkg/llmproxy/translator/.*\.go$"; then
echo "::warning::Translator logic changed - requires maintainer review"
fiProtected Paths:
pkg/llmproxy/translator/- Core translation logicpkg/llmproxy/auth/- Authentication flowspkg/llmproxy/provider/- Provider execution
Authorization Rules:
- Only repository maintainers can modify
- All changes require at least 2 maintainer approvals
- Must pass security review
Signed Releases
Purpose: Ensure released artifacts are authentic and tamper-proof.
Implementation (.goreleaser.yml):
signs:
- artifacts: checksum
args:
- "--batch"
- "--local-user"
- "${GPG_FINGERPRINT}"Verification:
# Download release
wget https://github.com/KooshaPari/cliproxyapi-plusplus/releases/download/v6.0.0/cliproxyapi-plusplus_6.0.0_checksums.txt
# Download signature
wget https://github.com/KooshaPari/cliproxyapi-plusplus/releases/download/v6.0.0/cliproxyapi-plusplus_6.0.0_checksums.txt.sig
# Import GPG key
gpg --keyserver keyserver.ubuntu.com --recv-keys XXXXXXXX
# Verify signature
gpg --verify cliproxyapi-plusplus_6.0.0_checksums.txt.sig cliproxyapi-plusplus_6.0.0_checksums.txt
# Verify checksum
sha256sum -c cliproxyapi-plusplus_6.0.0_checksums.txtMulti-Arch Builds
Purpose: Provide consistent security across architectures.
Platforms:
linux/amd64linux/arm64darwin/amd64darwin/arm64
CI Build Matrix:
strategy:
matrix:
goos: [linux, darwin]
goarch: [amd64, arm64]Layer 2: Container Hardening
Minimal Base Image
Base: Alpine Linux 3.22.0
Dockerfile:
FROM alpine:3.22.0 AS builder
# Install build dependencies
RUN apk add --no-cache \
ca-certificates \
gcc \
musl-dev
# Build application
COPY . .
RUN go build -o cliproxyapi cmd/server/main.go
# Final stage - minimal runtime
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /cliproxyapi /cliproxyapi
# Non-root user
USER 65534:65534
# Read-only filesystem
VOLUME ["/config", "/auths", "/logs"]
ENTRYPOINT ["/cliproxyapi"]Security Benefits:
- Minimal attack surface (no shell, no package manager)
- No unnecessary packages
- Static binary linking
- Reproducible builds
Security Context
docker-compose.yml:
services:
cliproxy:
image: KooshaPari/cliproxyapi-plusplus:latest
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp:noexec,nosuid,size=100m
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
user: "65534:65534"Explanation:
no-new-privileges: Prevent privilege escalationread_only: Immutable filesystemtmpfs: Noexec on temporary filescap_drop:ALL: Drop all capabilitiescap_add:NET_BIND_SERVICE: Only allow binding portsuser:65534:65534: Run as non-root (nobody)
Seccomp Profiles
Custom seccomp profile (seccomp-profile.json):
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_AARCH64"],
"syscalls": [
{
"names": ["read", "write", "open", "close", "stat", "fstat", "lstat"],
"action": "SCMP_ACT_ALLOW"
},
{
"names": ["socket", "bind", "listen", "accept", "connect"],
"action": "SCMP_ACT_ALLOW"
},
{
"names": ["execve", "fork", "clone"],
"action": "SCMP_ACT_DENY"
}
]
}Usage:
security_opt:
- seccomp:/path/to/seccomp-profile.jsonLayer 3: Credential Security
Encrypted Storage
Purpose: Protect credentials at rest.
Implementation:
type CredentialEncryptor struct {
key []byte
}
func NewCredentialEncryptor(key string) (*CredentialEncryptor, error) {
if len(key) != 32 {
return nil, fmt.Errorf("key must be 32 bytes")
}
return &CredentialEncryptor{
key: []byte(key),
}, nil
}
func (e *CredentialEncryptor) Encrypt(data []byte) ([]byte, error) {
block, err := aes.NewCipher(e.key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, data, nil), nil
}
func (e *CredentialEncryptor) Decrypt(data []byte) ([]byte, error) {
block, err := aes.NewCipher(e.key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
if len(data) < nonceSize {
return nil, fmt.Errorf("ciphertext too short")
}
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
return gcm.Open(nil, nonce, ciphertext, nil)
}Configuration:
auth:
encryption:
enabled: true
key: "YOUR_32_BYTE_ENCRYPTION_KEY_HERE"Secure File Permissions
Automatic enforcement:
func SetSecurePermissions(path string) error {
// File: 0600 (rw-------)
// Directory: 0700 (rwx------)
if info, err := os.Stat(path); err == nil {
if info.IsDir() {
return os.Chmod(path, 0700)
}
return os.Chmod(path, 0600)
}
return fmt.Errorf("file not found: %s", path)
}Verification:
func VerifySecurePermissions(path string) error {
info, err := os.Stat(path)
if err != nil {
return err
}
mode := info.Mode().Perm()
if info.IsDir() && mode != 0700 {
return fmt.Errorf("directory has insecure permissions: %o", mode)
}
if !info.IsDir() && mode != 0600 {
return fmt.Errorf("file has insecure permissions: %o", mode)
}
return nil
}Token Refresh Isolation
Purpose: Prevent credential leakage during refresh.
Implementation:
type RefreshWorker struct {
isolatedMemory bool
}
func (w *RefreshWorker) RefreshToken(auth *Auth) (*AuthToken, error) {
// Use isolated goroutine
result := make(chan *RefreshResult)
go w.isolatedRefresh(auth, result)
select {
case res := <-result:
if res.Error != nil {
return nil, res.Error
}
// Clear memory after use
defer w.scrubMemory(res.Token)
return res.Token, nil
case <-time.After(30 * time.Second):
return nil, fmt.Errorf("refresh timeout")
}
}
func (w *RefreshWorker) scrubMemory(token *AuthToken) {
// Zero out sensitive data
for i := range token.AccessToken {
token.AccessToken = ""
}
token.RefreshToken = ""
}Device Fingerprinting
Purpose: Generate unique, immutable device identifiers for provider security checks.
Implementation:
func GenerateDeviceFingerprint() (string, error) {
mac, err := getMACAddress()
if err != nil {
return "", err
}
hostname, err := os.Hostname()
if err != nil {
return "", err
}
// Create stable fingerprint
h := sha256.New()
h.Write([]byte(mac))
h.Write([]byte(hostname))
h.Write([]byte("cliproxyapi++")) // Salt
fingerprint := hex.EncodeToString(h.Sum(nil))
// Store for persistence
return fingerprint, nil
}
func getMACAddress() (string, error) {
interfaces, err := net.Interfaces()
if err != nil {
return "", err
}
for _, iface := range interfaces {
if iface.Flags&net.FlagUp == 0 {
continue
}
if len(iface.HardwareAddr) == 0 {
continue
}
return iface.HardwareAddr.String(), nil
}
return "", fmt.Errorf("no MAC address found")
}Usage:
fingerprint, _ := GenerateDeviceFingerprint()
// Send with requests
headers["X-Device-Fingerprint"] = fingerprintLayer 4: Network Security
TLS Enforcement
Configuration:
server:
port: 8317
tls:
enabled: true
cert_file: "/config/tls.crt"
key_file: "/config/tls.key"
min_version: "1.2"
cipher_suites:
- "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"HTTP Strict Transport Security (HSTS):
func addSecurityHeaders(c *gin.Context) {
c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
c.Header("X-Content-Type-Options", "nosniff")
c.Header("X-Frame-Options", "DENY")
c.Header("X-XSS-Protection", "1; mode=block")
c.Header("Content-Security-Policy", "default-src 'self'")
}Request Validation
Schema validation:
type ChatRequestValidator struct {
validator *validator.Validate
}
func (v *ChatRequestValidator) Validate(req *openai.ChatCompletionRequest) error {
return v.validator.Struct(req)
}
// Max tokens limits
func (v *ChatRequestValidator) ValidateMaxTokens(maxTokens int) error {
if maxTokens > 4096 {
return fmt.Errorf("max_tokens exceeds limit of 4096")
}
return nil
}Rate Limiting
Token bucket implementation:
type RateLimiter struct {
limiters map[string]*rate.Limiter
mu sync.RWMutex
}
func NewRateLimiter() *RateLimiter {
return &RateLimiter{
limiters: make(map[string]*rate.Limiter),
}
}
func (r *RateLimiter) Allow(ip string) bool {
r.mu.Lock()
defer r.mu.Unlock()
limiter, exists := r.limiters[ip]
if !exists {
limiter = rate.NewLimiter(rate.Limit(10), 20) // 10 req/s, burst 20
r.limiters[ip] = limiter
}
return limiter.Allow()
}Per-provider rate limiting:
providers:
claude:
rate_limit:
requests_per_minute: 100
tokens_per_minute: 100000IP Allowlisting
Configuration:
server:
security:
ip_allowlist:
enabled: true
allowed_ips:
- "10.0.0.0/8"
- "192.168.1.100"
ip_denylist:
- "0.0.0.0/0" # Block all except allowedImplementation:
type IPFilter struct {
allowed []*net.IPNet
denied []*net.IPNet
}
func (f *IPFilter) IsAllowed(ip net.IP) bool {
// Check denylist first
for _, deny := range f.denied {
if deny.Contains(ip) {
return false
}
}
// Check allowlist
if len(f.allowed) == 0 {
return true // No allowlist = allow all
}
for _, allow := range f.allowed {
if allow.Contains(ip) {
return true
}
}
return false
}Layer 5: Operational Security
Audit Logging
Structured logging:
type AuditLogger struct {
logger *slog.Logger
}
func (a *AuditLogger) LogAuthEvent(event AuthEvent) {
a.logger.LogAttrs(
context.Background(),
slog.LevelInfo,
"auth_event",
slog.String("event_type", event.Type),
slog.String("provider", event.Provider),
slog.String("user_id", event.UserID),
slog.String("ip", event.IP),
slog.Time("timestamp", event.Timestamp),
slog.String("result", event.Result),
)
}Audit events:
- Authentication attempts (success/failure)
- Token refresh
- Credential access
- Configuration changes
- Provider requests
Secret Scanning
Pre-commit hook (.git/hooks/pre-commit):
#!/bin/bash
# Scan for potential secrets
if git diff --cached --name-only | xargs grep -lE "sk-[a-zA-Z0-9]{48}|AIza[a-zA-Z0-9_-]{35}"; then
echo "::error::Potential secrets detected in staged files"
exit 1
fiCI secret scanning:
- name: Scan for secrets
run: |
pip install git-secrets
git secrets --register-aws
git secrets --scanDependency Scanning
CI integration:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'Vulnerability Management
Weekly scan schedule:
name: Vulnerability Scan
on:
schedule:
- cron: '0 0 * * 0' # Weekly
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy
run: |
trivy fs --severity HIGH,CRITICAL --exit-code 1 .Security Monitoring
Metrics
Security metrics exposed:
type SecurityMetrics struct {
AuthFailures int64
RateLimitViolations int64
SuspiciousActivity int64
BlockedIPs int64
}Alerting:
alerts:
- name: High auth failure rate
condition: auth_failures > 100
duration: 5m
action: notify_admin
- name: Rate limit violations
condition: rate_limit_violations > 50
duration: 1m
action: block_ipIncident Response
Procedure:
- Detect anomaly via metrics/logs
- Verify incident (false positive check)
- Contain (block IP, disable provider)
- Investigate (analyze logs)
- Remediate (patch, rotate credentials)
- Document (incident report)
Compliance
SOC 2 Readiness
- Access Control: Role-based access, MFA support
- Change Management: CI enforcement, audit trails
- Data Protection: Encryption at rest/transit
- Monitoring: 24/7 logging, alerting
- Incident Response: Documented procedures
GDPR Compliance
- Data Minimization: Only store necessary data
- Right to Erasure: Credential deletion API
- Data Portability: Export credentials API
- Audit Trails: Complete logging
Security Checklist
Pre-Deployment:
- [ ] All dependencies scanned (no HIGH/CRITICAL)
- [ ] Secrets scanned and removed
- [ ] TLS enabled with strong ciphers
- [ ] File permissions set (0600/0700)
- [ ] Rate limiting enabled
- [ ] IP allowlisting configured
- [ ] Audit logging enabled
- [ ] Container hardened (non-root, read-only)
Post-Deployment:
- [ ] Monitor security metrics
- [ ] Review audit logs daily
- [ ] Update dependencies monthly
- [ ] Rotate credentials quarterly
- [ ] Test incident response procedures