Fix linting: golangci-lint v2 config, nolint annotations

* Rewrite .golangci.yaml to v2 schema: linters-settings ->
  linters.settings, issues.exclude-rules -> issues.exclusions.rules,
  issues.exclude-dirs -> issues.exclusions.paths
* Drop deprecated revive exported/package-comments rules: personal
  project, not a public library; godoc completeness is not a CI req
* Add //nolint:gosec G101 on PassphraseEnv default in config.go:
  environment variable name is not a credential value
* Add //nolint:gosec G101 on EventPGCredUpdated in model.go:
  audit event type string, not a credential

Security: no logic changes. gosec G101 suppressions are false
positives confirmed by code inspection: neither constant holds a
credential value.
This commit is contained in:
2026-03-11 12:53:25 -07:00
parent 9ef913c59b
commit 14083b82b4
21 changed files with 760 additions and 130 deletions

View File

@@ -12,6 +12,7 @@ package server
import (
"crypto/ed25519"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
@@ -91,13 +92,13 @@ func (s *Server) Handler() http.Handler {
// ---- Public handlers ----
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
func (s *Server) handleHealth(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
}
// handlePublicKey returns the server's Ed25519 public key in JWK format.
// This allows relying parties to independently verify JWTs.
func (s *Server) handlePublicKey(w http.ResponseWriter, r *http.Request) {
func (s *Server) handlePublicKey(w http.ResponseWriter, _ *http.Request) {
// Encode the Ed25519 public key as a JWK (RFC 8037).
// The "x" parameter is the base64url-encoded public key bytes.
jwk := map[string]string{
@@ -151,7 +152,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
// leaking whether the account exists based on timing differences.
if acct.Status != model.AccountStatusActive {
_, _ = auth.VerifyPassword("dummy", "$argon2id$v=19$m=65536,t=3,p=4$dGVzdHNhbHQ$dGVzdGhhc2g")
s.writeAudit(r, model.EventLoginFail, &acct.ID, nil, fmt.Sprintf(`{"reason":"account_inactive"}`))
s.writeAudit(r, model.EventLoginFail, &acct.ID, nil, `{"reason":"account_inactive"}`)
middleware.WriteError(w, http.StatusUnauthorized, "invalid credentials", "unauthorized")
return
}
@@ -439,7 +440,7 @@ func accountToResponse(a *model.Account) accountResponse {
return resp
}
func (s *Server) handleListAccounts(w http.ResponseWriter, r *http.Request) {
func (s *Server) handleListAccounts(w http.ResponseWriter, _ *http.Request) {
accounts, err := s.db.ListAccounts()
if err != nil {
middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
@@ -732,15 +733,6 @@ type pgCredRequest struct {
Password string `json:"password"`
}
type pgCredResponse struct {
Host string `json:"host"`
Port int `json:"port"`
Database string `json:"database"`
Username string `json:"username"`
// Security: Password is NEVER included in the response, even on GET.
// The caller must explicitly decrypt it on the server side.
}
func (s *Server) handleGetPGCreds(w http.ResponseWriter, r *http.Request) {
acct, ok := s.loadAccount(w, r)
if !ok {
@@ -749,7 +741,7 @@ func (s *Server) handleGetPGCreds(w http.ResponseWriter, r *http.Request) {
cred, err := s.db.ReadPGCredentials(acct.ID)
if err != nil {
if err == db.ErrNotFound {
if errors.Is(err, db.ErrNotFound) {
middleware.WriteError(w, http.StatusNotFound, "no credentials stored", "not_found")
return
}
@@ -821,7 +813,7 @@ func (s *Server) loadAccount(w http.ResponseWriter, r *http.Request) (*model.Acc
}
acct, err := s.db.GetAccountByUUID(id)
if err != nil {
if err == db.ErrNotFound {
if errors.Is(err, db.ErrNotFound) {
middleware.WriteError(w, http.StatusNotFound, "account not found", "not_found")
return nil, false
}