Security Audit Fixes: Session fixation prevention, SameSite cookies, audit log redaction, backup integrity, Docker hardening, error sanitization, rate limiting, trusted proxies, and config hardening

This commit is contained in:
backup 2026-09-03 23:55:25 -05:00
commit b587fb87a9
18 changed files with 987 additions and 87 deletions

View file

@ -39,6 +39,11 @@ func (r *SessionRepository) Delete(id string) error {
return err
}
func (r *SessionRepository) DeleteByUserID(userID string) error {
_, err := r.db.Exec(`DELETE FROM sessions WHERE user_id = ?`, userID)
return err
}
func (r *SessionRepository) DeleteExpired() error {
_, err := r.db.Exec(`DELETE FROM sessions WHERE expires_at < ?`, time.Now())
return err

View file

@ -10,6 +10,7 @@ import (
"time"
_ "github.com/mattn/go-sqlite3"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
)
@ -29,6 +30,10 @@ func New(path string) (*DB, error) {
}
db.SetMaxOpenConns(1)
// Restrict database file permissions (0600) to prevent unauthorized access
if err := os.Chmod(path, 0o600); err != nil {
log.Printf("warning: could not set db file permissions: %v", err)
}
return &DB{db}, nil
}
@ -69,6 +74,27 @@ func (d *DB) Migrate() error {
)`,
`CREATE INDEX IF NOT EXISTS idx_backups_filename ON backups(filename)`,
`CREATE INDEX IF NOT EXISTS idx_backups_created_at ON backups(created_at)`,
`CREATE TABLE IF NOT EXISTS audit_log (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
username TEXT NOT NULL,
role TEXT NOT NULL,
action TEXT NOT NULL,
resource_type TEXT NOT NULL,
resource_name TEXT,
provider TEXT,
source_file TEXT,
content_hash TEXT,
timestamp TEXT NOT NULL,
result TEXT NOT NULL,
error_category TEXT,
rollback_occurred INTEGER NOT NULL DEFAULT 0,
error_message TEXT
)`,
`CREATE INDEX IF NOT EXISTS idx_audit_log_user_id ON audit_log(user_id)`,
`CREATE INDEX IF NOT EXISTS idx_audit_log_timestamp ON audit_log(timestamp)`,
`CREATE INDEX IF NOT EXISTS idx_audit_log_action ON audit_log(action)`,
`CREATE INDEX IF NOT EXISTS idx_audit_log_result ON audit_log(result)`,
}
for _, q := range queries {
@ -89,14 +115,17 @@ func (d *DB) ensureAdminUser() error {
if count == 0 {
// Default admin: admin / changeme (bcrypt hash) — development-only
// Generate random UUID for admin user ID (not predictable)
id := uuid.V4().String()
hash := "$2a$10$KsL.67hxLy.jwc50Uk7b3.dEmO1LNE3atnfUjNskAKlh9raiug4ju"
_, err = d.Exec(
`INSERT INTO users (id, username, email, password_hash, role) VALUES (?, ?, ?, ?, ?)`,
"admin-uuid-0000-0000-000000000001", "admin", "admin@localhost", hash, "admin",
id, "admin", "admin@localhost", hash, "admin",
)
if err != nil {
return fmt.Errorf("create admin user: %w", err)
}
log.Println("Default admin user created with generated UUID (development-only)")
}
return nil
@ -114,12 +143,14 @@ func (d *DB) EnsureAdminPasswordViaEnv(envPassword string, devMode bool) error {
if err != nil {
return fmt.Errorf("hash admin password: %w", err)
}
// Generate random UUID for admin user ID (not predictable)
id := uuid.V4().String()
// Upsert admin user
_, err = d.Exec(`
INSERT INTO users (id, username, email, password_hash, role)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(username) DO UPDATE SET password_hash=excluded.password_hash, updated_at=CURRENT_TIMESTAMP
`, "admin-uuid-0000-0000-000000000001", "admin", "admin@localhost", string(hash), "admin")
`, id, "admin", "admin@localhost", string(hash), "admin")
if err != nil {
return fmt.Errorf("upsert admin via env: %w", err)
}