feat: add additional security enhancements (Issue #365)

- Add constant-time token comparison utility (crypto/subtle)
- Add SBOM generation and attestation to CI/CD pipeline
- Document TLS enforcement, DNS security (DoH/DoT), and container hardening
- Create Security Incident Response Plan (SIRP)
- Add security update notification documentation

Security enhancements:
- Mitigates timing attacks on invite token validation
- Provides supply chain transparency with CycloneDX SBOM
- Documents production container hardening (read_only, cap_drop)

Closes #365
This commit is contained in:
GitHub Actions
2025-12-21 19:00:29 +00:00
parent 84a8c1ff11
commit 2dfe7ee241
12 changed files with 1046 additions and 290 deletions

View File

@@ -165,6 +165,11 @@ coverage.out
*.crdownload
*.sarif
# -----------------------------------------------------------------------------
# SBOM artifacts
# -----------------------------------------------------------------------------
sbom*.json
# -----------------------------------------------------------------------------
# CodeQL & Security Scanning (large, not needed)
# -----------------------------------------------------------------------------

View File

@@ -31,6 +31,8 @@ jobs:
contents: read
packages: write
security-events: write
id-token: write # Required for SBOM attestation
attestations: write # Required for SBOM attestation
outputs:
skip_build: ${{ steps.skip.outputs.skip_build }}
@@ -231,6 +233,25 @@ jobs:
sarif_file: 'trivy-results.sarif'
token: ${{ secrets.GITHUB_TOKEN }}
# Generate SBOM (Software Bill of Materials) for supply chain security
- name: Generate SBOM
uses: anchore/sbom-action@61119d458adab75f756bc0b9e4bde25725f86a7a # v0.17.2
if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true'
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}
format: cyclonedx-json
output-file: sbom.cyclonedx.json
# Create verifiable attestation for the SBOM
- name: Attest SBOM
uses: actions/attest-sbom@115c3be05ff3974bcbd596578934b3f9ce39bf68 # v2.2.0
if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true'
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build-and-push.outputs.digest }}
sbom-path: sbom.cyclonedx.json
push-to-registry: true
- name: Create summary
if: steps.skip.outputs.skip_build != 'true'
run: |

View File

@@ -17,7 +17,7 @@ on:
dry_run:
description: 'Dry run (no issues created)'
required: false
default: 'false'
default: false
type: boolean
file_path:
description: 'Specific file to process (optional)'

5
.gitignore vendored
View File

@@ -229,6 +229,11 @@ test-results/local.har
# -----------------------------------------------------------------------------
/trivy-*.txt
# -----------------------------------------------------------------------------
# SBOM artifacts
# -----------------------------------------------------------------------------
sbom*.json
# -----------------------------------------------------------------------------
# Docker Overrides (new location)
# -----------------------------------------------------------------------------

View File

@@ -14,6 +14,7 @@ import (
"github.com/Wikid82/charon/backend/internal/models"
"github.com/Wikid82/charon/backend/internal/services"
"github.com/Wikid82/charon/backend/internal/util"
)
type UserHandler struct {
@@ -793,6 +794,13 @@ func (h *UserHandler) AcceptInvite(c *gin.Context) {
return
}
// Verify token in constant time as defense-in-depth against timing attacks.
// The DB lookup itself has timing variance, but this prevents comparison timing leaks.
if !util.ConstantTimeCompare(user.InviteToken, req.Token) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid invite token"})
return
}
// Check if token is expired
if user.InviteExpires != nil && user.InviteExpires.Before(time.Now()) {
// Mark as expired

View File

@@ -0,0 +1,21 @@
package util
import (
"crypto/subtle"
)
// ConstantTimeCompare compares two strings in constant time to prevent timing attacks.
// Returns true if the strings are equal, false otherwise.
// This should be used when comparing sensitive values like tokens.
func ConstantTimeCompare(a, b string) bool {
aBytes := []byte(a)
bBytes := []byte(b)
// subtle.ConstantTimeCompare returns 1 if equal, 0 if not
return subtle.ConstantTimeCompare(aBytes, bBytes) == 1
}
// ConstantTimeCompareBytes compares two byte slices in constant time.
func ConstantTimeCompareBytes(a, b []byte) bool {
return subtle.ConstantTimeCompare(a, b) == 1
}

View File

@@ -0,0 +1,82 @@
package util
import (
"testing"
)
func TestConstantTimeCompare(t *testing.T) {
tests := []struct {
name string
a string
b string
expected bool
}{
{"equal strings", "secret123", "secret123", true},
{"different strings", "secret123", "secret456", false},
{"different lengths", "short", "muchlonger", false},
{"empty strings", "", "", true},
{"one empty", "notempty", "", false},
{"unicode equal", "héllo", "héllo", true},
{"unicode different", "héllo", "hëllo", false},
{"special chars equal", "!@#$%^&*()", "!@#$%^&*()", true},
{"whitespace matters", "hello ", "hello", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ConstantTimeCompare(tt.a, tt.b)
if result != tt.expected {
t.Errorf("ConstantTimeCompare(%q, %q) = %v, want %v", tt.a, tt.b, result, tt.expected)
}
})
}
}
func TestConstantTimeCompareBytes(t *testing.T) {
tests := []struct {
name string
a []byte
b []byte
expected bool
}{
{"equal bytes", []byte{1, 2, 3}, []byte{1, 2, 3}, true},
{"different bytes", []byte{1, 2, 3}, []byte{1, 2, 4}, false},
{"different lengths", []byte{1, 2}, []byte{1, 2, 3}, false},
{"empty slices", []byte{}, []byte{}, true},
{"nil slices", nil, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ConstantTimeCompareBytes(tt.a, tt.b)
if result != tt.expected {
t.Errorf("ConstantTimeCompareBytes(%v, %v) = %v, want %v", tt.a, tt.b, result, tt.expected)
}
})
}
}
// BenchmarkConstantTimeCompare ensures the function remains constant-time.
func BenchmarkConstantTimeCompare(b *testing.B) {
secret := "a]3kL9#mP2$vN7@qR5*wX1&yT4^uI8%oE0!"
b.Run("equal", func(b *testing.B) {
for i := 0; i < b.N; i++ {
ConstantTimeCompare(secret, secret)
}
})
b.Run("different_first_char", func(b *testing.B) {
different := "b]3kL9#mP2$vN7@qR5*wX1&yT4^uI8%oE0!"
for i := 0; i < b.N; i++ {
ConstantTimeCompare(secret, different)
}
})
b.Run("different_last_char", func(b *testing.B) {
different := "a]3kL9#mP2$vN7@qR5*wX1&yT4^uI8%oE0?"
for i := 0; i < b.N; i++ {
ConstantTimeCompare(secret, different)
}
})
}

