diff --git a/.gitignore b/.gitignore index 219dce3..41f013e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /shell2.exe /BOT_PLAN.md /SECURITY_REVIEW.md +/DragonCoreSSH-NewWEB.zip diff --git a/admin/index.html b/admin/index.html index 233b063..90e0f54 100644 --- a/admin/index.html +++ b/admin/index.html @@ -16,7 +16,7 @@ setTimeout(function(){document.documentElement.classList.remove("i18n-pending");},2500); })(); - +
@@ -1542,17 +1542,17 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/security_http.go b/security_http.go index d683902..ebc7468 100644 --- a/security_http.go +++ b/security_http.go @@ -20,8 +20,15 @@ func securePanelHandler(next http.Handler) http.Handler { w.Header().Set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), payment=()") w.Header().Set("Cross-Origin-Opener-Policy", "same-origin") w.Header().Set("Content-Security-Policy", "default-src 'self'; base-uri 'none'; frame-ancestors 'none'; object-src 'none'; form-action 'self'; img-src 'self' data:; connect-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'") - if strings.HasPrefix(r.URL.Path, "/api/") || r.URL.Path == "/" || r.URL.Path == "/index.html" { - w.Header().Set("Cache-Control", "no-store") + // The panel is deployed in-place by update.sh. Do not let browsers or + // reverse proxies keep an older JavaScript bundle after an update, because + // stale form serializers can silently omit newly-added config fields. + if strings.HasPrefix(r.URL.Path, "/api/") || + r.URL.Path == "/" || r.URL.Path == "/index.html" || + strings.HasPrefix(r.URL.Path, "/assets/") { + w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate") + w.Header().Set("Pragma", "no-cache") + w.Header().Set("Expires", "0") } if r.Body != nil && r.Method != http.MethodGet && r.Method != http.MethodHead { r.Body = http.MaxBytesReader(w, r.Body, maxAdminRequestBody) diff --git a/server_config_api.go b/server_config_api.go index 9c044f9..bed5691 100644 --- a/server_config_api.go +++ b/server_config_api.go @@ -87,6 +87,17 @@ func serverConfigPost(w http.ResponseWriter, r *http.Request) { http.Error(w, "config exceeds 512 KiB", http.StatusRequestEntityTooLarge) return } + // Keep track of optional field presence separately from its boolean value. + // This protects a newly-added setting from being reset by a stale cached + // panel bundle that does not know how to send the field yet. + var fieldPresence struct { + PAMAuthEnabled *bool `json:"pam_auth_enabled"` + } + if err := json.Unmarshal(body, &fieldPresence); err != nil { + http.Error(w, "invalid JSON: "+err.Error(), http.StatusBadRequest) + return + } + var newCfg Config if err := json.Unmarshal(body, &newCfg); err != nil { http.Error(w, "invalid JSON: "+err.Error(), http.StatusBadRequest) @@ -104,6 +115,9 @@ func serverConfigPost(w http.ResponseWriter, r *http.Request) { globalCfgMu.RLock() if globalCfg != nil { newCfg.Users = globalCfg.Users + if fieldPresence.PAMAuthEnabled == nil { + newCfg.PAMAuthEnabled = globalCfg.PAMAuthEnabled + } } globalCfgMu.RUnlock()