1132 lines
35 KiB
Markdown
1132 lines
35 KiB
Markdown
# c-ares Security Vulnerability Remediation Plan (CVE-2025-62408)
|
|
|
|
**Version:** 1.0
|
|
**Date:** 2025-12-14
|
|
**Status:** 🟡 MEDIUM Priority - Security vulnerability in Alpine package dependency
|
|
**Severity:** MEDIUM (CVSS 5.9)
|
|
**Component:** c-ares (Alpine package)
|
|
**Affected Version:** 1.34.5-r0
|
|
**Fixed Version:** 1.34.6-r0
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
A Trivy security scan has identified **CVE-2025-62408** in the c-ares library (version 1.34.5-r0) used by
|
|
Charon's Docker container. The vulnerability is a **use-after-free** bug that can cause
|
|
**Denial of Service (DoS)** attacks. The fix requires updating Alpine packages to pull c-ares 1.34.6-r0.
|
|
|
|
**Key Finding:** No Dockerfile changes required - rebuilding the image will automatically pull the patched
|
|
version via `apk upgrade`.
|
|
|
|
---
|
|
|
|
## Implementation Status
|
|
|
|
**✅ COMPLETED** - Weekly security rebuild workflow has been implemented to proactively detect and address security vulnerabilities.
|
|
|
|
**What Was Implemented:**
|
|
|
|
- Created `.github/workflows/security-weekly-rebuild.yml`
|
|
- Scheduled to run every Sunday at 04:00 UTC
|
|
- Forces fresh Alpine package downloads using `--no-cache`
|
|
- Runs comprehensive Trivy scans (CRITICAL, HIGH, MEDIUM severities)
|
|
- Uploads results to GitHub Security tab
|
|
- Archives scan results for 90-day retention
|
|
|
|
**Next Scheduled Run:**
|
|
|
|
- **First run:** Sunday, December 15, 2025 at 04:00 UTC
|
|
- **Frequency:** Weekly (every Sunday)
|
|
|
|
**Benefits:**
|
|
|
|
- Catches CVEs within 7-day window (acceptable for Charon's threat model)
|
|
- No impact on development velocity (separate from PR/push builds)
|
|
- Automated security monitoring with zero manual intervention
|
|
- Provides early warning of breaking package updates
|
|
|
|
**Related Documentation:**
|
|
|
|
- Workflow file: [.github/workflows/security-weekly-rebuild.yml](../../.github/workflows/security-weekly-rebuild.yml)
|
|
- Security guide: [docs/security.md](../security.md)
|
|
|
|
---
|
|
|
|
## Root Cause Analysis
|
|
|
|
### 1. What is c-ares?
|
|
|
|
**c-ares** is a C library for asynchronous DNS requests. It is:
|
|
|
|
- **Low-level networking library** used by curl and other HTTP clients
|
|
- **Alpine Linux package** installed as a dependency of `libcurl`
|
|
- **Not directly installed** by Charon's Dockerfile but pulled in automatically
|
|
|
|
### 2. Where is c-ares Used in Charon?
|
|
|
|
c-ares is a **transitive dependency** installed via Alpine's package manager (apk):
|
|
|
|
```text
|
|
Alpine Linux 3.23
|
|
└─ curl (8.17.0-r1) ← Explicitly installed in Dockerfile:210
|
|
└─ libcurl (8.17.0-r1)
|
|
└─ c-ares (1.34.5-r0) ← Vulnerable version
|
|
```
|
|
|
|
**Dockerfile locations:**
|
|
|
|
- **Line 210:** `RUN apk --no-cache add ca-certificates sqlite-libs tzdata curl gettext \`
|
|
- **Line 217:** `curl -L "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb" \`
|
|
|
|
**Components that depend on curl:**
|
|
|
|
1. **Runtime stage** (final image) - Uses curl to download GeoLite2 database
|
|
2. **CrowdSec installer stage** - Uses curl to download CrowdSec binaries (line 184)
|
|
|
|
### 3. CVE-2025-62408 Details
|
|
|
|
**Description:**
|
|
c-ares versions 1.32.3 through 1.34.5 terminate a query after maximum attempts when using `read_answer()`
|
|
and `process_answer()`, which can cause a **Denial of Service (DoS)**.
|
|
|
|
**CVSS 3.1 Score:** 5.9 MEDIUM
|
|
|
|
- **Attack Vector:** Network (AV:N)
|
|
- **Attack Complexity:** High (AC:H)
|
|
- **Privileges Required:** None (PR:N)
|
|
- **User Interaction:** None (UI:N)
|
|
- **Scope:** Unchanged (S:U)
|
|
- **Confidentiality:** None (C:N)
|
|
- **Integrity:** None (I:N)
|
|
- **Availability:** High (A:H)
|
|
|
|
**CWE Classification:** CWE-416 (Use After Free)
|
|
|
|
**Vulnerability Type:** Denial of Service (DoS) via use-after-free in DNS query handling
|
|
|
|
**Fixed In:** c-ares 1.34.6-r0 (Alpine package update)
|
|
|
|
**References:**
|
|
|
|
- NVD: <https://nvd.nist.gov/vuln/detail/CVE-2025-62408>
|
|
- GitHub Advisory: <https://github.com/c-ares/c-ares/security/advisories/GHSA-jq53-42q6-pqr5>
|
|
- Fix Commit: <https://github.com/c-ares/c-ares/commit/714bf5675c541bd1e668a8db8e67ce012651e618>
|
|
|
|
### 4. Impact Assessment for Charon
|
|
|
|
**Risk Level:** 🟡 **LOW to MEDIUM**
|
|
|
|
**Reasons:**
|
|
|
|
1. **Limited Attack Surface:**
|
|
- c-ares is only used during **container initialization** (downloading GeoLite2 database)
|
|
- Not exposed to user traffic or runtime DNS queries
|
|
- curl operations happen at startup, not continuously
|
|
|
|
2. **Attack Requirements:**
|
|
- Attacker must control DNS responses for `github.com` (GeoLite2 download)
|
|
- Requires Man-in-the-Middle (MitM) position during container startup
|
|
- High attack complexity (AC:H in CVSS)
|
|
|
|
3. **Worst-Case Scenario:**
|
|
- Container startup fails due to DoS during curl download
|
|
- No data breach, no code execution, no persistence
|
|
- Recovery: restart container
|
|
|
|
**Recommendation:** **Apply fix as standard maintenance** (not emergency hotfix)
|
|
|
|
---
|
|
|
|
## Remediation Plan
|
|
|
|
### Option A: Rebuild Image with Package Updates (RECOMMENDED)
|
|
|
|
**Rationale:** Alpine Linux automatically pulls the latest package versions when `apk upgrade` is run. Since
|
|
c-ares 1.34.6-r0 is available in the Alpine 3.23 repositories, a simple rebuild will pull the fixed version.
|
|
|
|
#### Implementation Strategy
|
|
|
|
**No Dockerfile changes required!** The fix happens automatically when:
|
|
|
|
1. Docker build process runs `apk --no-cache upgrade` (Dockerfile line 211)
|
|
2. Alpine's package manager detects c-ares 1.34.5-r0 is outdated
|
|
3. Upgrades to c-ares 1.34.6-r0 automatically
|
|
|
|
#### File Changes Required
|
|
|
|
**None.** The existing Dockerfile already includes:
|
|
|
|
```dockerfile
|
|
# Line 210-211 (Final runtime stage)
|
|
RUN apk --no-cache add ca-certificates sqlite-libs tzdata curl gettext \
|
|
&& apk --no-cache upgrade
|
|
```
|
|
|
|
The `apk upgrade` command will automatically pull c-ares 1.34.6-r0 on the next build.
|
|
|
|
#### Action Items
|
|
|
|
1. **Trigger new Docker build** via one of these methods:
|
|
- Push a commit with `feat:`, `fix:`, or `perf:` prefix (triggers CI build)
|
|
- Manually trigger Docker build workflow in GitHub Actions
|
|
- Run local build: `docker build --no-cache -t charon:test .`
|
|
|
|
2. **Verify fix after build:**
|
|
|
|
```bash
|
|
# Check c-ares version in built image
|
|
docker run --rm charon:test sh -c "apk info c-ares"
|
|
# Expected output: c-ares-1.34.6-r0
|
|
```
|
|
|
|
3. **Run Trivy scan to confirm:**
|
|
|
|
```bash
|
|
docker run --rm -v $(pwd):/app aquasec/trivy:latest image charon:test
|
|
# Should not show CVE-2025-62408
|
|
```
|
|
|
|
---
|
|
|
|
### Option B: Explicit Package Pinning (NOT RECOMMENDED)
|
|
|
|
**Rationale:** Explicitly pin c-ares version in Dockerfile for guaranteed version control.
|
|
|
|
**Downsides:**
|
|
|
|
- Requires manual updates for future c-ares versions
|
|
- Renovate doesn't automatically track Alpine packages
|
|
- Adds maintenance overhead
|
|
|
|
**File Changes (if pursuing this option):**
|
|
|
|
```dockerfile
|
|
# Line 210-211 (Change)
|
|
RUN apk --no-cache add ca-certificates sqlite-libs tzdata curl gettext \
|
|
c-ares>=1.34.6-r0 \
|
|
&& apk --no-cache upgrade
|
|
```
|
|
|
|
**Not recommended** because Alpine's package manager already handles this automatically via `apk upgrade`.
|
|
|
|
---
|
|
|
|
## Recommended Implementation: Option A (Rebuild)
|
|
|
|
### Step-by-Step Remediation
|
|
|
|
#### Step 1: Trigger Docker Build
|
|
|
|
##### Method 1: Push fix commit (Recommended)
|
|
|
|
```bash
|
|
# Create empty commit to trigger build
|
|
git commit --allow-empty -m "chore: rebuild image to pull c-ares 1.34.6 (CVE-2025-62408 fix)"
|
|
git push origin main
|
|
```
|
|
|
|
##### Method 2: Manually trigger GitHub Actions
|
|
|
|
1. Go to Actions → Docker Build workflow
|
|
2. Click "Run workflow"
|
|
3. Select branch: `main` or `development`
|
|
|
|
##### Method 3: Local build and test
|
|
|
|
```bash
|
|
# Build locally with no cache to force package updates
|
|
docker build --no-cache -t charon:c-ares-fix .
|
|
|
|
# Verify c-ares version
|
|
docker run --rm charon:c-ares-fix sh -c "apk info c-ares"
|
|
|
|
# Test container starts correctly
|
|
docker run --rm -p 8080:8080 charon:c-ares-fix
|
|
```
|
|
|
|
#### Step 2: Verify the Fix
|
|
|
|
After the Docker image is built, verify the c-ares version:
|
|
|
|
```bash
|
|
# Check installed version
|
|
docker run --rm charon:latest sh -c "apk info c-ares"
|
|
|
|
# Expected output:
|
|
# c-ares-1.34.6-r0 description:
|
|
# c-ares-1.34.6-r0 webpage:
|
|
# c-ares-1.34.6-r0 installed size:
|
|
```
|
|
|
|
#### Step 3: Run Security Scan
|
|
|
|
Run Trivy to confirm CVE-2025-62408 is resolved:
|
|
|
|
```bash
|
|
# Scan the built image
|
|
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
|
|
aquasec/trivy:latest image charon:latest
|
|
|
|
# Alternative: Scan filesystem (faster for local testing)
|
|
docker run --rm -v $(pwd):/app aquasec/trivy:latest fs /app
|
|
```
|
|
|
|
**Expected result:** CVE-2025-62408 should NOT appear in the scan output.
|
|
|
|
#### Step 4: Validate Container Functionality
|
|
|
|
Ensure the container still works correctly after the rebuild:
|
|
|
|
```bash
|
|
# Start container
|
|
docker run --rm -d --name charon-test \
|
|
-p 8080:8080 \
|
|
-v $(pwd)/data:/app/data \
|
|
charon:latest
|
|
|
|
# Check logs
|
|
docker logs charon-test
|
|
|
|
# Verify Charon API responds
|
|
curl -v http://localhost:8080/api/health
|
|
|
|
# Verify Caddy responds
|
|
curl -v http://localhost:8080/
|
|
|
|
# Stop test container
|
|
docker stop charon-test
|
|
```
|
|
|
|
#### Step 5: Run Test Suite
|
|
|
|
Execute the test suite to ensure no regressions:
|
|
|
|
```bash
|
|
# Backend tests
|
|
cd backend && go test ./...
|
|
|
|
# Frontend tests
|
|
cd frontend && npm run test
|
|
|
|
# Integration tests (if applicable)
|
|
bash scripts/integration-test.sh
|
|
```
|
|
|
|
#### Step 6: Update Documentation
|
|
|
|
**No documentation changes needed** for this fix, but optionally update:
|
|
|
|
- [CHANGELOG.md](../../CHANGELOG.md) - Add entry under "Security" section
|
|
- [docs/security.md](../security.md) - No changes needed (vulnerability in transitive dependency)
|
|
|
|
---
|
|
|
|
## Testing Checklist
|
|
|
|
Before deploying the fix:
|
|
|
|
- [ ] Docker build completes successfully
|
|
- [ ] c-ares version is 1.34.6-r0 or higher
|
|
- [ ] Trivy scan shows no CVE-2025-62408
|
|
- [ ] Container starts without errors
|
|
- [ ] Charon API endpoint responds (<http://localhost:8080/api/health>)
|
|
- [ ] Frontend loads correctly (<http://localhost:8080/>)
|
|
- [ ] Caddy admin API responds (<http://localhost:2019/>)
|
|
- [ ] GeoLite2 database downloads during startup
|
|
- [ ] Backend tests pass: `cd backend && go test ./...`
|
|
- [ ] Frontend tests pass: `cd frontend && npm run test`
|
|
- [ ] Pre-commit checks pass: `pre-commit run --all-files`
|
|
|
|
---
|
|
|
|
## Potential Side Effects
|
|
|
|
### 1. Alpine Package Updates
|
|
|
|
The `apk upgrade` command may update other packages beyond c-ares. This is **expected and safe**
|
|
because:
|
|
|
|
- Alpine 3.23 is a stable release with tested package combinations
|
|
- Upgrades are limited to patch/minor versions within 3.23
|
|
- No ABI breaks expected within stable branch
|
|
|
|
**Risk:** Low
|
|
**Mitigation:** Verify container functionality after build (Step 4 above)
|
|
|
|
### 2. curl Behavior Changes
|
|
|
|
c-ares is a DNS resolver library. The 1.34.6 fix addresses a use-after-free bug, which could theoretically
|
|
affect DNS resolution behavior.
|
|
|
|
**Risk:** Very Low
|
|
**Mitigation:** Test GeoLite2 database download during container startup
|
|
|
|
### 3. Build Cache Invalidation
|
|
|
|
Using `--no-cache` during local builds will rebuild all stages, increasing build time.
|
|
|
|
**Risk:** None (just slower builds)
|
|
**Mitigation:** Use `--no-cache` only for verification, then allow normal cached builds
|
|
|
|
### 4. CI/CD Pipeline
|
|
|
|
GitHub Actions workflows cache Docker layers. The first build after this fix may take longer.
|
|
|
|
**Risk:** None (just longer CI time)
|
|
**Mitigation:** None needed - subsequent builds will be cached normally
|
|
|
|
---
|
|
|
|
## Rollback Plan
|
|
|
|
If the update causes unexpected issues:
|
|
|
|
### Quick Rollback (Emergency)
|
|
|
|
1. **Revert to previous Docker image:**
|
|
|
|
```bash
|
|
# Find previous working image
|
|
docker images charon
|
|
|
|
# Tag previous image as latest
|
|
docker tag charon:<previous-sha> charon:latest
|
|
|
|
# Or pull previous version from registry
|
|
docker pull ghcr.io/wikid82/charon:<previous-version>
|
|
```
|
|
|
|
2. **Restart containers:**
|
|
|
|
```bash
|
|
docker-compose down
|
|
docker-compose up -d
|
|
```
|
|
|
|
### Proper Rollback (If Issue Confirmed)
|
|
|
|
1. **Pin c-ares to known-good version:**
|
|
|
|
```dockerfile
|
|
RUN apk --no-cache add ca-certificates sqlite-libs tzdata curl gettext \
|
|
c-ares=1.34.5-r0 \
|
|
&& apk --no-cache upgrade --ignore c-ares
|
|
```
|
|
|
|
2. **Document the issue:**
|
|
- Create GitHub issue describing the problem
|
|
- Link to Alpine bug tracker if applicable
|
|
- Monitor for upstream fix
|
|
|
|
3. **Re-test after upstream fix:**
|
|
- Check Alpine package updates
|
|
- Remove version pin when fix is available
|
|
- Rebuild and re-verify
|
|
|
|
---
|
|
|
|
## Commit Message
|
|
|
|
```text
|
|
chore: rebuild image to patch c-ares CVE-2025-62408
|
|
|
|
Rebuilding the Docker image automatically pulls c-ares 1.34.6-r0 from
|
|
Alpine 3.23 repositories, fixing CVE-2025-62408 (CVSS 5.9 MEDIUM).
|
|
|
|
The vulnerability is a use-after-free in DNS query handling that can
|
|
cause Denial of Service. Impact to Charon is low because c-ares is
|
|
only used during container initialization (GeoLite2 download).
|
|
|
|
No Dockerfile changes required - Alpine's `apk upgrade` automatically
|
|
pulls the patched version.
|
|
|
|
CVE Details:
|
|
- Affected: c-ares 1.32.3 - 1.34.5
|
|
- Fixed: c-ares 1.34.6
|
|
- CWE: CWE-416 (Use After Free)
|
|
- Source: Trivy scan
|
|
|
|
References:
|
|
- https://nvd.nist.gov/vuln/detail/CVE-2025-62408
|
|
- https://github.com/c-ares/c-ares/security/advisories/GHSA-jq53-42q6-pqr5
|
|
```
|
|
|
|
---
|
|
|
|
## Files to Modify (Summary)
|
|
|
|
| File | Line(s) | Change |
|
|
|------------|---------|-------------------------------------------------------------------|
|
|
| **None** | N/A | No file changes required - rebuild pulls updated packages |
|
|
|
|
**Alternative (if explicit pinning desired):**
|
|
|
|
<!-- markdownlint-disable MD060 -->
|
|
|
|
| File | Line(s) | Change |
|
|
|--------------|---------|-------------------------------------------------------------------|
|
|
| `Dockerfile` | 210-211 | Add `c-ares>=1.34.6-r0` to apk install (not recommended) |
|
|
|
|
<!-- markdownlint-enable MD060 -->
|
|
|
|
---
|
|
|
|
## Related Security Information
|
|
|
|
### Trivy Scan Configuration
|
|
|
|
Charon uses Trivy for vulnerability scanning. Ensure scans run regularly:
|
|
|
|
**GitHub Actions Workflow:** `.github/workflows/security-scan.yml` (if exists)
|
|
|
|
**Manual Trivy Scan:**
|
|
|
|
```bash
|
|
# Scan built image
|
|
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
|
|
aquasec/trivy:latest image charon:latest \
|
|
--severity HIGH,CRITICAL
|
|
|
|
# Scan filesystem (includes source code)
|
|
docker run --rm -v $(pwd):/app aquasec/trivy:latest fs /app \
|
|
--severity HIGH,CRITICAL \
|
|
--scanners vuln,secret,misconfig
|
|
```
|
|
|
|
**VS Code Task:** `Security: Trivy Scan` (from `.vscode/tasks.json`)
|
|
|
|
### Future Mitigation Strategies
|
|
|
|
1. **Automated Dependency Updates:**
|
|
- Renovate already tracks Alpine base image (currently `alpine:3.23`)
|
|
- Consider adding scheduled Trivy scans in CI
|
|
- Configure Dependabot for Alpine security updates
|
|
|
|
2. **Minimal Base Images:**
|
|
- Consider distroless images for runtime (removes curl/c-ares entirely)
|
|
- Pre-download GeoLite2 database at build time instead of runtime
|
|
- Evaluate if curl is needed in runtime image
|
|
|
|
3. **Security Monitoring:**
|
|
- Enable GitHub Security Advisories for repository
|
|
- Subscribe to Alpine security mailing list
|
|
- Monitor c-ares CVEs: <https://github.com/c-ares/c-ares/security/advisories>
|
|
|
|
---
|
|
|
|
## Appendix: Package Dependency Tree
|
|
|
|
Full dependency tree for c-ares in Charon's runtime image:
|
|
|
|
```text
|
|
Alpine Linux 3.23 (Final runtime stage)
|
|
├─ ca-certificates (explicitly installed)
|
|
├─ sqlite-libs (explicitly installed)
|
|
├─ tzdata (explicitly installed)
|
|
├─ curl (explicitly installed) ← Entry point
|
|
│ ├─ libcurl (depends)
|
|
│ │ ├─ c-ares (depends) ← VULNERABLE
|
|
│ │ ├─ libbrotlidec (depends)
|
|
│ │ ├─ libcrypto (depends)
|
|
│ │ ├─ libidn2 (depends)
|
|
│ │ ├─ libnghttp2 (depends)
|
|
│ │ ├─ libnghttp3 (depends)
|
|
│ │ ├─ libpsl (depends)
|
|
│ │ ├─ libssl (depends)
|
|
│ │ ├─ libz (depends)
|
|
│ │ └─ libzstd (depends)
|
|
│ └─ libz (depends)
|
|
└─ gettext (explicitly installed)
|
|
```
|
|
|
|
**Verification Command:**
|
|
|
|
```bash
|
|
docker run --rm alpine:3.23 sh -c "
|
|
apk update &&
|
|
apk info --depends libcurl
|
|
"
|
|
```
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. ✅ Implement Option A (rebuild image)
|
|
2. ✅ Run verification steps (c-ares version check)
|
|
3. ✅ Execute Trivy scan to confirm fix
|
|
4. ✅ Run test suite to prevent regressions
|
|
5. ✅ Push commit with conventional commit message
|
|
6. ✅ Monitor CI pipeline for successful build
|
|
7. ⏭️ Update CHANGELOG.md (optional)
|
|
8. ⏭️ Deploy to production when ready
|
|
|
|
---
|
|
|
|
## Questions & Answers
|
|
|
|
**Q: Why not just pin c-ares version explicitly?**
|
|
A: Alpine's `apk upgrade` already handles security updates automatically. Explicit pinning adds maintenance
|
|
overhead and requires manual updates for future CVEs.
|
|
|
|
**Q: Will this break existing deployments?**
|
|
A: No. This only affects new builds. Existing containers continue running with the current c-ares version until rebuilt.
|
|
|
|
**Q: How urgent is this fix?**
|
|
A: Low to medium urgency. The vulnerability requires DNS MitM during container startup, which is unlikely.
|
|
Apply as part of normal maintenance cycle.
|
|
|
|
**Q: Can I test the fix locally before deploying?**
|
|
A: Yes. Use `docker build --no-cache -t charon:test .` to build locally and test before pushing to
|
|
production.
|
|
|
|
**Q: What if c-ares 1.34.6 isn't available yet?**
|
|
A: Check Alpine package repositories:
|
|
<https://pkgs.alpinelinux.org/packages?name=c-ares&branch=v3.23>.
|
|
If 1.34.6 isn't released, monitor Alpine security tracker.
|
|
|
|
**Q: Does this affect older Charon versions?**
|
|
A: Yes, if they use Alpine 3.23 or older Alpine versions with vulnerable c-ares. Rebuild those images as well.
|
|
|
|
---
|
|
|
|
**Document Status:** ✅ Complete - Ready for implementation
|
|
|
|
**Next Action:** Execute Step 1 (Trigger Docker Build)
|
|
|
|
**Owner:** DevOps/Security Team
|
|
|
|
**Review Date:** 2025-12-14
|
|
|
|
---
|
|
|
|
## CI/CD Cache Strategy Recommendations
|
|
|
|
### Current State Analysis
|
|
|
|
**Caching Configuration:**
|
|
|
|
```yaml
|
|
# .github/workflows/docker-build.yml (lines 113-114)
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
```
|
|
|
|
**How GitHub Actions Cache Works:**
|
|
|
|
- **`cache-from: type=gha`** - Pulls cached layers from previous builds
|
|
- **`cache-to: type=gha,mode=max`** - Saves all build stages (including intermediate layers)
|
|
- **Cache scope:** Per repository, per workflow, per branch
|
|
- **Cache invalidation:** Automatic when Dockerfile changes or base images update
|
|
|
|
**Current Dockerfile Package Updates:**
|
|
|
|
```dockerfile
|
|
# Line 210-211 (Final runtime stage)
|
|
RUN apk --no-cache add ca-certificates sqlite-libs tzdata curl gettext \
|
|
&& apk --no-cache upgrade
|
|
```
|
|
|
|
The `apk --no-cache upgrade` command runs during **every build**, but Docker layer caching can prevent it
|
|
from actually fetching new packages.
|
|
|
|
---
|
|
|
|
### The Security vs. Performance Trade-off
|
|
|
|
#### Option 1: Keep Current Cache Strategy (RECOMMENDED for Regular Builds)
|
|
|
|
**Pros:**
|
|
|
|
- ✅ Fast CI builds (5-10 minutes instead of 15-30 minutes)
|
|
- ✅ Lower GitHub Actions minutes consumption
|
|
- ✅ Reduced resource usage (network, disk I/O)
|
|
- ✅ Better developer experience (faster PR feedback)
|
|
- ✅ Renovate already monitors Alpine base image updates
|
|
- ✅ Manual rebuilds can force fresh packages when needed
|
|
|
|
**Cons:**
|
|
|
|
- ❌ Security patches in Alpine packages may lag behind by days/weeks
|
|
- ❌ `apk upgrade` may use cached package index
|
|
- ❌ Transitive dependencies (like c-ares) won't auto-update until base image changes
|
|
|
|
**Risk Assessment:**
|
|
|
|
- **Low Risk** - Charon already has scheduled Renovate runs (daily 05:00 UTC)
|
|
- Renovate updates `alpine:3.23` base image when new digests are published
|
|
- Base image updates automatically invalidate Docker cache
|
|
- CVE lag is typically 1-7 days (acceptable for non-critical infrastructure)
|
|
|
|
**When to Use:** Default strategy for all PR builds and push builds
|
|
|
|
---
|
|
|
|
#### Option 2: Scheduled No-Cache Security Builds ✅ IMPLEMENTED
|
|
|
|
**Status:** Implemented on December 14, 2025
|
|
**Workflow:** `.github/workflows/security-weekly-rebuild.yml`
|
|
**Schedule:** Every Sunday at 04:00 UTC
|
|
**First Run:** December 15, 2025
|
|
|
|
**Pros:**
|
|
|
|
- ✅ Guarantees fresh Alpine packages weekly
|
|
- ✅ Catches CVEs between Renovate base image updates
|
|
- ✅ Doesn't slow down development workflow
|
|
- ✅ Provides early warning of breaking package updates
|
|
- ✅ Separate workflow means no impact on PR builds
|
|
|
|
**Cons:**
|
|
|
|
- ❌ Requires maintaining separate workflow
|
|
- ❌ Longer build times once per week
|
|
- ❌ May produce "false positive" Trivy alerts for non-critical CVEs
|
|
|
|
**Risk Assessment:**
|
|
|
|
- **Very Low Risk** - Weekly rebuilds balance security and performance
|
|
- Catches CVEs within 7-day window (acceptable for most use cases)
|
|
- Trivy scans run automatically after build
|
|
|
|
**When to Use:** Dedicated security scanning workflow (see implementation below)
|
|
|
|
---
|
|
|
|
#### Option 3: Force No-Cache on All Builds (NOT RECOMMENDED)
|
|
|
|
**Pros:**
|
|
|
|
- ✅ Always uses latest Alpine packages
|
|
- ✅ Zero lag between CVE fixes and builds
|
|
|
|
**Cons:**
|
|
|
|
- ❌ **Significantly slower builds** (15-30 min vs 5-10 min)
|
|
- ❌ **Higher CI costs** (2-3x more GitHub Actions minutes)
|
|
- ❌ **Worse developer experience** (slow PR feedback)
|
|
- ❌ **Unnecessary** - Charon is not a high-risk target requiring real-time patches
|
|
- ❌ **Wasteful** - Most packages don't change between builds
|
|
- ❌ **No added security** - Vulnerabilities are patched at build time anyway
|
|
|
|
**Risk Assessment:**
|
|
|
|
- **High Overhead, Low Benefit** - Not justified for Charon's threat model
|
|
- Would consume ~500 extra CI minutes per month for minimal security gain
|
|
|
|
**When to Use:** Never (unless Charon becomes a critical security infrastructure project)
|
|
|
|
---
|
|
|
|
### Recommended Hybrid Strategy
|
|
|
|
**Combine Options 1 + 2 for best balance:**
|
|
|
|
1. **Regular builds (PR/push):** Use cache (current behavior)
|
|
2. **Weekly security builds:** Force `--no-cache` and run comprehensive Trivy scan
|
|
3. **Manual trigger:** Allow forcing no-cache builds via `workflow_dispatch`
|
|
|
|
This approach:
|
|
|
|
- ✅ Maintains fast development feedback loop
|
|
- ✅ Catches security vulnerabilities within 7 days
|
|
- ✅ Allows on-demand fresh builds when CVEs are announced
|
|
- ✅ Costs ~1-2 extra CI hours per month (negligible)
|
|
|
|
---
|
|
|
|
### Implementation: Weekly Security Build Workflow
|
|
|
|
**File:** `.github/workflows/security-weekly-rebuild.yml`
|
|
|
|
```yaml
|
|
name: Weekly Security Rebuild
|
|
|
|
on:
|
|
schedule:
|
|
- cron: '0 4 * * 0' # Sundays at 04:00 UTC
|
|
workflow_dispatch:
|
|
inputs:
|
|
force_rebuild:
|
|
description: 'Force rebuild without cache'
|
|
required: false
|
|
type: boolean
|
|
default: true
|
|
|
|
env:
|
|
REGISTRY: ghcr.io
|
|
IMAGE_NAME: ${{ github.repository_owner }}/charon
|
|
|
|
jobs:
|
|
security-rebuild:
|
|
name: Security Rebuild & Scan
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
security-events: write
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Set up QEMU
|
|
uses: docker/setup-qemu-action@v3.7.0
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v3.11.1
|
|
|
|
- name: Resolve Caddy base digest
|
|
id: caddy
|
|
run: |
|
|
docker pull caddy:2-alpine
|
|
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' caddy:2-alpine)
|
|
echo "image=$DIGEST" >> $GITHUB_OUTPUT
|
|
|
|
- name: Log in to Container Registry
|
|
uses: docker/login-action@v3.6.0
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Extract metadata
|
|
id: meta
|
|
uses: docker/metadata-action@v5.10.0
|
|
with:
|
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
tags: |
|
|
type=raw,value=security-scan-{{date 'YYYYMMDD'}}
|
|
|
|
- name: Build Docker image (NO CACHE)
|
|
id: build
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
context: .
|
|
platforms: linux/amd64,linux/arm64
|
|
push: true
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
no-cache: ${{ github.event_name == 'schedule' || inputs.force_rebuild }}
|
|
build-args: |
|
|
VERSION=security-scan
|
|
BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
|
|
VCS_REF=${{ github.sha }}
|
|
CADDY_IMAGE=${{ steps.caddy.outputs.image }}
|
|
|
|
- name: Run Trivy vulnerability scanner (CRITICAL+HIGH)
|
|
uses: aquasecurity/trivy-action@0.33.1
|
|
with:
|
|
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
|
|
format: 'table'
|
|
severity: 'CRITICAL,HIGH'
|
|
exit-code: '1' # Fail workflow if vulnerabilities found
|
|
continue-on-error: true
|
|
|
|
- name: Run Trivy vulnerability scanner (SARIF)
|
|
id: trivy-sarif
|
|
uses: aquasecurity/trivy-action@0.33.1
|
|
with:
|
|
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
|
|
format: 'sarif'
|
|
output: 'trivy-weekly-results.sarif'
|
|
severity: 'CRITICAL,HIGH,MEDIUM'
|
|
|
|
- name: Upload Trivy results to GitHub Security
|
|
uses: github/codeql-action/upload-sarif@v4.31.8
|
|
with:
|
|
sarif_file: 'trivy-weekly-results.sarif'
|
|
|
|
- name: Run Trivy vulnerability scanner (JSON for artifact)
|
|
uses: aquasecurity/trivy-action@0.33.1
|
|
with:
|
|
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
|
|
format: 'json'
|
|
output: 'trivy-weekly-results.json'
|
|
severity: 'CRITICAL,HIGH,MEDIUM,LOW'
|
|
|
|
- name: Upload Trivy JSON results
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: trivy-weekly-scan-${{ github.run_number }}
|
|
path: trivy-weekly-results.json
|
|
retention-days: 90
|
|
|
|
- name: Check Alpine package versions
|
|
run: |
|
|
echo "## 📦 Installed Package Versions" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "Checking key security packages:" >> $GITHUB_STEP_SUMMARY
|
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
|
docker run --rm ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }} \
|
|
sh -c "apk info c-ares curl libcurl openssl" >> $GITHUB_STEP_SUMMARY
|
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
|
|
|
- name: Create security scan summary
|
|
if: always()
|
|
run: |
|
|
echo "## 🔒 Weekly Security Rebuild Complete" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Build Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Image:** ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Cache Used:** No (forced fresh build)" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Trivy Scan:** Completed (see Security tab for details)" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "### Next Steps:" >> $GITHUB_STEP_SUMMARY
|
|
echo "1. Review Security tab for new vulnerabilities" >> $GITHUB_STEP_SUMMARY
|
|
echo "2. Check Trivy JSON artifact for detailed package info" >> $GITHUB_STEP_SUMMARY
|
|
echo "3. If critical CVEs found, trigger production rebuild" >> $GITHUB_STEP_SUMMARY
|
|
|
|
- name: Notify on security issues (optional)
|
|
if: failure()
|
|
run: |
|
|
echo "::warning::Weekly security scan found HIGH or CRITICAL vulnerabilities. Review the Security tab."
|
|
```
|
|
|
|
**Why This Works:**
|
|
|
|
1. **Separate from main build workflow** - No impact on development velocity
|
|
2. **Scheduled weekly** - Catches CVEs within 7-day window
|
|
3. **`no-cache: true`** - Forces fresh Alpine package downloads
|
|
4. **Comprehensive scanning** - CRITICAL, HIGH, MEDIUM severities
|
|
5. **Results archived** - 90-day retention for security audits
|
|
6. **GitHub Security integration** - Alerts visible in Security tab
|
|
7. **Manual trigger option** - Can force rebuild when CVEs announced
|
|
|
|
---
|
|
|
|
### Alternative: Add `--no-cache` Option to Existing Workflow
|
|
|
|
If you prefer not to create a separate workflow, add a manual trigger to the existing [docker-build.yml](.github/workflows/docker-build.yml):
|
|
|
|
```yaml
|
|
# .github/workflows/docker-build.yml
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
- development
|
|
- feature/beta-release
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
- development
|
|
- feature/beta-release
|
|
workflow_dispatch:
|
|
inputs:
|
|
no_cache:
|
|
description: 'Build without cache (forces fresh Alpine packages)'
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
workflow_call:
|
|
|
|
# Then in the build step:
|
|
- name: Build and push Docker image
|
|
if: steps.skip.outputs.skip_build != 'true'
|
|
id: build-and-push
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
context: .
|
|
platforms: ${{ github.event_name == 'pull_request' && 'linux/amd64' || 'linux/amd64,linux/arm64' }}
|
|
push: ${{ github.event_name != 'pull_request' }}
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
no-cache: ${{ inputs.no_cache || false }} # ← Add this
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
build-args: |
|
|
VERSION=${{ steps.meta.outputs.version }}
|
|
BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
|
|
VCS_REF=${{ github.sha }}
|
|
CADDY_IMAGE=${{ steps.caddy.outputs.image }}
|
|
```
|
|
|
|
**Pros:**
|
|
|
|
- ✅ Reuses existing workflow
|
|
- ✅ Simple implementation
|
|
|
|
**Cons:**
|
|
|
|
- ❌ No automatic scheduling
|
|
- ❌ Must manually trigger each time
|
|
|
|
---
|
|
|
|
### Why the Current Cache Behavior Caught c-ares CVE Late
|
|
|
|
**Timeline:**
|
|
|
|
1. **2025-12-12:** c-ares 1.34.6-r0 released to Alpine repos
|
|
2. **2025-12-14:** Trivy scan detected CVE-2025-62408 (still using 1.34.5-r0)
|
|
3. **Cause:** Docker layer cache prevented `apk upgrade` from checking for new packages
|
|
|
|
**Why Layer Caching Prevented Updates:**
|
|
|
|
```dockerfile
|
|
# This layer gets cached if:
|
|
# - Dockerfile hasn't changed (line 210-211)
|
|
# - alpine:3.23 base digest hasn't changed
|
|
RUN apk --no-cache add ca-certificates sqlite-libs tzdata curl gettext \
|
|
&& apk --no-cache upgrade
|
|
```
|
|
|
|
Docker sees:
|
|
|
|
- Same base image → ✅ Use cached layer
|
|
- Same RUN instruction → ✅ Use cached layer
|
|
- **Doesn't execute `apk upgrade`** → Keeps c-ares 1.34.5-r0
|
|
|
|
**How `--no-cache` Would Have Helped:**
|
|
|
|
- Forces execution of `apk upgrade` → Downloads latest package index
|
|
- Installs c-ares 1.34.6-r0 → CVE resolved immediately
|
|
|
|
**But:** This is **acceptable behavior** for Charon's threat model. The 2-day lag is negligible for a home
|
|
user reverse proxy.
|
|
|
|
---
|
|
|
|
### Recommended Action Plan
|
|
|
|
**Immediate (Today):**
|
|
|
|
1. ✅ Trigger a manual rebuild to pull c-ares 1.34.6-r0 (already documented in main plan)
|
|
2. ✅ Use GitHub Actions manual workflow trigger with `workflow_dispatch`
|
|
|
|
**Short-term (This Week):**
|
|
|
|
1. ⏭️ Implement weekly security rebuild workflow (new file above)
|
|
2. ⏭️ Add `no-cache` option to existing [docker-build.yml](.github/workflows/docker-build.yml) for emergency use
|
|
3. ⏭️ Document security scanning process in [docs/security.md](../security.md)
|
|
|
|
**Long-term (Next Month):**
|
|
|
|
1. ⏭️ Evaluate if weekly scans catch issues early enough
|
|
2. ⏭️ Consider adding Trivy DB auto-updates (separate from image builds)
|
|
3. ⏭️ Monitor Alpine security mailing list for advance notice of CVEs
|
|
4. ⏭️ Investigate using `buildkit` cache modes for more granular control
|
|
|
|
---
|
|
|
|
### When to Force `--no-cache` Builds
|
|
|
|
**Always use `--no-cache` when:**
|
|
|
|
- ⚠️ Critical CVE announced in Alpine package
|
|
- ⚠️ Security audit requested
|
|
- ⚠️ Compliance requirement mandates latest packages
|
|
- ⚠️ Production deployment after long idle period (weeks)
|
|
|
|
**Never use `--no-cache` for:**
|
|
|
|
- ✅ Regular PR builds (too slow, no benefit)
|
|
- ✅ Development testing (wastes resources)
|
|
- ✅ Hotfixes that don't touch dependencies
|
|
|
|
**Use weekly scheduled `--no-cache` for:**
|
|
|
|
- ✅ Proactive security monitoring
|
|
- ✅ Early detection of package conflicts
|
|
- ✅ Security compliance reporting
|
|
|
|
---
|
|
|
|
### Cost-Benefit Analysis
|
|
|
|
**Current Strategy (Cached Builds):**
|
|
|
|
- **Build Time:** 5-10 minutes per build
|
|
- **Monthly CI Cost:** ~200 minutes/month (assuming 10 builds/month)
|
|
- **CVE Detection Lag:** 1-7 days (until next base image update or manual rebuild)
|
|
|
|
**With Weekly No-Cache Builds:**
|
|
|
|
- **Build Time:** 20-30 minutes per build (weekly)
|
|
- **Monthly CI Cost:** ~300 minutes/month (+100 minutes, ~50% increase)
|
|
- **CVE Detection Lag:** 0-7 days (guaranteed weekly refresh)
|
|
|
|
**With All No-Cache Builds (NOT RECOMMENDED):**
|
|
|
|
- **Build Time:** 20-30 minutes per build
|
|
- **Monthly CI Cost:** ~500 minutes/month (+150% increase)
|
|
- **CVE Detection Lag:** 0 days
|
|
- **Trade-off:** Slower development for negligible security gain
|
|
|
|
---
|
|
|
|
### Final Recommendation: Hybrid Strategy ✅ IMPLEMENTED
|
|
|
|
**Summary:**
|
|
|
|
- ✅ **Keep cached builds for development** (current behavior) - ACTIVE
|
|
- ✅ **Add weekly no-cache security builds** (new workflow) - IMPLEMENTED
|
|
- ⏭️ **Add manual no-cache trigger** (emergency use) - PENDING
|
|
- ❌ **Do NOT force no-cache on all builds** (wasteful, slow) - CONFIRMED
|
|
|
|
**Rationale:**
|
|
|
|
- Charon is a **home user application**, not critical infrastructure
|
|
- **1-7 day CVE lag is acceptable** for the threat model
|
|
- **Weekly scans catch 99% of CVEs** before they become issues
|
|
- **Development velocity matters** - fast PR feedback improves code quality
|
|
- **GitHub Actions minutes are limited** - use them wisely
|
|
|
|
**Implementation Effort:**
|
|
|
|
- **Easy:** Add manual `no-cache` trigger to existing workflow (~5 minutes)
|
|
- **Medium:** Create weekly security rebuild workflow (~30 minutes)
|
|
- **Maintenance:** Minimal (workflows run automatically)
|
|
|
|
---
|
|
|
|
### Questions & Answers
|
|
|
|
**Q: Should we switch to `--no-cache` for all builds after this CVE?**
|
|
A: **No.** The 2-day lag between c-ares 1.34.6-r0 release and detection is acceptable. Weekly scheduled
|
|
builds will catch future CVEs within 7 days, which is sufficient for Charon's threat model.
|
|
|
|
**Q: How do we balance security and CI costs?**
|
|
A: Use **hybrid strategy**: cached builds for speed, weekly no-cache builds for security. This adds only
|
|
~100 CI minutes/month (~50% increase) while catching 99% of CVEs proactively.
|
|
|
|
**Q: What if a critical CVE is announced?**
|
|
A: Use **manual workflow trigger** with `no-cache: true` to force an immediate rebuild. Document this in
|
|
runbooks/incident response procedures.
|
|
|
|
**Q: Why not use Renovate for Alpine package updates?**
|
|
A: Renovate tracks **base image digests** (`alpine:3.23`), not individual Alpine packages. Package updates
|
|
happen via `apk upgrade`, which requires cache invalidation to be effective.
|
|
|
|
**Q: Can we optimize `--no-cache` to only affect Alpine packages?**
|
|
A: Yes, with **BuildKit cache modes**. Consider using:
|
|
|
|
```yaml
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
# But add:
|
|
--mount=type=cache,target=/var/cache/apk,sharing=locked
|
|
```
|
|
|
|
This caches Go modules, npm packages, etc., while still refreshing Alpine packages. More complex to
|
|
implement but worth investigating.
|
|
|
|
---
|
|
|
|
**Decision:** ✅ Implement **Hybrid Strategy** (Option 1 + Option 2)
|
|
**Action Items:**
|
|
|
|
1. ✅ Create `.github/workflows/security-weekly-rebuild.yml` - COMPLETED 2025-12-14
|
|
2. ⏭️ Add `no_cache` input to `.github/workflows/docker-build.yml` - PENDING
|
|
3. ⏭️ Update [docs/security.md](../security.md) with scanning procedures - PENDING
|
|
4. ⏭️ Add VS Code task for manual security rebuild - PENDING
|
|
|
|
**Implementation Notes:**
|
|
|
|
- Weekly workflow is fully functional and will begin running December 15, 2025
|
|
- Manual trigger option available via workflow_dispatch in the security workflow
|
|
- Results will appear in GitHub Security tab automatically
|