security: add authentication to /api/security/apply-restart endpoint

CRITICAL FIX: This endpoint previously allowed unauthenticated users to
trigger service restarts, which is a denial-of-service vulnerability.

Now requires:
- Authentication (CheckAuth) when auth is configured
- Admin role for proxy auth users
- settings:write scope for API tokens

Initial setup (no auth configured yet) remains accessible to allow
first-time security configuration to trigger restart.
This commit is contained in:
rcourtman
2026-02-03 19:55:29 +00:00
parent 832fda6c96
commit fdc99418d6
2 changed files with 35 additions and 5 deletions
@@ -440,9 +440,10 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
Running: <code class="font-mono">{scanProgress()?.current_command}</code>
</div>
</Show>
{/* Live elapsed time - always show while scanning */}
<div class="mt-1 text-xs text-blue-500 dark:text-blue-500">
Elapsed: {liveElapsedSeconds()}s
{/* Live elapsed time and hint */}
<div class="mt-2 flex items-center justify-between text-xs text-blue-500 dark:text-blue-400">
<span>Elapsed: {liveElapsedSeconds()}s</span>
<span class="text-blue-400 dark:text-blue-500">Analysis time varies by model. You can navigate away — results save automatically.</span>
</div>
</div>
</Show>
@@ -456,8 +457,9 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
Running discovery...
</span>
</div>
<div class="text-xs text-blue-500 dark:text-blue-500">
Elapsed: {liveElapsedSeconds()}s
<div class="flex items-center justify-between text-xs text-blue-500 dark:text-blue-400">
<span>Elapsed: {liveElapsedSeconds()}s</span>
<span class="text-blue-400 dark:text-blue-500">Analysis time varies by model. You can navigate away — results save automatically.</span>
</div>
</div>
</Show>
+28
View File
@@ -790,8 +790,36 @@ func (r *Router) setupRoutes() {
r.mux.HandleFunc("/api/security/validate-token", r.HandleValidateAPIToken)
// Apply security restart endpoint
// SECURITY: Require admin auth to prevent DoS via unauthenticated service restarts
r.mux.HandleFunc("/api/security/apply-restart", func(w http.ResponseWriter, req *http.Request) {
if req.Method == http.MethodPost {
// SECURITY: Require authentication - this endpoint can trigger service restart (DoS risk)
// Allow if: (1) auth is not configured yet (initial setup), or (2) caller is admin-authenticated
authConfigured := r.config.AuthUser != "" && r.config.AuthPass != "" || r.config.HasAPITokens()
if authConfigured {
if !CheckAuth(r.config, w, req) {
log.Warn().
Str("ip", GetClientIP(req)).
Msg("Unauthenticated apply-restart attempt blocked")
return // CheckAuth already wrote the error
}
// Check proxy auth for admin status (session users with basic auth are implicitly admin)
if r.config.ProxyAuthSecret != "" {
if valid, username, isAdmin := CheckProxyAuth(r.config, req); valid && !isAdmin {
log.Warn().
Str("ip", GetClientIP(req)).
Str("username", username).
Msg("Non-admin user attempted service restart")
http.Error(w, "Admin privileges required", http.StatusForbidden)
return
}
}
// Require settings:write scope for API tokens
if !ensureSettingsWriteScope(w, req) {
return
}
}
// Only allow restart if we're running under systemd (safer)
isSystemd := os.Getenv("INVOCATION_ID") != ""