Fix audit findings: lock architecture, security, deployment hardening
Close all 14 findings from the codebase audit (1 high, 5 medium, 8 low): - Locking: replace the global API and workspace mutexes with per-instance and per-user keyed locks (new internal/api/locks.go) so a slow restore, upgrade or transfer no longer blocks unrelated users or instances; move Docker I/O out of the build queue and invalidate concurrent image-index observations with a generation counter; serialize instance deletion behind in-flight creation. - Security: equalize login timing for unknown usernames, periodically purge expired sessions/refresh tokens/lock rows, URL-encode orphan IDs in the frontend, upgrade vitest 3.x->5.x to clear 3 npm audit vulnerabilities (2 critical RCE). - Deployment: add a -healthcheck mode probing /healthz (used by Dockerfile HEALTHCHECK and compose), make ADMIN_PASSWORD optional in compose with removal guidance after first start, exclude test scripts from the image, and cap panel resources (cpus: 2, pids_limit: 512). - Reliability: check rows.Err() on admin listing loops, surface intent polling errors without sticking, disable close during in-flight create. Verified: go build/vet, full go test -race -count=1 ./..., frontend tsc build + 32 tests, npm audit 0, compose config, real-container healthcheck.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -191,6 +193,34 @@ func (a *API) accountLocked(username string) (bool, error) {
|
||||
}
|
||||
return until > time.Now().Unix(), err
|
||||
}
|
||||
|
||||
// RunSessionCleanup removes expired sessions, their refresh tokens and elapsed
|
||||
// login locks every ten minutes; without it the sessions table grows without
|
||||
// bound because SessionUser only filters rows on read.
|
||||
func (a *API) RunSessionCleanup(ctx context.Context) {
|
||||
ticker := time.NewTicker(10 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
a.cleanupSessions(time.Now().Unix())
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *API) cleanupSessions(now int64) {
|
||||
for _, q := range []struct{ name, sql string }{
|
||||
{"refresh tokens", "DELETE FROM auth_refresh WHERE sid IN (SELECT id FROM sessions WHERE expires<=?)"},
|
||||
{"sessions", "DELETE FROM sessions WHERE expires<=?"},
|
||||
{"login locks", "DELETE FROM auth_failures WHERE locked_until>0 AND locked_until<=?"},
|
||||
} {
|
||||
if _, err := a.s.DB.Exec(q.sql, now); err != nil {
|
||||
log.Printf("session cleanup (%s): %v", q.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
func (a *API) failedLogin(username string) error {
|
||||
_, err := a.s.DB.Exec(`INSERT INTO auth_failures(username,failures,locked_until) VALUES(?,1,0)
|
||||
ON CONFLICT(username) DO UPDATE SET
|
||||
|
||||
Reference in New Issue
Block a user