From 2dfe7ee2416ef5be403bfc399044ed78a282dddc Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 21 Dec 2025 19:00:29 +0000 Subject: [PATCH] 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 --- .dockerignore | 5 + .github/workflows/docker-build.yml | 21 + .github/workflows/docs-to-issues.yml | 2 +- .gitignore | 5 + backend/internal/api/handlers/user_handler.go | 8 + backend/internal/util/crypto.go | 21 + backend/internal/util/crypto_test.go | 82 +++ docs/getting-started.md | 36 ++ .../created/issue-365-manual-test-plan.md | 99 +++ docs/reports/qa_report.md | 568 +++++++++--------- docs/security-incident-response.md | 400 ++++++++++++ docs/security.md | 89 +++ 12 files changed, 1046 insertions(+), 290 deletions(-) create mode 100644 backend/internal/util/crypto.go create mode 100644 backend/internal/util/crypto_test.go create mode 100644 docs/issues/created/issue-365-manual-test-plan.md create mode 100644 docs/security-incident-response.md diff --git a/.dockerignore b/.dockerignore index c3be57d0..19ab8eca 100644 --- a/.dockerignore +++ b/.dockerignore @@ -165,6 +165,11 @@ coverage.out *.crdownload *.sarif +# ----------------------------------------------------------------------------- +# SBOM artifacts +# ----------------------------------------------------------------------------- +sbom*.json + # ----------------------------------------------------------------------------- # CodeQL & Security Scanning (large, not needed) # ----------------------------------------------------------------------------- diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 68c28d16..b6e284c5 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -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: | diff --git a/.github/workflows/docs-to-issues.yml b/.github/workflows/docs-to-issues.yml index b72c6c16..04411fe0 100644 --- a/.github/workflows/docs-to-issues.yml +++ b/.github/workflows/docs-to-issues.yml @@ -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)' diff --git a/.gitignore b/.gitignore index 96576515..94281a0b 100644 --- a/.gitignore +++ b/.gitignore @@ -229,6 +229,11 @@ test-results/local.har # ----------------------------------------------------------------------------- /trivy-*.txt +# ----------------------------------------------------------------------------- +# SBOM artifacts +# ----------------------------------------------------------------------------- +sbom*.json + # ----------------------------------------------------------------------------- # Docker Overrides (new location) # ----------------------------------------------------------------------------- diff --git a/backend/internal/api/handlers/user_handler.go b/backend/internal/api/handlers/user_handler.go index 074e2560..8d4b1464 100644 --- a/backend/internal/api/handlers/user_handler.go +++ b/backend/internal/api/handlers/user_handler.go @@ -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 diff --git a/backend/internal/util/crypto.go b/backend/internal/util/crypto.go new file mode 100644 index 00000000..d51db891 --- /dev/null +++ b/backend/internal/util/crypto.go @@ -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 +} diff --git a/backend/internal/util/crypto_test.go b/backend/internal/util/crypto_test.go new file mode 100644 index 00000000..d67635a8 --- /dev/null +++ b/backend/internal/util/crypto_test.go @@ -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) + } + }) +} diff --git a/docs/getting-started.md b/docs/getting-started.md index 83e1c9f3..5035c695 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -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! diff --git a/docs/issues/created/issue-365-manual-test-plan.md b/docs/issues/created/issue-365-manual-test-plan.md new file mode 100644 index 00000000..5d1f272b --- /dev/null +++ b/docs/issues/created/issue-365-manual-test-plan.md @@ -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 diff --git a/docs/reports/qa_report.md b/docs/reports/qa_report.md index 868e7515..8e154926 100644 --- a/docs/reports/qa_report.md +++ b/docs/reports/qa_report.md @@ -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* diff --git a/docs/security-incident-response.md b/docs/security-incident-response.md new file mode 100644 index 00000000..43f63410 --- /dev/null +++ b/docs/security-incident-response.md @@ -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 --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 ` +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 --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 +``` diff --git a/docs/security.md b/docs/security.md index ba97316e..05da0a0e 100644 --- a/docs/security.md +++ b/docs/security.md @@ -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?"