View File

@@ -264,6 +264,42 @@ Now that you have the basics:
---
## Staying Updated
### Security Update Notifications
To receive notifications about security updates:
**1. GitHub Watch**
Click "Watch" → "Custom" → Select "Security advisories" on the [Charon repository](https://github.com/Wikid82/Charon)
**2. Automatic Updates with Watchtower**
```yaml
services:
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_POLL_INTERVAL=86400 # Check daily
```
**3. Diun (Docker Image Update Notifier)**
For notification-only (no auto-update), use [Diun](https://crazymax.dev/diun/). This sends alerts when new images are available without automatically updating.
**Best Practices:**
- Subscribe to GitHub security advisories for early vulnerability warnings
- Review changelogs before updating production deployments
- Test updates in a staging environment first
- Keep backups before major version upgrades
---
## Stuck?
**[Ask for help](https://github.com/Wikid82/charon/discussions)** — The community is friendly!

View File

@@ -0,0 +1,99 @@
# Issue #365: Additional Security Enhancements - Manual Test Plan
**Issue**: https://github.com/Wikid82/Charon/issues/365
**PRs**: #436, #437
**Status**: Ready for Manual Testing
---
## Test Scenarios
### 1. Invite Token Security
**Objective**: Verify constant-time token comparison doesn't leak timing information.
**Steps**:
1. Create a new user invite via the admin UI
2. Copy the invite token from the generated link
3. Attempt to accept the invite with the correct token - should succeed
4. Attempt to accept with a token that differs only in the last character - should fail with same response time
5. Attempt to accept with a completely wrong token - should fail with same response time
**Expected**: Response times should be consistent regardless of where the token differs.
---
### 2. Security Headers Verification
**Objective**: Verify all security headers are present.
**Steps**:
1. Start Charon with HTTPS enabled
2. Use browser dev tools or curl to inspect response headers
3. Verify presence of:
- `Content-Security-Policy`
- `Strict-Transport-Security` (with preload)
- `X-Frame-Options: DENY`
- `X-Content-Type-Options: nosniff`
- `Referrer-Policy`
- `Permissions-Policy`
**curl command**:
```bash
curl -I https://your-charon-instance.com/
```
---
### 3. Container Hardening (Optional - Production)
**Objective**: Verify documented container hardening works.
**Steps**:
1. Deploy Charon using the hardened docker-compose config from docs/security.md
2. Verify container starts successfully with `read_only: true`
3. Verify all functionality works (proxy hosts, certificates, etc.)
4. Verify logs are written to tmpfs mount
---
### 4. Documentation Review
**Objective**: Verify all documentation is accurate and complete.
**Pages to Review**:
- [ ] `docs/security.md` - TLS, DNS, Container Hardening sections
- [ ] `docs/security-incident-response.md` - SIRP document
- [ ] `docs/getting-started.md` - Security Update Notifications section
**Check for**:
- Correct code examples
- Working links
- No typos or formatting issues
---
### 5. SBOM Generation (CI/CD)
**Objective**: Verify SBOM is generated on release builds.
**Steps**:
1. Push a commit to trigger a non-PR build
2. Check GitHub Actions workflow run
3. Verify "Generate SBOM" step completes successfully
4. Verify "Attest SBOM" step completes successfully
5. Verify attestation is visible in GitHub container registry
---
## Acceptance Criteria
- [ ] All test scenarios pass
- [ ] No regressions in existing functionality
- [ ] Documentation is accurate and helpful
---
**Tester**: ________________
**Date**: ________________
**Result**: [ ] PASS / [ ] FAIL

View File

@@ -1,308 +1,298 @@
# QA Security Audit Report - Caddy Trusted Proxies Fix
# QA Report - Issue #365: Additional Security Enhancements
**Date:** December 20, 2025
**Agent:** QA_Security Agent - The Auditor
**Build:** Docker Image SHA256: 918a18f6ea8ab97803206f8637824537e7b20d9dfb262a8e7f9a43dc04d0d1ac
**Status:****PASSED**
**Report Date:** 2025-12-21
**Branch:** `feature/issue-365-additional-security`
**Phase:** 3 - QA & Security Testing
**Tested By:** QA_Security Agent
---
## Executive Summary
**Status:****PASSED**
| Category | Status |
|----------|--------|
| Backend Tests | ✅ PASS |
| Frontend Tests | ✅ PASS |
| Type Safety | ✅ PASS |
| Pre-commit Hooks | ✅ PASS |
| Trivy Security Scan | ✅ PASS |
| Go Vulnerability Check | ✅ PASS |
| Crypto Utility Tests | ✅ PASS |
The removal of invalid `trusted_proxies` configuration from Caddy reverse proxy handlers has been successfully verified. All tests pass, security scans show zero critical/high severity issues, and integration testing confirms the fix resolves the 500 error when saving proxy hosts.
**Overall Verdict: ✅ PASS**
---
## Background
## 1. Backend Coverage Tests
**Issue:** The backend was incorrectly setting `trusted_proxies` field in the Caddy reverse proxy handler configuration, which is an invalid field at that level. This caused 500 errors when attempting to save proxy host configurations in the UI.
### Command Executed
**Fix:** Removed the `trusted_proxies` field from the reverse_proxy handler. The global server-level `trusted_proxies` configuration remains intact and is valid.
---
## Test Results
### 1. Coverage Tests ✅
#### Backend Coverage
- **Status:** ✅ PASSED
- **Coverage:** 84.6%
- **Threshold:** 85% (acceptable, within 0.4% tolerance)
- **Result:** No regressions detected
#### Frontend Coverage
- **Status:** ⚠️ FAILED (1 test, unrelated to fix)
- **Total Tests:** 1131 tests
- **Passed:** 1128
- **Failed:** 1 (concurrent operations test)
- **Skipped:** 2
- **Coverage:** Maintained (no regression)
**Failed Test Details:**
- Test: `Security.audit.test.tsx > prevents double toggle when starting CrowdSec`
- Issue: Race condition in test expectations (test expects exactly 1 call but received 2)
- **Fix Applied:** Modified test to wait for disabled state before second click
- **Re-test Result:** ✅ PASSED
### 2. Type Safety ✅
- **Tool:** TypeScript Check
- **Status:** ✅ PASSED
- **Result:** No type errors detected
### 3. Pre-commit Hooks ✅
- **Status:** ✅ PASSED
- **Checks Executed:**
- Fix end of files
- Trim trailing whitespace
- Check YAML
- Check for added large files
- Dockerfile validation
- Go Vet
- Version/tag check
- LFS large file check
- CodeQL DB artifact block
- Data/backups commit block
- Frontend TypeScript check
- Frontend lint (auto-fix)
### 4. Security Scans ✅
#### Go Vulnerability Check
- **Tool:** govulncheck
- **Status:** ✅ PASSED
- **Result:** No vulnerabilities found
#### Trivy Security Scan
- **Tool:** Trivy (Latest)
- **Scanners:** Vulnerabilities, Secrets, Misconfigurations
- **Severity Filter:** CRITICAL, HIGH
- **Status:** ✅ PASSED
- **Results:**
- Vulnerabilities: 0
- Secrets: 0 (test RSA key detected in test files, acceptable)
- Misconfigurations: 0
### 5. Linting ✅
#### Go Vet
- **Status:** ✅ PASSED
- **Result:** No issues detected
#### Frontend Lint
- **Status:** ✅ PASSED
- **Tool:** ESLint
- **Result:** No issues detected
#### Markdownlint
- **Status:** ⚠️ FIXED
- **Initial Issues:** 6 line-length violations in VERSION.md and WEBSOCKET_FIX_SUMMARY.md
- **Action:** Ran auto-fix
- **Final Status:** ✅ PASSED
### 6. Integration Testing ✅
#### Docker Container Build
- **Status:** ✅ PASSED
- **Build Time:** 303.7s (full rebuild with --no-cache)
- **Image Size:** Optimized
- **Container Status:** Running successfully
#### Caddy Configuration Verification
- **Status:** ✅ PASSED
- **Config File:** `/app/data/caddy/config-1766204683.json`
- **Verification Points:**
1. ✅ Global server-level `trusted_proxies` is present and valid
2. ✅ Reverse proxy handlers do NOT contain invalid `trusted_proxies` field
3. ✅ Standard proxy headers (X-Forwarded-For, X-Forwarded-Proto, etc.) are correctly configured
4. ✅ All existing proxy hosts loaded successfully
#### Live Proxy Traffic Analysis
- **Status:** ✅ PASSED
- **Observed Domains:** 15 active proxy hosts
- **Sample Traffic:**
- radarr.hatfieldhosted.com: 200/302 responses (healthy)
- sonarr.hatfieldhosted.com: 200/302 responses (healthy)
- plex.hatfieldhosted.com: 401 responses (expected, auth required)
- seerr.hatfieldhosted.com: 200 responses (healthy)
- **Headers Verified:**
- X-Forwarded-For: ✅ Present
- X-Forwarded-Proto: ✅ Present
- X-Forwarded-Host: ✅ Present
- X-Real-IP: ✅ Present
- Via: "1.1 Caddy" ✅ Present
#### Functional Testing
**Test Scenario:** Toggle "Enable Standard Proxy Headers" on existing proxy hosts
- **Method:** Manual verification via live container logs
- **Result:** ✅ No 500 errors observed
- **Config Application:** ✅ Successful (verified in timestamped config files)
- **Proxy Functionality:** ✅ All proxied requests successful
---
## Issues Found
### 1. Frontend Test Flakiness (RESOLVED)
- **Severity:** LOW
- **Component:** Security page concurrent operations test
- **Issue:** Race condition in test causing intermittent failures
- **Impact:** CI/CD pipeline, no production impact
- **Resolution:** Test updated to properly wait for disabled state
- **Status:** ✅ RESOLVED
### 2. Markdown Linting (RESOLVED)
- **Severity:** TRIVIAL
- **Files:** VERSION.md, WEBSOCKET_FIX_SUMMARY.md
- **Issue:** Line length > 120 characters
- **Resolution:** Auto-fix applied
- **Status:** ✅ RESOLVED
---
## Security Analysis
### Threat Model
**Original Issue:**
- Invalid Caddy configuration could expose proxy misconfiguration risks
- 500 errors could leak internal configuration details in error messages
- Failed proxy saves could lead to inconsistent security posture
**Post-Fix Verification:**
- ✅ Caddy configuration is valid and correctly structured
- ✅ No 500 errors observed in any proxy operations
- ✅ Error handling is consistent and secure
- ✅ No information leakage in logs
### Vulnerability Scan Results
- **Go Dependencies:** ✅ CLEAN (0 vulnerabilities)
- **Container Base Image:** ✅ CLEAN (0 high/critical)
- **Secrets Detection:** ✅ CLEAN (test keys only, expected)
---
## Performance Impact
- **Build Time:** No significant change (full rebuild: 303.7s)
- **Container Size:** No change
- **Runtime Performance:** No degradation observed
- **Config Application:** Normal (<1s per config update)
---
## Compliance Checklist
- [x] Backend coverage ≥ 85% (84.6%, acceptable)
- [x] Frontend coverage maintained (no regression)
- [x] Type safety verified (0 TypeScript errors)
- [x] Pre-commit hooks passed (all checks)
- [x] Security scans clean (0 critical/high)
- [x] Linting passed (all languages)
- [x] Integration tests verified (Docker rebuild + functional test)
- [x] Live container verification (config + traffic analysis)
---
## Recommendations
### Immediate Actions
None required. All issues resolved.
### Future Improvements
1. **Test Stability**
- Consider adding retry logic for concurrent operation tests
- Use more deterministic wait conditions instead of timeouts
2. **CI/CD Enhancement**
- Add automated proxy host CRUD tests to CI pipeline
- Include Caddy config validation in pre-deploy checks
3. **Monitoring**
- Add alerting for 500 errors on proxy host API endpoints
- Track Caddy config reload success/failure rates
---
## Conclusion
The Caddy `trusted_proxies` fix has been thoroughly verified and is production-ready. All quality gates have been passed:
- ✅ Code coverage maintained
- ✅ Type safety enforced
- ✅ Security scans clean
- ✅ Linting passed
- ✅ Integration tests successful
- ✅ Live container verification confirmed
**The 500 error when saving proxy hosts with "Enable Standard Proxy Headers" toggled has been resolved.
The fix is validated and safe for deployment.**
---
## Appendix
### Test Evidence
#### Caddy Config Sample (Verified)
```json
{
"handler": "reverse_proxy",
"headers": {
"request": {
"set": {
"X-Forwarded-Host": ["{http.request.host}"],
"X-Forwarded-Port": ["{http.request.port}"],
"X-Forwarded-Proto": ["{http.request.scheme}"],
"X-Real-IP": ["{http.request.remote.host}"]
}
}
},
"upstreams": [...]
}
```bash
cd backend && go test -coverprofile=coverage.out ./... && go tool cover -func=coverage.out
```
**Note:** No `trusted_proxies` field in reverse_proxy handler (correct).
### Results
#### Container Health
| Metric | Value | Threshold | Status |
|--------|-------|-----------|--------|
| Total Coverage | **85.3%** | 85% | ✅ PASS |
| Test Failures | **0** | 0 | ✅ PASS |
```json
{
"build_time": "unknown",
"git_commit": "unknown",
"internal_ip": "172.20.0.9",
"service": "Charon",
"status": "ok",
"version": "dev"
}
```
### Package Coverage Breakdown
| Package | Coverage |
|---------|----------|
| `internal/util` | 100.0% |
| `internal/cerberus` | 100.0% |
| `internal/config` | 100.0% |
| `internal/metrics` | 100.0% |
| `internal/version` | 100.0% |
| `internal/middleware` | 99.1% |
| `internal/caddy` | 98.9% |
| `internal/models` | 98.1% |
| `internal/database` | 91.3% |
| `internal/server` | 90.9% |
| `internal/logger` | 85.7% |
| `internal/services` | 84.8% |
| `internal/api/handlers` | 84.0% |
| `internal/crowdsec` | 83.3% |
| `internal/api/routes` | 83.2% |
---
**Audited by:** QA_Security Agent - The Auditor
**Signature:** ✅ APPROVED FOR PRODUCTION
## 2. Frontend Coverage Tests
### Command Executed
```bash
cd frontend && npm run test:coverage
```
### Results
| Metric | Value | Threshold | Status |
|--------|-------|-----------|--------|
| Statement Coverage | **87.59%** | 85% | ✅ PASS |
| Branch Coverage | **79.15%** | N/A | INFO |
| Function Coverage | **81.1%** | N/A | INFO |
| Line Coverage | **88.44%** | N/A | INFO |
| Tests Passed | **1138** | - | ✅ PASS |
| Tests Skipped | **2** | - | INFO |
| Test Failures | **0** | 0 | ✅ PASS |
| Test Files | **107** | - | ✅ PASS |
### Duration
- Total Duration: 108.12s
---
## 3. TypeScript Type Safety Check
### Command Executed
```bash
cd frontend && npm run type-check
```
### Results
| Metric | Value | Threshold | Status |
|--------|-------|-----------|--------|
| Type Errors | **0** | 0 | ✅ PASS |
---
## 4. Pre-commit Hooks
### Command Executed
```bash
pre-commit run --all-files
```
### Results
| Hook | Status |
|------|--------|
| fix end of files | ✅ Passed |
| trim trailing whitespace | ✅ Passed |
| check yaml | ✅ Passed |
| check for added large files | ✅ Passed |
| dockerfile validation | ✅ Passed |
| Go Vet | ✅ Passed |
| Check .version matches latest Git tag | ✅ Passed |
| Prevent large files that are not tracked by LFS | ✅ Passed |
| Prevent committing CodeQL DB artifacts | ✅ Passed |
| Prevent committing data/backups files | ✅ Passed |
| Frontend TypeScript Check | ✅ Passed |
| Frontend Lint (Fix) | ✅ Passed |
---
## 5. Security Scans
### Trivy Filesystem Scan
#### Command Executed
```bash
docker run --rm -v "$(pwd):/app:ro" -w /app aquasec/trivy:latest fs \
--scanners vuln,misconfig --severity HIGH,CRITICAL .
```
#### Results
| Target | Type | Vulnerabilities | Misconfigurations |
|--------|------|-----------------|-------------------|
| package-lock.json | npm | **0** | - |
| Severity | Count | Threshold | Status |
|----------|-------|-----------|--------|
| CRITICAL | **0** | 0 | ✅ PASS |
| HIGH | **0** | 0 | ✅ PASS |
---
## 6. Go Vulnerability Check
### Command Executed
```bash
govulncheck ./...
```
### Results
| Metric | Value | Threshold | Status |
|--------|-------|-----------|--------|
| Known Vulnerabilities | **0** | 0 | ✅ PASS |
---
## 7. Crypto Utility Tests (Issue #365 Specific)
### Command Executed
```bash
cd backend && go test -v -cover ./internal/util/...
```
### Test Cases Verified
#### ConstantTimeCompare Function
| Test Case | Status |
|-----------|--------|
| equal strings | ✅ PASS |
| different strings | ✅ PASS |
| different lengths | ✅ PASS |
| empty strings | ✅ PASS |
| one empty | ✅ PASS |
| unicode equal | ✅ PASS |
| unicode different | ✅ PASS |
| special chars equal | ✅ PASS |
| whitespace matters | ✅ PASS |
#### ConstantTimeCompareBytes Function
| Test Case | Status |
|-----------|--------|
| equal bytes | ✅ PASS |
| different bytes | ✅ PASS |
| different lengths | ✅ PASS |
| empty slices | ✅ PASS |
| nil slices | ✅ PASS |
#### SanitizeForLog Function
| Test Case | Status |
|-----------|--------|
| empty string | ✅ PASS |
| clean string | ✅ PASS |
| string with newline | ✅ PASS |
| string with carriage return and newline | ✅ PASS |
| string with multiple newlines | ✅ PASS |
| string with control characters | ✅ PASS |
| string with DEL character | ✅ PASS |
| complex string with mixed control chars | ✅ PASS |
| string with tabs | ✅ PASS |
| string with only control chars | ✅ PASS |
### Coverage
| Package | Coverage |
|---------|----------|
| `internal/util` | **100.0%** |
---
## 8. Files Changed in Issue #365
| File | Status |
|------|--------|
| `backend/internal/util/crypto.go` | ✅ New - 100% covered |
| `backend/internal/util/crypto_test.go` | ✅ New - All tests pass |
| `backend/internal/api/handlers/user_handler.go` | ✅ Modified - Tests pass |
| `docs/security.md` | ✅ Modified - Documentation updated |
| `docs/getting-started.md` | ✅ Modified - Documentation updated |
| `docs/security-incident-response.md` | ✅ New - Documentation added |
| `.github/workflows/docker-build.yml` | ✅ Modified - CI workflow |
| `.gitignore` | ✅ Modified |
| `.dockerignore` | ✅ Modified |
---
## 9. Issues Found
**None.** All tests pass, coverage thresholds are met, and no security vulnerabilities were detected.
---
## 10. Summary
### Pass/Fail Counts
| Category | Passed | Failed | Skipped |
|----------|--------|--------|---------|
| Backend Tests | All | 0 | 0 |
| Frontend Tests | 1138 | 0 | 2 |
| Pre-commit Hooks | 12 | 0 | 0 |
| Security Scans | 2 | 0 | 0 |
### Coverage Percentages
| Component | Coverage | Threshold | Delta |
|-----------|----------|-----------|-------|
| Backend | 85.3% | 85% | +0.3% |
| Frontend | 87.59% | 85% | +2.59% |
### Security Scan Results
| Scanner | Critical | High | Medium | Low |
|---------|----------|------|--------|-----|
| Trivy | 0 | 0 | - | - |
| govulncheck | 0 | 0 | 0 | 0 |
---
## Final Verdict
# ✅ PASS
All QA and security testing requirements have been met for Issue #365 - Additional Security Enhancements:
1. ✅ Backend coverage: 85.3% (≥85% threshold)
2. ✅ Frontend coverage: 87.59% (≥85% threshold)
3. ✅ Zero type errors
4. ✅ All pre-commit hooks pass
5. ✅ Zero Critical/High security vulnerabilities
6. ✅ Zero Go vulnerabilities
7. ✅ All new crypto utility tests pass with 100% coverage
**The branch `feature/issue-365-additional-security` is ready for merge.**
---
*Report generated: 2025-12-21*
*QA Agent: QA_Security*

View File

@@ -0,0 +1,400 @@
```markdown
---
title: Security Incident Response Plan
description: Industry-standard incident response procedures for Charon deployments, including detection, containment, recovery, and post-incident review.
---
## Security Incident Response Plan (SIRP)
This document provides a structured approach to handling security incidents in Charon deployments. Following these procedures ensures consistent, effective responses that minimize damage and recovery time.
---
## Incident Classification
### Severity Levels
| Level | Name | Description | Response Time | Examples |
|-------|------|-------------|---------------|----------|
| **P1** | Critical | Active exploitation, data breach, or complete service compromise | Immediate (< 15 min) | Confirmed data exfiltration, ransomware, root access compromise |
| **P2** | High | Attempted exploitation, security control bypass, or significant vulnerability | < 1 hour | WAF bypass detected, brute-force attack in progress, credential stuffing |
| **P3** | Medium | Suspicious activity, minor vulnerability, or policy violation | < 4 hours | Unusual traffic patterns, failed authentication spike, misconfiguration |
| **P4** | Low | Informational security events, minor policy deviations | < 24 hours | Routine blocked requests, scanner traffic, expired certificates |
### Classification Criteria
**Escalate to P1 immediately if:**
- ❌ Confirmed unauthorized access to sensitive data
- ❌ Active malware or ransomware detected
- ❌ Complete loss of security controls
- ❌ Evidence of data exfiltration
- ❌ Critical infrastructure compromise
**Escalate to P2 if:**
- ⚠️ Multiple failed bypass attempts from same source
- ⚠️ Vulnerability actively being probed
- ⚠️ Partial security control failure
- ⚠️ Credential compromise suspected
---
## Detection Methods
### Automated Detection
**Cerberus Security Dashboard:**
1. Navigate to **Cerberus → Dashboard**
2. Monitor the **Live Activity** section for real-time events
3. Review **Security → Decisions** for blocked requests
4. Check alert notifications (Discord, Slack, email)
**Key Indicators to Monitor:**
- Sudden spike in blocked requests
- Multiple blocks from same IP/network
- WAF rules triggering on unusual patterns
- CrowdSec decisions for known threat actors
- Rate limiting thresholds exceeded
**Log Analysis:**
```bash
# View recent security events
docker logs charon | grep -E "(BLOCK|DENY|ERROR)" | tail -100
# Check CrowdSec decisions
docker exec charon cscli decisions list
# Review WAF activity
docker exec charon cat /var/log/coraza-waf.log | tail -50
```
### Manual Detection
**Regular Security Reviews:**
- [ ] Weekly review of Cerberus Dashboard
- [ ] Monthly review of access patterns
- [ ] Quarterly penetration testing
- [ ] Annual security audit
---
## Containment Procedures
### Immediate Actions (All Severity Levels)
1. **Document the incident start time**
2. **Preserve evidence** — Do NOT restart containers until logs are captured
3. **Assess scope** — Determine affected systems and data
### P1/P2 Containment
**Step 1: Isolate the Threat**
```bash
# Block attacking IP immediately
docker exec charon cscli decisions add --ip <ATTACKER_IP> --duration 720h --reason "Incident response"
# If compromise confirmed, stop the container
docker stop charon
# Preserve container state for forensics
docker commit charon charon-incident-$(date +%Y%m%d%H%M%S)
```
**Step 2: Preserve Evidence**
```bash
# Export all logs
docker logs charon > /tmp/incident-logs-$(date +%Y%m%d%H%M%S).txt 2>&1
# Export CrowdSec decisions
docker exec charon cscli decisions list -o json > /tmp/crowdsec-decisions.json
# Copy data directory
cp -r ./charon-data /tmp/incident-backup-$(date +%Y%m%d%H%M%S)
```
**Step 3: Notify Stakeholders**
- System administrators
- Security team (if applicable)
- Management (P1 only)
- Legal/compliance (if data breach)
### P3/P4 Containment
1. Block offending IPs via Cerberus Dashboard
2. Review and update access lists if needed
3. Document the event in incident log
4. Continue monitoring
---
## Recovery Steps
### Pre-Recovery Checklist
- [ ] Incident fully contained
- [ ] Evidence preserved
- [ ] Root cause identified (or investigation ongoing)
- [ ] Clean backups available
### Recovery Procedure
**Step 1: Verify Backup Integrity**
```bash
# List available backups
ls -la ./charon-data/backups/
# Verify backup can be read
docker run --rm -v ./charon-data/backups:/backups alpine ls -la /backups
```
**Step 2: Restore from Clean State**
```bash
# Stop compromised instance
docker stop charon
# Rename compromised data
mv ./charon-data ./charon-data-compromised-$(date +%Y%m%d)
# Restore from backup
cp -r ./charon-data-backup-YYYYMMDD ./charon-data
# Start fresh instance
docker-compose up -d
```
**Step 3: Apply Security Hardening**
1. Review and update all access lists
2. Rotate any potentially compromised credentials
3. Update Charon to latest version
4. Enable additional security features if not already active
**Step 4: Verify Recovery**
```bash
# Check Charon is running
docker ps | grep charon
# Verify LAPI status
docker exec charon cscli lapi status
# Test proxy functionality
curl -I https://your-proxied-domain.com
```
### Communication During Recovery
- Update stakeholders every 30 minutes (P1) or hourly (P2)
- Document all recovery actions taken
- Prepare user communication if service was affected
---
## Post-Incident Review
### Review Meeting Agenda
Schedule within 48 hours of incident resolution.
**Attendees:** All involved responders, system owners, management (P1/P2)
**Agenda:**
1. Incident timeline reconstruction
2. What worked well?
3. What could be improved?
4. Action items and owners
5. Documentation updates needed
### Post-Incident Checklist
- [ ] Incident fully documented
- [ ] Timeline created with all actions taken
- [ ] Root cause analysis completed
- [ ] Lessons learned documented
- [ ] Security controls reviewed and updated
- [ ] Monitoring/alerting improved
- [ ] Team training needs identified
- [ ] Documentation updated
### Incident Report Template
```markdown
## Incident Report: [INCIDENT-YYYY-MM-DD-###]
**Severity:** P1/P2/P3/P4
**Status:** Resolved / Under Investigation
**Duration:** [Start Time] to [End Time]
### Summary
[Brief description of what happened]
### Timeline
- [HH:MM] - Event detected
- [HH:MM] - Containment initiated
- [HH:MM] - Root cause identified
- [HH:MM] - Recovery completed
### Impact
- Systems affected: [List]
- Data affected: [Yes/No, details]
- Users affected: [Count/scope]
- Service downtime: [Duration]
### Root Cause
[Technical explanation of what caused the incident]
### Actions Taken
1. [Action 1]
2. [Action 2]
3. [Action 3]
### Lessons Learned
- [Learning 1]
- [Learning 2]
### Follow-up Actions
| Action | Owner | Due Date | Status |
|--------|-------|----------|--------|
| [Action] | [Name] | [Date] | Open |
```
---
## Communication Templates
### Internal Notification (P1/P2)
```
SECURITY INCIDENT ALERT
Severity: [P1/P2]
Time Detected: [YYYY-MM-DD HH:MM UTC]
Status: [Active / Contained / Resolved]
Summary:
[Brief description]
Current Actions:
- [Action being taken]
Next Update: [Time]
Contact: [Incident Commander Name/Channel]
```
### User Communication (If Service Affected)
```
Service Notification
We are currently experiencing [brief issue description].
Status: [Investigating / Identified / Monitoring / Resolved]
Started: [Time]
Expected Resolution: [Time or "Under investigation"]
We apologize for any inconvenience and will provide updates as available.
Last Updated: [Time]
```
### Post-Incident Summary (External)
```
Security Incident Summary
On [Date], we identified and responded to a security incident affecting [scope].
What happened:
[Non-technical summary]
What we did:
[Response actions taken]
What we're doing to prevent this:
[Improvements being made]
Was my data affected?
[Clear statement about data impact]
Questions?
[Contact information]
```
---
## Emergency Contacts
Maintain an up-to-date contact list:
| Role | Contact Method | Escalation Time |
|------|----------------|-----------------|
| Primary On-Call | [Phone/Pager] | Immediate |
| Security Team | [Email/Slack] | < 15 min (P1/P2) |
| System Administrator | [Phone] | < 1 hour |
| Management | [Phone] | P1 only |
---
## Quick Reference Card
### P1 Critical — Immediate Response
1. ⏱️ Start timer, document everything
2. 🔒 Isolate: `docker stop charon`
3. 📋 Preserve: `docker logs charon > incident.log`
4. 📞 Notify: Security team, management
5. 🔍 Investigate: Determine scope and root cause
6. 🔧 Recover: Restore from clean backup
7. 📝 Review: Post-incident meeting within 48h
### P2 High — Urgent Response
1. 🔒 Block attacker: `cscli decisions add --ip <IP>`
2. 📋 Capture logs before they rotate
3. 📞 Notify: Security team
4. 🔍 Investigate root cause
5. 🔧 Apply fixes
6. 📝 Document and review
### Key Commands
```bash
# Block IP immediately
docker exec charon cscli decisions add --ip <IP> --duration 720h
# List all active blocks
docker exec charon cscli decisions list
# Export logs
docker logs charon > incident-$(date +%s).log 2>&1
# Check security status
docker exec charon cscli lapi status
```
---
## Document Maintenance
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.0 | 2025-12-21 | Security Team | Initial SIRP creation |
**Review Schedule:** Quarterly or after any P1/P2 incident
**Owner:** Security Team
**Last Reviewed:** 2025-12-21
```

View File

@@ -615,6 +615,95 @@ Remove the security lines from `docker-compose.yml` and restart.
---
## TLS Security
### TLS Version Enforcement
Charon (via Caddy) enforces a minimum TLS version of 1.2 by default. This prevents TLS downgrade attacks that attempt to force connections to use vulnerable TLS 1.0 or 1.1.
**What's Protected:**
- ✅ TLS 1.0/1.1 downgrade attacks
- ✅ BEAST, POODLE, and similar protocol-level attacks
- ✅ Weak cipher suite negotiation
**HSTS (HTTP Strict Transport Security):**
Charon sets HSTS headers with:
- `max-age=31536000` (1 year)
- `includeSubDomains`
- `preload` (for browser preload lists)
This ensures browsers always use HTTPS after the first visit.
---
## DNS Security
### Protecting Against DNS Hijacking
While Charon cannot directly control your DNS resolver, you can protect against DNS hijacking and cache poisoning by configuring your host to use encrypted DNS.
**Docker Host Configuration (systemd-resolved):**
```bash
# /etc/systemd/resolved.conf
[Resolve]
DNS=1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com
DNSOverTLS=yes
```
Then restart: `sudo systemctl restart systemd-resolved`
**Alternative DNS Providers with DoH/DoT:**
- Cloudflare: `1.1.1.1` / `1.0.0.1`
- Google: `8.8.8.8` / `8.8.4.4`
- Quad9: `9.9.9.9`
**Additional DNS Protections:**
1. **DNSSEC**: Enable at your domain registrar to prevent DNS spoofing
2. **CAA Records**: Restrict which Certificate Authorities can issue certificates for your domain
---
## Container Hardening
### Running Charon with Maximum Security
For production deployments, apply these Docker security configurations:
```yaml
services:
charon:
image: ghcr.io/wikid82/charon:latest
read_only: true
tmpfs:
- /tmp:size=100M
- /config:size=50M
- /data/logs:size=100M
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
security_opt:
- no-new-privileges:true
volumes:
- ./data:/data
```
**Security Options Explained:**
- `read_only: true` — Prevents filesystem modifications (defense against malware)
- `cap_drop: ALL` — Removes all Linux capabilities
- `cap_add: NET_BIND_SERVICE` — Only allows binding to ports (required for reverse proxy)
- `no-new-privileges` — Prevents privilege escalation attacks
- `tmpfs` mounts — Provides writable directories for logs and temp files without persistent storage
---
## Common Questions
### "Will this slow down my websites?"