- Introduced optional keepalive settings: `keepalive_idle` and `keepalive_count` in the Server struct. - Implemented UI controls for keepalive settings in System Settings, including validation and persistence. - Added localization support for new keepalive fields in multiple languages. - Created a manual test tracking plan for verifying keepalive controls and their behavior. - Updated existing tests to cover new functionality and ensure proper validation of keepalive inputs. - Ensured safe defaults and fallback behavior for missing or invalid keepalive values.
37 KiB
post_title, categories, tags, summary, post_date
| post_title | categories | tags | summary | post_date | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Current Spec: Caddy 2.11.1 Compatibility, Security, and UX Impact Plan |
|
|
Comprehensive, phased plan to evaluate and safely adopt Caddy v2.11.1 in Charon, covering plugin compatibility, CVE impact, xcaddy patch retirement decisions, UI/UX exposure opportunities, and PR slicing strategy with strict validation gates. | 2026-02-23 |
Active Plan: Caddy 2.11.1 Deep Compatibility and Security Rollout
Date: 2026-02-23 Status: Active and authoritative Scope Type: Architecture/security/dependency research and implementation planning Authority: This is the only active authoritative plan section in this file.
Focused Plan: GitHub Actions setup-go Cache Warning (go.sum path)
Date: 2026-02-23
Status: Planned
Scope: Warning-only fix for GitHub Actions cache restore message:
Restore cache failed: Dependencies file is not found in /home/runner/work/Charon/Charon. Supported file pattern: go.sum.
Introduction
This focused section addresses a CI warning caused by actions/setup-go cache
configuration assuming go.sum at repository root. Charon stores Go module
dependencies in backend/go.sum.
Research Findings
Verified workflow inventory (.github/workflows/**):
- All workflows using
actions/setup-gowere identified. - Five workflows already set
cache-dependency-path: backend/go.sum:.github/workflows/codecov-upload.yml.github/workflows/quality-checks.yml.github/workflows/codeql.yml.github/workflows/benchmark.yml.github/workflows/e2e-tests-split.yml
- Two workflows use
actions/setup-gowithout cache dependency path and are the warning source:.github/workflows/caddy-compat.yml.github/workflows/release-goreleaser.yml
- Repository check confirms only one
go.sumexists:backend/go.sum
Technical Specification (Minimal Fix)
Apply a warning-only cache path correction in both affected workflow steps:
-
.github/workflows/caddy-compat.yml- In
Set up Gostep, add:cache-dependency-path: backend/go.sum
- In
-
.github/workflows/release-goreleaser.yml- In
Set up Gostep, add:cache-dependency-path: backend/go.sum
- In
No other workflow behavior, triggers, permissions, or build/test logic will be changed.
Implementation Plan
Phase 1 — Workflow patch
- Update only the two targeted workflow files listed above.
Phase 2 — Validation
- Run workflow YAML validation/lint checks already used by repository CI.
- Confirm no cache restore warning appears in subsequent runs of:
Caddy Compatibility GateRelease (GoReleaser)
Phase 3 — Closeout
- Mark warning remediated once both workflows execute without the missing
go.sumcache warning.
Acceptance Criteria
- Both targeted workflows include
cache-dependency-path: backend/go.sumin theiractions/setup-gostep. - No unrelated workflow files are modified.
- No behavior changes beyond warning elimination.
- CI logs for affected workflows no longer show the missing dependencies-file warning.
PR Slicing Strategy
- Decision: Single PR.
- Rationale: Two-line, warning-only correction in two workflow files with no cross-domain behavior impact.
- Slice:
PR-1: Addcache-dependency-pathto the twosetup-gosteps and verify workflow run logs.
- Rollback:
- Revert only these two workflow edits if unexpected cache behavior appears.
Focused Remediation Plan Addendum: 3 Failing Playwright Tests
Date: 2026-02-23
Scope: Only the 3 failures reported in docs/reports/qa_report.md:
tests/core/proxy-hosts.spec.ts—should open edit modal with existing valuestests/core/proxy-hosts.spec.ts—should update forward host and porttests/settings/smtp-settings.spec.ts—should update existing SMTP configuration
Introduction
This addendum defines a minimal, deterministic remediation for the three reported flaky/timeout E2E failures. The objective is to stabilize test synchronization and preconditions while preserving existing assertions and behavior intent.
Research Findings
1) tests/core/proxy-hosts.spec.ts (2 timeouts)
Observed test pattern:
- Uses broad selector
page.getByRole('button', { name: /edit/i }).first(). - Uses conditional execution (
if (editCount > 0)) with no explicit precondition that at least one editable row exists. - Waits for modal after clicking the first matched "Edit" button.
Likely root causes:
- Broad role/name selector can resolve to non-row or non-visible edit controls first, causing click auto-wait timeout.
- Test data state is non-deterministic (no guaranteed editable proxy host before the update tests).
- In-file parallel execution (
fullyParallel: trueglobally) increases race potential for shared host list mutations.
2) tests/settings/smtp-settings.spec.ts (waitForResponse timeout)
Observed test pattern:
- Uses
clickAndWaitForResponse(page, saveButton, /\/api\/v1\/settings\/smtp/), which internally waits for response status200by default. - Test updates only host field, relying on pre-existing validity of other required fields.
Likely root causes:
- If backend returns non-
200(e.g.,400validation), helper waits indefinitely for200and times out instead of failing fast. - The test assumes existing SMTP state is valid; this is brittle under parallel execution and prior test mutations.
Technical Specifications (Exact Test Changes)
A) tests/core/proxy-hosts.spec.ts
- In
test.describe('Update Proxy Host', ...), add serial mode:
- Add
test.describe.configure({ mode: 'serial' })at the top of that describe block.
- Add a local helper in this file for deterministic precondition and row-scoped edit action:
- Helper name:
ensureEditableProxyHost(page, testData) - Behavior:
- Check
tbody trcount. - If count is
0, create one host viatestData.createProxyHost({ domain: ..., forwardHost: ..., forwardPort: ... }). - Reload
/proxy-hostsand wait for content readiness using existing wait helpers.
- Check
- Replace broad edit-button lookup in both failing tests with row-scoped visible locator:
- Replace:
page.getByRole('button', { name: /edit/i }).first()
- With:
const firstRow = page.locator('tbody tr').first()const editButton = firstRow.getByRole('button', { name: /edit proxy host|edit/i }).first()await expect(editButton).toBeVisible()await editButton.click()
- Remove silent pass-through for missing rows in these two tests:
- Replace
if (editCount > 0) { ... }branching with deterministic precondition call and explicit assertion that dialog appears.
Affected tests:
should open edit modal with existing valuesshould update forward host and port
Preserved assertions:
- Edit modal opens.
- Existing values are present.
- Forward host/port fields accept and retain edited values before cancel.
B) tests/settings/smtp-settings.spec.ts
- In
test.describe('CRUD Operations', ...), add serial mode:
- Add
test.describe.configure({ mode: 'serial' })to avoid concurrent mutation of shared SMTP configuration.
- Strengthen required-field preconditions in failing test before save:
- In
should update existing SMTP configuration, explicitly set:#smtp-hosttoupdated-smtp.test.local#smtp-portto587#smtp-fromtonoreply@test.local
- Replace status-constrained response wait that can timeout on non-200:
- Replace
clickAndWaitForResponse(...)call withPromise.all([page.waitForResponse(...) , saveButton.click()])matching URL +POSTmethod (not status). - Immediately assert returned status is
200and then keep success-toast assertion.
- Keep existing persistence verification and cleanup step:
- Reload and assert host persisted.
- Restore original host value after assertion.
Preserved assertions:
- Save request succeeds.
- Success feedback shown.
- Updated value persists after reload.
- Original value restoration still performed.
Implementation Plan
Phase 1 — Targeted test edits
- Update only:
tests/core/proxy-hosts.spec.tstests/settings/smtp-settings.spec.ts
Phase 2 — Focused verification
- Run only the 3 failing cases first (grep-targeted).
- Then run both files fully on Firefox to validate no local regressions.
Phase 3 — Gate confirmation
- Re-run the previously failing targeted suite:
tests/coretests/settings/smtp-settings.spec.ts
Acceptance Criteria
should open edit modal with existing valuespasses without timeout.should update forward host and portpasses without timeout.should update existing SMTP configurationpasses withoutwaitForResponsetimeout.- No assertion scope is broadened; test intent remains unchanged.
- No non-target files are modified.
PR Slicing Strategy
- Decision: Single PR.
- Rationale: 3 deterministic test-only fixes, same domain (Playwright stabilization), low blast radius.
- Slice:
PR-1: Update the two spec files above + rerun targeted Playwright validations.
- Rollback:
- Revert only spec-file changes if unintended side effects appear.
Introduction
Charon’s control plane and data plane rely on Caddy as a core runtime backbone.
Because Caddy is embedded and rebuilt via xcaddy, upgrading from
2.11.0-beta.2 to 2.11.1 is not a routine version bump: it impacts
runtime behavior, plugin compatibility, vulnerability posture, and potential UX
surface area.
This plan defines a low-risk, high-observability rollout strategy that answers:
- Which Caddy 2.11.x features should be exposed in Charon UI/API?
- Which existing Charon workarounds became redundant upstream?
- Which
xcaddydependency patches remain necessary vs removable? - Which known vulnerabilities are fixed now and which should remain on watch?
Research Findings
External release and security findings
- Official release statement confirms
v2.11.1has no runtime code delta fromv2.11.0except CI/release process correction. Practical implication: compatibility/security validation should target 2.11.x behavior, not 2.11.1-specific runtime changes. - Caddy release lists six security patches (mapped to GitHub advisories):
CVE-2026-27590→GHSA-5r3v-vc8m-m96g(FastCGI split_path confusion)CVE-2026-27589→GHSA-879p-475x-rqh2(admin API cross-origin no-cors)CVE-2026-27588→GHSA-x76f-jf84-rqj8(host matcher case bypass)CVE-2026-27587→GHSA-g7pc-pc7g-h8jh(path matcher escaped-case bypass)CVE-2026-27586→GHSA-hffm-g8v7-wrv7(mTLS client-auth fail-open)CVE-2026-27585→GHSA-4xrr-hq4w-6vf4(glob sanitization bypass)
- NVD/CVE.org entries are currently reserved/not fully enriched. GitHub advisories are the most actionable source right now.
Charon architecture and integration findings
- Charon compiles custom Caddy in
Dockerfileviaxcaddyand injects:github.com/greenpau/caddy-securitygithub.com/corazawaf/coraza-caddy/v2github.com/hslatman/caddy-crowdsec-bouncer@v0.10.0github.com/zhangjiayin/caddy-geoip2github.com/mholt/caddy-ratelimit
- Charon applies explicit post-generation
go getpatching inDockerfilefor:github.com/expr-lang/expr@v1.17.7github.com/hslatman/ipstore@v0.4.0github.com/slackhq/nebula@v1.9.7(with comment indicating temporary pin)
- Charon CI has explicit dependency inspection gate in
.github/workflows/docker-build.ymlto verify patchedexpr-lang/exprversions in built binaries.
Plugin compatibility findings (highest risk area)
Current plugin module declarations (upstream go.mod) target older Caddy cores:
greenpau/caddy-security:caddy/v2 v2.10.2hslatman/caddy-crowdsec-bouncer:caddy/v2 v2.10.2corazawaf/coraza-caddy/v2:caddy/v2 v2.9.1zhangjiayin/caddy-geoip2:caddy/v2 v2.10.0mholt/caddy-ratelimit:caddy/v2 v2.8.0
Implication: compile success against 2.11.1 is plausible but not guaranteed. The plan must include matrix build/provision tests before merge.
Charon UX and config-surface findings
Current Caddy-related UI/API exposure is narrow:
frontend/src/pages/SystemSettings.tsx- state:
caddyAdminAPI,sslProvider - saves keys:
caddy.admin_api,caddy.ssl_provider
- state:
frontend/src/pages/ImportCaddy.tsxand import components:- Caddyfile parsing/import workflow, not runtime feature toggles
frontend/src/api/import.ts,frontend/src/api/settings.ts- Backend routes and handlers:
backend/internal/api/routes/routes.gobackend/internal/api/handlers/settings_handler.gobackend/internal/api/handlers/import_handler.gobackend/internal/caddy/manager.gobackend/internal/caddy/config.gobackend/internal/caddy/types.go
No UI controls currently exist for new Caddy 2.11.x capabilities such as
keepalive_idle, keepalive_count, trusted_proxies_unix,
renewal_window_ratio, or 0-RTT behavior.
Requirements (EARS)
- WHEN evaluating Caddy
v2.11.1, THE SYSTEM SHALL validate compatibility against all currently enabledxcaddyplugins before changing production defaults. - WHEN security advisories in Caddy 2.11.x affect modules Charon may use, THE SYSTEM SHALL document exploitability for Charon’s deployment model and prioritize remediation accordingly.
- WHEN an
xcaddypatch/workaround no longer provides value, THE SYSTEM SHALL remove it only after reproducible build and runtime validation gates pass. - IF a Caddy 2.11.x feature maps to an existing Charon concept, THEN THE SYSTEM SHALL prefer extending existing UI/components over adding new parallel controls.
- WHEN no direct UX value exists, THE SYSTEM SHALL avoid adding UI for upstream options and keep behavior backend-managed.
- WHEN this rollout completes, THE SYSTEM SHALL provide explicit upstream watch criteria for unresolved/reserved CVEs and plugin dependency lag.
Technical Specifications
Compatibility scope map (code touch inventory)
Build/packaging
DockerfileARG CADDY_VERSIONARG XCADDY_VERSIONcaddy-builderstage (xcaddy build, plugin list,go getpatches)
.github/workflows/docker-build.yml- binary dependency checks (
go version -mextraction/gates)
- binary dependency checks (
.github/renovate.json- regex managers tracking
Dockerfilepatch dependencies
- regex managers tracking
Caddy runtime config generation
backend/internal/caddy/manager.goNewManager(...)ApplyConfig(ctx)
backend/internal/caddy/config.goGenerateConfig(...)
backend/internal/caddy/types.go- JSON struct model for Caddy config (
Server,TrustedProxies, etc.)
- JSON struct model for Caddy config (
Settings and admin surface
backend/internal/api/handlers/settings_handler.goUpdateSetting(...),PatchConfig(...)
backend/internal/api/routes/routes.go- Caddy manager wiring + settings routes
frontend/src/pages/SystemSettings.tsx- current Caddy-related controls
Caddyfile import behavior
backend/internal/api/handlers/import_handler.goRegisterRoutes(...),Upload(...),GetPreview(...)
backend/internal/caddy/importer.goNormalizeCaddyfile(...),ParseCaddyfile(...),ExtractHosts(...)
frontend/src/pages/ImportCaddy.tsx- import UX and warning handling
Feature impact assessment (2.11.x)
Candidate features for potential Charon exposure
- Keepalive server options (
keepalive_idle,keepalive_count)- Candidate mapping: advanced per-host connection tuning
- Likely files:
backend/internal/caddy/types.go,backend/internal/caddy/config.go, host settings API + UI
trusted_proxies_unix- Candidate mapping: trusted local socket proxy chains
- Current
TrustedProxiesstruct lacks explicit unix-socket trust fields
- Certificate lifecycle tunables (
renewal_window_ratio, maintenance interval)- Candidate mapping: advanced TLS policy controls
- Potentially belongs under system-level TLS settings, not per-host UI
Features likely backend-only / no new UI by default
- Reverse-proxy automatic
Hostrewrite for TLS upstreams - ECH key auto-rotation
SIGUSR1reload fallback behavior- Logging backend internals (
timberjack, ordering fixes)
Plan decision rule: expose only options that produce clear operator value and can be represented without adding UX complexity.
Security patch relevance matrix
Advisory exploitability rubric and ownership
Use the following deterministic rubric for each advisory before any promotion:
| Field | Required Values | Rule |
|---|---|---|
| Exploitability | Affected / Not affected / Mitigated |
Affected means a reachable vulnerable path exists in Charon runtime; Not affected means required feature/path is not present; Mitigated means vulnerable path exists upstream but Charon deployment/runtime controls prevent exploitation. |
| Evidence source | advisory + code/config/runtime proof | Must include at least one authoritative upstream source (GitHub advisory/Caddy release) and one Charon-local proof (config path, test, scan, or runtime verification). |
| Owner | named role | Security owner for final disposition (QA_Security lead or delegated maintainer). |
| Recheck cadence | weekly / release-candidate / on-upstream-change |
Minimum cadence: weekly until CVE enrichment is complete and disposition is stable for two consecutive checks. |
Promotion gate: every advisory must have all four fields populated and signed by owner in the PR evidence bundle.
High-priority for Charon context
GHSA-879p-475x-rqh2(admin API cross-origin no-cors)- Charon binds admin API internally but still uses
0.0.0.0:2019in generated config. Must verify actual network isolation and container exposure assumptions.
- Charon binds admin API internally but still uses
GHSA-hffm-g8v7-wrv7(mTLS fail-open)- Relevant if client-auth CA pools are configured anywhere in generated or imported config paths.
- matcher bypass advisories (
GHSA-x76f-jf84-rqj8,GHSA-g7pc-pc7g-h8jh)- Potentially relevant to host/path-based access control routing in Caddy.
Contextual/conditional relevance
GHSA-5r3v-vc8m-m96g(FastCGI split_path)- Relevant only if FastCGI transport is in active use.
GHSA-4xrr-hq4w-6vf4(file matcher glob sanitization)- Relevant when file matchers are used in route logic.
xcaddy patch retirement candidates
Candidate to re-evaluate for removal
go get github.com/slackhq/nebula@v1.9.7- Upstream Caddy has moved forward to
nebula v1.10.3and references security-related maintenance in the 2.11.x line. - Existing Charon pin comment may be stale after upstream smallstep updates.
- Upstream Caddy has moved forward to
Likely retain until proven redundant
go get github.com/expr-lang/expr@v1.17.7go get github.com/hslatman/ipstore@v0.4.0
Retention/removal decision must be made using reproducible build + binary inspection evidence, not assumption.
Hard retirement gates (mandatory before removing any pin)
Pin removal is blocked unless all gates pass:
- Binary module diff gate
- Produce before/after
go version -mmodule diff for Caddy binary. - No unexpected module major-version jumps outside approved advisory scope.
- Produce before/after
- Security regression gate
- No new HIGH/CRITICAL findings in CodeQL/Trivy/Grype compared to baseline.
- Reproducible build parity gate
- Two clean rebuilds produce equivalent module inventory and matching runtime smoke results.
- Rollback proof gate (mandatory, with explicit
nebulafocus)- Demonstrate one-command rollback to previous pin set, with successful compile + runtime smoke set after rollback.
Retirement decision for nebula cannot proceed without explicit rollback proof
artifact attached to PR evidence.
Feature-to-control mapping (exposure decision matrix)
| Feature | Control surface | Expose vs backend-only rationale | Persistence path |
|---|---|---|---|
keepalive_idle, keepalive_count |
Existing advanced system settings (if approved) | Expose only if operators need deterministic upstream connection control; otherwise keep backend defaults to avoid UX bloat. | frontend/src/pages/SystemSettings.tsx → frontend/src/api/settings.ts → backend/internal/api/handlers/settings_handler.go → DB settings → backend/internal/caddy/config.go (GenerateConfig) |
trusted_proxies_unix |
Backend-only default initially | Backend-only until proven demand for unix-socket trust tuning; avoid misconfiguration risk in general UI. | backend config model (backend/internal/caddy/types.go) + generated config path (backend/internal/caddy/config.go) |
renewal_window_ratio, cert maintenance interval |
Backend-only policy | Keep backend-only unless operations requires explicit lifecycle tuning controls. | settings store (if introduced) → settings_handler.go → GenerateConfig |
| Reverse-proxy Host rewrite / ECH rotation / reload fallback internals | Backend-only | Operational internals with low direct UI value; exposing would increase complexity without clear user benefit. | backend runtime defaults and generated Caddy config only |
Implementation Plan
Phase 1: Playwright and behavior baselining (mandatory first)
Objective: capture stable pre-upgrade behavior and ensure UI/UX parity checks.
- Run targeted E2E suites covering Caddy-critical flows:
tests/tasks/import-caddyfile.spec.tstests/security-enforcement/zzz-caddy-imports/*.spec.ts- system settings-related tests around Caddy admin API and SSL provider
- Capture baseline artifacts:
- Caddy import warning behavior
- security settings save/reload behavior
- admin API connectivity assumptions from test fixtures
- Produce a baseline report in
docs/reports/for diffing in later phases.
Phase 2: Backend and build compatibility research implementation
Objective: validate compile/runtime compatibility of Caddy 2.11.1 with current plugin set and patch set.
-
Bump candidate in
Dockerfile:ARG CADDY_VERSION=2.11.1
-
Execute matrix builds with toggles:
- Scenario A: current patch set unchanged
- Scenario B: remove
nebulapin only - Scenario C: remove
nebula+ retainexpr/ipstore
-
Execute explicit compatibility gate matrix (deterministic):
Dimension Values Plugin set caddy-security,coraza-caddy,caddy-crowdsec-bouncer,caddy-geoip2,caddy-ratelimitPatch scenario Acurrent pins,Bnonebulapin,Cnonebulapin + retainedexpr/ipstorepinsPlatform/arch linux/amd64,linux/arm64Runtime smoke set boot Caddy, apply generated config, admin API health, import preview, one secured proxy request path Deterministic pass/fail rule:
- Pass: all plugin modules compile/load for the matrix cell AND all smoke tests pass.
- Fail: any compile/load error, missing module, or smoke failure.
Promotion criteria:
- PR-1 promotion requires 100% pass for Scenario A on both architectures.
- Scenario B/C may progress only as candidate evidence; they cannot promote to default unless all hard retirement gates pass.
-
Validate generated binary dependencies from CI/local:
- verify
expr,ipstore,nebula,smallstep/certificatesversions
- verify
-
Validate runtime config application path:
backend/internal/caddy/manager.go→ApplyConfig(ctx)backend/internal/caddy/config.go→GenerateConfig(...)
-
Run Caddy package tests and relevant integration tests:
backend/internal/caddy/*- security middleware integration paths that rely on Caddy behavior
Phase 3: Security hardening and vulnerability posture updates
Objective: translate upstream advisories into Charon policy and tests.
- Add/adjust regression tests for advisory-sensitive behavior in
backend/internal/caddyand integration test suites, especially:- host matcher behavior with large host lists
- escaped path matcher handling
- admin API cross-origin assumptions
- Update security documentation and operational guidance:
- identify which advisories are mitigated by upgrade alone
- identify deployment assumptions (e.g., local admin API exposure)
- Introduce watchlist process for RESERVED CVEs pending NVD enrichment:
- monitor Caddy advisories and module-level disclosures weekly
Phase 4: Frontend and API exposure decisions (only if justified)
Objective: decide whether 2.11.x features merit UI controls.
- Evaluate additions to existing
SystemSettingsUX only (no new page):- optional advanced toggles for keepalive tuning and trusted proxy unix scope
- Add backend settings keys and mapping only where persisted behavior is
needed:
- settings handler support in
backend/internal/api/handlers/settings_handler.go - propagation to config generation in
GenerateConfig(...)
- settings handler support in
- If no high-value operator need is proven, keep features backend-default and document rationale.
Phase 5: Validation, docs, and release readiness
Objective: ensure secure, reversible, and auditable rollout.
- Re-run full DoD sequence (E2E, patch report, security scans, coverage).
- Update architectural docs if behavior/config model changes.
- Publish release decision memo:
- accepted changes
- rejected/deferred UX features
- retained/removed patches with evidence
PR Slicing Strategy
Decision
Use multiple PRs (PR-1/PR-2/PR-3).
Reasoning:
- Work spans infra/build security + backend runtime + potential frontend UX.
- Caddy is a blast-radius-critical dependency; rollback safety is mandatory.
- Review quality and CI signal are stronger with isolated, testable slices.
PR-1: Compatibility and evidence foundation
Scope:
DockerfileCaddy candidate bump (and temporary feature branch matrix toggles)- CI/workflow compatibility instrumentation if needed
- compatibility report artifacts and plan-linked documentation
Dependencies:
- None
Acceptance criteria:
- Caddy 2.11.1 compiles with existing plugin set under at least one stable patch scenario.
- Compatibility gate matrix (plugin × patch scenario × platform/arch × runtime smoke set) executed with deterministic pass/fail output and attached evidence.
- Binary module inventory report generated and attached.
- No production behavior changes merged beyond compatibility scaffolding.
Release guard (mandatory for PR-1):
- Candidate tag only (
*-rc/*-candidate) is allowed. - Release pipeline exclusion is required; PR-1 artifacts must not be eligible for production release jobs.
- Promotion to releasable tag is blocked until PR-2 security/retirement gates pass.
Rollback notes:
- Revert
Dockerfilearg changes and instrumentation only.
PR-2: Security patch posture + patch retirement decision
Scope:
- finalize retained/removed
go getpatch lines inDockerfile - update security tests/docs tied to six Caddy advisories
- tighten/confirm admin API exposure assumptions
Dependencies:
- PR-1 evidence
Acceptance criteria:
- Decision logged for each patch (
expr,ipstore,nebula) with rationale. - Advisory coverage matrix completed with Charon applicability labels.
- Security scans clean at required policy thresholds.
Rollback notes:
- Revert patch retirement lines and keep previous pinned patch model.
PR-3: Optional UX/API exposure and cleanup (Focused Execution Update)
Decision summary:
- PR-3 remains optional and value-gated.
- Expose only controls with clear operator value on existing
SystemSettings. - Keep low-value/high-risk knobs backend-default and non-exposed.
Operator-value exposure decision:
| Candidate | Operator value | Decision in PR-3 |
|---|---|---|
keepalive_idle, keepalive_count |
Helps operators tune long-lived upstream behavior (streaming, websocket-heavy, high-connection churn) without editing config by hand. | Expose minimally (only if PR-2 confirms stable runtime behavior). |
trusted_proxies_unix |
Niche socket-chain use case, easy to misconfigure, low value for default Charon operators. | Do not expose; backend-default only. |
renewal_window_ratio / cert maintenance internals |
Advanced certificate lifecycle tuning with low day-to-day value and higher support burden. | Do not expose; backend-default only. |
Strict scope constraints:
- No new routes, pages, tabs, or modals.
- UI changes limited to existing
frontend/src/pages/SystemSettings.tsxgeneral/system section. - API surface remains existing settings endpoints only (
POST /settings,PATCH /config). - Preserve backend defaults when setting is absent, empty, or invalid.
Minimum viable controls (if PR-3 is activated):
caddy.keepalive_idle(optional)- Surface:
SystemSettingsunder existing Caddy/system controls. - UX: bounded select/input for duration-like value (validated server-side).
- Persistence: existing
updateSetting()flow.
- Surface:
caddy.keepalive_count(optional)- Surface:
SystemSettingsadjacent to keepalive idle. - UX: bounded numeric control (validated server-side).
- Persistence: existing
updateSetting()flow.
- Surface:
Exact files/functions/components to change:
Backend (no new endpoints):
backend/internal/caddy/manager.go- Function:
ApplyConfig(ctx context.Context) error - Change: read optional settings keys (
caddy.keepalive_idle,caddy.keepalive_count), normalize/validate parsed values, pass sanitized values into config generation. - Default rule: on missing/invalid values, pass empty/zero equivalents so generated config keeps current backend-default behavior.
- Function:
backend/internal/caddy/config.go- Function:
GenerateConfig(...) - Change: extend function parameters with optional keepalive values and apply them only when non-default/valid.
- Change location: HTTP server construction block where server-level settings (including trusted proxies) are assembled.
- Function:
backend/internal/caddy/types.go- Type:
Server - Change: add optional fields required to emit keepalive keys in Caddy JSON only when provided.
- Type:
backend/internal/api/handlers/settings_handler.go- Functions:
UpdateSetting(...),PatchConfig(...) - Change: add narrow validation for
caddy.keepalive_idleandcaddy.keepalive_countto reject malformed/out-of-range values while preserving existing generic settings behavior for unrelated keys.
- Functions:
Frontend (existing surface only):
frontend/src/pages/SystemSettings.tsx- Component:
SystemSettings - Change: add local state load/save wiring for optional keepalive controls using existing settings query/mutation flow.
- Change: render controls in existing General/System card only.
- Component:
frontend/src/api/settings.ts- No contract expansion required; reuse
updateSetting(key, value, category, type).
- No contract expansion required; reuse
- Localization files (labels/help text only, if controls are exposed):
frontend/src/locales/en/translation.jsonfrontend/src/locales/de/translation.jsonfrontend/src/locales/es/translation.jsonfrontend/src/locales/fr/translation.jsonfrontend/src/locales/zh/translation.json
Tests to update/add (targeted):
frontend/src/pages/__tests__/SystemSettings.test.tsx- Verify control rendering, default-state behavior, and save calls for optional keepalive keys.
backend/internal/caddy/config_generate_test.go- Verify keepalive keys are omitted when unset/invalid and emitted when valid.
backend/internal/api/handlers/settings_handler_test.go- Verify validation pass/fail for keepalive keys via both
UpdateSettingandPatchConfigpaths.
- Verify validation pass/fail for keepalive keys via both
- Existing E2E settings coverage (no new suite)
- Extend existing settings-related specs only if UI controls are activated in PR-3.
Dependencies:
- PR-2 must establish stable runtime/security baseline first.
- PR-3 activation requires explicit operator-value confirmation from PR-2 evidence.
Acceptance criteria (PR-3 complete):
- No net-new page; all UI changes are within
SystemSettingsonly. - No new backend routes/endpoints; existing settings APIs are reused.
- Only approved controls (
caddy.keepalive_idle,caddy.keepalive_count) are exposed, and exposure is allowed only if the PR-3 Value Gate checklist is fully satisfied. trusted_proxies_unix,renewal_window_ratio, and certificate-maintenance internals remain backend-default and non-exposed.- Backend preserves current behavior when optional keepalive settings are absent or invalid (no generated-config drift).
- Unit tests pass for settings validation + config generation default/override behavior.
- Settings UI tests pass for load/save/default behavior on exposed controls.
- Deferred/non-exposed features are explicitly documented in PR notes as intentional non-goals.
PR-3 Value Gate (required evidence and approval)
Required evidence checklist (all items required):
- PR-2 evidence bundle contains an explicit operator-value decision record for PR-3 controls, naming
caddy.keepalive_idleandcaddy.keepalive_countindividually. - Decision record includes objective evidence for each exposed control from at least one concrete source: test/baseline artifact, compatibility/security report, or documented operator requirement.
- PR includes before/after evidence proving scope containment: no new page, no new route, and no additional exposed Caddy keys beyond the two approved controls.
- Validation artifacts for PR-3 are attached: backend unit tests, frontend settings tests, and generated-config assertions for default/override behavior.
Approval condition (pass/fail):
- Pass: all checklist items are complete and a maintainer approval explicitly states "PR-3 Value Gate approved".
- Fail: any checklist item is missing or approval text is absent; PR-3 control exposure is blocked and controls remain backend-default/non-exposed.
Rollback notes:
- Revert only PR-3 UI/settings mapping changes while retaining PR-1/PR-2 runtime and security upgrades.
Config File Review and Proposed Updates
Dockerfile (required updates)
- Update
ARG CADDY_VERSIONtarget to2.11.1after PR-1 gating. - Reassess and potentially remove stale
nebulapin in caddy-builder stage if matrix build proves compatibility and security posture improves. - Keep
expr/ipstorepatch enforcement until binary inspection proves upstream transitive versions are consistently non-vulnerable.
.gitignore (suggested updates)
No mandatory update for rollout, but recommended if new evidence artifacts are generated in temporary paths:
- ensure transient compatibility artifacts are ignored (for example,
test-results/caddy-compat/**if used).
.dockerignore (suggested updates)
No mandatory update; current file already excludes heavy test/docs/security artifacts and keeps build context lean. Revisit only if new compatibility fixture directories are introduced.
codecov.yml (suggested updates)
No mandatory change for version upgrade itself. If new compatibility harness tests are intentionally non-coverage-bearing, add explicit ignore patterns to avoid noise in project and patch coverage reports.
Risk Register and Mitigations
- Plugin/API incompatibility with Caddy 2.11.1
- Mitigation: matrix compile + targeted runtime tests before merge.
- False confidence from scanner-only dependency policies
- Mitigation: combine advisory-context review with binary-level inspection.
- Behavioral drift in reverse proxy/matcher semantics
- Mitigation: baseline E2E + focused security regression tests.
- UI sprawl from exposing too many Caddy internals
- Mitigation: only extend existing settings surface when operator value is clear and validated.
Acceptance Criteria
- Charon builds and runs with Caddy 2.11.1 and current plugin set under deterministic CI validation.
- A patch disposition table exists for
expr,ipstore, andnebula(retain/remove/replace + evidence). - Caddy advisory applicability matrix is documented, including exploitability notes for Charon deployment model.
- Any added settings are mapped end-to-end:
frontend state → API payload → persisted setting →
GenerateConfig(...). - E2E, security scans, and coverage gates pass without regression.
- PR-1/PR-2/PR-3 deliverables are independently reviewable and rollback-safe.
Handoff
After approval of this plan:
- Delegate PR-1 execution to implementation workflow.
- Require evidence artifacts before approving PR-2 scope reductions (especially patch removals).
- Treat PR-3 as optional and value-driven, not mandatory for the security update itself.
PR-3 QA Closure Addendum (2026-02-23)
Scope
PR-3 closure only:
- Keepalive controls (
caddy.keepalive_idle,caddy.keepalive_count) - Safe defaults/fallback behavior when keepalive values are missing or invalid
- Non-exposure constraints for deferred settings
Final QA Outcome
- Verdict: READY (PASS)
- Targeted PR-3 E2E rerun: 30 passed, 0 failed
- Local patch preflight: PASS with required LCOV artifact present
- Coverage/type-check/security gates: PASS
Scope Guardrails Confirmed
- UI scope remains constrained to existing System Settings surface.
- No PR-3 expansion beyond approved keepalive controls.
- Non-exposed settings remain non-exposed (
trusted_proxies_unixand certificate lifecycle internals). - Safe fallback/default behavior remains intact for invalid or absent keepalive input.
Reviewer References
- QA closure report:
docs/reports/qa_report.md - Manual verification plan:
docs/issues/manual_test_pr3_keepalive_controls_closure.md