diff --git a/.docker/README.md b/.docker/README.md
index c92cee899..07e289030 100644
--- a/.docker/README.md
+++ b/.docker/README.md
@@ -94,7 +94,7 @@ Configure the application via `docker-compose.yml`:
| `CHARON_ENV` | `production` | Set to `development` for verbose logging (`CPM_ENV` supported for backward compatibility). |
| `CHARON_HTTP_PORT` | `8080` | Port for the Web UI (`CPM_HTTP_PORT` supported for backward compatibility). |
| `CHARON_DB_PATH` | `/app/data/charon.db` | Path to the SQLite database (`CPM_DB_PATH` supported for backward compatibility). |
-| `CHARON_CADDY_ADMIN_API` | `http://localhost:2019` | Internal URL for Caddy API (`CPM_CADDY_ADMIN_API` supported for backward compatibility). |
+| `CHARON_CADDY_ADMIN_API` | `http://localhost:2019` | Internal URL for Caddy API (`CPM_CADDY_ADMIN_API` supported for backward compatibility). Must resolve to an internal allowlisted host on port `2019`. |
| `CHARON_CADDY_CONFIG_ROOT` | `/config` | Path to Caddy autosave configuration directory. |
| `CHARON_CADDY_LOG_DIR` | `/var/log/caddy` | Directory for Caddy access logs. |
| `CHARON_CROWDSEC_LOG_DIR` | `/var/log/crowdsec` | Directory for CrowdSec logs. |
@@ -218,6 +218,8 @@ environment:
- CPM_CADDY_ADMIN_API=http://your-caddy-host:2019
```
+If using a non-localhost internal hostname, add it to `CHARON_SSRF_INTERNAL_HOST_ALLOWLIST`.
+
**Warning**: Charon will replace Caddy's entire configuration. Backup first!
## Performance Tuning
diff --git a/.github/skills/test-backend-coverage-scripts/run.sh b/.github/skills/test-backend-coverage-scripts/run.sh
index 01b62efd4..c707d78a5 100755
--- a/.github/skills/test-backend-coverage-scripts/run.sh
+++ b/.github/skills/test-backend-coverage-scripts/run.sh
@@ -32,7 +32,7 @@ cd "${PROJECT_ROOT}"
validate_project_structure "backend" "scripts/go-test-coverage.sh" || error_exit "Invalid project structure"
# Set default environment variables
-set_default_env "CHARON_MIN_COVERAGE" "85"
+set_default_env "CHARON_MIN_COVERAGE" "87"
set_default_env "PERF_MAX_MS_GETSTATUS_P95" "25ms"
set_default_env "PERF_MAX_MS_GETSTATUS_P95_PARALLEL" "50ms"
set_default_env "PERF_MAX_MS_LISTDECISIONS_P95" "75ms"
diff --git a/.github/skills/test-frontend-coverage-scripts/run.sh b/.github/skills/test-frontend-coverage-scripts/run.sh
index fb81959cd..90afa0e08 100755
--- a/.github/skills/test-frontend-coverage-scripts/run.sh
+++ b/.github/skills/test-frontend-coverage-scripts/run.sh
@@ -32,7 +32,7 @@ cd "${PROJECT_ROOT}"
validate_project_structure "frontend" "scripts/frontend-test-coverage.sh" || error_exit "Invalid project structure"
# Set default environment variables
-set_default_env "CHARON_MIN_COVERAGE" "85"
+set_default_env "CHARON_MIN_COVERAGE" "87"
# Execute the legacy script
log_step "EXECUTION" "Running frontend tests with coverage"
diff --git a/.github/workflows/release-goreleaser.yml b/.github/workflows/release-goreleaser.yml
index 0bab3e02d..50120ff22 100644
--- a/.github/workflows/release-goreleaser.yml
+++ b/.github/workflows/release-goreleaser.yml
@@ -20,6 +20,7 @@ permissions:
jobs:
goreleaser:
+ if: ${{ !contains(github.ref_name, '-candidate') && !contains(github.ref_name, '-rc') }}
runs-on: ubuntu-latest
env:
# Use the built-in GITHUB_TOKEN by default for GitHub API operations.
@@ -32,10 +33,22 @@ jobs:
with:
fetch-depth: 0
+ - name: Enforce PR-2 release promotion guard
+ env:
+ REPO_VARS_JSON: ${{ toJSON(vars) }}
+ run: |
+ PR2_GATE_STATUS="$(printf '%s' "$REPO_VARS_JSON" | jq -r '.CHARON_PR2_GATES_PASSED // "false"')"
+ if [[ "$PR2_GATE_STATUS" != "true" ]]; then
+ echo "::error::Releasable tag promotion is blocked until PR-2 security/retirement gates pass."
+ echo "::error::Set repository variable CHARON_PR2_GATES_PASSED=true only after PR-2 approval."
+ exit 1
+ fi
+
- name: Set up Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
with:
go-version: ${{ env.GO_VERSION }}
+ cache-dependency-path: backend/go.sum
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml
index 36958d432..dd73e2cdf 100644
--- a/.github/workflows/renovate.yml
+++ b/.github/workflows/renovate.yml
@@ -25,7 +25,7 @@ jobs:
fetch-depth: 1
- name: Run Renovate
- uses: renovatebot/github-action@d65ef9e20512193cc070238b49c3873a361cd50c # v46.1.1
+ uses: renovatebot/github-action@8d75b92f43899d483728e9a8a7fd44238020f6e6 # v46.1.2
with:
configurationFile: .github/renovate.json
token: ${{ secrets.RENOVATE_TOKEN || secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/security-pr.yml b/.github/workflows/security-pr.yml
index 944064661..aea3e2781 100644
--- a/.github/workflows/security-pr.yml
+++ b/.github/workflows/security-pr.yml
@@ -174,7 +174,7 @@ jobs:
- name: Download PR image artifact
if: steps.check-artifact.outputs.artifact_exists == 'true'
# actions/download-artifact v4.1.8
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
+ uses: actions/download-artifact@ac21fcf45e0aaee541c0f7030558bdad38d77d6c
with:
name: ${{ steps.pr-info.outputs.is_push == 'true' && 'push-image' || format('pr-image-{0}', steps.pr-info.outputs.pr_number) }}
run-id: ${{ steps.check-artifact.outputs.run_id }}
@@ -280,7 +280,7 @@ jobs:
- name: Upload Trivy SARIF to GitHub Security
if: steps.check-artifact.outputs.artifact_exists == 'true' || github.event_name == 'push' || github.event_name == 'pull_request'
# github/codeql-action v4
- uses: github/codeql-action/upload-sarif@710e2945787622b429f8982cacb154faa182de18
+ uses: github/codeql-action/upload-sarif@cb4e075f119f8bccbc942d49655b2cd4dc6e615a
with:
sarif_file: 'trivy-binary-results.sarif'
category: ${{ steps.pr-info.outputs.is_push == 'true' && format('security-scan-{0}', github.event.workflow_run.head_branch) || format('security-scan-pr-{0}', steps.pr-info.outputs.pr_number) }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 78127bdcc..b48f855e4 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -113,7 +113,7 @@ repos:
stages: [manual] # Only runs when explicitly called
- id: frontend-type-check
name: Frontend TypeScript Check
- entry: bash -c 'cd frontend && npm run type-check'
+ entry: bash -c 'cd frontend && npx tsc --noEmit'
language: system
files: '^frontend/.*\.(ts|tsx)$'
pass_filenames: false
diff --git a/.version b/.version
index 96fb87f8e..3a7f17e42 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-v0.19.0
+v0.19.1
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index c8eef9bef..8cd3f920e 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -724,6 +724,13 @@
"group": "test",
"problemMatcher": []
},
+ {
+ "label": "Security: Caddy PR-1 Compatibility Matrix",
+ "type": "shell",
+ "command": "cd /projects/Charon && bash scripts/caddy-compat-matrix.sh --candidate-version 2.11.1 --patch-scenarios A,B,C --platforms linux/amd64,linux/arm64 --smoke-set boot_caddy,plugin_modules,config_validate,admin_api_health --output-dir test-results/caddy-compat --docs-report docs/reports/caddy-compatibility-matrix.md",
+ "group": "test",
+ "problemMatcher": []
+ },
{
"label": "Test: E2E Playwright (Skill)",
"type": "shell",
diff --git a/Dockerfile b/Dockerfile
index fa4218529..d5088a2a9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -16,6 +16,9 @@ ARG BUILD_DEBUG=0
## Try to build the requested Caddy v2.x tag (Renovate can update this ARG).
## If the requested tag isn't available, fall back to a known-good v2.11.0-beta.2 build.
ARG CADDY_VERSION=2.11.0-beta.2
+ARG CADDY_CANDIDATE_VERSION=2.11.1
+ARG CADDY_USE_CANDIDATE=0
+ARG CADDY_PATCH_SCENARIO=B
## When an official caddy image tag isn't available on the host, use a
## plain Alpine base image and overwrite its caddy binary with our
## xcaddy-built binary in the later COPY step. This avoids relying on
@@ -196,6 +199,9 @@ FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS caddy-builder
ARG TARGETOS
ARG TARGETARCH
ARG CADDY_VERSION
+ARG CADDY_CANDIDATE_VERSION
+ARG CADDY_USE_CANDIDATE
+ARG CADDY_PATCH_SCENARIO
# renovate: datasource=go depName=github.com/caddyserver/xcaddy
ARG XCADDY_VERSION=0.4.5
@@ -213,10 +219,16 @@ RUN --mount=type=cache,target=/go/pkg/mod \
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \
sh -c 'set -e; \
+ CADDY_TARGET_VERSION="${CADDY_VERSION}"; \
+ if [ "${CADDY_USE_CANDIDATE}" = "1" ]; then \
+ CADDY_TARGET_VERSION="${CADDY_CANDIDATE_VERSION}"; \
+ fi; \
+ echo "Using Caddy target version: v${CADDY_TARGET_VERSION}"; \
+ echo "Using Caddy patch scenario: ${CADDY_PATCH_SCENARIO}"; \
export XCADDY_SKIP_CLEANUP=1; \
echo "Stage 1: Generate go.mod with xcaddy..."; \
# Run xcaddy to generate the build directory and go.mod
- GOOS=$TARGETOS GOARCH=$TARGETARCH xcaddy build v${CADDY_VERSION} \
+ GOOS=$TARGETOS GOARCH=$TARGETARCH xcaddy build v${CADDY_TARGET_VERSION} \
--with github.com/greenpau/caddy-security \
--with github.com/corazawaf/coraza-caddy/v2 \
--with github.com/hslatman/caddy-crowdsec-bouncer@v0.10.0 \
@@ -239,12 +251,21 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
go get github.com/expr-lang/expr@v1.17.7; \
# renovate: datasource=go depName=github.com/hslatman/ipstore
go get github.com/hslatman/ipstore@v0.4.0; \
- # NOTE: smallstep/certificates (pulled by caddy-security stack) currently
- # uses legacy nebula APIs removed in nebula v1.10+, which causes compile
- # failures in authority/provisioner. Keep this pinned to a known-compatible
- # v1.9.x release until upstream stack supports nebula v1.10+.
- # renovate: datasource=go depName=github.com/slackhq/nebula
- go get github.com/slackhq/nebula@v1.9.7; \
+ if [ "${CADDY_PATCH_SCENARIO}" = "A" ]; then \
+ # Rollback scenario: keep explicit nebula pin if upstream compatibility regresses.
+ # NOTE: smallstep/certificates (pulled by caddy-security stack) currently
+ # uses legacy nebula APIs removed in nebula v1.10+, which causes compile
+ # failures in authority/provisioner. Keep this pinned to a known-compatible
+ # v1.9.x release until upstream stack supports nebula v1.10+.
+ # renovate: datasource=go depName=github.com/slackhq/nebula
+ go get github.com/slackhq/nebula@v1.9.7; \
+ elif [ "${CADDY_PATCH_SCENARIO}" = "B" ] || [ "${CADDY_PATCH_SCENARIO}" = "C" ]; then \
+ # Default PR-2 posture: retire explicit nebula pin and use upstream resolution.
+ echo "Skipping nebula pin for scenario ${CADDY_PATCH_SCENARIO}"; \
+ else \
+ echo "Unsupported CADDY_PATCH_SCENARIO=${CADDY_PATCH_SCENARIO}"; \
+ exit 1; \
+ fi; \
# Clean up go.mod and ensure all dependencies are resolved
go mod tidy; \
echo "Dependencies patched successfully"; \
diff --git a/backend/internal/api/handlers/settings_handler.go b/backend/internal/api/handlers/settings_handler.go
index 7d6603fdf..d2eca5a61 100644
--- a/backend/internal/api/handlers/settings_handler.go
+++ b/backend/internal/api/handlers/settings_handler.go
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net/http"
+ "strconv"
"strings"
"time"
@@ -37,6 +38,15 @@ type SettingsHandler struct {
DataRoot string
}
+const (
+ settingCaddyKeepaliveIdle = "caddy.keepalive_idle"
+ settingCaddyKeepaliveCount = "caddy.keepalive_count"
+ minCaddyKeepaliveIdleDuration = time.Second
+ maxCaddyKeepaliveIdleDuration = 24 * time.Hour
+ minCaddyKeepaliveCount = 1
+ maxCaddyKeepaliveCount = 100
+)
+
func NewSettingsHandler(db *gorm.DB) *SettingsHandler {
return &SettingsHandler{
DB: db,
@@ -109,6 +119,11 @@ func (h *SettingsHandler) UpdateSetting(c *gin.Context) {
}
}
+ if err := validateOptionalKeepaliveSetting(req.Key, req.Value); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
setting := models.Setting{
Key: req.Key,
Value: req.Value,
@@ -247,6 +262,10 @@ func (h *SettingsHandler) PatchConfig(c *gin.Context) {
}
}
+ if err := validateOptionalKeepaliveSetting(key, value); err != nil {
+ return err
+ }
+
setting := models.Setting{
Key: key,
Value: value,
@@ -284,6 +303,10 @@ func (h *SettingsHandler) PatchConfig(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid admin_whitelist"})
return
}
+ if strings.Contains(err.Error(), "invalid caddy.keepalive_idle") || strings.Contains(err.Error(), "invalid caddy.keepalive_count") {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
if respondPermissionError(c, h.SecuritySvc, "settings_save_failed", err, h.DataRoot) {
return
}
@@ -401,6 +424,53 @@ func validateAdminWhitelist(whitelist string) error {
return nil
}
+func validateOptionalKeepaliveSetting(key, value string) error {
+ switch key {
+ case settingCaddyKeepaliveIdle:
+ return validateKeepaliveIdleValue(value)
+ case settingCaddyKeepaliveCount:
+ return validateKeepaliveCountValue(value)
+ default:
+ return nil
+ }
+}
+
+func validateKeepaliveIdleValue(value string) error {
+ idle := strings.TrimSpace(value)
+ if idle == "" {
+ return nil
+ }
+
+ d, err := time.ParseDuration(idle)
+ if err != nil {
+ return fmt.Errorf("invalid caddy.keepalive_idle")
+ }
+
+ if d < minCaddyKeepaliveIdleDuration || d > maxCaddyKeepaliveIdleDuration {
+ return fmt.Errorf("invalid caddy.keepalive_idle")
+ }
+
+ return nil
+}
+
+func validateKeepaliveCountValue(value string) error {
+ raw := strings.TrimSpace(value)
+ if raw == "" {
+ return nil
+ }
+
+ count, err := strconv.Atoi(raw)
+ if err != nil {
+ return fmt.Errorf("invalid caddy.keepalive_count")
+ }
+
+ if count < minCaddyKeepaliveCount || count > maxCaddyKeepaliveCount {
+ return fmt.Errorf("invalid caddy.keepalive_count")
+ }
+
+ return nil
+}
+
func (h *SettingsHandler) syncAdminWhitelist(whitelist string) error {
return h.syncAdminWhitelistWithDB(h.DB, whitelist)
}
diff --git a/backend/internal/api/handlers/settings_handler_test.go b/backend/internal/api/handlers/settings_handler_test.go
index fdc1097dd..f64f4340e 100644
--- a/backend/internal/api/handlers/settings_handler_test.go
+++ b/backend/internal/api/handlers/settings_handler_test.go
@@ -413,6 +413,58 @@ func TestSettingsHandler_UpdateSetting_InvalidAdminWhitelist(t *testing.T) {
assert.Contains(t, w.Body.String(), "Invalid admin_whitelist")
}
+func TestSettingsHandler_UpdateSetting_InvalidKeepaliveIdle(t *testing.T) {
+ gin.SetMode(gin.TestMode)
+ db := setupSettingsTestDB(t)
+
+ handler := handlers.NewSettingsHandler(db)
+ router := newAdminRouter()
+ router.POST("/settings", handler.UpdateSetting)
+
+ payload := map[string]string{
+ "key": "caddy.keepalive_idle",
+ "value": "bad-duration",
+ }
+ body, _ := json.Marshal(payload)
+
+ w := httptest.NewRecorder()
+ req, _ := http.NewRequest("POST", "/settings", bytes.NewBuffer(body))
+ req.Header.Set("Content-Type", "application/json")
+ router.ServeHTTP(w, req)
+
+ assert.Equal(t, http.StatusBadRequest, w.Code)
+ assert.Contains(t, w.Body.String(), "invalid caddy.keepalive_idle")
+}
+
+func TestSettingsHandler_UpdateSetting_ValidKeepaliveCount(t *testing.T) {
+ gin.SetMode(gin.TestMode)
+ db := setupSettingsTestDB(t)
+
+ handler := handlers.NewSettingsHandler(db)
+ router := newAdminRouter()
+ router.POST("/settings", handler.UpdateSetting)
+
+ payload := map[string]string{
+ "key": "caddy.keepalive_count",
+ "value": "9",
+ "category": "caddy",
+ "type": "number",
+ }
+ body, _ := json.Marshal(payload)
+
+ w := httptest.NewRecorder()
+ req, _ := http.NewRequest("POST", "/settings", bytes.NewBuffer(body))
+ req.Header.Set("Content-Type", "application/json")
+ router.ServeHTTP(w, req)
+
+ assert.Equal(t, http.StatusOK, w.Code)
+
+ var setting models.Setting
+ err := db.Where("key = ?", "caddy.keepalive_count").First(&setting).Error
+ assert.NoError(t, err)
+ assert.Equal(t, "9", setting.Value)
+}
+
func TestSettingsHandler_UpdateSetting_SecurityKeyInvalidatesCache(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupSettingsTestDB(t)
@@ -538,6 +590,64 @@ func TestSettingsHandler_PatchConfig_InvalidAdminWhitelist(t *testing.T) {
assert.Contains(t, w.Body.String(), "Invalid admin_whitelist")
}
+func TestSettingsHandler_PatchConfig_InvalidKeepaliveCount(t *testing.T) {
+ gin.SetMode(gin.TestMode)
+ db := setupSettingsTestDB(t)
+
+ handler := handlers.NewSettingsHandler(db)
+ router := newAdminRouter()
+ router.PATCH("/config", handler.PatchConfig)
+
+ payload := map[string]any{
+ "caddy": map[string]any{
+ "keepalive_count": 0,
+ },
+ }
+ body, _ := json.Marshal(payload)
+
+ w := httptest.NewRecorder()
+ req, _ := http.NewRequest(http.MethodPatch, "/config", bytes.NewBuffer(body))
+ req.Header.Set("Content-Type", "application/json")
+ router.ServeHTTP(w, req)
+
+ assert.Equal(t, http.StatusBadRequest, w.Code)
+ assert.Contains(t, w.Body.String(), "invalid caddy.keepalive_count")
+}
+
+func TestSettingsHandler_PatchConfig_ValidKeepaliveSettings(t *testing.T) {
+ gin.SetMode(gin.TestMode)
+ db := setupSettingsTestDB(t)
+
+ handler := handlers.NewSettingsHandler(db)
+ router := newAdminRouter()
+ router.PATCH("/config", handler.PatchConfig)
+
+ payload := map[string]any{
+ "caddy": map[string]any{
+ "keepalive_idle": "30s",
+ "keepalive_count": 12,
+ },
+ }
+ body, _ := json.Marshal(payload)
+
+ w := httptest.NewRecorder()
+ req, _ := http.NewRequest(http.MethodPatch, "/config", bytes.NewBuffer(body))
+ req.Header.Set("Content-Type", "application/json")
+ router.ServeHTTP(w, req)
+
+ assert.Equal(t, http.StatusOK, w.Code)
+
+ var idle models.Setting
+ err := db.Where("key = ?", "caddy.keepalive_idle").First(&idle).Error
+ assert.NoError(t, err)
+ assert.Equal(t, "30s", idle.Value)
+
+ var count models.Setting
+ err = db.Where("key = ?", "caddy.keepalive_count").First(&count).Error
+ assert.NoError(t, err)
+ assert.Equal(t, "12", count.Value)
+}
+
func TestSettingsHandler_PatchConfig_ReloadFailureReturns500(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupSettingsTestDB(t)
diff --git a/backend/internal/caddy/config.go b/backend/internal/caddy/config.go
index 60008607e..63a8b8930 100644
--- a/backend/internal/caddy/config.go
+++ b/backend/internal/caddy/config.go
@@ -857,6 +857,27 @@ func normalizeHeaderOps(headerOps map[string]any) {
}
}
+func applyOptionalServerKeepalive(conf *Config, keepaliveIdle string, keepaliveCount int) {
+ if conf == nil || conf.Apps.HTTP == nil || conf.Apps.HTTP.Servers == nil {
+ return
+ }
+
+ server, ok := conf.Apps.HTTP.Servers["charon_server"]
+ if !ok || server == nil {
+ return
+ }
+
+ idle := strings.TrimSpace(keepaliveIdle)
+ if idle != "" {
+ server.KeepaliveIdle = &idle
+ }
+
+ if keepaliveCount > 0 {
+ count := keepaliveCount
+ server.KeepaliveCount = &count
+ }
+}
+
// NormalizeAdvancedConfig traverses a parsed JSON advanced config (map or array)
// and normalizes any headers blocks so that header values are arrays of strings.
// It returns the modified config object which can be JSON marshaled again.
diff --git a/backend/internal/caddy/config_generate_test.go b/backend/internal/caddy/config_generate_test.go
index d913f6693..c3242f65a 100644
--- a/backend/internal/caddy/config_generate_test.go
+++ b/backend/internal/caddy/config_generate_test.go
@@ -103,3 +103,43 @@ func TestGenerateConfig_EmergencyRoutesBypassSecurity(t *testing.T) {
require.NotEqual(t, "crowdsec", name)
}
}
+
+func TestApplyOptionalServerKeepalive_OmitsWhenUnset(t *testing.T) {
+ cfg := &Config{
+ Apps: Apps{
+ HTTP: &HTTPApp{Servers: map[string]*Server{
+ "charon_server": {
+ Listen: []string{":80", ":443"},
+ Routes: []*Route{},
+ },
+ }},
+ },
+ }
+
+ applyOptionalServerKeepalive(cfg, "", 0)
+
+ server := cfg.Apps.HTTP.Servers["charon_server"]
+ require.Nil(t, server.KeepaliveIdle)
+ require.Nil(t, server.KeepaliveCount)
+}
+
+func TestApplyOptionalServerKeepalive_AppliesValidValues(t *testing.T) {
+ cfg := &Config{
+ Apps: Apps{
+ HTTP: &HTTPApp{Servers: map[string]*Server{
+ "charon_server": {
+ Listen: []string{":80", ":443"},
+ Routes: []*Route{},
+ },
+ }},
+ },
+ }
+
+ applyOptionalServerKeepalive(cfg, "45s", 7)
+
+ server := cfg.Apps.HTTP.Servers["charon_server"]
+ require.NotNil(t, server.KeepaliveIdle)
+ require.Equal(t, "45s", *server.KeepaliveIdle)
+ require.NotNil(t, server.KeepaliveCount)
+ require.Equal(t, 7, *server.KeepaliveCount)
+}
diff --git a/backend/internal/caddy/manager.go b/backend/internal/caddy/manager.go
index 01cf5447a..c2cfab9d3 100644
--- a/backend/internal/caddy/manager.go
+++ b/backend/internal/caddy/manager.go
@@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"sort"
+ "strconv"
"strings"
"time"
@@ -33,6 +34,15 @@ var (
validateConfigFunc = Validate
)
+const (
+ minKeepaliveIdleDuration = time.Second
+ maxKeepaliveIdleDuration = 24 * time.Hour
+ minKeepaliveCount = 1
+ maxKeepaliveCount = 100
+ settingCaddyKeepaliveIdle = "caddy.keepalive_idle"
+ settingCaddyKeepaliveCnt = "caddy.keepalive_count"
+)
+
// DNSProviderConfig contains a DNS provider with its decrypted credentials
// for use in Caddy DNS challenge configuration generation
type DNSProviderConfig struct {
@@ -277,6 +287,18 @@ func (m *Manager) ApplyConfig(ctx context.Context) error {
// Compute effective security flags (re-read runtime overrides)
_, aclEnabled, wafEnabled, rateLimitEnabled, crowdsecEnabled := m.computeEffectiveFlags(ctx)
+ keepaliveIdle := ""
+ var keepaliveIdleSetting models.Setting
+ if err := m.db.Where("key = ?", settingCaddyKeepaliveIdle).First(&keepaliveIdleSetting).Error; err == nil {
+ keepaliveIdle = sanitizeKeepaliveIdle(keepaliveIdleSetting.Value)
+ }
+
+ keepaliveCount := 0
+ var keepaliveCountSetting models.Setting
+ if err := m.db.Where("key = ?", settingCaddyKeepaliveCnt).First(&keepaliveCountSetting).Error; err == nil {
+ keepaliveCount = sanitizeKeepaliveCount(keepaliveCountSetting.Value)
+ }
+
// Safety check: if Cerberus is enabled in DB and no admin whitelist configured,
// warn but allow initial startup to proceed. This prevents total lockout when
// the user has enabled Cerberus but hasn't configured admin_whitelist yet.
@@ -401,6 +423,8 @@ func (m *Manager) ApplyConfig(ctx context.Context) error {
return fmt.Errorf("generate config: %w", err)
}
+ applyOptionalServerKeepalive(generatedConfig, keepaliveIdle, keepaliveCount)
+
// Debug logging: WAF configuration state for troubleshooting integration issues
logger.Log().WithFields(map[string]any{
"waf_enabled": wafEnabled,
@@ -467,6 +491,42 @@ func (m *Manager) ApplyConfig(ctx context.Context) error {
return nil
}
+func sanitizeKeepaliveIdle(value string) string {
+ idle := strings.TrimSpace(value)
+ if idle == "" {
+ return ""
+ }
+
+ d, err := time.ParseDuration(idle)
+ if err != nil {
+ return ""
+ }
+
+ if d < minKeepaliveIdleDuration || d > maxKeepaliveIdleDuration {
+ return ""
+ }
+
+ return idle
+}
+
+func sanitizeKeepaliveCount(value string) int {
+ raw := strings.TrimSpace(value)
+ if raw == "" {
+ return 0
+ }
+
+ count, err := strconv.Atoi(raw)
+ if err != nil {
+ return 0
+ }
+
+ if count < minKeepaliveCount || count > maxKeepaliveCount {
+ return 0
+ }
+
+ return count
+}
+
// saveSnapshot stores the config to disk with timestamp.
func (m *Manager) saveSnapshot(conf *Config) (string, error) {
timestamp := time.Now().Unix()
diff --git a/backend/internal/caddy/manager_patch_coverage_test.go b/backend/internal/caddy/manager_patch_coverage_test.go
index d9fab9709..5939b3222 100644
--- a/backend/internal/caddy/manager_patch_coverage_test.go
+++ b/backend/internal/caddy/manager_patch_coverage_test.go
@@ -1,8 +1,10 @@
package caddy
import (
+ "bytes"
"context"
"encoding/base64"
+ "io"
"net/http"
"net/http/httptest"
"os"
@@ -185,3 +187,93 @@ func TestManagerApplyConfig_DNSProviders_SkipsDecryptOrJSONFailures(t *testing.T
require.Len(t, captured, 1)
require.Equal(t, uint(24), captured[0].ID)
}
+
+func TestManagerApplyConfig_MapsKeepaliveSettingsToGeneratedServer(t *testing.T) {
+ var loadBody []byte
+ caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path == "/load" && r.Method == http.MethodPost {
+ payload, _ := io.ReadAll(r.Body)
+ loadBody = append([]byte(nil), payload...)
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+ w.WriteHeader(http.StatusNotFound)
+ }))
+ defer caddyServer.Close()
+
+ dsn := "file:" + t.Name() + "?mode=memory&cache=shared"
+ db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
+ require.NoError(t, err)
+ require.NoError(t, db.AutoMigrate(
+ &models.ProxyHost{},
+ &models.Location{},
+ &models.Setting{},
+ &models.CaddyConfig{},
+ &models.SSLCertificate{},
+ &models.SecurityConfig{},
+ &models.SecurityRuleSet{},
+ &models.SecurityDecision{},
+ &models.DNSProvider{},
+ ))
+
+ db.Create(&models.ProxyHost{DomainNames: "keepalive.example.com", ForwardHost: "127.0.0.1", ForwardPort: 8080, Enabled: true})
+ db.Create(&models.SecurityConfig{Name: "default", Enabled: true})
+ db.Create(&models.Setting{Key: settingCaddyKeepaliveIdle, Value: "45s"})
+ db.Create(&models.Setting{Key: settingCaddyKeepaliveCnt, Value: "8"})
+
+ origVal := validateConfigFunc
+ defer func() { validateConfigFunc = origVal }()
+ validateConfigFunc = func(_ *Config) error { return nil }
+
+ manager := NewManager(newTestClient(t, caddyServer.URL), db, t.TempDir(), "", false, config.SecurityConfig{CerberusEnabled: true})
+ require.NoError(t, manager.ApplyConfig(context.Background()))
+ require.NotEmpty(t, loadBody)
+
+ require.True(t, bytes.Contains(loadBody, []byte(`"keepalive_idle":"45s"`)))
+ require.True(t, bytes.Contains(loadBody, []byte(`"keepalive_count":8`)))
+}
+
+func TestManagerApplyConfig_InvalidKeepaliveSettingsFallbackToDefaults(t *testing.T) {
+ var loadBody []byte
+ caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path == "/load" && r.Method == http.MethodPost {
+ payload, _ := io.ReadAll(r.Body)
+ loadBody = append([]byte(nil), payload...)
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+ w.WriteHeader(http.StatusNotFound)
+ }))
+ defer caddyServer.Close()
+
+ dsn := "file:" + t.Name() + "_invalid?mode=memory&cache=shared"
+ db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
+ require.NoError(t, err)
+ require.NoError(t, db.AutoMigrate(
+ &models.ProxyHost{},
+ &models.Location{},
+ &models.Setting{},
+ &models.CaddyConfig{},
+ &models.SSLCertificate{},
+ &models.SecurityConfig{},
+ &models.SecurityRuleSet{},
+ &models.SecurityDecision{},
+ &models.DNSProvider{},
+ ))
+
+ db.Create(&models.ProxyHost{DomainNames: "invalid-keepalive.example.com", ForwardHost: "127.0.0.1", ForwardPort: 8080, Enabled: true})
+ db.Create(&models.SecurityConfig{Name: "default", Enabled: true})
+ db.Create(&models.Setting{Key: settingCaddyKeepaliveIdle, Value: "bad"})
+ db.Create(&models.Setting{Key: settingCaddyKeepaliveCnt, Value: "-1"})
+
+ origVal := validateConfigFunc
+ defer func() { validateConfigFunc = origVal }()
+ validateConfigFunc = func(_ *Config) error { return nil }
+
+ manager := NewManager(newTestClient(t, caddyServer.URL), db, t.TempDir(), "", false, config.SecurityConfig{CerberusEnabled: true})
+ require.NoError(t, manager.ApplyConfig(context.Background()))
+ require.NotEmpty(t, loadBody)
+
+ require.False(t, bytes.Contains(loadBody, []byte(`"keepalive_idle"`)))
+ require.False(t, bytes.Contains(loadBody, []byte(`"keepalive_count"`)))
+}
diff --git a/backend/internal/caddy/types.go b/backend/internal/caddy/types.go
index 5fce7ba88..474964b13 100644
--- a/backend/internal/caddy/types.go
+++ b/backend/internal/caddy/types.go
@@ -83,6 +83,8 @@ type Server struct {
AutoHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"`
Logs *ServerLogs `json:"logs,omitempty"`
TrustedProxies *TrustedProxies `json:"trusted_proxies,omitempty"`
+ KeepaliveIdle *string `json:"keepalive_idle,omitempty"`
+ KeepaliveCount *int `json:"keepalive_count,omitempty"`
}
// TrustedProxies defines the module for configuring trusted proxy IP ranges.
diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go
index 1e2f95202..a68094561 100644
--- a/backend/internal/config/config.go
+++ b/backend/internal/config/config.go
@@ -7,6 +7,8 @@ import (
"path/filepath"
"strconv"
"strings"
+
+ "github.com/Wikid82/charon/backend/internal/security"
)
// Config captures runtime configuration sourced from environment variables.
@@ -106,6 +108,17 @@ func Load() (Config, error) {
Debug: getEnvAny("false", "CHARON_DEBUG", "CPM_DEBUG") == "true",
}
+ allowedInternalHosts := security.InternalServiceHostAllowlist()
+ normalizedCaddyAdminURL, err := security.ValidateInternalServiceBaseURL(
+ cfg.CaddyAdminAPI,
+ 2019,
+ allowedInternalHosts,
+ )
+ if err != nil {
+ return Config{}, fmt.Errorf("validate caddy admin api url: %w", err)
+ }
+ cfg.CaddyAdminAPI = normalizedCaddyAdminURL.String()
+
if err := os.MkdirAll(filepath.Dir(cfg.DatabasePath), 0o700); err != nil {
return Config{}, fmt.Errorf("ensure data directory: %w", err)
}
diff --git a/backend/internal/config/config_test.go b/backend/internal/config/config_test.go
index 4cbd3865b..98597da77 100644
--- a/backend/internal/config/config_test.go
+++ b/backend/internal/config/config_test.go
@@ -258,6 +258,32 @@ func TestLoad_EmergencyConfig(t *testing.T) {
assert.Equal(t, "testpass", cfg.Emergency.BasicAuthPassword)
}
+func TestLoad_CaddyAdminAPIValidationAndNormalization(t *testing.T) {
+ tempDir := t.TempDir()
+ t.Setenv("CHARON_DB_PATH", filepath.Join(tempDir, "test.db"))
+ t.Setenv("CHARON_CADDY_CONFIG_DIR", filepath.Join(tempDir, "caddy"))
+ t.Setenv("CHARON_IMPORT_DIR", filepath.Join(tempDir, "imports"))
+ t.Setenv("CHARON_SSRF_INTERNAL_HOST_ALLOWLIST", "")
+ t.Setenv("CHARON_CADDY_ADMIN_API", "http://localhost:2019/config/")
+
+ cfg, err := Load()
+ require.NoError(t, err)
+ assert.Equal(t, "http://localhost:2019", cfg.CaddyAdminAPI)
+}
+
+func TestLoad_CaddyAdminAPIValidationRejectsNonAllowlistedHost(t *testing.T) {
+ tempDir := t.TempDir()
+ t.Setenv("CHARON_DB_PATH", filepath.Join(tempDir, "test.db"))
+ t.Setenv("CHARON_CADDY_CONFIG_DIR", filepath.Join(tempDir, "caddy"))
+ t.Setenv("CHARON_IMPORT_DIR", filepath.Join(tempDir, "imports"))
+ t.Setenv("CHARON_SSRF_INTERNAL_HOST_ALLOWLIST", "")
+ t.Setenv("CHARON_CADDY_ADMIN_API", "http://example.com:2019")
+
+ _, err := Load()
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "validate caddy admin api url")
+}
+
// ============================================
// splitAndTrim Tests
// ============================================
diff --git a/codecov.yml b/codecov.yml
index 9463cfb1c..97e325ef1 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -7,8 +7,8 @@ coverage:
status:
project:
default:
- target: 85%
- threshold: 0%
+ target: 87%
+ threshold: 1%
# Fail CI if Codecov upload/report indicates a problem
require_ci_to_pass: yes
diff --git a/docs/issues/manual_test_pr1_caddy_compatibility_closure.md b/docs/issues/manual_test_pr1_caddy_compatibility_closure.md
new file mode 100644
index 000000000..b46d97111
--- /dev/null
+++ b/docs/issues/manual_test_pr1_caddy_compatibility_closure.md
@@ -0,0 +1,95 @@
+## Manual Test Tracking Plan — PR-1 Caddy Compatibility Closure
+
+- Date: 2026-02-23
+- Scope: PR-1 only
+- Goal: Track potential bugs in the completed PR-1 slice and confirm safe promotion.
+
+## In Scope Features
+
+1. Compatibility matrix execution and pass/fail outcomes
+2. Release guard behavior (promotion gate)
+3. Candidate build path behavior (`CADDY_USE_CANDIDATE=1`)
+4. Non-drift defaults (`CADDY_USE_CANDIDATE=0` remains default)
+
+## Out of Scope
+
+- PR-2 and later slices
+- Unrelated frontend feature behavior
+- Historical QA items not tied to PR-1
+
+## Environment Checklist
+
+- [ ] Local repository is up to date with PR-1 changes
+- [ ] Docker build completes successfully
+- [ ] Test output directory is clean or isolated for this run
+
+## Test Cases
+
+### TC-001 — Compatibility Matrix Completes
+
+- Area: Compatibility matrix
+- Risk: False PASS due to partial artifacts or mixed output paths
+- Steps:
+ 1. Run the matrix script with an isolated output directory.
+ 2. Verify all expected rows are present for scenarios A/B/C and amd64/arm64.
+ 3. Confirm each row has explicit PASS/FAIL values for required checks.
+- Expected:
+ - Matrix completes without missing rows.
+ - Row statuses are deterministic and readable.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+### TC-002 — Promotion Gate Enforces Scenario A Only
+
+- Area: Release guard
+- Risk: Incorrect gate logic blocks or allows promotion unexpectedly
+- Steps:
+ 1. Review matrix results for scenario A on amd64 and arm64.
+ 2. Confirm promotion decision uses scenario A on both architectures.
+ 3. Confirm scenario B/C are evidence-only and do not flip the promotion verdict.
+- Expected:
+ - Promotion gate follows PR-1 rule exactly.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+### TC-003 — Candidate Build Path Is Opt-In
+
+- Area: Candidate build path
+- Risk: Candidate path becomes active without explicit opt-in
+- Steps:
+ 1. Build with default arguments.
+ 2. Confirm runtime behavior is standard (non-candidate path).
+ 3. Build again with candidate opt-in enabled.
+ 4. Confirm candidate path is only active in the opt-in build.
+- Expected:
+ - Candidate behavior appears only when explicitly enabled.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+### TC-004 — Default Runtime Behavior Does Not Drift
+
+- Area: Non-drift defaults
+- Risk: Silent default drift after PR-1 merge
+- Steps:
+ 1. Verify Docker defaults used by standard build.
+ 2. Run a standard deployment path.
+ 3. Confirm behavior matches pre-PR-1 default expectations.
+- Expected:
+ - Default runtime remains non-candidate.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+## Defect Log
+
+Use this section for any issue found during manual testing.
+
+| ID | Test Case | Severity | Summary | Reproducible | Status |
+| --- | --- | --- | --- | --- | --- |
+| | | | | | |
+
+## Exit Criteria
+
+- [ ] All four PR-1 test cases executed
+- [ ] No unresolved critical defects
+- [ ] Promotion decision is traceable to matrix evidence
+- [ ] Any failures documented with clear next action
diff --git a/docs/issues/manual_test_pr2_security_posture_closure.md b/docs/issues/manual_test_pr2_security_posture_closure.md
new file mode 100644
index 000000000..0aabfc3cc
--- /dev/null
+++ b/docs/issues/manual_test_pr2_security_posture_closure.md
@@ -0,0 +1,96 @@
+---
+title: "Manual Test Tracking Plan - Security Posture Closure"
+labels:
+ - testing
+ - security
+ - caddy
+priority: high
+---
+
+# Manual Test Tracking Plan - PR-2 Security Posture Closure
+
+## Scope
+PR-2 only.
+
+This plan tracks manual verification for:
+- Patch disposition decisions
+- Admin API assumptions and guardrails
+- Rollback checks
+
+Out of scope:
+- PR-1 compatibility closure tasks
+- PR-3 feature or UX expansion
+
+## Preconditions
+- [ ] Branch contains PR-2 documentation and configuration changes only.
+- [ ] Environment starts cleanly with default PR-2 settings.
+- [ ] Tester can run container start/restart and review startup logs.
+
+## Track A - Patch Disposition Validation
+
+### TC-PR2-001 Retained patches remain retained
+- [ ] Verify `expr` and `ipstore` patch decisions are documented as retained in the PR-2 security posture report.
+- [ ] Confirm no conflicting PR-2 docs state these patches are retired.
+- Expected result: retained/retained remains consistent across PR-2 closure docs.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+### TC-PR2-002 Nebula default retirement is clearly bounded
+- [ ] Verify PR-2 report states `nebula` retirement is by default scenario switch.
+- [ ] Verify rollback instruction is present and explicit.
+- Expected result: reviewer can identify default posture and rollback without ambiguity.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+## Track B - Admin API Assumption Checks
+
+### TC-PR2-003 Internal-only admin API assumption
+- [ ] Confirm PR-2 report states admin API is expected to be internal-only.
+- [ ] Confirm PR-2 QA report includes admin API validation/normalization posture.
+- Expected result: both reports communicate the same assumption.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+### TC-PR2-004 Invalid admin endpoint fails fast
+- [ ] Start with an intentionally invalid/non-allowlisted admin API URL.
+- [ ] Verify startup fails fast with clear configuration rejection behavior.
+- [ ] Restore valid URL and confirm startup succeeds.
+- Expected result: unsafe endpoint rejected; safe endpoint accepted.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+### TC-PR2-005 Port exposure assumption holds
+- [ ] Verify deployment defaults do not publish admin API port `2019`.
+- [ ] Confirm no PR-2 doc contradicts this default posture.
+- Expected result: admin API remains non-published by default.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+## Track C - Rollback Safety Checks
+
+### TC-PR2-006 Scenario rollback switch
+- [ ] Set `CADDY_PATCH_SCENARIO=A`.
+- [ ] Restart and verify the rollback path is accepted by the runtime.
+- [ ] Return to PR-2 default scenario and verify normal startup.
+- Expected result: rollback is deterministic and reversible.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+### TC-PR2-007 QA report rollback statement alignment
+- [ ] Confirm QA report and security posture report use the same rollback instruction.
+- [ ] Confirm both reports remain strictly PR-2 scoped.
+- Expected result: no conflicting rollback guidance; no PR-3 references.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+## Defect Log
+
+| ID | Test Case | Severity | Summary | Reproducible | Status |
+| --- | --- | --- | --- | --- | --- |
+| | | | | | |
+
+## Exit Criteria
+- [ ] All PR-2 test cases executed.
+- [ ] No unresolved critical defects.
+- [ ] Patch disposition, admin API assumptions, and rollback checks are all verified.
+- [ ] No PR-3 material introduced in this tracking plan.
diff --git a/docs/issues/manual_test_pr3_keepalive_controls_closure.md b/docs/issues/manual_test_pr3_keepalive_controls_closure.md
new file mode 100644
index 000000000..af3ff00a6
--- /dev/null
+++ b/docs/issues/manual_test_pr3_keepalive_controls_closure.md
@@ -0,0 +1,102 @@
+---
+title: "Manual Test Tracking Plan - PR-3 Keepalive Controls Closure"
+labels:
+ - testing
+ - frontend
+ - backend
+ - security
+priority: high
+---
+
+# Manual Test Tracking Plan - PR-3 Keepalive Controls Closure
+
+## Scope
+PR-3 only.
+
+This plan tracks manual verification for:
+- Keepalive control behavior in System Settings
+- Safe default/fallback behavior for missing or invalid keepalive values
+- Non-exposure constraints for deferred advanced settings
+
+Out of scope:
+- PR-1 compatibility closure tasks
+- PR-2 security posture closure tasks
+- Any new page, route, or feature expansion beyond approved PR-3 controls
+
+## Preconditions
+- [ ] Branch includes PR-3 closure changes only.
+- [ ] Environment starts cleanly.
+- [ ] Tester can access System Settings and save settings.
+- [ ] Tester can restart and re-open the app to verify persisted behavior.
+
+## Track A - Keepalive Controls
+
+### TC-PR3-001 Keepalive controls are present and editable
+- [ ] Open System Settings.
+- [ ] Verify keepalive idle and keepalive count controls are visible.
+- [ ] Enter valid values and save.
+- Expected result: values save successfully and are shown after refresh.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+### TC-PR3-002 Keepalive values persist across reload
+- [ ] Save valid keepalive idle and count values.
+- [ ] Refresh the page.
+- [ ] Re-open System Settings.
+- Expected result: saved values are preserved.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+## Track B - Safe Defaults and Fallback
+
+### TC-PR3-003 Missing keepalive input keeps safe defaults
+- [ ] Clear optional keepalive inputs (leave unset/empty where allowed).
+- [ ] Save and reload settings.
+- Expected result: app remains stable and uses safe default behavior.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+### TC-PR3-004 Invalid keepalive input is handled safely
+- [ ] Enter invalid keepalive values (out-of-range or malformed).
+- [ ] Attempt to save.
+- [ ] Correct the values and save again.
+- Expected result: invalid values are rejected safely; system remains stable; valid correction saves.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+### TC-PR3-005 Regression check after fallback path
+- [ ] Trigger one invalid save attempt.
+- [ ] Save valid values immediately after.
+- [ ] Refresh and verify current values.
+- Expected result: no stuck state; final valid values are preserved.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+## Track C - Non-Exposure Constraints
+
+### TC-PR3-006 Deferred advanced settings remain non-exposed
+- [ ] Review System Settings controls.
+- [ ] Confirm `trusted_proxies_unix` is not exposed.
+- [ ] Confirm certificate lifecycle internals are not exposed.
+- Expected result: only approved PR-3 keepalive controls are user-visible.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+### TC-PR3-007 Scope containment remains intact
+- [ ] Verify no new page/tab/modal was introduced for PR-3 controls.
+- [ ] Verify settings flow still uses existing System Settings experience.
+- Expected result: PR-3 remains contained to approved existing surface.
+- Status: [ ] Not run [ ] Pass [ ] Fail
+- Notes:
+
+## Defect Log
+
+| ID | Test Case | Severity | Summary | Reproducible | Status |
+| --- | --- | --- | --- | --- | --- |
+| | | | | | |
+
+## Exit Criteria
+- [ ] All PR-3 test cases executed.
+- [ ] No unresolved critical defects.
+- [ ] Keepalive controls, safe fallback/default behavior, and non-exposure constraints are verified.
+- [ ] No PR-1 or PR-2 closure tasks introduced in this PR-3 plan.
diff --git a/docs/plans/current_spec.md b/docs/plans/current_spec.md
index ef2a46944..a7527a075 100644
--- a/docs/plans/current_spec.md
+++ b/docs/plans/current_spec.md
@@ -1,194 +1,857 @@
---
-post_title: "Current Spec: Resolve Proxy Host Hostname Validation Test Failures"
+post_title: "Current Spec: Caddy 2.11.1 Compatibility, Security, and UX Impact Plan"
categories:
- actions
- - testing
+ - security
- backend
+ - frontend
+ - infrastructure
tags:
- - go
- - proxyhost
- - unit-tests
- - validation
-summary: "Focused plan to resolve failing TestProxyHostService_ValidateHostname malformed URL cases by aligning test expectations with intended validation behavior and validating via targeted service tests and coverage gate."
-post_date: 2026-02-22
+ - caddy
+ - xcaddy
+ - dependency-management
+ - vulnerability-management
+ - release-planning
+summary: "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."
+post_date: 2026-02-23
---
-## Active Plan: Resolve Failing Hostname Validation Tests
+## Active Plan: Caddy 2.11.1 Deep Compatibility and Security Rollout
-Date: 2026-02-22
+Date: 2026-02-23
Status: Active and authoritative
-Scope Type: Backend test-failure remediation (service validation drift analysis)
+Scope Type: Architecture/security/dependency research and implementation planning
Authority: This is the only active authoritative plan section in this file.
-## Introduction
+## Focused Plan: GitHub Actions `setup-go` Cache Warning (`go.sum` path)
-This plan resolves backend run failures in `TestProxyHostService_ValidateHostname`
-for malformed URL cases while preserving intended hostname validation behavior.
+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`.
-Primary objective:
+### Introduction
-- Restore green test execution in `backend/internal/services` with a minimal,
- low-risk change path.
+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
+### Research Findings
+
+Verified workflow inventory (`.github/workflows/**`):
+
+- All workflows using `actions/setup-go` were 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-go` without cache dependency path and are
+ the warning source:
+ - `.github/workflows/caddy-compat.yml`
+ - `.github/workflows/release-goreleaser.yml`
+- Repository check confirms only one `go.sum` exists:
+ - `backend/go.sum`
+
+### Technical Specification (Minimal Fix)
+
+Apply a warning-only cache path correction in both affected workflow steps:
+
+1. `.github/workflows/caddy-compat.yml`
+ - In `Set up Go` step, add:
+ - `cache-dependency-path: backend/go.sum`
+
+2. `.github/workflows/release-goreleaser.yml`
+ - In `Set up Go` step, add:
+ - `cache-dependency-path: backend/go.sum`
+
+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 Gate`
+ - `Release (GoReleaser)`
+
+#### Phase 3 — Closeout
+
+- Mark warning remediated once both workflows execute without the missing
+ `go.sum` cache warning.
+
+### Acceptance Criteria
+
+1. Both targeted workflows include `cache-dependency-path: backend/go.sum` in
+ their `actions/setup-go` step.
+2. No unrelated workflow files are modified.
+3. No behavior changes beyond warning elimination.
+4. 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`: Add `cache-dependency-path` to the two `setup-go` steps 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 values`
+- `tests/core/proxy-hosts.spec.ts` — `should update forward host and port`
+- `tests/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: true` globally) increases race potential for shared host list mutations.
-### Evidence Collected
+#### 2) `tests/settings/smtp-settings.spec.ts` (waitForResponse timeout)
-- Failing command output confirms two failing subtests:
- - `TestProxyHostService_ValidateHostname/malformed_https_URL`
- - `TestProxyHostService_ValidateHostname/malformed_http_URL`
-- Failure message for both cases: `invalid hostname format`.
+Observed test pattern:
+- Uses `clickAndWaitForResponse(page, saveButton, /\/api\/v1\/settings\/smtp/)`, which internally waits for response status `200` by default.
+- Test updates only host field, relying on pre-existing validity of other required fields.
-### Exact Files Involved
+Likely root causes:
+- If backend returns non-`200` (e.g., `400` validation), helper waits indefinitely for `200` and times out instead of failing fast.
+- The test assumes existing SMTP state is valid; this is brittle under parallel execution and prior test mutations.
-1. `backend/internal/services/proxyhost_service_validation_test.go`
- - Test function: `TestProxyHostService_ValidateHostname`
- - Failing cases currently expect `wantErr: false` for malformed URLs.
-2. `backend/internal/services/proxyhost_service.go`
- - Service function: `ValidateHostname(host string) error`
- - Behavior: strips scheme, then validates hostname characters; malformed
- residual values containing `:` are rejected with `invalid hostname format`.
+### Technical Specifications (Exact Test Changes)
-### Root Cause Determination
+#### A) `tests/core/proxy-hosts.spec.ts`
-- Root cause is **test expectation drift**, not runtime service regression.
-- `git blame` shows malformed URL test cases were added on 2026-02-22 with
- permissive expectations, while validation behavior rejecting malformed host
- strings predates those additions.
-- Existing behavior aligns with stricter hostname validation and should remain
- the default unless product requirements explicitly demand permissive handling
- of malformed host inputs.
+1. In `test.describe('Update Proxy Host', ...)`, add serial mode:
+- Add `test.describe.configure({ mode: 'serial' })` at the top of that describe block.
-### Confidence Assessment
+2. Add a local helper in this file for deterministic precondition and row-scoped edit action:
+- Helper name: `ensureEditableProxyHost(page, testData)`
+- Behavior:
+ - Check `tbody tr` count.
+ - If count is `0`, create one host via `testData.createProxyHost({ domain: ..., forwardHost: ..., forwardPort: ... })`.
+ - Reload `/proxy-hosts` and wait for content readiness using existing wait helpers.
-- Confidence score: **95% (High)**
-- Rationale: direct reproduction, targeted file inspection, and blame history
- converge on expectation drift.
+3. 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()`
+
+4. 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 values`
+- `should 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`
+
+1. In `test.describe('CRUD Operations', ...)`, add serial mode:
+- Add `test.describe.configure({ mode: 'serial' })` to avoid concurrent mutation of shared SMTP configuration.
+
+2. Strengthen required-field preconditions in failing test before save:
+- In `should update existing SMTP configuration`, explicitly set:
+ - `#smtp-host` to `updated-smtp.test.local`
+ - `#smtp-port` to `587`
+ - `#smtp-from` to `noreply@test.local`
+
+3. Replace status-constrained response wait that can timeout on non-200:
+- Replace `clickAndWaitForResponse(...)` call with `Promise.all([page.waitForResponse(...) , saveButton.click()])` matching URL + `POST` method (not status).
+- Immediately assert returned status is `200` and then keep success-toast assertion.
+
+4. 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.ts`
+ - `tests/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/core`
+ - `tests/settings/smtp-settings.spec.ts`
+
+### Acceptance Criteria
+
+1. `should open edit modal with existing values` passes without timeout.
+2. `should update forward host and port` passes without timeout.
+3. `should update existing SMTP configuration` passes without `waitForResponse` timeout.
+4. No assertion scope is broadened; test intent remains unchanged.
+5. 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:
+
+1. Which Caddy 2.11.x features should be exposed in Charon UI/API?
+2. Which existing Charon workarounds became redundant upstream?
+3. Which `xcaddy` dependency patches remain necessary vs removable?
+4. Which known vulnerabilities are fixed now and which should remain on watch?
+
+## Research Findings
+
+### External release and security findings
+
+1. Official release statement confirms `v2.11.1` has no runtime code delta from
+ `v2.11.0` except CI/release process correction. Practical implication:
+ compatibility/security validation should target **2.11.x** behavior, not
+ 2.11.1-specific runtime changes.
+2. 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)
+3. NVD/CVE.org entries are currently reserved/not fully enriched. GitHub
+ advisories are the most actionable source right now.
+
+### Charon architecture and integration findings
+
+1. Charon compiles custom Caddy in `Dockerfile` via `xcaddy` and injects:
+ - `github.com/greenpau/caddy-security`
+ - `github.com/corazawaf/coraza-caddy/v2`
+ - `github.com/hslatman/caddy-crowdsec-bouncer@v0.10.0`
+ - `github.com/zhangjiayin/caddy-geoip2`
+ - `github.com/mholt/caddy-ratelimit`
+2. Charon applies explicit post-generation `go get` patching in `Dockerfile` for:
+ - `github.com/expr-lang/expr@v1.17.7`
+ - `github.com/hslatman/ipstore@v0.4.0`
+ - `github.com/slackhq/nebula@v1.9.7` (with comment indicating temporary pin)
+3. Charon CI has explicit dependency inspection gate in
+ `.github/workflows/docker-build.yml` to verify patched `expr-lang/expr`
+ versions 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.2`
+- `hslatman/caddy-crowdsec-bouncer`: `caddy/v2 v2.10.2`
+- `corazawaf/coraza-caddy/v2`: `caddy/v2 v2.9.1`
+- `zhangjiayin/caddy-geoip2`: `caddy/v2 v2.10.0`
+- `mholt/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`
+- `frontend/src/pages/ImportCaddy.tsx` and 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.go`
+ - `backend/internal/api/handlers/settings_handler.go`
+ - `backend/internal/api/handlers/import_handler.go`
+ - `backend/internal/caddy/manager.go`
+ - `backend/internal/caddy/config.go`
+ - `backend/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 malformed `http://` or `https://` host strings are passed to
- `ValidateHostname`, THE SYSTEM SHALL return a validation error.
-- WHEN service validation behavior is intentionally strict, THE TESTS SHALL
- assert rejection for malformed URL residual host strings.
-- IF product intent is permissive for malformed inputs, THEN THE SYSTEM SHALL
- minimally relax parsing logic without weakening valid invalid-character checks.
-- WHEN changes are completed, THE SYSTEM SHALL pass targeted service tests and
- the backend coverage gate script.
+1. WHEN evaluating Caddy `v2.11.1`, THE SYSTEM SHALL validate compatibility
+ against all currently enabled `xcaddy` plugins before changing production
+ defaults.
+2. 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.
+3. WHEN an `xcaddy` patch/workaround no longer provides value,
+ THE SYSTEM SHALL remove it only after reproducible build and runtime
+ validation gates pass.
+4. 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.
+5. WHEN no direct UX value exists, THE SYSTEM SHALL avoid adding UI for upstream
+ options and keep behavior backend-managed.
+6. 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
+
+- `Dockerfile`
+ - `ARG CADDY_VERSION`
+ - `ARG XCADDY_VERSION`
+ - `caddy-builder` stage (`xcaddy build`, plugin list, `go get` patches)
+- `.github/workflows/docker-build.yml`
+ - binary dependency checks (`go version -m` extraction/gates)
+- `.github/renovate.json`
+ - regex managers tracking `Dockerfile` patch dependencies
+
+#### Caddy runtime config generation
+
+- `backend/internal/caddy/manager.go`
+ - `NewManager(...)`
+ - `ApplyConfig(ctx)`
+- `backend/internal/caddy/config.go`
+ - `GenerateConfig(...)`
+- `backend/internal/caddy/types.go`
+ - JSON struct model for Caddy config (`Server`, `TrustedProxies`, etc.)
+
+#### Settings and admin surface
-## Technical Specification
+- `backend/internal/api/handlers/settings_handler.go`
+ - `UpdateSetting(...)`, `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.go`
+ - `RegisterRoutes(...)`, `Upload(...)`, `GetPreview(...)`
+- `backend/internal/caddy/importer.go`
+ - `NormalizeCaddyfile(...)`, `ParseCaddyfile(...)`, `ExtractHosts(...)`
+- `frontend/src/pages/ImportCaddy.tsx`
+ - import UX and warning handling
+
+### Feature impact assessment (2.11.x)
+
+#### Candidate features for potential Charon exposure
+
+1. 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
+2. `trusted_proxies_unix`
+ - Candidate mapping: trusted local socket proxy chains
+ - Current `TrustedProxies` struct lacks explicit unix-socket trust fields
+3. 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
+
+1. Reverse-proxy automatic `Host` rewrite for TLS upstreams
+2. ECH key auto-rotation
+3. `SIGUSR1` reload fallback behavior
+4. 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
+
+1. `GHSA-879p-475x-rqh2` (admin API cross-origin no-cors)
+ - Charon binds admin API internally but still uses `0.0.0.0:2019` in
+ generated config. Must verify actual network isolation and container
+ exposure assumptions.
+2. `GHSA-hffm-g8v7-wrv7` (mTLS fail-open)
+ - Relevant if client-auth CA pools are configured anywhere in generated or
+ imported config paths.
+3. 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.3` and references
+ security-related maintenance in the 2.11.x line.
+ - Existing Charon pin comment may be stale after upstream smallstep updates.
-### Minimal Fix Path (Preferred)
+#### Likely retain until proven redundant
-Preferred path: **test-only correction**.
+- `go get github.com/expr-lang/expr@v1.17.7`
+- `go get github.com/hslatman/ipstore@v0.4.0`
-1. Update malformed URL table entries in
- `backend/internal/services/proxyhost_service_validation_test.go`:
- - `malformed https URL` -> `wantErr: true`
- - `malformed http URL` -> `wantErr: true`
-2. Keep current service behavior in
- `backend/internal/services/proxyhost_service.go` unchanged.
-3. Optional test hardening (still test-only): assert error contains
- `invalid hostname format` for those two cases.
+Retention/removal decision must be made using reproducible build + binary
+inspection evidence, not assumption.
-### Alternative Path (Only if Product Intent Differs)
+#### Hard retirement gates (mandatory before removing any pin)
-Use only if maintainers explicitly confirm malformed URL inputs should pass:
+Pin removal is blocked unless all gates pass:
-1. Apply minimal service correction in `ValidateHostname` to normalize malformed
- scheme inputs before character validation.
-2. Add or update tests to preserve strict rejection for truly invalid hostnames
- (e.g., `$`, `@`, `%`, `&`) so validation is not broadly weakened.
+1. Binary module diff gate
+ - Produce before/after `go version -m` module diff for Caddy binary.
+ - No unexpected module major-version jumps outside approved advisory scope.
+2. Security regression gate
+ - No new HIGH/CRITICAL findings in CodeQL/Trivy/Grype compared to baseline.
+3. Reproducible build parity gate
+ - Two clean rebuilds produce equivalent module inventory and matching runtime
+ smoke results.
+4. Rollback proof gate (mandatory, with explicit `nebula` focus)
+ - Demonstrate one-command rollback to previous pin set, with successful
+ compile + runtime smoke set after rollback.
-Decision default for this plan: **Preferred path (test updates only)**.
+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: Test-first Repro and Baseline
+### Phase 1: Playwright and behavior baselining (mandatory first)
+
+Objective: capture stable pre-upgrade behavior and ensure UI/UX parity checks.
+
+1. Run targeted E2E suites covering Caddy-critical flows:
+ - `tests/tasks/import-caddyfile.spec.ts`
+ - `tests/security-enforcement/zzz-caddy-imports/*.spec.ts`
+ - system settings-related tests around Caddy admin API and SSL provider
+2. Capture baseline artifacts:
+ - Caddy import warning behavior
+ - security settings save/reload behavior
+ - admin API connectivity assumptions from test fixtures
+3. 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.
+
+1. Bump candidate in `Dockerfile`:
+ - `ARG CADDY_VERSION=2.11.1`
+2. Execute matrix builds with toggles:
+ - Scenario A: current patch set unchanged
+ - Scenario B: remove `nebula` pin only
+ - Scenario C: remove `nebula` + retain `expr/ipstore`
+3. Execute explicit compatibility gate matrix (deterministic):
+
+ | Dimension | Values |
+ | --- | --- |
+ | Plugin set | `caddy-security`, `coraza-caddy`, `caddy-crowdsec-bouncer`, `caddy-geoip2`, `caddy-ratelimit` |
+ | Patch scenario | `A` current pins, `B` no `nebula` pin, `C` no `nebula` pin + retained `expr/ipstore` pins |
+ | Platform/arch | `linux/amd64`, `linux/arm64` |
+ | Runtime 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.
+4. Validate generated binary dependencies from CI/local:
+ - verify `expr`, `ipstore`, `nebula`, `smallstep/certificates` versions
+5. Validate runtime config application path:
+ - `backend/internal/caddy/manager.go` → `ApplyConfig(ctx)`
+ - `backend/internal/caddy/config.go` → `GenerateConfig(...)`
+6. 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.
+
+1. Add/adjust regression tests for advisory-sensitive behavior in
+ `backend/internal/caddy` and integration test suites, especially:
+ - host matcher behavior with large host lists
+ - escaped path matcher handling
+ - admin API cross-origin assumptions
+2. Update security documentation and operational guidance:
+ - identify which advisories are mitigated by upgrade alone
+ - identify deployment assumptions (e.g., local admin API exposure)
+3. 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.
+
+1. Evaluate additions to existing `SystemSettings` UX only (no new page):
+ - optional advanced toggles for keepalive tuning and trusted proxy unix scope
+2. 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(...)`
+3. 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.
+
+1. Re-run full DoD sequence (E2E, patch report, security scans, coverage).
+2. Update architectural docs if behavior/config model changes.
+3. Publish release decision memo:
+ - accepted changes
+ - rejected/deferred UX features
+ - retained/removed patches with evidence
-1. Confirm current failure (already reproduced).
-2. Record failing subtests and error signatures as baseline evidence.
+## PR Slicing Strategy
-### Phase 2: Minimal Remediation
+### Decision
-1. Apply preferred test expectation update in
- `backend/internal/services/proxyhost_service_validation_test.go`.
-2. Keep service code unchanged unless product intent is clarified otherwise.
+Use **multiple PRs (PR-1/PR-2/PR-3)**.
-### Phase 3: Targeted Validation
+Reasoning:
-Run in this order:
+1. Work spans infra/build security + backend runtime + potential frontend UX.
+2. Caddy is a blast-radius-critical dependency; rollback safety is mandatory.
+3. Review quality and CI signal are stronger with isolated, testable slices.
-1. `go test ./backend/internal/services -run TestProxyHostService_ValidateHostname -v`
-2. Related service package tests:
- - `go test ./backend/internal/services -run TestProxyHostService -v`
- - `go test ./backend/internal/services -v`
-3. Final gate:
- - `bash scripts/go-test-coverage.sh`
+### PR-1: Compatibility and evidence foundation
-## Risk Assessment
+Scope:
-### Key Risks
+- `Dockerfile` Caddy candidate bump (and temporary feature branch matrix toggles)
+- CI/workflow compatibility instrumentation if needed
+- compatibility report artifacts and plan-linked documentation
-1. **Semantic risk (low):** updating tests could mask an intended behavior
- change if malformed URL permissiveness was deliberate.
-2. **Coverage risk (low):** test expectation changes may alter branch coverage
- marginally but should not threaten gate based on current context.
-3. **Regression risk (low):** service runtime behavior remains unchanged in the
- preferred path.
+Dependencies:
-### Mitigations
+- None
-- Keep change surgical to two table entries.
-- Preserve existing invalid-character rejection coverage.
-- Require full service package run plus coverage script before merge.
+Acceptance criteria:
-## Rollback Plan
+1. Caddy 2.11.1 compiles with existing plugin set under at least one stable
+ patch scenario.
+2. Compatibility gate matrix (plugin × patch scenario × platform/arch × runtime
+ smoke set) executed with deterministic pass/fail output and attached evidence.
+3. Binary module inventory report generated and attached.
+4. No production behavior changes merged beyond compatibility scaffolding.
-If maintainer/product decision confirms permissive malformed URL handling is
-required:
+Release guard (mandatory for PR-1):
-1. Revert the test expectation update commit.
-2. Implement minimal service normalization change in
- `backend/internal/services/proxyhost_service.go`.
-3. Add explicit tests documenting the accepted malformed-input behavior and
- retain strict negative tests for illegal hostname characters.
-4. Re-run targeted validation commands and coverage gate.
+- 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.
-## PR Slicing Strategy
+Rollback notes:
-Decision: **Single PR**.
+- Revert `Dockerfile` arg changes and instrumentation only.
-Rationale:
+### PR-2: Security patch posture + patch retirement decision
-- Scope is tightly bounded to one service test suite and one failure cluster.
-- Preferred remediation is test-only with low rollback complexity.
-- Review surface is small and dependency-free.
+Scope:
-Contingency split trigger:
+- finalize retained/removed `go get` patch lines in `Dockerfile`
+- update security tests/docs tied to six Caddy advisories
+- tighten/confirm admin API exposure assumptions
-- Only split if product intent forces service logic change, in which case:
- - PR-1: test expectation alignment rollback + service behavior decision record
- - PR-2: minimal service correction + adjusted tests
+Dependencies:
-## Config/Infra File Impact Review
+- PR-1 evidence
-Reviewed for required updates:
+Acceptance criteria:
-- `.gitignore`
-- `.dockerignore`
-- `codecov.yml`
-- `Dockerfile`
+1. Decision logged for each patch (`expr`, `ipstore`, `nebula`) with rationale.
+2. Advisory coverage matrix completed with Charon applicability labels.
+3. 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.tsx` general/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):
+
+1. `caddy.keepalive_idle` (optional)
+ - Surface: `SystemSettings` under existing Caddy/system controls.
+ - UX: bounded select/input for duration-like value (validated server-side).
+ - Persistence: existing `updateSetting()` flow.
+2. `caddy.keepalive_count` (optional)
+ - Surface: `SystemSettings` adjacent to keepalive idle.
+ - UX: bounded numeric control (validated server-side).
+ - Persistence: existing `updateSetting()` flow.
+
+Exact files/functions/components to change:
+
+Backend (no new endpoints):
+
+1. `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.
+2. `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.
+3. `backend/internal/caddy/types.go`
+ - Type: `Server`
+ - Change: add optional fields required to emit keepalive keys in Caddy JSON only when provided.
+4. `backend/internal/api/handlers/settings_handler.go`
+ - Functions: `UpdateSetting(...)`, `PatchConfig(...)`
+ - Change: add narrow validation for `caddy.keepalive_idle` and `caddy.keepalive_count` to reject malformed/out-of-range values while preserving existing generic settings behavior for unrelated keys.
+
+Frontend (existing surface only):
+
+1. `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.
+2. `frontend/src/api/settings.ts`
+ - No contract expansion required; reuse `updateSetting(key, value, category, type)`.
+3. Localization files (labels/help text only, if controls are exposed):
+ - `frontend/src/locales/en/translation.json`
+ - `frontend/src/locales/de/translation.json`
+ - `frontend/src/locales/es/translation.json`
+ - `frontend/src/locales/fr/translation.json`
+ - `frontend/src/locales/zh/translation.json`
+
+Tests to update/add (targeted):
+
+1. `frontend/src/pages/__tests__/SystemSettings.test.tsx`
+ - Verify control rendering, default-state behavior, and save calls for optional keepalive keys.
+2. `backend/internal/caddy/config_generate_test.go`
+ - Verify keepalive keys are omitted when unset/invalid and emitted when valid.
+3. `backend/internal/api/handlers/settings_handler_test.go`
+ - Verify validation pass/fail for keepalive keys via both `UpdateSetting` and `PatchConfig` paths.
+4. 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):
+
+1. No net-new page; all UI changes are within `SystemSettings` only.
+2. No new backend routes/endpoints; existing settings APIs are reused.
+3. 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.
+4. `trusted_proxies_unix`, `renewal_window_ratio`, and certificate-maintenance internals remain backend-default and non-exposed.
+5. Backend preserves current behavior when optional keepalive settings are absent or invalid (no generated-config drift).
+6. Unit tests pass for settings validation + config generation default/override behavior.
+7. Settings UI tests pass for load/save/default behavior on exposed controls.
+8. Deferred/non-exposed features are explicitly documented in PR notes as intentional non-goals.
+
+#### PR-3 Value Gate (required evidence and approval)
-Planned changes: **None required** for this focused backend test-remediation
-scope.
+Required evidence checklist (all items required):
+
+- [ ] PR-2 evidence bundle contains an explicit operator-value decision record for PR-3 controls, naming `caddy.keepalive_idle` and `caddy.keepalive_count` individually.
+- [ ] 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)
+
+1. Update `ARG CADDY_VERSION` target to `2.11.1` after PR-1 gating.
+2. Reassess and potentially remove stale `nebula` pin in caddy-builder stage
+ if matrix build proves compatibility and security posture improves.
+3. Keep `expr`/`ipstore` patch 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
+
+1. Plugin/API incompatibility with Caddy 2.11.1
+ - Mitigation: matrix compile + targeted runtime tests before merge.
+2. False confidence from scanner-only dependency policies
+ - Mitigation: combine advisory-context review with binary-level inspection.
+3. Behavioral drift in reverse proxy/matcher semantics
+ - Mitigation: baseline E2E + focused security regression tests.
+4. UI sprawl from exposing too many Caddy internals
+ - Mitigation: only extend existing settings surface when operator value is
+ clear and validated.
## Acceptance Criteria
-1. `TestProxyHostService_ValidateHostname` passes, including malformed URL
- subtests.
-2. `go test ./backend/internal/services -run TestProxyHostService -v` passes.
-3. `go test ./backend/internal/services -v` passes.
-4. `bash scripts/go-test-coverage.sh` passes final gate.
-5. Root cause is documented as expectation drift vs. service behavior drift, and
- chosen path is explicitly recorded.
+1. Charon builds and runs with Caddy 2.11.1 and current plugin set under
+ deterministic CI validation.
+2. A patch disposition table exists for `expr`, `ipstore`, and `nebula`
+ (retain/remove/replace + evidence).
+3. Caddy advisory applicability matrix is documented, including exploitability
+ notes for Charon deployment model.
+4. Any added settings are mapped end-to-end:
+ frontend state → API payload → persisted setting → `GenerateConfig(...)`.
+5. E2E, security scans, and coverage gates pass without regression.
+6. PR-1/PR-2/PR-3 deliverables are independently reviewable and rollback-safe.
+
+## Handoff
+
+After approval of this plan:
+
+1. Delegate PR-1 execution to implementation workflow.
+2. Require evidence artifacts before approving PR-2 scope reductions
+ (especially patch removals).
+3. 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:
+
+1. Keepalive controls (`caddy.keepalive_idle`, `caddy.keepalive_count`)
+2. Safe defaults/fallback behavior when keepalive values are missing or invalid
+3. 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_unix` and 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`
diff --git a/docs/reports/caddy-compatibility-matrix.md b/docs/reports/caddy-compatibility-matrix.md
new file mode 100644
index 000000000..15f104a4d
--- /dev/null
+++ b/docs/reports/caddy-compatibility-matrix.md
@@ -0,0 +1,32 @@
+# PR-1 Caddy Compatibility Matrix Report
+
+- Generated at: 2026-02-23T13:52:26Z
+- Candidate Caddy version: 2.11.1
+- Plugin set: caddy-security,coraza-caddy,caddy-crowdsec-bouncer,caddy-geoip2,caddy-ratelimit
+- Smoke set: boot_caddy,plugin_modules,config_validate,admin_api_health
+- Matrix dimensions: patch scenario × platform/arch × checked plugin modules
+
+## Deterministic Pass/Fail
+
+A matrix cell is PASS only when every smoke check and module inventory extraction passes.
+
+Promotion gate semantics (spec-aligned):
+- Scenario A on linux/amd64 and linux/arm64 is promotion-gating.
+- Scenario B/C are evidence-only; failures in B/C do not fail the PR-1 promotion gate.
+
+## Matrix Output
+
+| Scenario | Platform | Plugins Checked | boot_caddy | plugin_modules | config_validate | admin_api_health | module_inventory | Status |
+| --- | --- | --- | --- | --- | --- | --- | --- | --- |
+| A | linux/amd64 | http.handlers.auth_portal, http.handlers.waf, http.handlers.crowdsec, http.handlers.geoip2, http.handlers.rate_limit | PASS | PASS | PASS | PASS | PASS | PASS |
+| A | linux/arm64 | http.handlers.auth_portal, http.handlers.waf, http.handlers.crowdsec, http.handlers.geoip2, http.handlers.rate_limit | PASS | PASS | PASS | PASS | PASS | PASS |
+| B | linux/amd64 | http.handlers.auth_portal, http.handlers.waf, http.handlers.crowdsec, http.handlers.geoip2, http.handlers.rate_limit | PASS | PASS | PASS | PASS | PASS | PASS |
+| B | linux/arm64 | http.handlers.auth_portal, http.handlers.waf, http.handlers.crowdsec, http.handlers.geoip2, http.handlers.rate_limit | PASS | PASS | PASS | PASS | PASS | PASS |
+| C | linux/amd64 | http.handlers.auth_portal, http.handlers.waf, http.handlers.crowdsec, http.handlers.geoip2, http.handlers.rate_limit | PASS | PASS | PASS | PASS | PASS | PASS |
+| C | linux/arm64 | http.handlers.auth_portal, http.handlers.waf, http.handlers.crowdsec, http.handlers.geoip2, http.handlers.rate_limit | PASS | PASS | PASS | PASS | PASS | PASS |
+
+## Artifacts
+
+- Matrix CSV: test-results/caddy-compat/matrix-summary.csv
+- Per-cell module inventories: test-results/caddy-compat/module-inventory-*-go-version-m.txt
+- Per-cell Caddy module listings: test-results/caddy-compat/module-inventory-*-modules.txt
diff --git a/docs/reports/caddy-security-posture.md b/docs/reports/caddy-security-posture.md
new file mode 100644
index 000000000..893e6d551
--- /dev/null
+++ b/docs/reports/caddy-security-posture.md
@@ -0,0 +1,65 @@
+## PR-2 Security Patch Posture and Advisory Disposition
+
+- Date: 2026-02-23
+- Scope: PR-2 only (security patch posture + xcaddy patch retirement decision)
+- Upstream target: Caddy 2.11.x line (`2.11.1` candidate in this repository)
+- Inputs:
+ - PR-1 compatibility matrix: `docs/reports/caddy-compatibility-matrix.md`
+ - Plan authority: `docs/plans/current_spec.md`
+ - Runtime and bootstrap assumptions: `.docker/docker-entrypoint.sh`, `.docker/compose/docker-compose.yml`
+
+### 1) Final patch disposition
+
+| Patch target | Decision | Rationale (evidence-backed) | Rollback path |
+| --- | --- | --- | --- |
+| `github.com/expr-lang/expr@v1.17.7` | Retain | Enforced by current builder patching and CI dependency checks. | Keep current pin. |
+| `github.com/hslatman/ipstore@v0.4.0` | Retain | No PR-2 evidence supports safe retirement. | Keep current pin. |
+| `github.com/slackhq/nebula@v1.9.7` | Retire by default | Matrix evidence supports scenario `B`/`C`; default moved to `B` with rollback preserved. | Set `CADDY_PATCH_SCENARIO=A`. |
+
+### 2) Caddy 2.11.x advisory disposition
+
+| Advisory | Component summary | Exploitability | Evidence source | Owner | Recheck cadence |
+| --- | --- | --- | --- | --- | --- |
+| `GHSA-5r3v-vc8m-m96g` (`CVE-2026-27590`) | FastCGI `split_path` confusion | Not affected | Upstream advisory + Charon runtime path review (no FastCGI transport in default generated config path) | QA_Security | weekly |
+| `GHSA-879p-475x-rqh2` (`CVE-2026-27589`) | Admin API cross-origin no-cors | Mitigated | Upstream advisory + local controls: `CHARON_CADDY_ADMIN_API` now validated against internal allowlist and expected port 2019; production compose does not publish 2019 by default | QA_Security | weekly |
+| `GHSA-x76f-jf84-rqj8` (`CVE-2026-27588`) | Host matcher case bypass | Mitigated | Upstream advisory + PR-1 Caddy 2.11.x matrix compatibility evidence and Charon route/security test reliance on upgraded line | QA_Security | release-candidate |
+| `GHSA-g7pc-pc7g-h8jh` (`CVE-2026-27587`) | Path matcher escaped-case bypass | Mitigated | Upstream advisory + PR-1 matrix evidence and maintained security enforcement suite coverage | QA_Security | release-candidate |
+| `GHSA-hffm-g8v7-wrv7` (`CVE-2026-27586`) | mTLS client-auth fail-open | Not affected | Upstream advisory + Charon default deployment model does not enable mTLS client-auth CA pool configuration by default | QA_Security | on-upstream-change |
+| `GHSA-4xrr-hq4w-6vf4` (`CVE-2026-27585`) | File matcher glob sanitization bypass | Not affected | Upstream advisory + no default Charon generated config dependency on vulnerable matcher pattern | QA_Security | on-upstream-change |
+
+### 3) Admin API exposure assumptions and hardening status
+
+- Assumption: only internal Caddy admin endpoints are valid management targets.
+- PR-2 enforcement:
+ - validate and normalize `CHARON_CADDY_ADMIN_API`/`CPM_CADDY_ADMIN_API`
+ - host allowlist + expected port `2019`
+ - fail-fast startup on invalid/non-allowlisted endpoint
+- Exposure check: production compose defaults do not publish port `2019`.
+
+### 4) Runtime safety and rollback preservation
+
+- Runtime defaults keep `expr` and `ipstore` pinned.
+- `nebula` pin retirement is controlled by scenario switch, not hard deletion.
+- Emergency rollback remains one-step: `CADDY_PATCH_SCENARIO=A`.
+
+### Validation executed for PR-2
+
+| Command / Task | Outcome |
+| --- | --- |
+| `cd /projects/Charon/backend && go test ./internal/config` | PASS |
+| VS Code task `Security: Caddy PR-1 Compatibility Matrix` | PASS (A/B/C scenarios pass on `linux/amd64` and `linux/arm64`; promotion gate PASS) |
+
+Relevant generated artifacts:
+- `docs/reports/caddy-compatibility-matrix.md`
+- `test-results/caddy-compat/matrix-summary.csv`
+- `test-results/caddy-compat/module-inventory-*-go-version-m.txt`
+- `test-results/caddy-compat/module-inventory-*-modules.txt`
+
+### Residual risks / follow-up watch
+
+1. Caddy advisories with reserved or evolving CVE enrichment may change exploitability interpretation; recheck cadence remains active.
+2. Caddy bootstrap still binds admin listener to container interface (`0.0.0.0:2019`) for compatibility, so operator misconfiguration that publishes port `2019` can expand attack surface; production compose defaults avoid publishing this port.
+
+### PR-2 closure statement
+
+PR-2 posture decisions are review-ready: patch disposition is explicit, admin API assumptions are enforced, and rollback remains deterministic. No PR-3 scope is included.
diff --git a/docs/reports/qa_report.md b/docs/reports/qa_report.md
index 9f5cdb213..6b0e0ebaa 100644
--- a/docs/reports/qa_report.md
+++ b/docs/reports/qa_report.md
@@ -1,143 +1,57 @@
-## QA/Security Validation Report - Governance Documentation Slice
+## QA Report — PR-2 Security Patch Posture Audit
-Date: 2026-02-20
-Repository: /projects/Charon
-Scope files:
-- `.github/instructions/copilot-instructions.md`
-- `.github/instructions/testing.instructions.md`
-- `.github/instructions/security-and-owasp.instructions.md`
-- `.github/agents/Management.agent.md`
-- `.github/agents/Backend_Dev.agent.md`
-- `.github/agents/QA_Security.agent.md`
-- `SECURITY.md`
-- `docs/security.md`
-- `docs/features/notifications.md`
+- Date: 2026-02-23
+- Scope: PR-2 only (security patch posture, admin API hardening, rollback viability)
+- Verdict: **READY (PASS)**
-### Result Summary
+## Gate Summary
-| Check | Status | Notes |
-|---|---|---|
-| 1) No secrets/tokens introduced in changed docs | PASS | No raw token values, API keys, or private credential material detected in scoped diffs; only policy/example strings were found. |
-| 2) Policy consistency verification | PASS | GORM conditional DoD gate, check-mode semantics, include/exclude trigger matrix, Gotify no-exposure + URL redaction, and precedence hierarchy are consistently present across canonical instructions and aligned agent/operator docs. |
-| 3) Markdown lint on scoped files | PASS | `markdownlint-cli2` reports baseline debt (`319` total), but intersection of lint hits with added hunk ranges for this governance slice returned no new lint hits in added sections. |
-| 4) Confirm governance-only scope for this slice | PASS | Scoped diff over the 9 target files confirms this implementation slice touches only those 9 governance files for evaluation. Unrelated branch changes were explicitly excluded by scope criteria. |
-| 5) QA report update for governance slice | PASS | This section added as the governance-slice QA record. |
+| Gate | Status | Evidence |
+| --- | --- | --- |
+| Targeted E2E for PR-2 | PASS | Security settings test for Caddy Admin API URL passed (2/2). |
+| Local patch preflight artifacts | PASS | `test-results/local-patch-report.md` and `.json` regenerated. |
+| Coverage and type-check | PASS | Backend coverage 87.7% line / 87.4% statement; frontend type-check passed; frontend coverage preflight input passed (88.99% lines). |
+| Pre-commit gate | PASS | `pre-commit run --all-files` passed after resolving version and type-check hook issues. |
+| Security scans | PASS | CodeQL Go/JS CI-aligned scans passed; findings gate passed with no HIGH/CRITICAL; Trivy passed at configured severities. |
+| Runtime posture + rollback | PASS | Default scenario shifted `A -> B` for PR-2 posture; rollback remains explicit via `CADDY_PATCH_SCENARIO=A`; admin API URL now validated and normalized at config load. |
-### Commands Executed
+## Resolved Items
-```bash
-git diff --name-only -- .github/instructions/copilot-instructions.md .github/instructions/testing.instructions.md .github/instructions/security-and-owasp.instructions.md .github/agents/Management.agent.md .github/agents/Backend_Dev.agent.md .github/agents/QA_Security.agent.md SECURITY.md docs/security.md docs/features/notifications.md
+1. `check-version-match` mismatch fixed by syncing `.version` to `v0.19.1`.
+2. `frontend-type-check` hook stabilized to `npx tsc --noEmit` for deterministic pre-commit behavior.
-git diff -U0 --
`npx playwright test tests/security-enforcement/zzz-security-ui/system-security-settings.spec.ts --project=security-tests` | PASS | Notifications suite (prior run): `27/27` passed. Security settings focused suite (latest): `21/21` passed. |
-| 5 | Pre-commit fast hooks | `pre-commit run --files $(git diff --name-only --diff-filter=ACMRTUXB)` | PASS | Fast hooks passed, including `golangci-lint (Fast Linters - BLOCKING)`, `Go Vet`, `dockerfile validation`, `Frontend TypeScript Check`, and `Frontend Lint (Fix)`. |
-| 6 | CodeQL findings gate status (CI-aligned outputs) | Task `Security: CodeQL Go Scan (CI-Aligned) [~60s]`
Task `Security: CodeQL JS Scan (CI-Aligned) [~90s]`
`pre-commit run --hook-stage manual codeql-check-findings --all-files` | PASS | Fresh SARIF artifacts present (`codeql-results-go.sarif`, `codeql-results-js.sarif`); manual findings gate reports no HIGH/CRITICAL findings. |
-| 7 | Dockerized Trivy + Docker image scan status | `.github/skills/scripts/skill-runner.sh security-scan-trivy vuln,secret,misconfig json`
Task `Security: Scan Docker Image (Local)` | PASS | Existing Dockerized Trivy result remains passing from prior run. Latest local Docker image gate: `Critical: 0`, `High: 0` (effective gate pass). |
-
-### Confirmation of Prior Passing Gates (No Re-run)
-
-- Frontend tests/type-check/coverage remain confirmed PASS from prior validated run.
-- Pre-commit fast hooks remain confirmed PASS from prior validated run.
-- CodeQL Go + JS CI-aligned scans remain confirmed PASS from prior validated run.
-- Dockerized Trivy scan remains confirmed PASS from prior validated run.
-
-### Blocking Items
-
-- None for PR-2 focused QA/security scope.
-
-### Final Verdict
-
-- Overall Result: **PASS**
-- Full E2E regression remains deferred to CI as requested.
-- No remaining focused blockers identified.
-
-### Handoff References
-
-- Manual test plan (PR-1 + PR-2): `docs/issues/manual_test_provider_security_notifications_pr1_pr2.md`
-- Existing focused QA evidence in this report remains the baseline for automated validation.
-
-## QA/Security Validation Report - SMTP Flaky Test Fix (Test-Only Backend Change)
-
-Date: 2026-02-22
-Repository: /projects/Charon
-Scope: Validate SMTP STARTTLS test-stability fix without production behavior change.
-
-### Scope Verification
-
-| Check | Status | Evidence |
-|---|---|---|
-| Changed files are test-only (no production code changes) | PASS | `git status --short` shows only `backend/internal/services/mail_service_test.go` and `docs/plans/current_spec.md` modified. |
-| Production behavior unchanged by diff scope | PASS | No non-test backend/service implementation files modified. |
-
-### Required Validation Results
-
-| # | Command | Status | Evidence Snippet |
-|---|---|---|---|
-| 1 | `go test ./backend/internal/services -run TestMailService_TestConnection_StartTLSSuccessWithAuth -count=20` | PASS | `ok github.com/Wikid82/charon/backend/internal/services 1.403s` |
-| 2 | `go test -race ./backend/internal/services -run 'TestMailService_(TestConnection|Send)' -count=1` | PASS | `ok github.com/Wikid82/charon/backend/internal/services 1.270s` |
-| 3 | `bash scripts/go-test-coverage.sh` | PASS | `Statement coverage: 86.1%` / `Line coverage: 86.4%` / `Coverage requirement met` |
-| 4 | `pre-commit run --all-files` | PASS | All hooks passed, including `golangci-lint (Fast Linters - BLOCKING)`, `Go Vet`, `Frontend TypeScript Check`, `Frontend Lint (Fix)`. |
-
-### Additional QA Context
-
-| Check | Status | Evidence |
-|---|---|---|
-| Local patch coverage preflight artifacts generated | PASS | `bash scripts/local-patch-report.sh` produced `test-results/local-patch-report.md` and `test-results/local-patch-report.json`. |
-| Patch coverage threshold warning (advisory) | WARN (non-blocking) | Report output: `WARN: Overall patch coverage 53.8% ...` and `WARN: Backend patch coverage 52.0% ...`. |
-
-### Security Stance
-
-| Check | Status | Notes |
-|---|---|---|
-| New secret/token exposure risk introduced by test changes | PASS | Change scope is test helper logic only; no credentials/tokens were added to production paths, logs, or API outputs. |
-| Gotify token leakage pattern introduced | PASS | No Gotify tokenized URLs or token fields were added in the changed test file. |
-
-### Blockers
-
-- None.
-
-### Verdict
-
-**PASS** — SMTP flaky test fix validates as test-only, stable under repetition/race checks, meets backend coverage gate, passes full pre-commit, and introduces no new secret/token exposure risk.
+PR-3 is **ready to merge** with no open QA blockers.
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 1a9af5e21..0f937e0a7 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -27,22 +27,22 @@
"react-hook-form": "^7.71.2",
"react-hot-toast": "^2.6.0",
"react-i18next": "^16.5.4",
- "react-router-dom": "^7.13.0",
+ "react-router-dom": "^7.13.1",
"tailwind-merge": "^3.5.0",
"tldts": "^7.0.23"
},
"devDependencies": {
"@eslint/js": "^9.39.3 <10.0.0",
"@playwright/test": "^1.58.2",
- "@tailwindcss/postcss": "^4.2.0",
+ "@tailwindcss/postcss": "^4.2.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^25.3.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
- "@typescript-eslint/eslint-plugin": "^8.56.0",
- "@typescript-eslint/parser": "^8.56.0",
+ "@typescript-eslint/eslint-plugin": "^8.56.1",
+ "@typescript-eslint/parser": "^8.56.1",
"@vitejs/plugin-react": "^5.1.4",
"@vitest/coverage-istanbul": "^4.0.18",
"@vitest/coverage-v8": "^4.0.18",
@@ -50,13 +50,13 @@
"autoprefixer": "^10.4.24",
"eslint": "^9.39.3 <10.0.0",
"eslint-plugin-react-hooks": "^7.0.1",
- "eslint-plugin-react-refresh": "^0.5.0",
+ "eslint-plugin-react-refresh": "^0.5.2",
"jsdom": "28.1.0",
"knip": "^5.85.0",
"postcss": "^8.5.6",
- "tailwindcss": "^4.2.0",
+ "tailwindcss": "^4.2.1",
"typescript": "^5.9.3",
- "typescript-eslint": "^8.56.0",
+ "typescript-eslint": "^8.56.1",
"vite": "^7.3.1",
"vitest": "^4.0.18"
}
@@ -89,17 +89,20 @@
}
},
"node_modules/@asamuzakjp/css-color": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.2.tgz",
- "integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz",
+ "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@csstools/css-calc": "^3.0.0",
- "@csstools/css-color-parser": "^4.0.1",
+ "@csstools/css-calc": "^3.1.1",
+ "@csstools/css-color-parser": "^4.0.2",
"@csstools/css-parser-algorithms": "^4.0.0",
"@csstools/css-tokenizer": "^4.0.0",
- "lru-cache": "^11.2.5"
+ "lru-cache": "^11.2.6"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
@@ -1129,6 +1132,13 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@eslint/config-array/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -1141,9 +1151,9 @@
}
},
"node_modules/@eslint/config-array/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
+ "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -1203,6 +1213,13 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/@eslint/eslintrc/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -1225,9 +1242,9 @@
}
},
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
+ "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -2572,9 +2589,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.58.0.tgz",
- "integrity": "sha512-mr0tmS/4FoVk1cnaeN244A/wjvGDNItZKR8hRhnmCzygyRXYtKF5jVDSIILR1U97CTzAYmbgIj/Dukg62ggG5w==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
"cpu": [
"arm"
],
@@ -2586,9 +2603,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.58.0.tgz",
- "integrity": "sha512-+s++dbp+/RTte62mQD9wLSbiMTV+xr/PeRJEc/sFZFSBRlHPNPVaf5FXlzAL77Mr8FtSfQqCN+I598M8U41ccQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
"cpu": [
"arm64"
],
@@ -2600,9 +2617,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.58.0.tgz",
- "integrity": "sha512-MFWBwTcYs0jZbINQBXHfSrpSQJq3IUOakcKPzfeSznONop14Pxuqa0Kg19GD0rNBMPQI2tFtu3UzapZpH0Uc1Q==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
"cpu": [
"arm64"
],
@@ -2614,9 +2631,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.58.0.tgz",
- "integrity": "sha512-yiKJY7pj9c9JwzuKYLFaDZw5gma3fI9bkPEIyofvVfsPqjCWPglSHdpdwXpKGvDeYDms3Qal8qGMEHZ1M/4Udg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
"cpu": [
"x64"
],
@@ -2628,9 +2645,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.58.0.tgz",
- "integrity": "sha512-x97kCoBh5MOevpn/CNK9W1x8BEzO238541BGWBc315uOlN0AD/ifZ1msg+ZQB05Ux+VF6EcYqpiagfLJ8U3LvQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
"cpu": [
"arm64"
],
@@ -2642,9 +2659,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.58.0.tgz",
- "integrity": "sha512-Aa8jPoZ6IQAG2eIrcXPpjRcMjROMFxCt1UYPZZtCxRV68WkuSigYtQ/7Zwrcr2IvtNJo7T2JfDXyMLxq5L4Jlg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
"cpu": [
"x64"
],
@@ -2656,9 +2673,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.58.0.tgz",
- "integrity": "sha512-Ob8YgT5kD/lSIYW2Rcngs5kNB/44Q2RzBSPz9brf2WEtcGR7/f/E9HeHn1wYaAwKBni+bdXEwgHvUd0x12lQSA==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
"cpu": [
"arm"
],
@@ -2670,9 +2687,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.58.0.tgz",
- "integrity": "sha512-K+RI5oP1ceqoadvNt1FecL17Qtw/n9BgRSzxif3rTL2QlIu88ccvY+Y9nnHe/cmT5zbH9+bpiJuG1mGHRVwF4Q==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
"cpu": [
"arm"
],
@@ -2684,9 +2701,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.58.0.tgz",
- "integrity": "sha512-T+17JAsCKUjmbopcKepJjHWHXSjeW7O5PL7lEFaeQmiVyw4kkc5/lyYKzrv6ElWRX/MrEWfPiJWqbTvfIvjM1Q==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
"cpu": [
"arm64"
],
@@ -2698,9 +2715,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.58.0.tgz",
- "integrity": "sha512-cCePktb9+6R9itIJdeCFF9txPU7pQeEHB5AbHu/MKsfH/k70ZtOeq1k4YAtBv9Z7mmKI5/wOLYjQ+B9QdxR6LA==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
"cpu": [
"arm64"
],
@@ -2712,9 +2729,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.58.0.tgz",
- "integrity": "sha512-iekUaLkfliAsDl4/xSdoCJ1gnnIXvoNz85C8U8+ZxknM5pBStfZjeXgB8lXobDQvvPRCN8FPmmuTtH+z95HTmg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
"cpu": [
"loong64"
],
@@ -2726,9 +2743,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.58.0.tgz",
- "integrity": "sha512-68ofRgJNl/jYJbxFjCKE7IwhbfxOl1muPN4KbIqAIe32lm22KmU7E8OPvyy68HTNkI2iV/c8y2kSPSm2mW/Q9Q==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
"cpu": [
"loong64"
],
@@ -2740,9 +2757,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.58.0.tgz",
- "integrity": "sha512-dpz8vT0i+JqUKuSNPCP5SYyIV2Lh0sNL1+FhM7eLC457d5B9/BC3kDPp5BBftMmTNsBarcPcoz5UGSsnCiw4XQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
"cpu": [
"ppc64"
],
@@ -2754,9 +2771,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.58.0.tgz",
- "integrity": "sha512-4gdkkf9UJ7tafnweBCR/mk4jf3Jfl0cKX9Np80t5i78kjIH0ZdezUv/JDI2VtruE5lunfACqftJ8dIMGN4oHew==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
"cpu": [
"ppc64"
],
@@ -2768,9 +2785,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.58.0.tgz",
- "integrity": "sha512-YFS4vPnOkDTD/JriUeeZurFYoJhPf9GQQEF/v4lltp3mVcBmnsAdjEWhr2cjUCZzZNzxCG0HZOvJU44UGHSdzw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
"cpu": [
"riscv64"
],
@@ -2782,9 +2799,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.58.0.tgz",
- "integrity": "sha512-x2xgZlFne+QVNKV8b4wwaCS8pwq3y14zedZ5DqLzjdRITvreBk//4Knbcvm7+lWmms9V9qFp60MtUd0/t/PXPw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
"cpu": [
"riscv64"
],
@@ -2796,9 +2813,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.58.0.tgz",
- "integrity": "sha512-jIhrujyn4UnWF8S+DHSkAkDEO3hLX0cjzxJZPLF80xFyzyUIYgSMRcYQ3+uqEoyDD2beGq7Dj7edi8OnJcS/hg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
"cpu": [
"s390x"
],
@@ -2810,9 +2827,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.58.0.tgz",
- "integrity": "sha512-+410Srdoh78MKSJxTQ+hZ/Mx+ajd6RjjPwBPNd0R3J9FtL6ZA0GqiiyNjCO9In0IzZkCNrpGymSfn+kgyPQocg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
"cpu": [
"x64"
],
@@ -2824,9 +2841,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.58.0.tgz",
- "integrity": "sha512-ZjMyby5SICi227y1MTR3VYBpFTdZs823Rs/hpakufleBoufoOIB6jtm9FEoxn/cgO7l6PM2rCEl5Kre5vX0QrQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
"cpu": [
"x64"
],
@@ -2838,9 +2855,9 @@
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.58.0.tgz",
- "integrity": "sha512-ds4iwfYkSQ0k1nb8LTcyXw//ToHOnNTJtceySpL3fa7tc/AsE+UpUFphW126A6fKBGJD5dhRvg8zw1rvoGFxmw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
"cpu": [
"x64"
],
@@ -2852,9 +2869,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.58.0.tgz",
- "integrity": "sha512-fd/zpJniln4ICdPkjWFhZYeY/bpnaN9pGa6ko+5WD38I0tTqk9lXMgXZg09MNdhpARngmxiCg0B0XUamNw/5BQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
"cpu": [
"arm64"
],
@@ -2866,9 +2883,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.58.0.tgz",
- "integrity": "sha512-YpG8dUOip7DCz3nr/JUfPbIUo+2d/dy++5bFzgi4ugOGBIox+qMbbqt/JoORwvI/C9Kn2tz6+Bieoqd5+B1CjA==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
"cpu": [
"arm64"
],
@@ -2880,9 +2897,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.58.0.tgz",
- "integrity": "sha512-b9DI8jpFQVh4hIXFr0/+N/TzLdpBIoPzjt0Rt4xJbW3mzguV3mduR9cNgiuFcuL/TeORejJhCWiAXe3E/6PxWA==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
"cpu": [
"ia32"
],
@@ -2894,9 +2911,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.58.0.tgz",
- "integrity": "sha512-CSrVpmoRJFN06LL9xhkitkwUcTZtIotYAF5p6XOR2zW0Zz5mzb3IPpcoPhB02frzMHFNo1reQ9xSF5fFm3hUsQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
"cpu": [
"x64"
],
@@ -2908,9 +2925,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.58.0.tgz",
- "integrity": "sha512-QFsBgQNTnh5K0t/sBsjJLq24YVqEIVkGpfN2VHsnN90soZyhaiA9UUHufcctVNL4ypJY0wrwad0wslx2KJQ1/w==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
"cpu": [
"x64"
],
@@ -2929,9 +2946,9 @@
"license": "MIT"
},
"node_modules/@tailwindcss/node": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.0.tgz",
- "integrity": "sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz",
+ "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2941,37 +2958,37 @@
"lightningcss": "1.31.1",
"magic-string": "^0.30.21",
"source-map-js": "^1.2.1",
- "tailwindcss": "4.2.0"
+ "tailwindcss": "4.2.1"
}
},
"node_modules/@tailwindcss/oxide": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.0.tgz",
- "integrity": "sha512-AZqQzADaj742oqn2xjl5JbIOzZB/DGCYF/7bpvhA8KvjUj9HJkag6bBuwZvH1ps6dfgxNHyuJVlzSr2VpMgdTQ==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz",
+ "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 20"
},
"optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.2.0",
- "@tailwindcss/oxide-darwin-arm64": "4.2.0",
- "@tailwindcss/oxide-darwin-x64": "4.2.0",
- "@tailwindcss/oxide-freebsd-x64": "4.2.0",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.0",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.2.0",
- "@tailwindcss/oxide-linux-arm64-musl": "4.2.0",
- "@tailwindcss/oxide-linux-x64-gnu": "4.2.0",
- "@tailwindcss/oxide-linux-x64-musl": "4.2.0",
- "@tailwindcss/oxide-wasm32-wasi": "4.2.0",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.2.0",
- "@tailwindcss/oxide-win32-x64-msvc": "4.2.0"
+ "@tailwindcss/oxide-android-arm64": "4.2.1",
+ "@tailwindcss/oxide-darwin-arm64": "4.2.1",
+ "@tailwindcss/oxide-darwin-x64": "4.2.1",
+ "@tailwindcss/oxide-freebsd-x64": "4.2.1",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.2.1",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.2.1",
+ "@tailwindcss/oxide-linux-x64-musl": "4.2.1",
+ "@tailwindcss/oxide-wasm32-wasi": "4.2.1",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.2.1"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.0.tgz",
- "integrity": "sha512-F0QkHAVaW/JNBWl4CEKWdZ9PMb0khw5DCELAOnu+RtjAfx5Zgw+gqCHFvqg3AirU1IAd181fwOtJQ5I8Yx5wtw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz",
+ "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==",
"cpu": [
"arm64"
],
@@ -2986,9 +3003,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.0.tgz",
- "integrity": "sha512-I0QylkXsBsJMZ4nkUNSR04p6+UptjcwhcVo3Zu828ikiEqHjVmQL9RuQ6uT/cVIiKpvtVA25msu/eRV97JeNSA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz",
+ "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==",
"cpu": [
"arm64"
],
@@ -3003,9 +3020,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.0.tgz",
- "integrity": "sha512-6TmQIn4p09PBrmnkvbYQ0wbZhLtbaksCDx7Y7R3FYYx0yxNA7xg5KP7dowmQ3d2JVdabIHvs3Hx4K3d5uCf8xg==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz",
+ "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==",
"cpu": [
"x64"
],
@@ -3020,9 +3037,9 @@
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.0.tgz",
- "integrity": "sha512-qBudxDvAa2QwGlq9y7VIzhTvp2mLJ6nD/G8/tI70DCDoneaUeLWBJaPcbfzqRIWraj+o969aDQKvKW9dvkUizw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz",
+ "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==",
"cpu": [
"x64"
],
@@ -3037,9 +3054,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.0.tgz",
- "integrity": "sha512-7XKkitpy5NIjFZNUQPeUyNJNJn1CJeV7rmMR+exHfTuOsg8rxIO9eNV5TSEnqRcaOK77zQpsyUkBWmPy8FgdSg==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz",
+ "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==",
"cpu": [
"arm"
],
@@ -3054,9 +3071,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.0.tgz",
- "integrity": "sha512-Mff5a5Q3WoQR01pGU1gr29hHM1N93xYrKkGXfPw/aRtK4bOc331Ho4Tgfsm5WDGvpevqMpdlkCojT3qlCQbCpA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz",
+ "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==",
"cpu": [
"arm64"
],
@@ -3071,9 +3088,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.0.tgz",
- "integrity": "sha512-XKcSStleEVnbH6W/9DHzZv1YhjE4eSS6zOu2eRtYAIh7aV4o3vIBs+t/B15xlqoxt6ef/0uiqJVB6hkHjWD/0A==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz",
+ "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==",
"cpu": [
"arm64"
],
@@ -3088,9 +3105,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.0.tgz",
- "integrity": "sha512-/hlXCBqn9K6fi7eAM0RsobHwJYa5V/xzWspVTzxnX+Ft9v6n+30Pz8+RxCn7sQL/vRHHLS30iQPrHQunu6/vJA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz",
+ "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==",
"cpu": [
"x64"
],
@@ -3105,9 +3122,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.0.tgz",
- "integrity": "sha512-lKUaygq4G7sWkhQbfdRRBkaq4LY39IriqBQ+Gk6l5nKq6Ay2M2ZZb1tlIyRNgZKS8cbErTwuYSor0IIULC0SHw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz",
+ "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==",
"cpu": [
"x64"
],
@@ -3122,9 +3139,9 @@
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.0.tgz",
- "integrity": "sha512-xuDjhAsFdUuFP5W9Ze4k/o4AskUtI8bcAGU4puTYprr89QaYFmhYOPfP+d1pH+k9ets6RoE23BXZM1X1jJqoyw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz",
+ "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
@@ -3152,9 +3169,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.0.tgz",
- "integrity": "sha512-2UU/15y1sWDEDNJXxEIrfWKC2Yb4YgIW5Xz2fKFqGzFWfoMHWFlfa1EJlGO2Xzjkq/tvSarh9ZTjvbxqWvLLXA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz",
+ "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==",
"cpu": [
"arm64"
],
@@ -3169,9 +3186,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.0.tgz",
- "integrity": "sha512-CrFadmFoc+z76EV6LPG1jx6XceDsaCG3lFhyLNo/bV9ByPrE+FnBPckXQVP4XRkN76h3Fjt/a+5Er/oA/nCBvQ==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz",
+ "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==",
"cpu": [
"x64"
],
@@ -3186,17 +3203,17 @@
}
},
"node_modules/@tailwindcss/postcss": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.0.tgz",
- "integrity": "sha512-u6YBacGpOm/ixPfKqfgrJEjMfrYmPD7gEFRoygS/hnQaRtV0VCBdpkx5Ouw9pnaLRwwlgGCuJw8xLpaR0hOrQg==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.1.tgz",
+ "integrity": "sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
- "@tailwindcss/node": "4.2.0",
- "@tailwindcss/oxide": "4.2.0",
+ "@tailwindcss/node": "4.2.1",
+ "@tailwindcss/oxide": "4.2.1",
"postcss": "^8.5.6",
- "tailwindcss": "4.2.0"
+ "tailwindcss": "4.2.1"
}
},
"node_modules/@tanstack/query-core": {
@@ -3442,17 +3459,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz",
- "integrity": "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz",
+ "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
- "@typescript-eslint/scope-manager": "8.56.0",
- "@typescript-eslint/type-utils": "8.56.0",
- "@typescript-eslint/utils": "8.56.0",
- "@typescript-eslint/visitor-keys": "8.56.0",
+ "@typescript-eslint/scope-manager": "8.56.1",
+ "@typescript-eslint/type-utils": "8.56.1",
+ "@typescript-eslint/utils": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.4.0"
@@ -3465,22 +3482,22 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.56.0",
+ "@typescript-eslint/parser": "^8.56.1",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.0.tgz",
- "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz",
+ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.56.0",
- "@typescript-eslint/types": "8.56.0",
- "@typescript-eslint/typescript-estree": "8.56.0",
- "@typescript-eslint/visitor-keys": "8.56.0",
+ "@typescript-eslint/scope-manager": "8.56.1",
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/typescript-estree": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1",
"debug": "^4.4.3"
},
"engines": {
@@ -3496,14 +3513,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.0.tgz",
- "integrity": "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz",
+ "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.56.0",
- "@typescript-eslint/types": "^8.56.0",
+ "@typescript-eslint/tsconfig-utils": "^8.56.1",
+ "@typescript-eslint/types": "^8.56.1",
"debug": "^4.4.3"
},
"engines": {
@@ -3518,14 +3535,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz",
- "integrity": "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz",
+ "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.56.0",
- "@typescript-eslint/visitor-keys": "8.56.0"
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3536,9 +3553,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz",
- "integrity": "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz",
+ "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3553,15 +3570,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz",
- "integrity": "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz",
+ "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.56.0",
- "@typescript-eslint/typescript-estree": "8.56.0",
- "@typescript-eslint/utils": "8.56.0",
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/typescript-estree": "8.56.1",
+ "@typescript-eslint/utils": "8.56.1",
"debug": "^4.4.3",
"ts-api-utils": "^2.4.0"
},
@@ -3578,9 +3595,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz",
- "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz",
+ "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3592,18 +3609,18 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz",
- "integrity": "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz",
+ "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.56.0",
- "@typescript-eslint/tsconfig-utils": "8.56.0",
- "@typescript-eslint/types": "8.56.0",
- "@typescript-eslint/visitor-keys": "8.56.0",
+ "@typescript-eslint/project-service": "8.56.1",
+ "@typescript-eslint/tsconfig-utils": "8.56.1",
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1",
"debug": "^4.4.3",
- "minimatch": "^9.0.5",
+ "minimatch": "^10.2.2",
"semver": "^7.7.3",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.4.0"
@@ -3620,16 +3637,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.0.tgz",
- "integrity": "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz",
+ "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
- "@typescript-eslint/scope-manager": "8.56.0",
- "@typescript-eslint/types": "8.56.0",
- "@typescript-eslint/typescript-estree": "8.56.0"
+ "@typescript-eslint/scope-manager": "8.56.1",
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/typescript-estree": "8.56.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3644,13 +3661,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz",
- "integrity": "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz",
+ "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/types": "8.56.1",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -4074,11 +4091,14 @@
}
},
"node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
},
"node_modules/baseline-browser-mapping": {
"version": "2.10.0",
@@ -4104,13 +4124,16 @@
}
},
"node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
+ "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0"
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
}
},
"node_modules/braces": {
@@ -4184,9 +4207,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001770",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz",
- "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==",
+ "version": "1.0.30001774",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz",
+ "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==",
"dev": true,
"funding": [
{
@@ -4348,16 +4371,16 @@
"license": "MIT"
},
"node_modules/cssstyle": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.0.1.tgz",
- "integrity": "sha512-IoJs7La+oFp/AB033wBStxNOJt4+9hHMxsXUPANcoXL2b3W4DZKghlJ2cI/eyeRZIQ9ysvYEorVhjrcYctWbog==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.1.0.tgz",
+ "integrity": "sha512-Ml4fP2UT2K3CUBQnVlbdV/8aFDdlY69E+YnwJM+3VUWl08S3J8c8aRuJqCkD9Py8DHZ7zNNvsfKl8psocHZEFg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@asamuzakjp/css-color": "^4.1.2",
- "@csstools/css-syntax-patches-for-csstree": "^1.0.26",
+ "@asamuzakjp/css-color": "^5.0.0",
+ "@csstools/css-syntax-patches-for-csstree": "^1.0.28",
"css-tree": "^3.1.0",
- "lru-cache": "^11.2.5"
+ "lru-cache": "^11.2.6"
},
"engines": {
"node": ">=20"
@@ -4724,13 +4747,13 @@
}
},
"node_modules/eslint-plugin-react-refresh": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.0.tgz",
- "integrity": "sha512-ZYvmh7VfVgqR/7wR71I3Zl6hK/C5CcxdWYKZSpHawS5JCNgE4efhQWg/+/WPpgGAp9Ngp/rRZYyaIwmPQBq/lA==",
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz",
+ "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==",
"dev": true,
"license": "MIT",
"peerDependencies": {
- "eslint": ">=9"
+ "eslint": "^9 || ^10"
}
},
"node_modules/eslint-scope": {
@@ -4763,6 +4786,13 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/eslint/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/eslint/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -4798,9 +4828,9 @@
}
},
"node_modules/eslint/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
+ "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -6214,16 +6244,16 @@
}
},
"node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz",
+ "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==",
"dev": true,
- "license": "ISC",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "brace-expansion": "^5.0.2"
},
"engines": {
- "node": ">=16 || 14 >=14.17"
+ "node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -6747,9 +6777,9 @@
}
},
"node_modules/react-router": {
- "version": "7.13.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
- "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
+ "version": "7.13.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz",
+ "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
@@ -6769,12 +6799,12 @@
}
},
"node_modules/react-router-dom": {
- "version": "7.13.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz",
- "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==",
+ "version": "7.13.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz",
+ "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==",
"license": "MIT",
"dependencies": {
- "react-router": "7.13.0"
+ "react-router": "7.13.1"
},
"engines": {
"node": ">=20.0.0"
@@ -6852,9 +6882,9 @@
}
},
"node_modules/rollup": {
- "version": "4.58.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.58.0.tgz",
- "integrity": "sha512-wbT0mBmWbIvvq8NeEYWWvevvxnOyhKChir47S66WCxw1SXqhw7ssIYejnQEVt7XYQpsj2y8F9PM+Cr3SNEa0gw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
+ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6868,31 +6898,31 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.58.0",
- "@rollup/rollup-android-arm64": "4.58.0",
- "@rollup/rollup-darwin-arm64": "4.58.0",
- "@rollup/rollup-darwin-x64": "4.58.0",
- "@rollup/rollup-freebsd-arm64": "4.58.0",
- "@rollup/rollup-freebsd-x64": "4.58.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.58.0",
- "@rollup/rollup-linux-arm-musleabihf": "4.58.0",
- "@rollup/rollup-linux-arm64-gnu": "4.58.0",
- "@rollup/rollup-linux-arm64-musl": "4.58.0",
- "@rollup/rollup-linux-loong64-gnu": "4.58.0",
- "@rollup/rollup-linux-loong64-musl": "4.58.0",
- "@rollup/rollup-linux-ppc64-gnu": "4.58.0",
- "@rollup/rollup-linux-ppc64-musl": "4.58.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.58.0",
- "@rollup/rollup-linux-riscv64-musl": "4.58.0",
- "@rollup/rollup-linux-s390x-gnu": "4.58.0",
- "@rollup/rollup-linux-x64-gnu": "4.58.0",
- "@rollup/rollup-linux-x64-musl": "4.58.0",
- "@rollup/rollup-openbsd-x64": "4.58.0",
- "@rollup/rollup-openharmony-arm64": "4.58.0",
- "@rollup/rollup-win32-arm64-msvc": "4.58.0",
- "@rollup/rollup-win32-ia32-msvc": "4.58.0",
- "@rollup/rollup-win32-x64-gnu": "4.58.0",
- "@rollup/rollup-win32-x64-msvc": "4.58.0",
+ "@rollup/rollup-android-arm-eabi": "4.59.0",
+ "@rollup/rollup-android-arm64": "4.59.0",
+ "@rollup/rollup-darwin-arm64": "4.59.0",
+ "@rollup/rollup-darwin-x64": "4.59.0",
+ "@rollup/rollup-freebsd-arm64": "4.59.0",
+ "@rollup/rollup-freebsd-x64": "4.59.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.59.0",
+ "@rollup/rollup-linux-arm64-musl": "4.59.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.59.0",
+ "@rollup/rollup-linux-loong64-musl": "4.59.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.59.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.59.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-musl": "4.59.0",
+ "@rollup/rollup-openbsd-x64": "4.59.0",
+ "@rollup/rollup-openharmony-arm64": "4.59.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.59.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.59.0",
+ "@rollup/rollup-win32-x64-gnu": "4.59.0",
+ "@rollup/rollup-win32-x64-msvc": "4.59.0",
"fsevents": "~2.3.2"
}
},
@@ -7097,9 +7127,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.0.tgz",
- "integrity": "sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz",
+ "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==",
"dev": true,
"license": "MIT"
},
@@ -7275,16 +7305,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.0.tgz",
- "integrity": "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz",
+ "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.56.0",
- "@typescript-eslint/parser": "8.56.0",
- "@typescript-eslint/typescript-estree": "8.56.0",
- "@typescript-eslint/utils": "8.56.0"
+ "@typescript-eslint/eslint-plugin": "8.56.1",
+ "@typescript-eslint/parser": "8.56.1",
+ "@typescript-eslint/typescript-estree": "8.56.1",
+ "@typescript-eslint/utils": "8.56.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
diff --git a/frontend/package.json b/frontend/package.json
index 047b39b7a..c3aa5ea0a 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -46,22 +46,22 @@
"react-hook-form": "^7.71.2",
"react-hot-toast": "^2.6.0",
"react-i18next": "^16.5.4",
- "react-router-dom": "^7.13.0",
+ "react-router-dom": "^7.13.1",
"tailwind-merge": "^3.5.0",
"tldts": "^7.0.23"
},
"devDependencies": {
"@eslint/js": "^9.39.3 <10.0.0",
"@playwright/test": "^1.58.2",
- "@tailwindcss/postcss": "^4.2.0",
+ "@tailwindcss/postcss": "^4.2.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^25.3.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
- "@typescript-eslint/eslint-plugin": "^8.56.0",
- "@typescript-eslint/parser": "^8.56.0",
+ "@typescript-eslint/eslint-plugin": "^8.56.1",
+ "@typescript-eslint/parser": "^8.56.1",
"@vitejs/plugin-react": "^5.1.4",
"@vitest/coverage-istanbul": "^4.0.18",
"@vitest/coverage-v8": "^4.0.18",
@@ -69,13 +69,13 @@
"autoprefixer": "^10.4.24",
"eslint": "^9.39.3 <10.0.0",
"eslint-plugin-react-hooks": "^7.0.1",
- "eslint-plugin-react-refresh": "^0.5.0",
+ "eslint-plugin-react-refresh": "^0.5.2",
"jsdom": "28.1.0",
"knip": "^5.85.0",
"postcss": "^8.5.6",
- "tailwindcss": "^4.2.0",
+ "tailwindcss": "^4.2.1",
"typescript": "^5.9.3",
- "typescript-eslint": "^8.56.0",
+ "typescript-eslint": "^8.56.1",
"vite": "^7.3.1",
"vitest": "^4.0.18"
}
diff --git a/frontend/src/locales/de/translation.json b/frontend/src/locales/de/translation.json
index 33af5ccbd..e8610749d 100644
--- a/frontend/src/locales/de/translation.json
+++ b/frontend/src/locales/de/translation.json
@@ -768,6 +768,13 @@
"newTab": "Neuer Tab (Standard)",
"newWindow": "Neues Fenster",
"domainLinkBehaviorHelper": "Steuern Sie, wie Domain-Links in der Proxy-Hosts-Liste geöffnet werden.",
+ "keepaliveIdle": "Keepalive Idle (Optional)",
+ "keepaliveIdleHelper": "Optionale Caddy-Dauer (z. B. 2m, 30s). Leer lassen, um Backend-Standardwerte zu verwenden.",
+ "keepaliveIdleError": "Geben Sie eine gültige Dauer ein (z. B. 30s, 2m, 1h).",
+ "keepaliveCount": "Keepalive Count (Optional)",
+ "keepaliveCountHelper": "Optionale maximale Keepalive-Tests (1-1000). Leer lassen, um Backend-Standardwerte zu verwenden.",
+ "keepaliveCountError": "Geben Sie eine ganze Zahl zwischen 1 und 1000 ein.",
+ "keepaliveValidationFailed": "Keepalive-Einstellungen enthalten ungültige Werte.",
"languageHelper": "Wählen Sie Ihre bevorzugte Sprache. Änderungen werden sofort wirksam."
},
"applicationUrl": {
diff --git a/frontend/src/locales/en/translation.json b/frontend/src/locales/en/translation.json
index fb769b1da..e89e2d99b 100644
--- a/frontend/src/locales/en/translation.json
+++ b/frontend/src/locales/en/translation.json
@@ -876,6 +876,13 @@
"newTab": "New Tab (Default)",
"newWindow": "New Window",
"domainLinkBehaviorHelper": "Control how domain links open in the Proxy Hosts list.",
+ "keepaliveIdle": "Keepalive Idle (Optional)",
+ "keepaliveIdleHelper": "Optional Caddy duration (e.g., 2m, 30s). Leave blank to keep backend defaults.",
+ "keepaliveIdleError": "Enter a valid duration (for example: 30s, 2m, 1h).",
+ "keepaliveCount": "Keepalive Count (Optional)",
+ "keepaliveCountHelper": "Optional max keepalive probes (1-1000). Leave blank to keep backend defaults.",
+ "keepaliveCountError": "Enter a whole number between 1 and 1000.",
+ "keepaliveValidationFailed": "Keepalive settings contain invalid values.",
"languageHelper": "Select your preferred language. Changes take effect immediately."
},
"applicationUrl": {
diff --git a/frontend/src/locales/es/translation.json b/frontend/src/locales/es/translation.json
index d30ca0f25..075935700 100644
--- a/frontend/src/locales/es/translation.json
+++ b/frontend/src/locales/es/translation.json
@@ -768,6 +768,13 @@
"newTab": "Nueva Pestaña (Por defecto)",
"newWindow": "Nueva Ventana",
"domainLinkBehaviorHelper": "Controla cómo se abren los enlaces de dominio en la lista de Hosts Proxy.",
+ "keepaliveIdle": "Keepalive Idle (Opcional)",
+ "keepaliveIdleHelper": "Duración opcional de Caddy (por ejemplo, 2m, 30s). Déjelo vacío para mantener los valores predeterminados del backend.",
+ "keepaliveIdleError": "Ingrese una duración válida (por ejemplo: 30s, 2m, 1h).",
+ "keepaliveCount": "Keepalive Count (Opcional)",
+ "keepaliveCountHelper": "Número máximo opcional de sondeos keepalive (1-1000). Déjelo vacío para mantener los valores predeterminados del backend.",
+ "keepaliveCountError": "Ingrese un número entero entre 1 y 1000.",
+ "keepaliveValidationFailed": "La configuración de keepalive contiene valores no válidos.",
"languageHelper": "Selecciona tu idioma preferido. Los cambios surten efecto inmediatamente."
}, "applicationUrl": {
"title": "URL de aplicación",
diff --git a/frontend/src/locales/fr/translation.json b/frontend/src/locales/fr/translation.json
index ab3793139..9853dffc5 100644
--- a/frontend/src/locales/fr/translation.json
+++ b/frontend/src/locales/fr/translation.json
@@ -768,6 +768,13 @@
"newTab": "Nouvel Onglet (Par défaut)",
"newWindow": "Nouvelle Fenêtre",
"domainLinkBehaviorHelper": "Contrôle comment les liens de domaine s'ouvrent dans la liste des Hôtes Proxy.",
+ "keepaliveIdle": "Keepalive Idle (Optionnel)",
+ "keepaliveIdleHelper": "Durée Caddy optionnelle (ex. 2m, 30s). Laissez vide pour conserver les valeurs par défaut du backend.",
+ "keepaliveIdleError": "Entrez une durée valide (par exemple : 30s, 2m, 1h).",
+ "keepaliveCount": "Keepalive Count (Optionnel)",
+ "keepaliveCountHelper": "Nombre maximal optionnel de sondes keepalive (1-1000). Laissez vide pour conserver les valeurs par défaut du backend.",
+ "keepaliveCountError": "Entrez un nombre entier entre 1 et 1000.",
+ "keepaliveValidationFailed": "Les paramètres keepalive contiennent des valeurs invalides.",
"languageHelper": "Sélectionnez votre langue préférée. Les modifications prennent effet immédiatement."
}, "applicationUrl": {
"title": "URL de l'application",
diff --git a/frontend/src/locales/zh/translation.json b/frontend/src/locales/zh/translation.json
index b74471c47..09e96cdda 100644
--- a/frontend/src/locales/zh/translation.json
+++ b/frontend/src/locales/zh/translation.json
@@ -768,6 +768,13 @@
"newTab": "新标签页(默认)",
"newWindow": "新窗口",
"domainLinkBehaviorHelper": "控制代理主机列表中的域名链接如何打开。",
+ "keepaliveIdle": "Keepalive Idle(可选)",
+ "keepaliveIdleHelper": "可选的 Caddy 时长(例如 2m、30s)。留空可使用后端默认值。",
+ "keepaliveIdleError": "请输入有效时长(例如:30s、2m、1h)。",
+ "keepaliveCount": "Keepalive Count(可选)",
+ "keepaliveCountHelper": "可选的 keepalive 最大探测次数(1-1000)。留空可使用后端默认值。",
+ "keepaliveCountError": "请输入 1 到 1000 之间的整数。",
+ "keepaliveValidationFailed": "keepalive 设置包含无效值。",
"languageHelper": "选择您的首选语言。更改立即生效。"
},
"applicationUrl": {
diff --git a/frontend/src/pages/SystemSettings.tsx b/frontend/src/pages/SystemSettings.tsx
index 4cd9f1a8a..3ef8a24ee 100644
--- a/frontend/src/pages/SystemSettings.tsx
+++ b/frontend/src/pages/SystemSettings.tsx
@@ -41,11 +41,32 @@ export default function SystemSettings() {
const queryClient = useQueryClient()
const [caddyAdminAPI, setCaddyAdminAPI] = useState('http://localhost:2019')
const [sslProvider, setSslProvider] = useState('auto')
+ const [keepaliveIdle, setKeepaliveIdle] = useState('')
+ const [keepaliveCount, setKeepaliveCount] = useState('')
const [domainLinkBehavior, setDomainLinkBehavior] = useState('new_tab')
const [publicURL, setPublicURL] = useState('')
const [publicURLValid, setPublicURLValid] = useState