mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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") != ""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user