Harden the surface: 0600 databases, checksum-verified installs, rate-limited login and key exchange
coordinator / test (push) Canceled after 0s
python / test (push) Canceled after 0s
release / binaries (amd64, darwin) (push) Canceled after 0s
release / binaries (amd64, linux) (push) Canceled after 0s
release / binaries (amd64, windows) (push) Canceled after 0s
release / binaries (arm64, darwin) (push) Canceled after 0s
release / binaries (arm64, linux) (push) Canceled after 0s
release / binaries (arm64, windows) (push) Canceled after 0s
release / wheel (push) Canceled after 0s
release / image (push) Canceled after 0s
users / test (push) Canceled after 0s
release / release (push) Canceled after 0s

This commit is contained in:
Emil
2026-08-03 18:40:27 +03:00
parent 330f95a375
commit 049113cec8
10 changed files with 378 additions and 4 deletions
@@ -18,6 +18,7 @@ import (
"encoding/json"
"errors"
"fmt"
"os"
"time"
_ "modernc.org/sqlite"
@@ -37,9 +38,29 @@ func Open(path string) (*sql.DB, error) {
_ = db.Close()
return nil, fmt.Errorf("ping sqlite database: %w", err)
}
if err := lockDownDatabase(path); err != nil {
_ = db.Close()
return nil, fmt.Errorf("lock down sqlite database: %w", err)
}
return db, nil
}
// lockDownDatabase restricts the database files to the owner: sqlite creates
// them with the process umask (0644), which would let any local user read job
// metadata and password hashes. WAL/SHM siblings inherit the main file's mode,
// so existing ones are corrected too. Best-effort: failures only warn callers
// via the returned error, never corrupt state.
func lockDownDatabase(path string) error {
for _, candidate := range []string{path, path + "-wal", path + "-shm"} {
if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
if err := os.Chmod(candidate, 0o600); err != nil {
return err
}
}
}
return nil
}
// querier is satisfied by both *sql.DB and *sql.Tx, letting every repository
// method run identically inside or outside a transaction.
type querier interface {
@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"errors"
"os"
"path/filepath"
"testing"
"time"
@@ -347,3 +348,19 @@ func TestCancelByJobInvalidatesTasks(t *testing.T) {
t.Errorf("cancelled task = %+v", got)
}
}
func TestOpenRestrictsDatabasePermissions(t *testing.T) {
path := filepath.Join(t.TempDir(), "locked.db")
db, err := Open(path)
if err != nil {
t.Fatal(err)
}
_ = db.Close()
info, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
if perm := info.Mode().Perm(); perm != 0o600 {
t.Errorf("db perms = %o, want 600", perm)
}
}