Release v0.6.0 #31

Merged
Mike Bros merged 60 commits from release/0.6.0 into main 2026-02-23 03:04:30 +00:00
Contributor

Release v0.6.0 — Auth Expansion

Changes

Traditional Username/Password Authentication

  • OP#1097: DB migration — auth_tokens, password hash, auth provider columns
  • OP#1098: Password hashing service and auth token helpers
  • OP#1101: Login flow — email/password, session creation, rate limiting
  • OP#1099: Registration flow — form, validation, account creation
  • OP#1100: Email confirmation flow
  • OP#1102: Password reset and magic sign-in link flows
  • OP#1103: Invite system — admin-generated signup links
  • OP#1104: Setup wizard — local auth option and admin bootstrap
  • OP#1201: Integration tests — Traditional Auth flows

Multi-Provider OIDC Support

  • OP#1105: DB migration — oidc_providers table, user provider tracking
  • OP#1106: Refactor OIDCManager for multiple providers
  • OP#1108: Admin UI — OIDC provider CRUD management
  • OP#1107: Login page — multi-provider selection UI
  • OP#1109: Per-provider logout with end-session redirect
  • OP#1110: Setup wizard — multi-provider OIDC configuration
  • OP#1202: Integration tests — Multi-provider OIDC flows

Real-Time Status Updates (SSE)

  • OP#1437: SSE hub and status broadcast
  • OP#1438: Status batch collector client JS

Frontend Performance

  • OP#1440: Page prefetch on tab hover
  • OP#1441: Static caching and compression middleware
  • OP#1442: Page tabs, group highlight, page transitions

Database Schema

  • OP#1444: visible_to_groups column and group-based filtering
  • OP#1445: Page transitions, toolbar config, dashboard transition migrations

Performance Demo & Speedtest

  • OP#1447: Demo page infrastructure and test page CRUD
  • OP#1448: Speedtest runner with progressive scaling tests
  • OP#1449: Chart rendering library and speedtest template
  • OP#1450: Multi-tab browsing session test
  • OP#1451: Concurrent load test with server-side goroutines

Security & Code Review Fixes

  • OP#1637: Gate demo write endpoints behind admin auth (CRITICAL)
  • OP#1638: Fix XSS in SSE status HTML builder and templ Raw() calls
  • OP#1639: Fix OIDC session data persistence after login
  • OP#1640: Convert magic login from GET to two-step POST flow
  • OP#1641: Make ProviderSetDefault atomic with single UPDATE
  • OP#1642: Fix seed script to match current schema
  • OP#1643: Fix open redirect via next parameter validation
  • OP#1644: Fix XSS in renderLoginError with html.EscapeString
  • OP#1645: Add per-email rate limit on magic link requests
  • OP#1646: Add cleanup goroutines for in-memory rate limit maps
  • OP#1647: Handle registration uniqueness constraint violation (TOCTOU)
  • OP#1648: Prevent OIDC provider deletion when users are linked
  • OP#1649: Sanitize setup error messages to prevent DB detail leaks
  • OP#1650: Add goose Down sections to migrations 00027 and 00028
  • OP#1651: Cache LoadSettings to avoid per-request DB query
  • OP#1652: Add EventSource cleanup on visibilitychange and beforeunload
  • OP#1653: Add TTL eviction to prefetch cache

Checklist

  • All version tasks closed in Gravity PM
  • Tests passing (go test ./...)
  • go vet clean
  • Version file matches Gravity PM version (manifest.json → 0.6.0)
  • Security review fixes applied (17 findings resolved)

References

Version: v0.6.0 — Auth Expansion (Gravity PM)

## Release v0.6.0 — Auth Expansion ### Changes **Traditional Username/Password Authentication** - OP#1097: DB migration — auth_tokens, password hash, auth provider columns - OP#1098: Password hashing service and auth token helpers - OP#1101: Login flow — email/password, session creation, rate limiting - OP#1099: Registration flow — form, validation, account creation - OP#1100: Email confirmation flow - OP#1102: Password reset and magic sign-in link flows - OP#1103: Invite system — admin-generated signup links - OP#1104: Setup wizard — local auth option and admin bootstrap - OP#1201: Integration tests — Traditional Auth flows **Multi-Provider OIDC Support** - OP#1105: DB migration — oidc_providers table, user provider tracking - OP#1106: Refactor OIDCManager for multiple providers - OP#1108: Admin UI — OIDC provider CRUD management - OP#1107: Login page — multi-provider selection UI - OP#1109: Per-provider logout with end-session redirect - OP#1110: Setup wizard — multi-provider OIDC configuration - OP#1202: Integration tests — Multi-provider OIDC flows **Real-Time Status Updates (SSE)** - OP#1437: SSE hub and status broadcast - OP#1438: Status batch collector client JS **Frontend Performance** - OP#1440: Page prefetch on tab hover - OP#1441: Static caching and compression middleware - OP#1442: Page tabs, group highlight, page transitions **Database Schema** - OP#1444: visible_to_groups column and group-based filtering - OP#1445: Page transitions, toolbar config, dashboard transition migrations **Performance Demo & Speedtest** - OP#1447: Demo page infrastructure and test page CRUD - OP#1448: Speedtest runner with progressive scaling tests - OP#1449: Chart rendering library and speedtest template - OP#1450: Multi-tab browsing session test - OP#1451: Concurrent load test with server-side goroutines **Security & Code Review Fixes** - OP#1637: Gate demo write endpoints behind admin auth (CRITICAL) - OP#1638: Fix XSS in SSE status HTML builder and templ Raw() calls - OP#1639: Fix OIDC session data persistence after login - OP#1640: Convert magic login from GET to two-step POST flow - OP#1641: Make ProviderSetDefault atomic with single UPDATE - OP#1642: Fix seed script to match current schema - OP#1643: Fix open redirect via next parameter validation - OP#1644: Fix XSS in renderLoginError with html.EscapeString - OP#1645: Add per-email rate limit on magic link requests - OP#1646: Add cleanup goroutines for in-memory rate limit maps - OP#1647: Handle registration uniqueness constraint violation (TOCTOU) - OP#1648: Prevent OIDC provider deletion when users are linked - OP#1649: Sanitize setup error messages to prevent DB detail leaks - OP#1650: Add goose Down sections to migrations 00027 and 00028 - OP#1651: Cache LoadSettings to avoid per-request DB query - OP#1652: Add EventSource cleanup on visibilitychange and beforeunload - OP#1653: Add TTL eviction to prefetch cache ### Checklist - [x] All version tasks closed in Gravity PM - [x] Tests passing (`go test ./...`) - [x] go vet clean - [x] Version file matches Gravity PM version (manifest.json → 0.6.0) - [x] Security review fixes applied (17 findings resolved) ### References Version: v0.6.0 — Auth Expansion (Gravity PM)
Create migration 00023 with local auth foundation:
- Add password_hash, auth_provider, email_verified, email_verified_at to users
- Make oidc_subject nullable with partial unique index
- Create auth_tokens table for confirmation/reset/magic/invite tokens
- Update User model (OIDCSubject now *string, add AuthToken struct)
- Update all user query/scan sites for new columns
- Update templ component to use AuthProvider instead of OIDCSubject

Closes OP#1097

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Password hashing via bcrypt (cost 12) with strength validation
- Auth token service: generate, create, validate, consume, cleanup
- Tokens use SHA-256 hash storage with crypto/rand generation
- Add golang.org/x/crypto dependency for bcrypt
- Comprehensive unit tests for both services

Closes OP#1098

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add LocalAuthHandler with email/password validation, bcrypt verification,
  and in-memory rate limiting (5 attempts / 15 min lockout)
- Update SessionStore.Create() to accept TTL parameter for remember-me
- Update login page template to support both local auth and OIDC (with
  "or" divider when both are enabled)
- Add local_auth_enabled column to app_settings via migration 00024
- Wire POST /auth/login route with rate limiting middleware
- Add 15 unit tests covering credentials, rate limiting, HTMX/full-page
  error rendering, cleanup, and open redirect prevention

Closes OP#1101

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add RegisterHandler with form validation (username, email, password,
  confirmation), uniqueness checks, bcrypt hashing, and account creation
- Create register.templ component with inline validation errors
- Add open_registration column to app_settings via migration 00025
- Wire GET/POST /register routes with rate limiting
- Add /register to public paths in mode middleware
- Update login page to show "Register" link when open registration enabled
- Add 10 unit tests covering success, duplicate username/email, weak
  password, mismatch, disabled auth, closed registration, invalid username

Closes OP#1099

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ConfirmHandler with token validation, email verification update,
  and token consumption
- Add resend confirmation endpoint with rate limiting (5 min cooldown)
  and previous token invalidation
- Create confirm.templ components: success page, error page with resend
  form, and HTMX resend result fragment
- Wire GET /auth/confirm and POST /auth/resend-confirmation routes
- Add 8 unit tests covering valid/expired/used tokens, resend success,
  rate limiting, already verified, empty email, and missing token

Closes OP#1100

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements forgot-password, reset-password, magic-link, and magic-login
endpoints with rate limiting, anti-enumeration, and session invalidation.
Adds SessionStore.DestroyAllForUser and CreateSession methods. Includes
14 unit tests and "Forgot password?" link on login page.

Closes OP#1102

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Admins can generate invite links with optional email lock, groups, and
admin flag. Invites are validated during registration; matching emails
skip confirmation. Adds admin invite list/create/revoke UI, nav link,
ListTokensByType service method, and 9 invite-specific unit tests.

Closes OP#1103

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a "Local Accounts" card to the setup wizard chooser, with a
dedicated form step for creating the initial admin account (username,
email, password, site title, open registration toggle). On success the
admin is auto-signed in and redirected to the dashboard.

Closes OP#1104

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Eight cross-cutting handler-layer tests covering end-to-end user
stories: registration→confirmation→login, invite-based registration,
rate limiting/lockout, password reset with session invalidation,
magic link sign-in, setup wizard bootstrap, and login guards for
unverified and OIDC-only users.

Closes OP#1201

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create migration 00026 for multi-provider OIDC support:
- oidc_providers table with full provider config columns
- Migrate existing singleton config from app_settings
- Add oidc_provider_id FK on users, back-fill existing OIDC users
- Replace partial unique index with composite (provider_id, subject)

Update OIDCProvider/Manager to store and pass provider ID for
correct composite ON CONFLICT upserts. Refactor DevAuth to
SELECT-first approach (avoids dependency on specific index structure).

Add OIDCProvider model struct and OIDCProviderID field to User.
Update all user scan queries across the codebase (12 files).

Closes OP#1105

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the double-submit cookie CSRF pattern with Sec-Fetch-Site header
validation (same approach Forgejo v14 adopted). This is stateless, requires
no client-side JavaScript, and eliminates stale-token issues in long-lived tabs.

Falls back to Origin header checking for older browsers.

Refs OP#1106

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refactor OIDCManager from single-provider to multi-provider architecture:
- providers map[uuid.UUID]*OIDCProvider replaces single provider field
- ReloadFromDB queries oidc_providers table directly (not app_settings)
- LoginHandler supports ?provider=slug for explicit selection, auto-redirect
  for single provider or when default has auto_redirect set
- CallbackHandler routes to correct provider via state parameter encoding
  (base64 providerID:csrfToken)
- New methods: GetProvider, GetProviderBySlug, ListProviders
- OIDCChecker interface gains ListProviders for login page rendering
- OIDCProvider gains display fields (Name, Slug, Icon, AutoRedirect)
- Session stores provider_id for per-provider logout support

Closes OP#1106

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace singleton OIDC config at /admin/auth with multi-provider management:
- Provider list page with status, user counts, and action buttons
- Add/edit form with all provider fields (issuer, client, scopes, admin mapping)
- Delete with last-enabled-provider safety check
- Set default provider, per-provider OIDC discovery verify
- Access control settings (operation mode, guest access) preserved on same page
- Activity logging for create/update/delete operations
- 11 unit tests covering all CRUD operations

Closes OP#1108

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrite the login page to support multiple OIDC providers with provider
selection buttons, auto-redirect for single/default providers, and
circular redirect prevention. Update ListProviders to include IsDefault.

Closes OP#1107

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update LogoutHandler to read provider_id from session data and redirect
to the OIDC provider's end_session_endpoint with id_token_hint. Falls
back to / when provider is unavailable. Add GetSessionData to SessionStore,
cache end_session_endpoint during provider init, store id_token in session.

Closes OP#1109

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Setup wizard now inserts/upserts into oidc_providers as the primary
source of truth, with app_settings maintained for backward compat.
Simplify AuthUpdate to only handle access control (operation_mode,
oidc_enabled, guest_access). Remove unused singleton AuthPage handler.

Closes OP#1110

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 7 cross-cutting integration tests: multi-provider login selection,
single-provider auto-redirect, CRUD-to-login page update, setup wizard
provider migration, last-provider delete guard, provider slug selection,
and local auth + OIDC combined page.

Closes OP#1202

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add SSE hub for broadcasting status check results to connected clients.
Replace per-item HTTP polling with centralized batch collection and
real-time push updates, reducing network chatter significantly.

Refs OP#1436, Closes OP#1437, Closes OP#1438

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add page prefetch on tab hover (30s cache, ~7x speedup), horizontal
tab scrolling, group access visualization, static file caching (7d/30d),
gzip compression middleware, and default favicon support.

Refs OP#1439, Closes OP#1440, Closes OP#1441, Closes OP#1442

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add visible_to_groups column for group-based item filtering, page
transition settings (moved from page to dashboard level), and
toolbar_config JSONB for app settings.

Refs OP#1443, Closes OP#1444, Closes OP#1445

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comprehensive benchmarking at /demo/speedtest with A/B tests for
batching (44x), lazy loading (60% reduction), prefetch (7x), full
experience (25x), multi-tab browsing (13x), and concurrent load
testing with server-side Go goroutines (p50/p95/p99 percentiles).

Refs OP#1446, Closes OP#1447, Closes OP#1448, Closes OP#1449,
Closes OP#1450, Closes OP#1451

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Integrate SSE hub, status batching, lazy loading, page prefetch,
group filtering, page transitions, compression, and demo routes
into the main application. Update templates for new UI features
and adapt handler interfaces.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
chore(release): bump version to 0.6.0
Some checks failed
CI / validate-branch (pull_request) Successful in 2s
CI / security (pull_request) Successful in 57s
CI / lint (pull_request) Failing after 1m16s
CI / test (pull_request) Has been skipped
CI / validate-release-pr (pull_request) Successful in 4m1s
CI / build (pull_request) Successful in 5m55s
9c21cd7f19
Refs OP#1200

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(release): apply gofumpt/goimports formatting
Some checks failed
CI / validate-branch (pull_request) Successful in 1s
CI / validate-release-pr (pull_request) Successful in 6s
CI / build (pull_request) Successful in 43s
CI / security (pull_request) Successful in 1m26s
CI / lint (pull_request) Failing after 5m27s
CI / test (pull_request) Has been skipped
07b4f6550c
Refs OP#1200

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(lint): resolve golangci-lint errors in handlers
All checks were successful
CI / validate-branch (pull_request) Successful in 2s
CI / validate-release-pr (pull_request) Successful in 3s
CI / build (pull_request) Successful in 31s
CI / security (pull_request) Successful in 36s
CI / lint (pull_request) Successful in 2m17s
CI / test (pull_request) Successful in 3m48s
d1535d310d
Remove unused func, ineffectual assignment, dead append, and unchecked
io.Copy return values flagged by errcheck/staticcheck/ineffassign.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Contributor

Code Review: Release v0.6.0 — Request Changes

Scope: 116 files, +16,398 / -914 lines, 23 commits

Overall architecture is solid — crypto fundamentals are correct (bcrypt cost 12, crypto/rand tokens, SHA-256 token hashing, HMAC-SHA256 sessions with constant-time comparison), cookie security is good (HttpOnly, SameSite=Lax), CSRF via Sec-Fetch-Site is a sound modern approach, and the integration test suite validates complete user journeys. However, there are several issues that need to be addressed before merge.


CRITICAL — Demo endpoints allow unauthenticated database writes and self-DoS

internal/handlers/demo.go + cmd/gashy/main.go:211-237

/demo/api/* endpoints are registered without authentication and isPublicPath in mode.go explicitly includes /demo. Any anonymous user can:

  • Create thousands of DB rows (up to 5,000 items per request via CreateTestPage)
  • Delete any page in the systemCleanupTestPage takes an arbitrary UUID with no ownership check (DELETE FROM pages WHERE id = $1)
  • Self-DoS — LoadTest spawns 50 goroutines × 50 iterations = 2,500 internal HTTP requests per call with no mutual exclusion

Fix: Gate write endpoints behind admin auth, restrict deletes to the showcase dashboard (WHERE dashboard_id = $2), add a feature flag to disable the demo.


HIGH — XSS in SSE status HTML builder

cmd/gashy/main.go:641-667

errMsg from status checks is interpolated into a data-status-tip='...' single-quoted attribute via fmt.Sprintf. The escapeJSONString helper only escapes JSON chars, not HTML. A crafted error like foo' onmouseover='alert(1) breaks out of the attribute.

Same pattern in components/partials/status.templ:187,226templ.Raw() with color values and JSON error messages bypasses auto-escaping.

Fix: Use html.EscapeString() on the JSON before embedding in HTML attributes. Validate color values server-side (regex for hex/var patterns). Consider migrating buildSSEStatusHTML to a templ component.


HIGH — OIDC session data never persisted (provider logout broken)

internal/auth/oidc.go:227-237

After sessions.Create() sets the cookie on w, SetSessionData() reads the cookie from r (the original request), which doesn't have it. The error is silently discarded (_ =). This means provider_id and id_token are never stored, so per-provider end-session logout will never work.

Fix: Return session ID from Create and pass it to a SetSessionDataByID method, or add the new cookie to the request before calling SetSessionData.


HIGH — Magic login token consumed on GET

internal/handlers/reset.go:270-308

MagicLogin is a GET handler that consumes the token and creates a session. Browser prefetchers, Slack/Discord link previews, and crawlers will consume the token before the user clicks.

Fix: Two-step flow — GET renders a confirmation page with a POST form that actually consumes the token.


HIGH — ProviderSetDefault is non-atomic

internal/handlers/admin_providers.go:358-371

Two separate UPDATEs: clear all defaults, then set new default. If the second fails, the system has no default provider.

Fix: Wrap in a transaction or use UPDATE ... SET is_default = (id = $1).


HIGH — Seed script references dropped columns

scripts/seed-test-dashboard.sql

All 8 INSERT INTO pages reference transition_in/transition_out, which no longer exist after migration 00030. Running this script will fail.


⚠️ MEDIUM (12)

  1. Open redirect via next parameter//evil.com passes next[0] == '/' check (internal/auth/local.go:185-192). Use url.Parse and verify Host is empty.
  2. XSS-fragile renderLoginErrorfmt.Fprintf with unescaped message (internal/auth/local.go:204). Use html.EscapeString().
  3. No per-email rate limit on magic links — IP-level only; ForgotPassword has per-email throttle but magic links don't (internal/handlers/reset.go:217-267).
  4. Unbounded in-memory rate limit mapsresendTimes and attempts grow without cleanup goroutines (confirm.go:29, reset.go:43).
  5. TOCTOU on registration uniqueness — SELECT then INSERT without constraint-violation handling (internal/handlers/register.go:184-234).
  6. OIDC client_secret in cleartext — no at-rest encryption (internal/handlers/admin_providers.go).
  7. ProviderDelete allows deletion with active users — no user count check (internal/handlers/admin_providers.go:256-303).
  8. Setup error messages leak DB details — raw err.Error() returned to user (internal/handlers/setup.go:185,207).
  9. Missing -- +goose Down in migrations 00027 and 00028 — prevents clean rollback.
  10. Per-request settings queryLoadSettings hits DB on every HTTP request with no caching (internal/middleware/mode.go:33).
  11. SSE EventSource never closed — no visibilitychange/beforeunload cleanup (static/js/status-batch.js:131-174).
  12. Prefetch cache never evicts — stale entries persist in memory (static/js/page-prefetch.js).

ℹ️ LOW (15)

No max password length (bcrypt truncates at 72 bytes), weak email validation (@ and . only), email existence leak on confirm resend, silent JSON unmarshal on invite metadata, impersonation middleware active in production, SSE Events channel not closed on unsubscribe, no max SSE client count, no SSE reconnection backoff, stale polling IDs on DOM changes, animating flag can get stuck, resize listener leak in page-tabs, Chart.js CDN without SRI, missing ARIA labels on tab controls, modals missing role="dialog" and focus traps, redundant migration 00028.

Test coverage gaps

All tests pass including race detector. Key gaps: HandleLogin HTTP success path untested (session creation, remember-me, next redirect), OIDCManager.ReloadFromDB at 0% coverage, admin endpoint authorization not negatively tested.


Verdict

Requesting changes. The critical demo endpoint exposure and the HIGH-severity XSS/OIDC/magic-link issues need to be resolved before merge. The MEDIUM items are worth addressing in this release cycle but could be follow-up commits on the release branch.

## Code Review: Release v0.6.0 — Request Changes **Scope:** 116 files, +16,398 / -914 lines, 23 commits Overall architecture is solid — crypto fundamentals are correct (bcrypt cost 12, `crypto/rand` tokens, SHA-256 token hashing, HMAC-SHA256 sessions with constant-time comparison), cookie security is good (HttpOnly, SameSite=Lax), CSRF via Sec-Fetch-Site is a sound modern approach, and the integration test suite validates complete user journeys. However, there are several issues that need to be addressed before merge. --- ### ❌ CRITICAL — Demo endpoints allow unauthenticated database writes and self-DoS **`internal/handlers/demo.go` + `cmd/gashy/main.go:211-237`** `/demo/api/*` endpoints are registered without authentication and `isPublicPath` in `mode.go` explicitly includes `/demo`. Any anonymous user can: - Create thousands of DB rows (up to 5,000 items per request via `CreateTestPage`) - **Delete any page in the system** — `CleanupTestPage` takes an arbitrary UUID with no ownership check (`DELETE FROM pages WHERE id = $1`) - Self-DoS — `LoadTest` spawns 50 goroutines × 50 iterations = 2,500 internal HTTP requests per call with no mutual exclusion **Fix:** Gate write endpoints behind admin auth, restrict deletes to the showcase dashboard (`WHERE dashboard_id = $2`), add a feature flag to disable the demo. --- ### ❌ HIGH — XSS in SSE status HTML builder **`cmd/gashy/main.go:641-667`** `errMsg` from status checks is interpolated into a `data-status-tip='...'` single-quoted attribute via `fmt.Sprintf`. The `escapeJSONString` helper only escapes JSON chars, not HTML. A crafted error like `foo' onmouseover='alert(1)` breaks out of the attribute. Same pattern in `components/partials/status.templ:187,226` — `templ.Raw()` with color values and JSON error messages bypasses auto-escaping. **Fix:** Use `html.EscapeString()` on the JSON before embedding in HTML attributes. Validate color values server-side (regex for hex/var patterns). Consider migrating `buildSSEStatusHTML` to a templ component. --- ### ❌ HIGH — OIDC session data never persisted (provider logout broken) **`internal/auth/oidc.go:227-237`** After `sessions.Create()` sets the cookie on `w`, `SetSessionData()` reads the cookie from `r` (the original request), which doesn't have it. The error is silently discarded (`_ =`). This means `provider_id` and `id_token` are never stored, so per-provider end-session logout will never work. **Fix:** Return session ID from `Create` and pass it to a `SetSessionDataByID` method, or add the new cookie to the request before calling `SetSessionData`. --- ### ❌ HIGH — Magic login token consumed on GET **`internal/handlers/reset.go:270-308`** `MagicLogin` is a GET handler that consumes the token and creates a session. Browser prefetchers, Slack/Discord link previews, and crawlers will consume the token before the user clicks. **Fix:** Two-step flow — GET renders a confirmation page with a POST form that actually consumes the token. --- ### ❌ HIGH — ProviderSetDefault is non-atomic **`internal/handlers/admin_providers.go:358-371`** Two separate UPDATEs: clear all defaults, then set new default. If the second fails, the system has no default provider. **Fix:** Wrap in a transaction or use `UPDATE ... SET is_default = (id = $1)`. --- ### ❌ HIGH — Seed script references dropped columns **`scripts/seed-test-dashboard.sql`** All 8 `INSERT INTO pages` reference `transition_in`/`transition_out`, which no longer exist after migration 00030. Running this script will fail. --- ### ⚠️ MEDIUM (12) 1. **Open redirect via `next` parameter** — `//evil.com` passes `next[0] == '/'` check (`internal/auth/local.go:185-192`). Use `url.Parse` and verify `Host` is empty. 2. **XSS-fragile `renderLoginError`** — `fmt.Fprintf` with unescaped `message` (`internal/auth/local.go:204`). Use `html.EscapeString()`. 3. **No per-email rate limit on magic links** — IP-level only; `ForgotPassword` has per-email throttle but magic links don't (`internal/handlers/reset.go:217-267`). 4. **Unbounded in-memory rate limit maps** — `resendTimes` and `attempts` grow without cleanup goroutines (`confirm.go:29`, `reset.go:43`). 5. **TOCTOU on registration uniqueness** — SELECT then INSERT without constraint-violation handling (`internal/handlers/register.go:184-234`). 6. **OIDC client_secret in cleartext** — no at-rest encryption (`internal/handlers/admin_providers.go`). 7. **ProviderDelete allows deletion with active users** — no user count check (`internal/handlers/admin_providers.go:256-303`). 8. **Setup error messages leak DB details** — raw `err.Error()` returned to user (`internal/handlers/setup.go:185,207`). 9. **Missing `-- +goose Down`** in migrations 00027 and 00028 — prevents clean rollback. 10. **Per-request settings query** — `LoadSettings` hits DB on every HTTP request with no caching (`internal/middleware/mode.go:33`). 11. **SSE EventSource never closed** — no `visibilitychange`/`beforeunload` cleanup (`static/js/status-batch.js:131-174`). 12. **Prefetch cache never evicts** — stale entries persist in memory (`static/js/page-prefetch.js`). ### ℹ️ LOW (15) No max password length (bcrypt truncates at 72 bytes), weak email validation (`@` and `.` only), email existence leak on confirm resend, silent JSON unmarshal on invite metadata, impersonation middleware active in production, SSE `Events` channel not closed on unsubscribe, no max SSE client count, no SSE reconnection backoff, stale polling IDs on DOM changes, `animating` flag can get stuck, resize listener leak in page-tabs, Chart.js CDN without SRI, missing ARIA labels on tab controls, modals missing `role="dialog"` and focus traps, redundant migration 00028. ### Test coverage gaps All tests pass including race detector. Key gaps: `HandleLogin` HTTP success path untested (session creation, remember-me, `next` redirect), `OIDCManager.ReloadFromDB` at 0% coverage, admin endpoint authorization not negatively tested. --- ### Verdict **Requesting changes.** The critical demo endpoint exposure and the HIGH-severity XSS/OIDC/magic-link issues need to be resolved before merge. The MEDIUM items are worth addressing in this release cycle but could be follow-up commits on the release branch.
fix(security): gate demo write endpoints behind admin auth
Some checks failed
CI / test (pull_request) Blocked by required conditions
CI / validate-branch (pull_request) Successful in 1s
CI / validate-release-pr (pull_request) Successful in 6s
CI / security (pull_request) Successful in 1m38s
CI / build (pull_request) Has been cancelled
CI / lint (pull_request) Has been cancelled
2e557c4062
Move demo write API endpoints (CreateTestPage, ScaleTestPage,
CleanupTestPage, LoadTest) behind RequireAdmin middleware. Read-only
endpoints (Overview, Speedtest, StatusIndividual, PageEager, TestPageItems,
TestPageSections) remain public.

Additional hardening:
- CleanupTestPage now scoped to showcase dashboard only (WHERE dashboard_id)
- LoadTest protected by sync.Mutex to prevent concurrent self-DoS
- isPublicPath excludes demo write paths from unauthenticated access

Closes OP#1637

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(security): escape SSE status HTML and validate color values
Some checks failed
CI / test (pull_request) Blocked by required conditions
CI / validate-branch (pull_request) Successful in 1s
CI / validate-release-pr (pull_request) Successful in 4s
CI / security (pull_request) Successful in 41s
CI / build (pull_request) Has been cancelled
CI / lint (pull_request) Has been cancelled
6a19271660
- HTML-escape JSON in buildSSEStatusHTML before embedding in data attribute
- HTML-escape tooltip JSON in statusTooltipOOBTag to prevent attribute breakout
- Add safeColor() regex validator for CSS color values in status indicators
- Apply safeColor() to dotColorStyle, iconColor, and borderStyle functions

Prevents XSS via crafted error messages or malicious color values injected
through status check responses or admin-configured display settings.

Closes OP#1638

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(auth): persist OIDC session data after login
Some checks failed
CI / test (pull_request) Blocked by required conditions
CI / validate-branch (pull_request) Successful in 1s
CI / build (pull_request) Successful in 48s
CI / validate-release-pr (pull_request) Successful in 1m25s
CI / security (pull_request) Has been cancelled
CI / lint (pull_request) Has been cancelled
c66818872a
Create() now returns the session ID so callers can set session data
immediately without reading the cookie back from the request. Added
SetSessionDataByID() for this use case.

Previously, SetSessionData() read the cookie from r (the original
request), which didn't have the newly created session cookie yet.
This silently failed, leaving provider_id and id_token empty, which
broke per-provider OIDC logout.

Closes OP#1639

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(security): convert magic login from GET to two-step POST flow
Some checks failed
CI / test (pull_request) Blocked by required conditions
CI / validate-branch (pull_request) Successful in 4s
CI / validate-release-pr (pull_request) Successful in 7s
CI / security (pull_request) Successful in 38s
CI / build (pull_request) Successful in 1m35s
CI / lint (pull_request) Has been cancelled
c64ec079c5
GET /auth/magic-login now shows a confirmation page without consuming
the token. POST /auth/magic-login consumes the token and creates the
session. This prevents browser prefetchers, Slack/Discord link previews,
and crawlers from invalidating tokens before users click them.

Closes OP#1640

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace two separate UPDATEs (clear all defaults, then set new default)
with a single atomic UPDATE that sets is_default = (id = $1). Prevents
the system from having no default provider if the second query failed.

Closes OP#1641

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(scripts): remove dropped transition columns from seed script
Some checks failed
CI / validate-branch (pull_request) Successful in 2s
CI / validate-release-pr (pull_request) Successful in 4s
CI / build (pull_request) Successful in 47s
CI / security (pull_request) Successful in 1m54s
CI / lint (pull_request) Successful in 2m18s
CI / test (pull_request) Has been cancelled
b462763964
Remove transition_in and transition_out from all INSERT INTO pages
statements in the seed script. These columns were dropped in migration
00030, causing the seed script to fail.

Closes OP#1642

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(auth): prevent open redirect and XSS in login flow
Some checks failed
CI / test (pull_request) Blocked by required conditions
CI / validate-branch (pull_request) Successful in 1s
CI / validate-release-pr (pull_request) Successful in 4s
CI / lint (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / security (pull_request) Has been cancelled
92ede612c4
Validate the `next` query parameter to reject absolute URLs (open
redirect) and HTML-escape error messages in renderLoginError (XSS).
Also update ProviderSetDefault test to match atomic UPDATE from OP#1641.

Closes OP#1643
Closes OP#1644

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(auth): add per-email rate limit on magic link requests
Some checks failed
CI / test (pull_request) Blocked by required conditions
CI / validate-release-pr (pull_request) Successful in 5s
CI / validate-branch (pull_request) Successful in 37s
CI / security (pull_request) Successful in 1m19s
CI / lint (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
fb47d86c16
Reuse the existing isRateLimited/recordAttempt mechanism from the
password-reset flow so magic link requests are throttled per email
address (3 per hour window).

Closes OP#1645

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(auth): add cleanup goroutines for rate limit maps
Some checks failed
CI / test (pull_request) Blocked by required conditions
CI / validate-branch (pull_request) Successful in 0s
CI / validate-release-pr (pull_request) Successful in 4s
CI / security (pull_request) Successful in 1m33s
CI / build (pull_request) Successful in 1m47s
CI / lint (pull_request) Has been cancelled
4c6ebd23aa
Add StartCleanup methods to ConfirmHandler and ResetHandler that
periodically evict expired entries from in-memory rate limit maps,
preventing unbounded growth.

Closes OP#1646

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(auth): handle registration uniqueness constraint violations
Some checks failed
CI / validate-release-pr (pull_request) Successful in 5s
CI / build (pull_request) Has been cancelled
CI / lint (pull_request) Has been cancelled
CI / validate-branch (pull_request) Successful in 2s
CI / security (pull_request) Successful in 38s
CI / test (pull_request) Has been cancelled
e2ebdebe6f
Catch duplicate key errors from INSERT on user creation to handle
TOCTOU races where two concurrent registrations pass the SELECT
uniqueness check. Returns user-friendly field-specific errors.

Closes OP#1647

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(admin): prevent OIDC provider deletion with linked users
Some checks failed
CI / validate-branch (pull_request) Successful in 5s
CI / test (pull_request) Blocked by required conditions
CI / validate-release-pr (pull_request) Successful in 5s
CI / lint (pull_request) Has been cancelled
CI / security (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
402ddec4ea
Check user count before deleting a provider. If users are still
linked via oidc_provider_id, block deletion with an error toast
instructing the admin to migrate or remove them first.

Closes OP#1648

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(setup): sanitize error messages to prevent DB detail leaks
Some checks failed
CI / test (pull_request) Blocked by required conditions
CI / validate-branch (pull_request) Successful in 2s
CI / security (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / validate-release-pr (pull_request) Successful in 5s
CI / lint (pull_request) Has been cancelled
1c882e3e66
Replace raw err.Error() in setup handler responses with generic
user-facing messages. The detailed errors are still logged server-side
via slog.Error for debugging.

Closes OP#1649

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(db): add goose Down sections to migrations 00027 and 00028
Some checks failed
CI / security (pull_request) Successful in 46s
CI / test (pull_request) Blocked by required conditions
CI / validate-branch (pull_request) Successful in 2s
CI / validate-release-pr (pull_request) Successful in 6s
CI / build (pull_request) Successful in 1m43s
CI / lint (pull_request) Has been cancelled
9f73b24aef
Add reverse DDL operations for clean rollback of the
visible_to_groups column and page transition columns.

Closes OP#1650

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
perf(settings): add in-memory cache with 30s TTL for LoadSettings
Some checks failed
CI / test (pull_request) Blocked by required conditions
CI / validate-branch (pull_request) Successful in 2s
CI / validate-release-pr (pull_request) Successful in 4s
CI / lint (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / security (pull_request) Successful in 32s
cc5e5b4d9c
Cache the singleton app_settings query result to avoid hitting the
database on every HTTP request. The cache auto-expires after 30s and
can be explicitly invalidated via InvalidateSettings().

Closes OP#1651

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(sse): add EventSource cleanup on visibilitychange and beforeunload
Some checks failed
CI / test (pull_request) Blocked by required conditions
CI / security (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / lint (pull_request) Has been cancelled
CI / validate-branch (pull_request) Successful in 2s
CI / validate-release-pr (pull_request) Has been cancelled
117e5294e1
Close the SSE connection when the page is hidden or unloaded to
prevent orphaned server-side connections. Reconnect automatically
when the page becomes visible again.

Closes OP#1652

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(prefetch): add TTL eviction to page prefetch cache
All checks were successful
CI / validate-release-pr (pull_request) Successful in 4s
CI / validate-branch (pull_request) Successful in 32s
CI / security (pull_request) Successful in 1m2s
CI / build (pull_request) Successful in 1m45s
CI / lint (pull_request) Successful in 2m34s
CI / test (pull_request) Successful in 1m56s
2ca6e106c9
Add a periodic sweep (every 30s matching TTL) that removes stale
entries from the in-memory prefetch cache to prevent unbounded
memory growth in long-lived browser sessions.

Closes OP#1653

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Contributor

PR #31 Review: Release v0.6.0 — Auth Expansion

41 commits | 116 files | +16.7k / -928 lines


Database Migrations: APPROVED

All 8 migrations (00023–00030) are clean:

  • Schema types, constraints, and indexes are correct
  • All have proper Down sections (00027/00028 fixed in 9f73b24)
  • FK strategy is sound (CASCADE for tokens, SET NULL for provider refs)
  • Data migrations are conditional and safe
  • Model-to-schema alignment is perfect

Security Findings

CRITICAL (3)

1. CSRF bypass via Sec-Fetch-Site fallbackinternal/middleware/csrf.go:41-43
When both Sec-Fetch-Site and Origin headers are absent, the request is allowed unconditionally. Older browsers or crafted requests can bypass CSRF protection entirely on all state-changing endpoints. Consider requiring at least one security header to be present, or falling back to a session-bound check.

2. Email enumeration via timinginternal/auth/local.go:91-141
Locked accounts return early (~85ms) vs non-existent accounts that hit bcrypt (~300-500ms). Attackers can differentiate valid from invalid emails by response time. Fix: always run bcrypt (even on a dummy hash) for non-existent accounts to normalize timing.

3. XSS via innerHTML in SSE/prefetchstatic/js/status-batch.js:63,73,141 and static/js/page-prefetch.js:186,330,505
Direct innerHTML assignment with server-provided HTML fragments. If backend data is ever tainted (e.g., stored XSS in status values), arbitrary script execution is possible. Consider using textContent for safe content, or a sanitizer like DOMPurify for HTML fragments.

WARNING (12)

# Issue Location
1 OIDC nonce not validated — token replay risk auth/oidc.go:183
2 Magic link tokens not single-use before confirmation page handlers/reset.go:301-320
3 Session secret has no minimum length/entropy check config/config.go:65-68
4 Rate limit cleanup goroutines lack graceful shutdown auth/local.go:52-66
5 Demo endpoints lack auth gating for read operations handlers/demo.go
6 SSE hub has no connection limits — resource exhaustion risk handlers/sse.go:11-53
7 Race condition in rate limit window reset auth/local.go:234-255
8 Open redirect: //evil.com/path passes validation auth/local.go:186-190
9 CSS injection if ItemID is not UUID-validated status.templ:194-196
10 SSE reconnect on rapid visibility toggle could create multiple connections status-batch.js:194-200
11 setInterval not cleared in tooltip hide status.templ:372
12 No fetch timeouts (AbortController) in JS Multiple files

Code Quality: GOOD

  • Error handling is consistent with proper %w wrapping
  • Resource management (DB pools, connections) is solid
  • Interface design and dependency injection are clean
  • No dead code detected
  • Minor: silent error ignore at cmd/gashy/main.go:93 and handlers/register.go:185,193

Test Coverage: SOLID with GAPS

Well covered: Login flows, registration, email confirmation, password reset, magic links, rate limiting, token lifecycle, OIDC provider CRUD, CSRF validation.

Missing coverage:

  • No concurrent access tests (rate limit maps, token consumption races)
  • No session fixation/invalidation verification tests
  • No OAuth state parameter corruption tests
  • No input fuzzing (special chars, reserved names, SQL/XSS payloads in usernames)
  • No transaction rollback tests at handler level

Recommendation: Add -race flag to CI and add goroutine-based concurrency tests for auth code.


Verdict

The PR is well-structured with good architecture, clean migrations, and solid test foundations. The 3 critical issues (CSRF fallback bypass, timing-based email enumeration, innerHTML XSS) should be addressed before merge. The warnings are worth tracking but are lower risk.

## PR #31 Review: Release v0.6.0 — Auth Expansion **41 commits** | **116 files** | **+16.7k / -928 lines** --- ### Database Migrations: ✅ APPROVED All 8 migrations (00023–00030) are clean: - Schema types, constraints, and indexes are correct - All have proper `Down` sections (00027/00028 fixed in 9f73b24) - FK strategy is sound (`CASCADE` for tokens, `SET NULL` for provider refs) - Data migrations are conditional and safe - Model-to-schema alignment is perfect --- ### Security Findings #### CRITICAL (3) **1. CSRF bypass via Sec-Fetch-Site fallback** — `internal/middleware/csrf.go:41-43` When both `Sec-Fetch-Site` and `Origin` headers are absent, the request is allowed unconditionally. Older browsers or crafted requests can bypass CSRF protection entirely on all state-changing endpoints. Consider requiring at least one security header to be present, or falling back to a session-bound check. **2. Email enumeration via timing** — `internal/auth/local.go:91-141` Locked accounts return early (~85ms) vs non-existent accounts that hit bcrypt (~300-500ms). Attackers can differentiate valid from invalid emails by response time. Fix: always run bcrypt (even on a dummy hash) for non-existent accounts to normalize timing. **3. XSS via innerHTML in SSE/prefetch** — `static/js/status-batch.js:63,73,141` and `static/js/page-prefetch.js:186,330,505` Direct `innerHTML` assignment with server-provided HTML fragments. If backend data is ever tainted (e.g., stored XSS in status values), arbitrary script execution is possible. Consider using `textContent` for safe content, or a sanitizer like DOMPurify for HTML fragments. #### WARNING (12) | # | Issue | Location | |---|-------|----------| | 1 | OIDC nonce not validated — token replay risk | `auth/oidc.go:183` | | 2 | Magic link tokens not single-use before confirmation page | `handlers/reset.go:301-320` | | 3 | Session secret has no minimum length/entropy check | `config/config.go:65-68` | | 4 | Rate limit cleanup goroutines lack graceful shutdown | `auth/local.go:52-66` | | 5 | Demo endpoints lack auth gating for read operations | `handlers/demo.go` | | 6 | SSE hub has no connection limits — resource exhaustion risk | `handlers/sse.go:11-53` | | 7 | Race condition in rate limit window reset | `auth/local.go:234-255` | | 8 | Open redirect: `//evil.com/path` passes validation | `auth/local.go:186-190` | | 9 | CSS injection if ItemID is not UUID-validated | `status.templ:194-196` | | 10 | SSE reconnect on rapid visibility toggle could create multiple connections | `status-batch.js:194-200` | | 11 | setInterval not cleared in tooltip hide | `status.templ:372` | | 12 | No fetch timeouts (AbortController) in JS | Multiple files | --- ### Code Quality: ✅ GOOD - Error handling is consistent with proper `%w` wrapping - Resource management (DB pools, connections) is solid - Interface design and dependency injection are clean - No dead code detected - Minor: silent error ignore at `cmd/gashy/main.go:93` and `handlers/register.go:185,193` --- ### Test Coverage: SOLID with GAPS **Well covered**: Login flows, registration, email confirmation, password reset, magic links, rate limiting, token lifecycle, OIDC provider CRUD, CSRF validation. **Missing coverage**: - No concurrent access tests (rate limit maps, token consumption races) - No session fixation/invalidation verification tests - No OAuth state parameter corruption tests - No input fuzzing (special chars, reserved names, SQL/XSS payloads in usernames) - No transaction rollback tests at handler level **Recommendation**: Add `-race` flag to CI and add goroutine-based concurrency tests for auth code. --- ### Verdict The PR is well-structured with good architecture, clean migrations, and solid test foundations. The **3 critical issues** (CSRF fallback bypass, timing-based email enumeration, innerHTML XSS) should be addressed before merge. The warnings are worth tracking but are lower risk.
Modern browsers always send at least one of these headers on state-changing
requests. Defaulting to allow when both are missing created a CSRF bypass
for non-browser clients or crafted requests.

Closes OP#1656

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Run bcrypt against a dummy hash for non-existent accounts to ensure
consistent response times regardless of whether an email exists.
Also harden open redirect validation to reject protocol-relative URLs.

Closes OP#1657
Closes OP#1665

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add safeParseHTML helper that strips <script> elements and on* event
handlers as defense-in-depth against stored XSS. Add AbortController
timeouts to fetch calls. Debounce SSE reconnect on visibility toggle.

Closes OP#1658
Closes OP#1667
Closes OP#1668

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generate and store a nonce cookie before OIDC redirect, then verify the
nonce claim in the ID token on callback. Uses constant-time comparison
to prevent timing attacks on the nonce value.

Closes OP#1659

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reject startup with a clear error when SESSION_SECRET is shorter than
32 characters, preventing trivially forgeable session tokens.

Closes OP#1661

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move demo read endpoints (overview, speedtest, API reads) into the same
admin-gated mux as write endpoints. Anonymous users can no longer access
internal test data or performance metrics.

Closes OP#1663

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enforce a configurable maximum connection count (default 1000) to prevent
resource exhaustion via unbounded SSE connections. Returns 429 when the
limit is reached.

Closes OP#1664

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(status): validate item ID as UUID before CSS interpolation
Some checks failed
CI / validate-branch (pull_request) Successful in 1s
CI / build (pull_request) Successful in 58s
CI / lint (pull_request) Failing after 1m6s
CI / test (pull_request) Has been skipped
CI / security (pull_request) Successful in 1m33s
CI / validate-release-pr (pull_request) Successful in 6s
41f09ae151
Add safeItemID validator to prevent CSS injection via crafted item IDs
in border style generation. Non-UUID IDs are rejected and no style is
emitted.

Closes OP#1666

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Contributor

PR Review Followup

Responding to review posted on 2026-02-23T01:14:16Z (comment #712).

Triage Summary

Severity Count Action
Critical (Blocking) 3 Fixed in this branch
Warning (Important) 12 9 fixed, 2 already addressed, 1 fixed (grouped)
Low (Minor) 15 Deferred to v0.7.0 (6 grouped WPs)
Human Review 0 N/A

Fixes Applied

The following findings have been resolved on this branch:

  1. [CRITICAL] CSRF bypass when Sec-Fetch-Site and Origin absent — fixed in c2d7717
    • OP#1656: fix: CSRF bypass when Sec-Fetch-Site and Origin headers absent (csrf.go)
  2. [CRITICAL] Email enumeration via timing side-channel — fixed in dc7c7db
    • OP#1657: fix: email enumeration via timing side-channel (local.go)
  3. [CRITICAL] XSS via innerHTML in SSE and prefetch JS — fixed in e7be6ac
    • OP#1658: fix: XSS via innerHTML in SSE and prefetch JS (status-batch.js, page-prefetch.js)
  4. [WARNING] OIDC nonce not validated (token replay risk) — fixed in 3e78df0
    • OP#1659: fix: validate OIDC nonce on token exchange to prevent replay (oidc.go)
  5. [WARNING] Session secret no minimum length — fixed in 0a7b1a5
    • OP#1661: fix: add minimum length and entropy check for session secret (config.go)
  6. [WARNING] Demo endpoints lack auth gating for reads — fixed in 16c1cb1
    • OP#1663: fix: gate demo read endpoints behind authentication (demo.go)
  7. [WARNING] SSE hub has no connection limits — fixed in 1df70b7
    • OP#1664: fix: add connection limits to SSE hub (sse.go)
  8. [WARNING] Open redirect bypass via //evil.com — fixed in dc7c7db
    • OP#1665: fix: open redirect bypass via protocol-relative URL (local.go)
  9. [WARNING] CSS injection if ItemID not UUID-validated — fixed in 41f09ae
    • OP#1666: fix: CSS injection and interval leak in status template (status.templ)
  10. [WARNING] SSE reconnect on rapid visibility toggle — fixed in e7be6ac
    • OP#1667: fix: SSE reconnect debounce on rapid visibility toggle (status-batch.js)
  11. [WARNING] No fetch timeouts (AbortController) — fixed in e7be6ac
    • OP#1668: fix: add fetch timeouts via AbortController in JS modules

Already Addressed (Closed Without Changes)

  1. [WARNING] Magic link tokens not single-use before confirmation — already implemented in OP#1640
    • OP#1660: GET validates without consuming, POST consumes and creates session
  2. [WARNING] Rate limit graceful shutdown + race condition — already implemented in OP#1646
    • OP#1662: StartCleanup uses ctx.Done(), recordFailure protected by sync.Mutex

Deferred to v0.7.0

The following low-severity items have been tracked for the next version:

  1. [LOW] Auth input validation hardening (password length, email format, enumeration)
  2. [LOW] SSE resource management (channel close, client limits, backoff)
  3. [LOW] Frontend lifecycle issues (stale polling, animating flag, resize leak)
  4. [LOW] Accessibility improvements (ARIA labels, focus traps, dialog roles)
  5. [LOW] Security hardening (Chart.js SRI, impersonation middleware flag)
  6. [LOW] Remove redundant migration 00028

Test Results

  • Tests: All passing (go test ./... — 0 failures)
  • go vet: Clean
  • All fixes verified: Yes

This followup was generated by the pr-followup skill.

## PR Review Followup Responding to review posted on 2026-02-23T01:14:16Z ([comment #712](https://git.bros.ninja/mike/gashy/pulls/31#issuecomment-712)). ### Triage Summary | Severity | Count | Action | | -------- | ----- | ------ | | Critical (Blocking) | 3 | Fixed in this branch | | Warning (Important) | 12 | 9 fixed, 2 already addressed, 1 fixed (grouped) | | Low (Minor) | 15 | Deferred to v0.7.0 (6 grouped WPs) | | Human Review | 0 | N/A | ### Fixes Applied The following findings have been resolved on this branch: 1. **[CRITICAL]** CSRF bypass when Sec-Fetch-Site and Origin absent — fixed in c2d7717 - OP#1656: fix: CSRF bypass when Sec-Fetch-Site and Origin headers absent (csrf.go) 2. **[CRITICAL]** Email enumeration via timing side-channel — fixed in dc7c7db - OP#1657: fix: email enumeration via timing side-channel (local.go) 3. **[CRITICAL]** XSS via innerHTML in SSE and prefetch JS — fixed in e7be6ac - OP#1658: fix: XSS via innerHTML in SSE and prefetch JS (status-batch.js, page-prefetch.js) 4. **[WARNING]** OIDC nonce not validated (token replay risk) — fixed in 3e78df0 - OP#1659: fix: validate OIDC nonce on token exchange to prevent replay (oidc.go) 5. **[WARNING]** Session secret no minimum length — fixed in 0a7b1a5 - OP#1661: fix: add minimum length and entropy check for session secret (config.go) 6. **[WARNING]** Demo endpoints lack auth gating for reads — fixed in 16c1cb1 - OP#1663: fix: gate demo read endpoints behind authentication (demo.go) 7. **[WARNING]** SSE hub has no connection limits — fixed in 1df70b7 - OP#1664: fix: add connection limits to SSE hub (sse.go) 8. **[WARNING]** Open redirect bypass via `//evil.com` — fixed in dc7c7db - OP#1665: fix: open redirect bypass via protocol-relative URL (local.go) 9. **[WARNING]** CSS injection if ItemID not UUID-validated — fixed in 41f09ae - OP#1666: fix: CSS injection and interval leak in status template (status.templ) 10. **[WARNING]** SSE reconnect on rapid visibility toggle — fixed in e7be6ac - OP#1667: fix: SSE reconnect debounce on rapid visibility toggle (status-batch.js) 11. **[WARNING]** No fetch timeouts (AbortController) — fixed in e7be6ac - OP#1668: fix: add fetch timeouts via AbortController in JS modules ### Already Addressed (Closed Without Changes) 12. **[WARNING]** Magic link tokens not single-use before confirmation — already implemented in OP#1640 - OP#1660: GET validates without consuming, POST consumes and creates session ✅ 13. **[WARNING]** Rate limit graceful shutdown + race condition — already implemented in OP#1646 - OP#1662: StartCleanup uses ctx.Done(), recordFailure protected by sync.Mutex ✅ ### Deferred to v0.7.0 The following low-severity items have been tracked for the next version: 1. **[LOW]** Auth input validation hardening (password length, email format, enumeration) - OP#1669 2. **[LOW]** SSE resource management (channel close, client limits, backoff) - OP#1670 3. **[LOW]** Frontend lifecycle issues (stale polling, animating flag, resize leak) - OP#1671 4. **[LOW]** Accessibility improvements (ARIA labels, focus traps, dialog roles) - OP#1672 5. **[LOW]** Security hardening (Chart.js SRI, impersonation middleware flag) - OP#1673 6. **[LOW]** Remove redundant migration 00028 - OP#1674 ### Test Results - **Tests:** All passing (`go test ./...` — 0 failures) - **go vet:** Clean - **All fixes verified:** Yes --- _This followup was generated by the pr-followup skill._
style(sse): fix gofumpt struct field alignment in Hub
All checks were successful
CI / validate-branch (pull_request) Successful in 1s
CI / validate-release-pr (pull_request) Successful in 4s
CI / security (pull_request) Successful in 39s
CI / build (pull_request) Successful in 1m41s
CI / lint (pull_request) Successful in 2m27s
CI / test (pull_request) Successful in 4m37s
f8a340cf32
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Contributor

PR Review — Updated Review (post-fix verification)

Verdict: Acceptable

Previous reviews posted on 2026-02-23T00:34:29Z and 2026-02-23T01:14:16Z. This review verifies the 11 applied fixes and performs a fresh deep analysis of the current branch state (50 commits, 117 files, ~16.8k insertions).

Fix Verification

# Finding Verdict
1 CSRF bypass (absent headers) Correct — deny-by-default when both headers absent
2 Email enumeration timing Correct — dummyHash normalizes to ~228ms (verified)
3 XSS via innerHTML in JS Correct — DOMParser + sanitization in both files
4 OIDC nonce validation ⚠️ Partial — see finding #1 below
5 Session secret min length Correct — 32-char minimum enforced
6 Demo endpoints auth gating Correct — all routes wrapped in RequireAdmin
7 SSE hub connection limits Correct — default 1000, nil check returns 429
8 Open redirect bypass Correct — comprehensive validation including // prefix
9 CSS injection via ItemID Correct — UUID regex validation
10 SSE reconnect debounce Correct — timers for connect/disconnect
11 Fetch timeouts (AbortController) Correct — 10s and 15s timeouts

10 of 11 fixes fully correct and complete.

Findings

Severity Count
Blocking 0
Important 13
Minor 14
Nitpick 6

Test Results

  • Tests: All passed (0 failures, race detection enabled)
  • go vet: Clean
  • Docker: 2 services running (gashy, gashy-db healthy)
  • Health endpoint: Returns 302 (behind auth — no dedicated unauthenticated health route)

Human Review Items

3 items flagged for human review — see HUMAN REVIEW markers below.


Important Findings (13)

Security (5)

1. [IMPORTANT] OIDC nonce validation conditional bypass — internal/auth/oidc.go:220

The nonce check is skipped when the ID token lacks a nonce claim:

if err := idToken.Claims(&nonceClaims); err == nil && nonceClaims.Nonce != "" {

If an attacker replays a valid signed ID token that was issued without a nonce (e.g., from a different flow or a provider that doesn't include nonce by default), the check is bypassed entirely. The fix should require the nonce when one was requested:

if err := idToken.Claims(&nonceClaims); err != nil || nonceClaims.Nonce == "" {
    slog.Warn("OIDC token missing nonce claim")
    http.Error(w, "Invalid nonce", http.StatusBadRequest)
    return
}
if subtle.ConstantTimeCompare([]byte(nonceClaims.Nonce), []byte(nonceCookie.Value)) != 1 {
    // existing mismatch handling
}

2. [IMPORTANT] Stored XSS via admin CSS injection — components/layouts/base.templ:73,76

cssVarsStyle() and customCSSStyle() emit admin-provided CSS keys/values through templ.Raw() with no sanitization. A CSS variable value like red; }</style><script>alert(1)</script><style>:root{--x: breaks out of the <style> tag and injects arbitrary HTML/JS. This executes for ALL users viewing any page.

HUMAN REVIEW: This requires admin access to exploit. In a single-admin homelab context, the admin already controls everything and this is low risk. In a multi-admin or multi-tenant deployment, this becomes a privilege escalation vector (one admin can inject persistent JS affecting all users). The decision to sanitize depends on the intended deployment model. If addressed: validate CSS var keys match ^--[a-zA-Z0-9_-]+$, escape </style> sequences in values, and consider a CSS sanitizer for custom CSS.

3. [IMPORTANT] dummyHash should be a valid bcrypt hash — internal/auth/local.go:30

The current hash ($2a$12$xxxx...) is 59 characters (valid is 60). Testing confirms Go's current bcrypt implementation still performs the full ~228ms computation, so timing protection works today. However, a future library update could short-circuit on malformed hashes and silently break the protection. Replace with a real pre-computed hash for robustness:

// Hash of a random 64-char string, pre-computed at development time
dummyHash = "$2a$12$DjXEbMnRic0lVxSqQw5ji.StyydSXNrRCEkz/zj3vdHwP7U/efxg2"

4. [IMPORTANT] generateSessionID silently drops rand.Read error — internal/auth/session.go:238

_, _ = rand.Read(b) — if the system CSPRNG fails, the session ID defaults to all zeros. A broken entropy source is a critical security failure that should panic or return an error, not silently produce predictable session IDs.

5. [IMPORTANT] HandleLogin success path and redirect validation untested — internal/auth/local_test.go

The full HandleLogin POST flow (session creation, remember_me TTL, HTMX HX-Redirect, standard redirect) has no test. More critically, the ?next= open redirect prevention logic (line 200) — which rejects //evil.com, absolute URLs, and non-/-prefixed paths — is security-critical with zero coverage. Add tests for: valid relative path, protocol-relative URL rejection, absolute URL rejection, empty next default.

Code Quality (5)

6. [IMPORTANT] Pervasive handler-level SQL (SoC violations)

Handlers across register.go, confirm.go, reset.go, admin_providers.go, setup.go, and dashboard.go execute raw SQL directly (h.pool.QueryRow, h.pool.Exec). The DashboardService pattern is well-established but wasn't extended to user CRUD, OIDC provider management, invite/token operations, or status cache lookups. This makes handlers untestable without a database and mixes HTTP concerns with persistence.

HUMAN REVIEW: This is an architectural pattern that affects many files. Refactoring to full service-layer extraction is substantial work. Consider whether to address now or track as a v0.7.0 improvement. The current code works correctly — this is about maintainability and testability.

7. [IMPORTANT] Multiple swallowed DB errors — register.go:185,193, admin_providers.go:267-289

Several _ = h.pool.QueryRow(...) calls silently discard errors. In register.go, if the uniqueness check query fails, exists defaults to false, allowing the INSERT to proceed (which will fail with a confusing cascade). In admin_providers.go, silent failures on safety-check queries (enabled count, user count, default count) can bypass deletion guards. At minimum, log errors and return 500.

8. [IMPORTANT] Functions exceeding recommended length

Function File Lines
main() cmd/gashy/main.go 626
Index handlers/dashboard.go 208
Register handlers/register.go 203
LoadTest handlers/demo.go 157
ScaleTestPage handlers/demo.go 135
SaveSetup handlers/setup.go 118

Extract sub-operations into named helpers. main.go in particular would benefit from registerRoutes() and createHandlers() extraction.

9. [IMPORTANT] Separate settings cache in mode middleware — internal/middleware/mode.go:29-31

EnforceMode creates a new DashboardService with its own settings cache, separate from the instance in main.go. After a settings change, the middleware and handlers may see different cached values for up to 30 seconds. Pass the existing DashboardService instance instead of creating a new one.

10. [IMPORTANT] SaveSetup lacks transactional consistency — internal/handlers/setup.go:132-208

The OIDC setup path executes INSERT INTO oidc_providers and UPDATE app_settings as separate queries. If the provider insert succeeds but the settings update fails, the system is left in an inconsistent state (provider exists but setup isn't marked complete).

Frontend (3)

11. [IMPORTANT] DOM XSS vector in status tooltip — components/partials/status.templ:310,326

Inline script uses innerHTML with label that includes data.err from server error messages. While data.err is truncated at 40 chars, if status check errors can be influenced by a malicious upstream service, this is a DOM XSS path. Use textContent for the label portion.

12. [IMPORTANT] Resize listener leak — static/js/page-tabs.js:16-18

init() adds a window resize listener that's never removed. After repeated htmx page swaps, old listeners accumulate. Store the handler reference and remove it in Alpine's destroy() lifecycle hook.

13. [IMPORTANT] Event listener accumulation — static/js/confirm-modal.js:12

The confirm-action custom event listener added in init() is never cleaned up. If the component is destroyed and re-created during navigation, old listeners accumulate.


Minor Findings (14)

# File Issue
1 register.go:288-352 baseData helper duplicated verbatim across 4 handlers (register, confirm, reset, setup). Extract to shared function.
2 dashboard.go:69-76 User groups resolution duplicated 4x in same file. Extract to helper.
3 invite.go:68-77 Invite status logic has overlapping conditions (used vs revoked vs expired). Refactor to clear if-else chain.
4 admin_providers.go:334 OIDC discovery error exposed to user: "Discovery failed: %v" may leak internal hostnames/DNS errors. Use generic message.
5 services/dashboard.go:62-114 LoadDashboard and LoadDashboardPreview are near-identical. Extract shared loadDashboardByID.
6 oidc.go:58-61 NewOIDCProvider returns (nil, nil) for missing config — ambiguous API. Return sentinel error.
7 status-batch.js:92,167 target.innerHTML = el.innerHTML re-parses sanitized content. Use node cloning instead for defense-in-depth.
8 demo-tests.js:330,505,761 Raw innerHTML without sanitization (admin-only pages — lower risk but inconsistent with other files).
9 demo-tests.js:1026-1029 getItemIds()/getSectionIds() call r.json() without checking r.ok.
10 toast.js:14,19 Two separate "toast" event listeners could produce duplicate toasts. JSON.parse lacks try/catch.
11 confirm.templ:42 Email input for resend-confirmation has no <label> element.
12 demo/layout.templ:60-72 Chart fullscreen modal lacks role="dialog", aria-modal="true".
13 section.templ:49,133 Section ID interpolated into JS event handler string. Use data attributes instead.
14 demo/layout.templ:120 ChartWrap concatenates title into JS dispatch — would break on titles containing single quotes.

Nitpick Findings (6)

# File Issue
1 local.go:193 Dead code: _ = metadata with TODO comment. Wire into logging or remove.
2 invite.go:98 Dead variable: _ = user exists only to suppress unused-variable error.
3 demo.go:352-356 sectionNames array duplicated in two methods. Extract to package constant.
4 mode.go:103-113 isPublicPath reimplements strings.HasPrefix. Use it directly for clarity.
5 services/dashboard.go:374 Variable name s2 for settings is confusing. Rename receiver to svc.
6 demo-tests.js (whole file) 1043 lines with deeply nested promise chains (8+ indent levels).

Test Coverage Gaps (Notable)

Well covered: Login flows, registration, email confirmation, password reset, magic links, rate limiting, token lifecycle, OIDC provider CRUD, CSRF validation. Integration tests cover complete user journeys.

Key gaps:

  • HandleLogin POST success path (session creation, remember-me, redirect)
  • ?next= open redirect prevention (security-critical, zero coverage)
  • DashboardHandler.Index authenticated render path
  • Several DashboardHandler methods (Search, Status, StatusBatch, PageContent, SectionItems)
  • InvalidateTokens and ListTokensByType in AuthTokenService
  • Non-admin authorization negative tests for invite and provider CRUD
  • Concurrent access tests for rate limit maps

Migration Review

All 8 migrations (00023-00030) are structurally correct:

  • Types, constraints, and indexes are appropriate
  • All have proper Down sections (previously missing ones fixed in 9f73b24)
  • FK strategy is sound (CASCADE for tokens, SET NULL for provider refs)
  • Seed script (scripts/seed-test-dashboard.sql) is compatible with current schema

HUMAN REVIEW: Migration 00030 drops transition_in/transition_out from pages without migrating existing values to dashboards.transition. Rollback restores columns with defaults only. This is acceptable for schema evolution but worth acknowledging that it's a one-way data migration.


Summary

The 11 security fixes from previous reviews are verified correct (10 fully, 1 partial). The codebase has solid cryptographic fundamentals, clean integration tests, and a well-structured authentication system. The remaining important findings are primarily:

  1. OIDC nonce bypass — straightforward fix, narrow attack surface
  2. Admin CSS injection — architectural decision about trust model
  3. Handler-level SQL — systematic SoC pattern to address over time
  4. Swallowed errors — targeted fixes needed in register.go and admin_providers.go
  5. Frontend lifecycle — listener leaks in page-tabs.js and confirm-modal.js

No blocking findings. The PR is release-worthy with the advisory items tracked for follow-up.

## PR Review — Updated Review (post-fix verification) **Verdict: Acceptable** Previous reviews posted on 2026-02-23T00:34:29Z and 2026-02-23T01:14:16Z. This review verifies the 11 applied fixes and performs a fresh deep analysis of the current branch state (50 commits, 117 files, ~16.8k insertions). ### Fix Verification | # | Finding | Verdict | |---|---------|---------| | 1 | CSRF bypass (absent headers) | ✅ Correct — deny-by-default when both headers absent | | 2 | Email enumeration timing | ✅ Correct — dummyHash normalizes to ~228ms (verified) | | 3 | XSS via innerHTML in JS | ✅ Correct — DOMParser + sanitization in both files | | 4 | OIDC nonce validation | ⚠️ Partial — see finding #1 below | | 5 | Session secret min length | ✅ Correct — 32-char minimum enforced | | 6 | Demo endpoints auth gating | ✅ Correct — all routes wrapped in RequireAdmin | | 7 | SSE hub connection limits | ✅ Correct — default 1000, nil check returns 429 | | 8 | Open redirect bypass | ✅ Correct — comprehensive validation including `//` prefix | | 9 | CSS injection via ItemID | ✅ Correct — UUID regex validation | | 10 | SSE reconnect debounce | ✅ Correct — timers for connect/disconnect | | 11 | Fetch timeouts (AbortController) | ✅ Correct — 10s and 15s timeouts | **10 of 11 fixes fully correct and complete.** ### Findings | Severity | Count | | -------- | ----- | | Blocking | 0 | | Important | 13 | | Minor | 14 | | Nitpick | 6 | ### Test Results - **Tests:** All passed (0 failures, race detection enabled) - **go vet:** Clean - **Docker:** 2 services running (gashy, gashy-db healthy) - **Health endpoint:** Returns 302 (behind auth — no dedicated unauthenticated health route) ### Human Review Items 3 items flagged for human review — see HUMAN REVIEW markers below. --- ### Important Findings (13) #### Security (5) **1. [IMPORTANT] OIDC nonce validation conditional bypass — `internal/auth/oidc.go:220`** The nonce check is skipped when the ID token lacks a nonce claim: ```go if err := idToken.Claims(&nonceClaims); err == nil && nonceClaims.Nonce != "" { ``` If an attacker replays a valid signed ID token that was issued without a nonce (e.g., from a different flow or a provider that doesn't include nonce by default), the check is bypassed entirely. The fix should require the nonce when one was requested: ```go if err := idToken.Claims(&nonceClaims); err != nil || nonceClaims.Nonce == "" { slog.Warn("OIDC token missing nonce claim") http.Error(w, "Invalid nonce", http.StatusBadRequest) return } if subtle.ConstantTimeCompare([]byte(nonceClaims.Nonce), []byte(nonceCookie.Value)) != 1 { // existing mismatch handling } ``` **2. [IMPORTANT] Stored XSS via admin CSS injection — `components/layouts/base.templ:73,76`** `cssVarsStyle()` and `customCSSStyle()` emit admin-provided CSS keys/values through `templ.Raw()` with no sanitization. A CSS variable value like `red; }</style><script>alert(1)</script><style>:root{--x:` breaks out of the `<style>` tag and injects arbitrary HTML/JS. This executes for ALL users viewing any page. **HUMAN REVIEW:** This requires admin access to exploit. In a single-admin homelab context, the admin already controls everything and this is low risk. In a multi-admin or multi-tenant deployment, this becomes a privilege escalation vector (one admin can inject persistent JS affecting all users). The decision to sanitize depends on the intended deployment model. If addressed: validate CSS var keys match `^--[a-zA-Z0-9_-]+$`, escape `</style>` sequences in values, and consider a CSS sanitizer for custom CSS. **3. [IMPORTANT] dummyHash should be a valid bcrypt hash — `internal/auth/local.go:30`** The current hash (`$2a$12$xxxx...`) is 59 characters (valid is 60). Testing confirms Go's current bcrypt implementation still performs the full ~228ms computation, so timing protection works today. However, a future library update could short-circuit on malformed hashes and silently break the protection. Replace with a real pre-computed hash for robustness: ```go // Hash of a random 64-char string, pre-computed at development time dummyHash = "$2a$12$DjXEbMnRic0lVxSqQw5ji.StyydSXNrRCEkz/zj3vdHwP7U/efxg2" ``` **4. [IMPORTANT] generateSessionID silently drops rand.Read error — `internal/auth/session.go:238`** `_, _ = rand.Read(b)` — if the system CSPRNG fails, the session ID defaults to all zeros. A broken entropy source is a critical security failure that should panic or return an error, not silently produce predictable session IDs. **5. [IMPORTANT] HandleLogin success path and redirect validation untested — `internal/auth/local_test.go`** The full `HandleLogin` POST flow (session creation, `remember_me` TTL, HTMX `HX-Redirect`, standard redirect) has no test. More critically, the `?next=` open redirect prevention logic (line 200) — which rejects `//evil.com`, absolute URLs, and non-`/`-prefixed paths — is security-critical with zero coverage. Add tests for: valid relative path, protocol-relative URL rejection, absolute URL rejection, empty `next` default. #### Code Quality (5) **6. [IMPORTANT] Pervasive handler-level SQL (SoC violations)** Handlers across register.go, confirm.go, reset.go, admin_providers.go, setup.go, and dashboard.go execute raw SQL directly (`h.pool.QueryRow`, `h.pool.Exec`). The `DashboardService` pattern is well-established but wasn't extended to user CRUD, OIDC provider management, invite/token operations, or status cache lookups. This makes handlers untestable without a database and mixes HTTP concerns with persistence. **HUMAN REVIEW:** This is an architectural pattern that affects many files. Refactoring to full service-layer extraction is substantial work. Consider whether to address now or track as a v0.7.0 improvement. The current code works correctly — this is about maintainability and testability. **7. [IMPORTANT] Multiple swallowed DB errors — `register.go:185,193`, `admin_providers.go:267-289`** Several `_ = h.pool.QueryRow(...)` calls silently discard errors. In `register.go`, if the uniqueness check query fails, `exists` defaults to `false`, allowing the INSERT to proceed (which will fail with a confusing cascade). In `admin_providers.go`, silent failures on safety-check queries (enabled count, user count, default count) can bypass deletion guards. At minimum, log errors and return 500. **8. [IMPORTANT] Functions exceeding recommended length** | Function | File | Lines | |----------|------|-------| | `main()` | cmd/gashy/main.go | 626 | | `Index` | handlers/dashboard.go | 208 | | `Register` | handlers/register.go | 203 | | `LoadTest` | handlers/demo.go | 157 | | `ScaleTestPage` | handlers/demo.go | 135 | | `SaveSetup` | handlers/setup.go | 118 | Extract sub-operations into named helpers. `main.go` in particular would benefit from `registerRoutes()` and `createHandlers()` extraction. **9. [IMPORTANT] Separate settings cache in mode middleware — `internal/middleware/mode.go:29-31`** `EnforceMode` creates a new `DashboardService` with its own settings cache, separate from the instance in `main.go`. After a settings change, the middleware and handlers may see different cached values for up to 30 seconds. Pass the existing `DashboardService` instance instead of creating a new one. **10. [IMPORTANT] SaveSetup lacks transactional consistency — `internal/handlers/setup.go:132-208`** The OIDC setup path executes `INSERT INTO oidc_providers` and `UPDATE app_settings` as separate queries. If the provider insert succeeds but the settings update fails, the system is left in an inconsistent state (provider exists but setup isn't marked complete). #### Frontend (3) **11. [IMPORTANT] DOM XSS vector in status tooltip — `components/partials/status.templ:310,326`** Inline script uses `innerHTML` with `label` that includes `data.err` from server error messages. While `data.err` is truncated at 40 chars, if status check errors can be influenced by a malicious upstream service, this is a DOM XSS path. Use `textContent` for the label portion. **12. [IMPORTANT] Resize listener leak — `static/js/page-tabs.js:16-18`** `init()` adds a window resize listener that's never removed. After repeated htmx page swaps, old listeners accumulate. Store the handler reference and remove it in Alpine's `destroy()` lifecycle hook. **13. [IMPORTANT] Event listener accumulation — `static/js/confirm-modal.js:12`** The `confirm-action` custom event listener added in `init()` is never cleaned up. If the component is destroyed and re-created during navigation, old listeners accumulate. --- ### Minor Findings (14) | # | File | Issue | |---|------|-------| | 1 | `register.go:288-352` | `baseData` helper duplicated verbatim across 4 handlers (register, confirm, reset, setup). Extract to shared function. | | 2 | `dashboard.go:69-76` | User groups resolution duplicated 4x in same file. Extract to helper. | | 3 | `invite.go:68-77` | Invite status logic has overlapping conditions (used vs revoked vs expired). Refactor to clear if-else chain. | | 4 | `admin_providers.go:334` | OIDC discovery error exposed to user: `"Discovery failed: %v"` may leak internal hostnames/DNS errors. Use generic message. | | 5 | `services/dashboard.go:62-114` | `LoadDashboard` and `LoadDashboardPreview` are near-identical. Extract shared `loadDashboardByID`. | | 6 | `oidc.go:58-61` | `NewOIDCProvider` returns `(nil, nil)` for missing config — ambiguous API. Return sentinel error. | | 7 | `status-batch.js:92,167` | `target.innerHTML = el.innerHTML` re-parses sanitized content. Use node cloning instead for defense-in-depth. | | 8 | `demo-tests.js:330,505,761` | Raw `innerHTML` without sanitization (admin-only pages — lower risk but inconsistent with other files). | | 9 | `demo-tests.js:1026-1029` | `getItemIds()`/`getSectionIds()` call `r.json()` without checking `r.ok`. | | 10 | `toast.js:14,19` | Two separate "toast" event listeners could produce duplicate toasts. JSON.parse lacks try/catch. | | 11 | `confirm.templ:42` | Email input for resend-confirmation has no `<label>` element. | | 12 | `demo/layout.templ:60-72` | Chart fullscreen modal lacks `role="dialog"`, `aria-modal="true"`. | | 13 | `section.templ:49,133` | Section ID interpolated into JS event handler string. Use data attributes instead. | | 14 | `demo/layout.templ:120` | `ChartWrap` concatenates `title` into JS dispatch — would break on titles containing single quotes. | ### Nitpick Findings (6) | # | File | Issue | |---|------|-------| | 1 | `local.go:193` | Dead code: `_ = metadata` with TODO comment. Wire into logging or remove. | | 2 | `invite.go:98` | Dead variable: `_ = user` exists only to suppress unused-variable error. | | 3 | `demo.go:352-356` | `sectionNames` array duplicated in two methods. Extract to package constant. | | 4 | `mode.go:103-113` | `isPublicPath` reimplements `strings.HasPrefix`. Use it directly for clarity. | | 5 | `services/dashboard.go:374` | Variable name `s2` for settings is confusing. Rename receiver to `svc`. | | 6 | `demo-tests.js` (whole file) | 1043 lines with deeply nested promise chains (8+ indent levels). | ### Test Coverage Gaps (Notable) **Well covered:** Login flows, registration, email confirmation, password reset, magic links, rate limiting, token lifecycle, OIDC provider CRUD, CSRF validation. Integration tests cover complete user journeys. **Key gaps:** - `HandleLogin` POST success path (session creation, remember-me, redirect) - `?next=` open redirect prevention (security-critical, zero coverage) - `DashboardHandler.Index` authenticated render path - Several `DashboardHandler` methods (`Search`, `Status`, `StatusBatch`, `PageContent`, `SectionItems`) - `InvalidateTokens` and `ListTokensByType` in AuthTokenService - Non-admin authorization negative tests for invite and provider CRUD - Concurrent access tests for rate limit maps ### Migration Review All 8 migrations (00023-00030) are structurally correct: - Types, constraints, and indexes are appropriate - All have proper Down sections (previously missing ones fixed in 9f73b24) - FK strategy is sound (CASCADE for tokens, SET NULL for provider refs) - Seed script (`scripts/seed-test-dashboard.sql`) is compatible with current schema **HUMAN REVIEW:** Migration 00030 drops `transition_in`/`transition_out` from `pages` without migrating existing values to `dashboards.transition`. Rollback restores columns with defaults only. This is acceptable for schema evolution but worth acknowledging that it's a one-way data migration. --- ### Summary The 11 security fixes from previous reviews are verified correct (10 fully, 1 partial). The codebase has solid cryptographic fundamentals, clean integration tests, and a well-structured authentication system. The remaining important findings are primarily: 1. **OIDC nonce bypass** — straightforward fix, narrow attack surface 2. **Admin CSS injection** — architectural decision about trust model 3. **Handler-level SQL** — systematic SoC pattern to address over time 4. **Swallowed errors** — targeted fixes needed in register.go and admin_providers.go 5. **Frontend lifecycle** — listener leaks in page-tabs.js and confirm-modal.js No blocking findings. The PR is release-worthy with the advisory items tracked for follow-up.
Owner

@ai wrote in #31 (comment):

2. [IMPORTANT] Stored XSS via admin CSS injection — components/layouts/base.templ:73,76

cssVarsStyle() and customCSSStyle() emit admin-provided CSS keys/values through templ.Raw() with no sanitization. A CSS variable value like red; }</style><script>alert(1)</script><style>:root{--x: breaks out of the <style> tag and injects arbitrary HTML/JS. This executes for ALL users viewing any page.

HUMAN REVIEW: This requires admin access to exploit. In a single-admin homelab context, the admin already controls everything and this is low risk. In a multi-admin or multi-tenant deployment, this becomes a privilege escalation vector (one admin can inject persistent JS affecting all users). The decision to sanitize depends on the intended deployment model. If addressed: validate CSS var keys match ^--[a-zA-Z0-9_-]+$, escape </style> sequences in values, and consider a CSS sanitizer for custom CSS.

I don't like leaving this vector open, we should work around this with at least sanitation.@ai wrote in #31 (comment):

6. [IMPORTANT] Pervasive handler-level SQL (SoC violations)

Handlers across register.go, confirm.go, reset.go, admin_providers.go, setup.go, and dashboard.go execute raw SQL directly (h.pool.QueryRow, h.pool.Exec). The DashboardService pattern is well-established but wasn't extended to user CRUD, OIDC provider management, invite/token operations, or status cache lookups. This makes handlers untestable without a database and mixes HTTP concerns with persistence.

HUMAN REVIEW: This is an architectural pattern that affects many files. Refactoring to full service-layer extraction is substantial work. Consider whether to address now or track as a v0.7.0 improvement. The current code works correctly — this is about maintainability and test-ability.

Slate this for 0.6.1 work. I do want to make sure this is clean and maintainable and testable without mixing concerns. You can also slot in other low priority work packages from this review into this followup minor version. @ai wrote in #31 (comment):

Migration Review

All 8 migrations (00023-00030) are structurally correct:

* Types, constraints, and indexes are appropriate

* All have proper Down sections (previously missing ones fixed in [`9f73b24`](/mike/gashy/commit/9f73b24))

* FK strategy is sound (CASCADE for tokens, SET NULL for provider refs)

* Seed script (`scripts/seed-test-dashboard.sql`) is compatible with current schema

HUMAN REVIEW: Migration 00030 drops transition_in/transition_out from pages without migrating existing values to dashboards.transition. Rollback restores columns with defaults only. This is acceptable for schema evolution but worth acknowledging that it's a one-way data migration.

We should create a 0.6.1 work package to track the migration fixes. Migrations should be single-purpose in nature when possible. shouldn't be mixing tables and we should make sure that all migrations are reversible. We are currently pre-1.0 so that means we have the opportunity now to fix this because there are no live deployments.

@ai wrote in https://git.bros.ninja/mike/gashy/pulls/31#issuecomment-720: > **2. [IMPORTANT] Stored XSS via admin CSS injection — `components/layouts/base.templ:73,76`** > > `cssVarsStyle()` and `customCSSStyle()` emit admin-provided CSS keys/values through `templ.Raw()` with no sanitization. A CSS variable value like `red; }</style><script>alert(1)</script><style>:root{--x:` breaks out of the `<style>` tag and injects arbitrary HTML/JS. This executes for ALL users viewing any page. > > **HUMAN REVIEW:** This requires admin access to exploit. In a single-admin homelab context, the admin already controls everything and this is low risk. In a multi-admin or multi-tenant deployment, this becomes a privilege escalation vector (one admin can inject persistent JS affecting all users). The decision to sanitize depends on the intended deployment model. If addressed: validate CSS var keys match `^--[a-zA-Z0-9_-]+$`, escape `</style>` sequences in values, and consider a CSS sanitizer for custom CSS. I don't like leaving this vector open, we should work around this with at least sanitation.@ai wrote in https://git.bros.ninja/mike/gashy/pulls/31#issuecomment-720: > **6. [IMPORTANT] Pervasive handler-level SQL (SoC violations)** > > Handlers across register.go, confirm.go, reset.go, admin_providers.go, setup.go, and dashboard.go execute raw SQL directly (`h.pool.QueryRow`, `h.pool.Exec`). The `DashboardService` pattern is well-established but wasn't extended to user CRUD, OIDC provider management, invite/token operations, or status cache lookups. This makes handlers untestable without a database and mixes HTTP concerns with persistence. > > **HUMAN REVIEW:** This is an architectural pattern that affects many files. Refactoring to full service-layer extraction is substantial work. Consider whether to address now or track as a v0.7.0 improvement. The current code works correctly — this is about maintainability and test-ability. Slate this for 0.6.1 work. I do want to make sure this is clean and maintainable and testable without mixing concerns. You can also slot in other low priority work packages from this review into this followup minor version. @ai wrote in https://git.bros.ninja/mike/gashy/pulls/31#issuecomment-720: > ### [](#migration-review)Migration Review > > All 8 migrations (00023-00030) are structurally correct: > > * Types, constraints, and indexes are appropriate > > * All have proper Down sections (previously missing ones fixed in [`9f73b24`](/mike/gashy/commit/9f73b24)) > > * FK strategy is sound (CASCADE for tokens, SET NULL for provider refs) > > * Seed script (`scripts/seed-test-dashboard.sql`) is compatible with current schema > > > **HUMAN REVIEW:** Migration 00030 drops `transition_in`/`transition_out` from `pages` without migrating existing values to `dashboards.transition`. Rollback restores columns with defaults only. This is acceptable for schema evolution but worth acknowledging that it's a one-way data migration. We should create a 0.6.1 work package to track the migration fixes. Migrations should be single-purpose in nature when possible. shouldn't be mixing tables and we should make sure that all migrations are reversible. We are currently pre-1.0 so that means we have the opportunity now to fix this because there are no live deployments.
Closes OP#1695

The nonce check was skipped when the ID token lacked a nonce claim.
Since we always send a nonce in the auth request, the token MUST
include it — rejecting tokens without nonce prevents replay attacks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes OP#1696

CSS variable keys are now validated against ^--[a-zA-Z0-9_-]+$ and
values are checked for </style> sequences that could break out of the
style tag and inject arbitrary HTML/JS. Custom CSS blocks receive the
same sanitization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes OP#1697

Replace the 59-char malformed dummy hash with a real pre-computed
bcrypt cost-12 hash (60 chars). Ensures bcrypt always performs the
full computation even if a future library update short-circuits on
invalid hashes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes OP#1698

If crypto/rand.Read fails, the session ID defaults to all zeros,
producing predictable session IDs. A broken entropy source is a
critical security failure that should halt execution rather than
silently produce insecure values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes OP#1699

Previously, uniqueness checks in register.go and safety-check queries
in admin_providers.go silently discarded errors via _ = QueryRow().
Failed queries now log errors and return HTTP 500 instead of falling
through with default zero values that could bypass guards.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes OP#1700

LoadSettings middleware was creating its own DashboardService with a
separate settings cache. After a settings change, the middleware and
handlers could see different cached values for up to 30 seconds.
Now accepts a SettingsLoader interface and uses the shared instance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes OP#1701

The OIDC setup path executed INSERT into oidc_providers and UPDATE
app_settings as separate queries. If the provider insert succeeded
but the settings update failed, the system was left inconsistent.
Both operations are now wrapped in a transaction with proper rollback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes OP#1702

The status tooltip used innerHTML with a label that could include
server error messages (data.err). Switched to textContent + DOM
element creation for the label portion to prevent XSS if upstream
status check errors contain malicious content.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(js): clean up event listeners on component destroy
Some checks failed
CI / validate-branch (pull_request) Successful in 1s
CI / validate-release-pr (pull_request) Successful in 4s
CI / security (pull_request) Successful in 1m26s
CI / build (pull_request) Successful in 2m7s
CI / lint (pull_request) Failing after 4m56s
CI / test (pull_request) Has been skipped
b4ef568fa9
Closes OP#1703

page-tabs.js: Store resize handler reference and remove it in
Alpine's destroy() lifecycle hook to prevent listener accumulation
after htmx page swaps.

confirm-modal.js: Store confirm-action handler reference and remove
it in destroy() to prevent listener accumulation on re-creation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Contributor

PR Review Followup

Responding to review posted on 2026-02-23T01:55:36Z (comment #720) and maintainer feedback in comment #722.

Triage Summary

Severity Count Action
Important (security/correctness) 9 Fixed in this branch
Important (architecture/quality) 2 Deferred to v0.6.1
Important (testing) 1 Deferred to v0.6.1
Human Review 3 1 fixed (CSS XSS), 2 deferred (SQL SoC + migrations)
Minor 14 Deferred to v0.6.1 (4 grouped WPs)
Nitpick 6 Deferred to v0.6.1 (1 grouped WP)

Fixes Applied

The following findings have been resolved on this branch:

  1. [IMPORTANT] OIDC nonce conditional bypass — fixed in 5629322
    • OP#1695: fix: require OIDC nonce claim when requested (oidc.go)
  2. [IMPORTANT] Stored XSS via admin CSS injection — fixed in 3c43343
    • OP#1696: fix: sanitize admin CSS to prevent stored XSS (base.templ)
  3. [IMPORTANT] dummyHash invalid bcrypt (59 chars) — fixed in be42865
    • OP#1697: fix: use valid bcrypt hash for timing normalization (local.go)
  4. [IMPORTANT] generateSessionID drops rand.Read error — fixed in e768638
    • OP#1698: fix: panic on CSPRNG failure in generateSessionID (session.go)
  5. [IMPORTANT] Swallowed DB errors in auth safety checks — fixed in 552b933
    • OP#1699: fix: handle DB errors in auth safety checks (register.go, admin_providers.go)
  6. [IMPORTANT] Separate settings cache in mode middleware — fixed in 291f059
    • OP#1700: fix: share DashboardService instance with EnforceMode middleware (mode.go)
  7. [IMPORTANT] SaveSetup lacks transactional consistency — fixed in b7daf19
    • OP#1701: fix: wrap SaveSetup OIDC path in database transaction (setup.go)
  8. [IMPORTANT] DOM XSS in status tooltip label — fixed in fa9cf74
    • OP#1702: fix: use textContent for status tooltip to prevent DOM XSS (status.templ)
  9. [IMPORTANT] Event listener accumulation — fixed in b4ef568
    • OP#1703: fix: clean up event listeners on component destroy (page-tabs.js, confirm-modal.js)

Deferred to v0.6.1 — PR Review Followup

Version created: v0.6.1 — 8 work packages

Architecture (per maintainer):

  1. OP#1704: refactor: extract service layer for auth handler SQL
  2. OP#1705: refactor: extract long functions into named helpers

Testing:
3. OP#1706: test: add HandleLogin success path and redirect validation tests

Migrations (per maintainer):
4. OP#1707: refactor: make migrations single-purpose and fully reversible

Minor findings (grouped):
5. OP#1708: refactor: extract duplicated helpers across auth handlers
6. OP#1709: fix: improve error handling and API clarity in auth subsystem
7. OP#1710: fix: frontend code quality (innerHTML safety, ARIA, event handling)

Nitpick findings (grouped):
8. OP#1711: chore: clean up dead code and naming inconsistencies

Test Results

  • Tests: All passed (go test ./... — 0 failures)
  • go vet: Clean
  • All fixes verified: Yes

This followup was generated by the pr-followup skill.

## PR Review Followup Responding to review posted on 2026-02-23T01:55:36Z ([comment #720](https://git.bros.ninja/mike/gashy/pulls/31#issuecomment-720)) and maintainer feedback in [comment #722](https://git.bros.ninja/mike/gashy/pulls/31#issuecomment-722). ### Triage Summary | Severity | Count | Action | | -------- | ----- | ------ | | Important (security/correctness) | 9 | Fixed in this branch | | Important (architecture/quality) | 2 | Deferred to v0.6.1 | | Important (testing) | 1 | Deferred to v0.6.1 | | Human Review | 3 | 1 fixed (CSS XSS), 2 deferred (SQL SoC + migrations) | | Minor | 14 | Deferred to v0.6.1 (4 grouped WPs) | | Nitpick | 6 | Deferred to v0.6.1 (1 grouped WP) | ### Fixes Applied The following findings have been resolved on this branch: 1. **[IMPORTANT]** OIDC nonce conditional bypass — fixed in 5629322 - OP#1695: fix: require OIDC nonce claim when requested (oidc.go) 2. **[IMPORTANT]** Stored XSS via admin CSS injection — fixed in 3c43343 - OP#1696: fix: sanitize admin CSS to prevent stored XSS (base.templ) 3. **[IMPORTANT]** dummyHash invalid bcrypt (59 chars) — fixed in be42865 - OP#1697: fix: use valid bcrypt hash for timing normalization (local.go) 4. **[IMPORTANT]** generateSessionID drops rand.Read error — fixed in e768638 - OP#1698: fix: panic on CSPRNG failure in generateSessionID (session.go) 5. **[IMPORTANT]** Swallowed DB errors in auth safety checks — fixed in 552b933 - OP#1699: fix: handle DB errors in auth safety checks (register.go, admin_providers.go) 6. **[IMPORTANT]** Separate settings cache in mode middleware — fixed in 291f059 - OP#1700: fix: share DashboardService instance with EnforceMode middleware (mode.go) 7. **[IMPORTANT]** SaveSetup lacks transactional consistency — fixed in b7daf19 - OP#1701: fix: wrap SaveSetup OIDC path in database transaction (setup.go) 8. **[IMPORTANT]** DOM XSS in status tooltip label — fixed in fa9cf74 - OP#1702: fix: use textContent for status tooltip to prevent DOM XSS (status.templ) 9. **[IMPORTANT]** Event listener accumulation — fixed in b4ef568 - OP#1703: fix: clean up event listeners on component destroy (page-tabs.js, confirm-modal.js) ### Deferred to v0.6.1 — PR Review Followup Version created: [v0.6.1](https://project.bros.ninja/versions/122) — 8 work packages **Architecture (per maintainer):** 1. OP#1704: refactor: extract service layer for auth handler SQL 2. OP#1705: refactor: extract long functions into named helpers **Testing:** 3. OP#1706: test: add HandleLogin success path and redirect validation tests **Migrations (per maintainer):** 4. OP#1707: refactor: make migrations single-purpose and fully reversible **Minor findings (grouped):** 5. OP#1708: refactor: extract duplicated helpers across auth handlers 6. OP#1709: fix: improve error handling and API clarity in auth subsystem 7. OP#1710: fix: frontend code quality (innerHTML safety, ARIA, event handling) **Nitpick findings (grouped):** 8. OP#1711: chore: clean up dead code and naming inconsistencies ### Test Results - **Tests:** All passed (`go test ./...` — 0 failures) - **go vet:** Clean - **All fixes verified:** Yes --- _This followup was generated by the pr-followup skill._
Author
Contributor

PR Review — Third Pass (fresh context, post-fix verification)

Verdict: Acceptable

Updated review — previous reviews posted on 2026-02-23. This review was conducted from a fresh context against the current branch state (59 commits, 117 files, ~17K insertions). All tests pass (0 failures). Focuses on findings not previously identified in the prior 5 comments.

Findings

Severity Count
Blocking 0
Important 10
Minor 7
Nitpick 3

Test Results

  • Tests: All passed (go test ./... — 0 failures)
  • Coverage: No new test files for compress middleware, static middleware, or SSE hub

Human Review Items

1 item flagged for human review — see marker below.


Important Findings (10)

Security (4)

1. [IMPORTANT] Token validate-then-consume TOCTOU race — internal/services/auth_token.go:83-121

ValidateToken (SELECT) and ConsumeToken (UPDATE) are two separate, non-transactional operations. Between validation and consumption, a concurrent request can validate and consume the same token. This affects all token flows: email confirmation (confirm.go:79-100), password reset (reset.go:198-222), magic link login (reset.go:331-339), and invite registration (register.go:98-103). For magic links specifically, this means two browser tabs could both create valid sessions from a single token.

Suggested fix: Replace the two-step pattern with an atomic consume:

func (s *AuthTokenService) ValidateAndConsume(ctx context.Context, rawToken, tokenType string) (*models.AuthToken, error) {
    hash := hashToken(rawToken)
    var token models.AuthToken
    err := s.db.QueryRow(ctx, `
        UPDATE auth_tokens SET used_at = now()
        WHERE token_hash = $1 AND token_type = $2 AND used_at IS NULL AND expires_at > now()
        RETURNING id, user_id, token_hash, token_type, expires_at, used_at, metadata, created_at
    `, hash, tokenType).Scan(...)
    // pgx.ErrNoRows means invalid/expired/already-used
}

2. [IMPORTANT] Migration 00023 Down fails when local users exist — internal/database/migrations/00023_auth_local_support.sql:40

The down migration runs ALTER TABLE users ALTER COLUMN oidc_subject SET NOT NULL, but any local-auth users will have NULL in oidc_subject, causing a NOT NULL constraint violation. Rollback becomes impossible once local users exist. Per maintainer feedback in comment #722, pre-1.0 is the time to fix migrations.

Suggested fix: Before SET NOT NULL, backfill a placeholder:

UPDATE users SET oidc_subject = 'removed-' || id::text WHERE oidc_subject IS NULL;
ALTER TABLE users ALTER COLUMN oidc_subject SET NOT NULL;

Or document that rollback past this point requires manual intervention.

3. [IMPORTANT] Missing index on users.oidc_provider_id FK — internal/database/migrations/00026_oidc_providers.sql:32

The oidc_provider_id column references oidc_providers(id) with ON DELETE SET NULL, but no index exists on the column. The cascade operation and any provider-based user queries will require sequential scans on users.

Suggested fix: Add to the migration:

CREATE INDEX idx_users_oidc_provider_id ON users (oidc_provider_id);

4. [IMPORTANT] PreviewStart sets cookie without authentication — internal/handlers/dashboard.go:281

PreviewStart handles POST /preview/start and sets a preview_as cookie for any request, authenticated or not. While the cookie is consumed safely (used as a group filter), unauthenticated users should not be able to set cookies that influence dashboard rendering. The mode value is also not validated against known groups.

Suggested fix: Add an auth check at the top of PreviewStart — require at minimum an authenticated user, ideally a dashboard owner or admin.

Code Quality (4)

5. [IMPORTANT] Compression middleware does not implement http.Flusherinternal/middleware/compress.go:96-123

gzipResponseWriter does not implement http.Flusher. The SSE path is correctly bypassed, but any future streaming handler not under /events/ will get a false type assertion on w.(http.Flusher). This is a latent bug.

Suggested fix: Implement Flush() on gzipResponseWriter:

func (w *gzipResponseWriter) Flush() {
    if w.compress {
        w.gz.Flush()
    }
    if f, ok := w.ResponseWriter.(http.Flusher); ok {
        f.Flush()
    }
}

6. [IMPORTANT] renderDashboard takes 18 positional parameters — internal/handlers/render.go:48

This function signature is unwieldy and error-prone. All parameters are positional with no compiler help if the order is wrong (e.g., swapping two string params compiles but breaks rendering).

HUMAN REVIEW: This is a refactoring candidate. Consider a DashboardRenderParams struct to make calls self-documenting. Could be bundled with the v0.6.1 architecture improvements (OP#1705).

7. [IMPORTANT] Demo write handlers lack transactions — internal/handlers/demo.go:205-295, 299-434

CreateTestPage inserts a page, multiple sections, and bulk items with status cache as separate queries on the raw pool. ScaleTestPage has the same pattern. If any step fails mid-way, orphaned rows remain. These are admin-only demo endpoints, but partial data can confuse the speedtest UI.

Suggested fix: Wrap the entire create sequence in a database transaction.

8. [IMPORTANT] No tests for 3 new components

Component File Risk
Compression middleware internal/middleware/compress.go Content-type sniffing, gzip correctness, header management
Static caching middleware internal/middleware/static.go Cache header values, suffix matching
SSE hub internal/sse/hub.go Concurrency-sensitive subscribe/unsubscribe/broadcast lifecycle

The SSE hub is particularly important to test with -race given its concurrent access patterns.

Frontend (2)

9. [IMPORTANT] fetch() POST calls lack CSRF protection — static/js/edit-mode.js:146,171,181,248,290

All direct fetch() POST/PUT/DELETE calls in edit-mode.js (section reorder, item reorder, move item, rename section) do not include CSRF tokens. These bypass htmx's CSRF handling. The server uses Sec-Fetch-Site validation which should protect against cross-origin form submissions, but Sec-Fetch-Site is not universally supported and can be absent (Firefox ESR, embedded WebViews). The CSRF middleware's deny-by-default fallback (fixed in c2d7717) protects when headers are absent, but this means these fetch() calls will be blocked on browsers that don't send Sec-Fetch-Site.

Suggested fix: Add Sec-Fetch-Site: same-origin header to fetch calls (browsers that support it set it automatically, so this is a no-op on modern browsers — but explicit headers ensure compatibility). Or better: include a CSRF token from a meta tag.

10. [IMPORTANT] SSE write errors silently ignored — internal/handlers/sse.go:50

fmt.Fprintf(w, "event: status-update\ndata: %s\n\n", data) ignores the returned error. If the client disconnects but ctx.Done() hasn't fired yet, the handler spins writing into a broken pipe until context cancellation.

Suggested fix:

if _, err := fmt.Fprintf(w, "event: status-update\ndata: %s\n\n", data); err != nil {
    return
}

Minor Findings (7)

# File Issue
1 demo.go:398 ScaleTestPage divides by len(allSectionIDs) — panics with division by zero if page has no sections. Add guard clause.
2 auth/local.go:99-102 Login rate limiting keyed solely by email. An attacker can enumerate emails with one wrong password each without hitting per-email limits. The global authLimiter (10 req/s burst 20) is generous for auth endpoints.
3 confirm.go:139-142 ResendConfirmation reveals email verification status — "already confirmed" vs generic "if registered, link sent" enables email enumeration. Return same generic message for all cases.
4 migrations/00026_oidc_providers.sql:10 OIDC client_secret stored as plaintext TEXT. Consider application-level encryption with an envelope key from environment.
5 middleware/compress.go:116-121 flushHeaders() + gz.Close() may produce a non-empty gzip body for handlers that write no bytes. Track bytes written and skip compression if zero.
6 dashboard.go:69-76 User groups resolution pattern duplicated 4x in same file. Extract to resolveUserGroups(user) []string helper.
7 services/dashboard.go:62-114 LoadDashboard and LoadDashboardPreview are near-identical. Extract shared query logic.

Nitpick Findings (3)

# File Issue
1 auth/local.go:192-194 Dead code: _ = metadata with TODO comment. Remove or implement.
2 demo.go:245-249, 352-356 sectionNames array duplicated in two methods. Extract to package-level constant.
3 migrations/00029_toolbar_config.sql:2 DEFAULT NULL is redundant in PostgreSQL.

Previous Fixes Verified

All 20 fixes from prior review cycles (comments #716 and #732) remain correctly applied in the current branch. Specific verifications:

  • CSRF deny-by-default (c2d7717)
  • Email timing normalization with valid dummyHash (be42865)
  • OIDC nonce required when requested (5629322)
  • SSE hub connection limits (1df70b7)
  • Admin CSS sanitization (3c43343)
  • Setup transaction consistency (b7daf19)
  • Shared settings cache (291f059)
  • Event listener cleanup (b4ef568)
  • Status tooltip textContent (fa9cf74)

Summary

The critical and high-severity security issues from prior reviews are all correctly resolved. The remaining important findings are primarily:

  1. Token TOCTOU race — narrow window but real, straightforward atomic fix
  2. Migration rollback safety — matters now pre-1.0, harder to fix later
  3. Missing FK index — performance issue that grows with user count
  4. Test coverage gaps — 3 new components with zero test files
  5. PreviewStart auth — defense-in-depth

No blocking findings. The PR is release-worthy with these items tracked for follow-up (some align with existing v0.6.1 work packages).

## PR Review — Third Pass (fresh context, post-fix verification) **Verdict: Acceptable** Updated review — previous reviews posted on 2026-02-23. This review was conducted from a fresh context against the current branch state (59 commits, 117 files, ~17K insertions). All tests pass (0 failures). Focuses on findings **not previously identified** in the prior 5 comments. ### Findings | Severity | Count | | -------- | ----- | | Blocking | 0 | | Important | 10 | | Minor | 7 | | Nitpick | 3 | ### Test Results - **Tests:** All passed (`go test ./...` — 0 failures) - **Coverage:** No new test files for compress middleware, static middleware, or SSE hub ### Human Review Items 1 item flagged for human review — see marker below. --- ### Important Findings (10) #### Security (4) **1. [IMPORTANT] Token validate-then-consume TOCTOU race — `internal/services/auth_token.go:83-121`** `ValidateToken` (SELECT) and `ConsumeToken` (UPDATE) are two separate, non-transactional operations. Between validation and consumption, a concurrent request can validate and consume the same token. This affects all token flows: email confirmation (`confirm.go:79-100`), password reset (`reset.go:198-222`), magic link login (`reset.go:331-339`), and invite registration (`register.go:98-103`). For magic links specifically, this means two browser tabs could both create valid sessions from a single token. **Suggested fix:** Replace the two-step pattern with an atomic consume: ```go func (s *AuthTokenService) ValidateAndConsume(ctx context.Context, rawToken, tokenType string) (*models.AuthToken, error) { hash := hashToken(rawToken) var token models.AuthToken err := s.db.QueryRow(ctx, ` UPDATE auth_tokens SET used_at = now() WHERE token_hash = $1 AND token_type = $2 AND used_at IS NULL AND expires_at > now() RETURNING id, user_id, token_hash, token_type, expires_at, used_at, metadata, created_at `, hash, tokenType).Scan(...) // pgx.ErrNoRows means invalid/expired/already-used } ``` **2. [IMPORTANT] Migration 00023 Down fails when local users exist — `internal/database/migrations/00023_auth_local_support.sql:40`** The down migration runs `ALTER TABLE users ALTER COLUMN oidc_subject SET NOT NULL`, but any local-auth users will have `NULL` in `oidc_subject`, causing a NOT NULL constraint violation. Rollback becomes impossible once local users exist. Per maintainer feedback in comment #722, pre-1.0 is the time to fix migrations. **Suggested fix:** Before `SET NOT NULL`, backfill a placeholder: ```sql UPDATE users SET oidc_subject = 'removed-' || id::text WHERE oidc_subject IS NULL; ALTER TABLE users ALTER COLUMN oidc_subject SET NOT NULL; ``` Or document that rollback past this point requires manual intervention. **3. [IMPORTANT] Missing index on `users.oidc_provider_id` FK — `internal/database/migrations/00026_oidc_providers.sql:32`** The `oidc_provider_id` column references `oidc_providers(id)` with `ON DELETE SET NULL`, but no index exists on the column. The cascade operation and any provider-based user queries will require sequential scans on `users`. **Suggested fix:** Add to the migration: ```sql CREATE INDEX idx_users_oidc_provider_id ON users (oidc_provider_id); ``` **4. [IMPORTANT] PreviewStart sets cookie without authentication — `internal/handlers/dashboard.go:281`** `PreviewStart` handles `POST /preview/start` and sets a `preview_as` cookie for any request, authenticated or not. While the cookie is consumed safely (used as a group filter), unauthenticated users should not be able to set cookies that influence dashboard rendering. The `mode` value is also not validated against known groups. **Suggested fix:** Add an auth check at the top of `PreviewStart` — require at minimum an authenticated user, ideally a dashboard owner or admin. #### Code Quality (4) **5. [IMPORTANT] Compression middleware does not implement `http.Flusher` — `internal/middleware/compress.go:96-123`** `gzipResponseWriter` does not implement `http.Flusher`. The SSE path is correctly bypassed, but any future streaming handler not under `/events/` will get a false type assertion on `w.(http.Flusher)`. This is a latent bug. **Suggested fix:** Implement `Flush()` on `gzipResponseWriter`: ```go func (w *gzipResponseWriter) Flush() { if w.compress { w.gz.Flush() } if f, ok := w.ResponseWriter.(http.Flusher); ok { f.Flush() } } ``` **6. [IMPORTANT] `renderDashboard` takes 18 positional parameters — `internal/handlers/render.go:48`** This function signature is unwieldy and error-prone. All parameters are positional with no compiler help if the order is wrong (e.g., swapping two `string` params compiles but breaks rendering). **HUMAN REVIEW:** This is a refactoring candidate. Consider a `DashboardRenderParams` struct to make calls self-documenting. Could be bundled with the v0.6.1 architecture improvements (OP#1705). **7. [IMPORTANT] Demo write handlers lack transactions — `internal/handlers/demo.go:205-295, 299-434`** `CreateTestPage` inserts a page, multiple sections, and bulk items with status cache as separate queries on the raw pool. `ScaleTestPage` has the same pattern. If any step fails mid-way, orphaned rows remain. These are admin-only demo endpoints, but partial data can confuse the speedtest UI. **Suggested fix:** Wrap the entire create sequence in a database transaction. **8. [IMPORTANT] No tests for 3 new components** | Component | File | Risk | |-----------|------|------| | Compression middleware | `internal/middleware/compress.go` | Content-type sniffing, gzip correctness, header management | | Static caching middleware | `internal/middleware/static.go` | Cache header values, suffix matching | | SSE hub | `internal/sse/hub.go` | Concurrency-sensitive subscribe/unsubscribe/broadcast lifecycle | The SSE hub is particularly important to test with `-race` given its concurrent access patterns. #### Frontend (2) **9. [IMPORTANT] `fetch()` POST calls lack CSRF protection — `static/js/edit-mode.js:146,171,181,248,290`** All direct `fetch()` POST/PUT/DELETE calls in `edit-mode.js` (section reorder, item reorder, move item, rename section) do not include CSRF tokens. These bypass htmx's CSRF handling. The server uses `Sec-Fetch-Site` validation which should protect against cross-origin form submissions, but `Sec-Fetch-Site` is not universally supported and can be absent (Firefox ESR, embedded WebViews). The CSRF middleware's deny-by-default fallback (fixed in c2d7717) protects when headers are absent, but this means these `fetch()` calls will be **blocked** on browsers that don't send `Sec-Fetch-Site`. **Suggested fix:** Add `Sec-Fetch-Site: same-origin` header to fetch calls (browsers that support it set it automatically, so this is a no-op on modern browsers — but explicit headers ensure compatibility). Or better: include a CSRF token from a meta tag. **10. [IMPORTANT] SSE write errors silently ignored — `internal/handlers/sse.go:50`** `fmt.Fprintf(w, "event: status-update\ndata: %s\n\n", data)` ignores the returned error. If the client disconnects but `ctx.Done()` hasn't fired yet, the handler spins writing into a broken pipe until context cancellation. **Suggested fix:** ```go if _, err := fmt.Fprintf(w, "event: status-update\ndata: %s\n\n", data); err != nil { return } ``` --- ### Minor Findings (7) | # | File | Issue | |---|------|-------| | 1 | `demo.go:398` | `ScaleTestPage` divides by `len(allSectionIDs)` — panics with division by zero if page has no sections. Add guard clause. | | 2 | `auth/local.go:99-102` | Login rate limiting keyed solely by email. An attacker can enumerate emails with one wrong password each without hitting per-email limits. The global `authLimiter` (10 req/s burst 20) is generous for auth endpoints. | | 3 | `confirm.go:139-142` | `ResendConfirmation` reveals email verification status — "already confirmed" vs generic "if registered, link sent" enables email enumeration. Return same generic message for all cases. | | 4 | `migrations/00026_oidc_providers.sql:10` | OIDC `client_secret` stored as plaintext `TEXT`. Consider application-level encryption with an envelope key from environment. | | 5 | `middleware/compress.go:116-121` | `flushHeaders()` + `gz.Close()` may produce a non-empty gzip body for handlers that write no bytes. Track bytes written and skip compression if zero. | | 6 | `dashboard.go:69-76` | User groups resolution pattern duplicated 4x in same file. Extract to `resolveUserGroups(user) []string` helper. | | 7 | `services/dashboard.go:62-114` | `LoadDashboard` and `LoadDashboardPreview` are near-identical. Extract shared query logic. | ### Nitpick Findings (3) | # | File | Issue | |---|------|-------| | 1 | `auth/local.go:192-194` | Dead code: `_ = metadata` with TODO comment. Remove or implement. | | 2 | `demo.go:245-249, 352-356` | `sectionNames` array duplicated in two methods. Extract to package-level constant. | | 3 | `migrations/00029_toolbar_config.sql:2` | `DEFAULT NULL` is redundant in PostgreSQL. | --- ### Previous Fixes Verified All 20 fixes from prior review cycles (comments #716 and #732) remain correctly applied in the current branch. Specific verifications: - CSRF deny-by-default (c2d7717) ✅ - Email timing normalization with valid dummyHash (be42865) ✅ - OIDC nonce required when requested (5629322) ✅ - SSE hub connection limits (1df70b7) ✅ - Admin CSS sanitization (3c43343) ✅ - Setup transaction consistency (b7daf19) ✅ - Shared settings cache (291f059) ✅ - Event listener cleanup (b4ef568) ✅ - Status tooltip textContent (fa9cf74) ✅ ### Summary The critical and high-severity security issues from prior reviews are all correctly resolved. The remaining important findings are primarily: 1. **Token TOCTOU race** — narrow window but real, straightforward atomic fix 2. **Migration rollback safety** — matters now pre-1.0, harder to fix later 3. **Missing FK index** — performance issue that grows with user count 4. **Test coverage gaps** — 3 new components with zero test files 5. **PreviewStart auth** — defense-in-depth No blocking findings. The PR is release-worthy with these items tracked for follow-up (some align with existing v0.6.1 work packages).
Owner

@ai wrote in #31 (comment):

6. [IMPORTANT] renderDashboard takes 18 positional parameters — internal/handlers/render.go:48

This function signature is unwieldy and error-prone. All parameters are positional with no compiler help if the order is wrong (e.g., swapping two string params compiles but breaks rendering).

HUMAN REVIEW: This is a refactoring candidate. Consider a DashboardRenderParams struct to make calls self-documenting. Could be bundled with the v0.6.1 architecture improvements (OP#1705).

Good call, this should be bundled into work along with the rest of the follow-up in 0.6.1 from the PR comment. Also there are pipeline errors:

fix(js): clean up event listeners on component destroy
Commit b4ef568fa9 pushed by Mike Bros
#115
Workflow ci.yaml
Job lint

=== Set up job ===
sao-runner-2(version:v6.4.0) received task 2063 of job lint, be triggered by event: pull_request
workflow prepared
🚀  Start image=golang:1.25
  🐳  docker pull image=golang:1.25 platform= username= forcePull=false
  🐳  docker create image=golang:1.25 platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="docker-shared"
  🐳  docker run image=golang:1.25 platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="docker-shared"
  ☁  git clone 'https://code.forgejo.org/actions/checkout' # ref=v4
  ☁  git clone 'https://code.forgejo.org/actions/cache' # ref=v4
⭐ Run Main Install Node.js
  🐳  docker exec cmd=[sh -e /var/run/act/workflow/0.sh] user= workdir=

=== Install Node.js ===
debconf: unable to initialize frontend: Dialog
debconf: (TERM is not set, so the dialog frontend is not usable.)
debconf: falling back to frontend: Readline
debconf: unable to initialize frontend: Readline
debconf: (This frontend requires a controlling tty.)
debconf: falling back to frontend: Teletype
debconf: unable to initialize frontend: Teletype
debconf: (This frontend requires a controlling tty.)
debconf: falling back to frontend: Noninteractive
Selecting previously unselected package libcares2:amd64.
(Reading database ... 
(Reading database ... 5%
(Reading database ... 10%
(Reading database ... 15%
(Reading database ... 20%
(Reading database ... 25%
(Reading database ... 30%
(Reading database ... 35%
(Reading database ... 40%
(Reading database ... 45%
(Reading database ... 50%
(Reading database ... 55%
(Reading database ... 60%
(Reading database ... 65%
(Reading database ... 70%
(Reading database ... 75%
(Reading database ... 80%
(Reading database ... 85%
(Reading database ... 90%
(Reading database ... 95%
(Reading database ... 100%
(Reading database ... 16724 files and directories currently installed.)
Preparing to unpack .../00-libcares2_1.34.5-1+deb13u1_amd64.deb ...
Unpacking libcares2:amd64 (1.34.5-1+deb13u1) ...
Selecting previously unselected package libicu76:amd64.
Preparing to unpack .../01-libicu76_76.1-4_amd64.deb ...
Unpacking libicu76:amd64 (76.1-4) ...
Selecting previously unselected package libuv1t64:amd64.
Preparing to unpack .../02-libuv1t64_1.50.0-2_amd64.deb ...
Unpacking libuv1t64:amd64 (1.50.0-2) ...
Selecting previously unselected package node-xtend.
Preparing to unpack .../03-node-xtend_4.0.2-3_all.deb ...
Unpacking node-xtend (4.0.2-3) ...
Selecting previously unselected package node-corepack.
Preparing to unpack .../04-node-corepack_0.24.0-5_all.deb ...
Unpacking node-corepack (0.24.0-5) ...
Selecting previously unselected package nodejs.
Preparing to unpack .../05-nodejs_20.19.2+dfsg-1_amd64.deb ...
Unpacking nodejs (20.19.2+dfsg-1) ...
Selecting previously unselected package node-acorn.
Preparing to unpack .../06-node-acorn_8.8.1+ds+~cs25.17.7-2_all.deb ...
Unpacking node-acorn (8.8.1+ds+~cs25.17.7-2) ...
Selecting previously unselected package node-cjs-module-lexer.
Preparing to unpack .../07-node-cjs-module-lexer_1.2.3+dfsg-1_all.deb ...
Unpacking node-cjs-module-lexer (1.2.3+dfsg-1) ...
Selecting previously unselected package node-balanced-match.
Preparing to unpack .../08-node-balanced-match_2.0.0-1_all.deb ...
Unpacking node-balanced-match (2.0.0-1) ...
Selecting previously unselected package node-brace-expansion.
Preparing to unpack .../09-node-brace-expansion_2.0.1+~1.1.0-2_all.deb ...
Unpacking node-brace-expansion (2.0.1+~1.1.0-2) ...
Selecting previously unselected package node-minimatch.
Preparing to unpack .../10-node-minimatch_9.0.3-6_all.deb ...
Unpacking node-minimatch (9.0.3-6) ...
Selecting previously unselected package node-undici.
Preparing to unpack .../11-node-undici_7.3.0+dfsg1+~cs24.12.11-1_all.deb ...
Unpacking node-undici (7.3.0+dfsg1+~cs24.12.11-1) ...
Selecting previously unselected package libnode115:amd64.
Preparing to unpack .../12-libnode115_20.19.2+dfsg-1_amd64.deb ...
Unpacking libnode115:amd64 (20.19.2+dfsg-1) ...
Setting up libuv1t64:amd64 (1.50.0-2) ...
Setting up node-cjs-module-lexer (1.2.3+dfsg-1) ...
Setting up node-balanced-match (2.0.0-1) ...
Setting up node-brace-expansion (2.0.1+~1.1.0-2) ...
Setting up libcares2:amd64 (1.34.5-1+deb13u1) ...
Setting up node-undici (7.3.0+dfsg1+~cs24.12.11-1) ...
Setting up libicu76:amd64 (76.1-4) ...
Setting up node-minimatch (9.0.3-6) ...
Setting up node-xtend (4.0.2-3) ...
Setting up node-acorn (8.8.1+ds+~cs25.17.7-2) ...
Setting up node-corepack (0.24.0-5) ...
Setting up libnode115:amd64 (20.19.2+dfsg-1) ...
Setting up nodejs (20.19.2+dfsg-1) ...
update-alternatives: using /usr/bin/nodejs to provide /usr/bin/js (js) in auto mode
Processing triggers for libc-bin (2.41-12+deb13u1) ...

=== actions/checkout@v4 ===
::add-matcher::/run/act/actions/actions-checkout@v4/dist/problem-matcher.json
Syncing repository: mike/gashy
Getting Git version info
Working directory is '/workspace/mike/gashy'
[command]/usr/bin/git version
git version 2.47.3
Temporarily overriding HOME='/tmp/894ff524-1965-4fc3-bf42-6660609e30b4' before making global git config changes
Adding repository directory to the temporary git global config as a safe directory
[command]/usr/bin/git config --global --add safe.directory /workspace/mike/gashy
Deleting the contents of '/workspace/mike/gashy'
Initializing the repository
[command]/usr/bin/git init /workspace/mike/gashy
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint: 	git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint: 	git branch -m <name>
Initialized empty Git repository in /workspace/mike/gashy/.git/
[command]/usr/bin/git remote add origin https://git.bros.ninja/mike/gashy
Disabling automatic garbage collection
[command]/usr/bin/git config --local gc.auto 0
Setting up auth
[command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand
[command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :"
[command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/git\.bros\.ninja\/\.extraheader
[command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/git\.bros\.ninja\/\.extraheader' && git config --local --unset-all 'http.https://git.bros.ninja/.extraheader' || :"
[command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir:
[command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url
[command]/usr/bin/git config --local http.https://git.bros.ninja/.extraheader AUTHORIZATION: basic ***
Fetching the repository
[command]/usr/bin/git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +b4ef568fa96be923d84f844a89afab5ba1e244f0:refs/remotes/pull/31/head
From https://git.bros.ninja/mike/gashy
 * [new ref]         b4ef568fa96be923d84f844a89afab5ba1e244f0 -> pull/31/head
Determining the checkout info
[command]/usr/bin/git sparse-checkout disable
[command]/usr/bin/git config --local --unset-all extensions.worktreeConfig
Checking out the ref
[command]/usr/bin/git checkout --progress --force refs/remotes/pull/31/head
Note: switching to 'refs/remotes/pull/31/head'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at b4ef568 fix(js): clean up event listeners on component destroy
[command]/usr/bin/git log -1 --format=%H
b4ef568fa96be923d84f844a89afab5ba1e244f0
::remove-matcher owner=checkout-git::

=== actions/cache@v4 ===
Cache Size: ~57 MB (59514381 B)
[command]/usr/bin/tar -xf /tmp/782dde95-2371-4d0c-bd23-7bd197415d1d/cache.tgz -P -C /workspace/mike/gashy -z
Cache restored successfully
Cache restored from key: go-2deec8d72462e435c9acc823ee62950ac14a3bccb2bf4a509ffef217c2b1bc49

=== Install tools ===
go: downloading github.com/a-h/templ v0.3.977
go: downloading github.com/fatih/color v1.16.0
go: downloading github.com/natefinch/atomic v1.0.1
go: downloading github.com/cenkalti/backoff/v4 v4.3.0
go: downloading github.com/cli/browser v1.3.0
go: downloading github.com/fsnotify/fsnotify v1.7.0
go: downloading golang.org/x/sync v0.16.0
go: downloading github.com/mattn/go-colorable v0.1.13
go: downloading github.com/mattn/go-isatty v0.0.20
go: downloading golang.org/x/mod v0.26.0
go: downloading github.com/andybalholm/brotli v1.1.0
go: downloading golang.org/x/net v0.42.0
go: downloading github.com/a-h/parse v0.0.0-20250122154542-74294addb73e
go: downloading golang.org/x/sys v0.34.0
go: downloading golang.org/x/tools v0.35.0
go: downloading mvdan.cc/gofumpt v0.9.2
go: downloading golang.org/x/mod v0.29.0
go: downloading golang.org/x/sync v0.17.0
go: downloading github.com/google/go-cmp v0.7.0
go: downloading golang.org/x/tools v0.38.0
go: downloading golang.org/x/tools v0.42.0
go: downloading golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4
go: downloading golang.org/x/mod v0.33.0
go: downloading golang.org/x/sync v0.19.0

=== Generate templ code ===
(✓) Post-generation event received, processing... [ needsRestart=true needsBrowserReload=true ]
(✓) Post-generation event received, processing... [ needsRestart=true needsBrowserReload=true ]
(✓) Complete [ updates=127 duration=640.659119ms ]

=== Install golangci-lint ===
go: downloading github.com/golangci/golangci-lint v1.64.8
go: downloading github.com/fatih/color v1.18.0
go: downloading github.com/gofrs/flock v0.12.1
go: downloading github.com/hashicorp/go-version v1.7.0
go: downloading github.com/pelletier/go-toml/v2 v2.2.3
go: downloading github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
go: downloading github.com/spf13/cobra v1.9.1
go: downloading github.com/pelletier/go-toml v1.9.5
go: downloading github.com/spf13/pflag v1.0.6
go: downloading github.com/spf13/viper v1.12.0
go: downloading go.uber.org/automaxprocs v1.6.0
go: downloading gopkg.in/yaml.v3 v3.0.1
go: downloading github.com/mattn/go-colorable v0.1.14
go: downloading golang.org/x/tools v0.31.0
go: downloading github.com/go-viper/mapstructure/v2 v2.2.1
go: downloading github.com/ldez/grignotin v0.9.0
go: downloading github.com/mitchellh/go-homedir v1.1.0
go: downloading golang.org/x/mod v0.24.0
go: downloading github.com/golangci/plugin-module-register v0.1.1
go: downloading github.com/sirupsen/logrus v1.9.3
go: downloading github.com/stretchr/testify v1.10.0
go: downloading golang.org/x/sys v0.31.0
go: downloading github.com/go-xmlfmt/xmlfmt v1.1.3
go: downloading github.com/golangci/revgrep v0.8.0
go: downloading golang.org/x/text v0.22.0
go: downloading github.com/fsnotify/fsnotify v1.5.4
go: downloading github.com/mitchellh/mapstructure v1.5.0
go: downloading github.com/spf13/afero v1.12.0
go: downloading github.com/spf13/cast v1.5.0
go: downloading github.com/spf13/jwalterweatherman v1.1.0
go: downloading github.com/rogpeppe/go-internal v1.14.1
go: downloading github.com/alingse/asasalint v0.0.11
go: downloading github.com/tdakkota/asciicheck v0.4.1
go: downloading github.com/breml/bidichk v0.3.2
go: downloading github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3
go: downloading github.com/lasiar/canonicalheader v1.1.2
go: downloading github.com/sivchari/containedctx v1.0.3
go: downloading github.com/kkHAIKE/contextcheck v1.1.6
go: downloading github.com/karamaru-alpha/copyloopvar v1.2.1
go: downloading github.com/bkielbasa/cyclop v1.2.3
go: downloading gitlab.com/bosi/decorder v0.4.2
go: downloading github.com/OpenPeeDeeP/depguard/v2 v2.2.1
go: downloading github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32
go: downloading github.com/Abirdcfly/dupword v0.1.3
go: downloading github.com/charithe/durationcheck v0.0.10
go: downloading github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24
go: downloading github.com/kisielk/errcheck v1.9.0
go: downloading github.com/breml/errchkjson v0.4.0
go: downloading github.com/Antonboom/errname v1.0.0
go: downloading github.com/polyfloyd/go-errorlint v1.7.1
go: downloading github.com/nishanths/exhaustive v0.12.0
go: downloading github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1
go: downloading github.com/ldez/exptostd v0.4.2
go: downloading github.com/Crocmagnon/fatcontext v0.7.1
go: downloading github.com/ashanbrown/forbidigo v1.6.0
go: downloading github.com/gostaticanalysis/forcetypeassert v0.2.0
go: downloading github.com/ultraware/funlen v0.2.0
go: downloading github.com/nunnatsa/ginkgolinter v0.19.1
go: downloading 4d63.com/gocheckcompilerdirectives v1.3.0
go: downloading 4d63.com/gochecknoglobals v0.2.2
go: downloading github.com/alecthomas/go-check-sumtype v0.3.1
go: downloading github.com/uudashr/gocognit v1.2.0
go: downloading github.com/jgautheron/goconst v1.7.1
go: downloading github.com/go-critic/go-critic v0.12.0
go: downloading github.com/quasilyte/go-ruleguard/dsl v0.3.22
go: downloading github.com/fzipp/gocyclo v0.6.0
go: downloading github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1
go: downloading github.com/tetafro/godot v1.5.0
go: downloading github.com/matoous/godox v1.1.0
go: downloading github.com/denis-tingaikin/go-header v0.5.0
go: downloading github.com/ldez/gomoddirectives v0.6.1
go: downloading github.com/ryancurrah/gomodguard v1.3.5
go: downloading github.com/golangci/go-printf-func-name v0.1.0
go: downloading github.com/securego/gosec/v2 v2.22.2
go: downloading github.com/xen0n/gosmopolitan v1.2.2
go: downloading honnef.co/go/tools v0.6.1
go: downloading github.com/leonklingele/grouper v1.1.2
go: downloading github.com/uudashr/iface v1.3.1
go: downloading github.com/julz/importas v0.2.0
go: downloading github.com/macabu/inamedparam v0.1.3
go: downloading github.com/gordonklaus/ineffassign v0.1.0
go: downloading github.com/sashamelentyev/interfacebloat v1.1.0
go: downloading github.com/ckaznocha/intrange v0.3.0
go: downloading github.com/butuzov/ireturn v0.3.1
go: downloading github.com/timonwong/loggercheck v0.10.1
go: downloading github.com/yagipy/maintidx v1.0.0
go: downloading github.com/ashanbrown/makezero v1.2.0
go: downloading github.com/butuzov/mirror v1.3.0
go: downloading github.com/golangci/misspell v0.6.0
go: downloading github.com/tommy-muehle/go-mnd/v2 v2.5.1
go: downloading go-simpler.org/musttag v0.13.0
go: downloading github.com/alexkohler/nakedret/v2 v2.0.5
go: downloading github.com/nakabonne/nestif v0.3.1
go: downloading github.com/gostaticanalysis/nilerr v0.1.1
go: downloading github.com/alingse/nilnesserr v0.1.2
go: downloading github.com/Antonboom/nilnil v1.0.1
go: downloading github.com/ssgreg/nlreturn/v2 v2.2.1
go: downloading github.com/sonatard/noctx v0.1.0
go: downloading github.com/firefart/nonamedreturns v1.0.5
go: downloading github.com/stbenjam/no-sprintf-host-port v0.2.0
go: downloading github.com/kunwardeep/paralleltest v1.0.10
go: downloading github.com/catenacyber/perfsprint v0.8.2
go: downloading github.com/alexkohler/prealloc v1.0.0
go: downloading github.com/nishanths/predeclared v0.2.2
go: downloading github.com/yeya24/promlinter v0.3.0
go: downloading github.com/ghostiam/protogetter v0.3.9
go: downloading github.com/curioswitch/go-reassign v0.3.0
go: downloading github.com/raeperd/recvcheck v0.2.0
go: downloading github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c
go: downloading github.com/mgechev/revive v1.7.0
go: downloading github.com/jingyugao/rowserrcheck v1.1.1
go: downloading go-simpler.org/sloglint v0.9.0
go: downloading github.com/jjti/go-spancheck v0.6.4
go: downloading github.com/ryanrolds/sqlclosecheck v0.5.1
go: downloading github.com/4meepo/tagalign v1.4.2
go: downloading github.com/ldez/tagliatelle v0.7.1
go: downloading github.com/sivchari/tenv v1.12.1
go: downloading github.com/maratori/testableexamples v1.0.0
go: downloading github.com/Antonboom/testifylint v1.5.2
go: downloading github.com/maratori/testpackage v1.1.1
go: downloading github.com/kulti/thelper v0.6.3
go: downloading github.com/moricho/tparallel v0.3.2
go: downloading github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed
go: downloading mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f
go: downloading github.com/sashamelentyev/usestdlibvars v1.28.0
go: downloading github.com/ldez/usetesting v0.4.2
go: downloading github.com/blizzy78/varnamelen v0.8.0
go: downloading github.com/sanposhiho/wastedassign/v2 v2.1.0
go: downloading github.com/ultraware/whitespace v0.2.0
go: downloading github.com/tomarrell/wrapcheck/v2 v2.10.0
go: downloading github.com/bombsimon/wsl/v4 v4.5.0
go: downloading github.com/ykadowak/zerologlint v0.1.5
go: downloading golang.org/x/sync v0.12.0
go: downloading github.com/davecgh/go-spew v1.1.1
go: downloading github.com/pmezard/go-difflib v1.0.0
go: downloading github.com/stretchr/objx v0.5.2
go: downloading github.com/daixiang0/gci v0.13.5
go: downloading github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d
go: downloading mvdan.cc/gofumpt v0.7.0
go: downloading github.com/subosito/gotenv v1.4.1
go: downloading github.com/hashicorp/hcl v1.0.0
go: downloading gopkg.in/ini.v1 v1.67.0
go: downloading github.com/magiconair/properties v1.8.6
go: downloading github.com/sourcegraph/go-diff v0.7.0
go: downloading github.com/gostaticanalysis/analysisutil v0.7.1
go: downloading github.com/go-toolsmith/astcast v1.1.0
go: downloading github.com/gobwas/glob v0.2.3
go: downloading gopkg.in/yaml.v2 v2.4.0
go: downloading github.com/go-toolsmith/astcopy v1.1.0
go: downloading github.com/go-toolsmith/astequal v1.2.0
go: downloading github.com/go-toolsmith/astfmt v1.1.0
go: downloading github.com/go-toolsmith/astp v1.1.0
go: downloading github.com/go-toolsmith/strparse v1.1.0
go: downloading github.com/go-toolsmith/typep v1.1.0
go: downloading github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727
go: downloading github.com/Masterminds/semver/v3 v3.3.0
go: downloading github.com/ccojocar/zxcvbn-go v1.0.2
go: downloading github.com/gostaticanalysis/comment v1.5.0
go: downloading github.com/prometheus/client_golang v1.12.1
go: downloading github.com/prometheus/client_model v0.2.0
go: downloading github.com/fatih/structtag v1.2.0
go: downloading github.com/ettle/strcase v0.2.0
go: downloading github.com/hashicorp/go-immutable-radix/v2 v2.1.0
go: downloading go.uber.org/zap v1.24.0
go: downloading github.com/hexops/gotextdiff v1.0.3
go: downloading golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac
go: downloading github.com/quasilyte/gogrep v0.5.0
go: downloading golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
go: downloading github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567
go: downloading github.com/beorn7/perks v1.0.1
go: downloading github.com/cespare/xxhash/v2 v2.3.0
go: downloading github.com/golang/protobuf v1.5.3
go: downloading github.com/prometheus/common v0.32.1
go: downloading github.com/prometheus/procfs v0.7.3
go: downloading google.golang.org/protobuf v1.36.5
go: downloading github.com/chavacava/garif v0.1.0
go: downloading github.com/olekukonko/tablewriter v0.0.5
go: downloading github.com/hashicorp/golang-lru/v2 v2.0.7
go: downloading go.uber.org/atomic v1.7.0
go: downloading go.uber.org/multierr v1.6.0
go: downloading github.com/matttproud/golang_protobuf_extensions v1.0.1
go: downloading github.com/mattn/go-runewidth v0.0.16
go: downloading github.com/rivo/uniseg v0.4.7

=== Lint (golangci-lint) ===
level=error msg="Timeout exceeded: try increasing it by passing --timeout option"

=== Complete job ===
exitcode '4': failure
  🐳  docker exec cmd=[node /var/run/act/workflow/hashfiles/index.js] user= workdir=
Skipping step 'actions/cache@v4' due to 'success()'
⭐ Run Post actions/checkout@v4
  🐳  docker exec cmd=[node /var/run/act/actions/actions-checkout@v4/dist/index.js] user= workdir=
  💬  ::debug::Getting git version
[command]/usr/bin/git version
git version 2.47.3
  💬  ::debug::0
  💬  ::debug::git version 2.47.3%0A
  💬  ::debug::Set git useragent to: git/2.47.3 (github-actions-checkout)
  ⚙  ***
Temporarily overriding HOME='/tmp/0897e64a-ae67-4ab9-98f4-80c3af9a5db1' before making global git config changes
Adding repository directory to the temporary git global config as a safe directory
[command]/usr/bin/git config --global --add safe.directory /workspace/mike/gashy
  💬  ::debug::0
  💬  ::debug::
[command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand
  💬  ::debug::1
  💬  ::debug::
[command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :"
  💬  ::debug::0
  💬  ::debug::
[command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/git\.bros\.ninja\/\.extraheader
http.https://git.bros.ninja/.extraheader
  💬  ::debug::0
  💬  ::debug::http.https://git.bros.ninja/.extraheader%0A
[command]/usr/bin/git config --local --unset-all http.https://git.bros.ninja/.extraheader
  💬  ::debug::0
  💬  ::debug::
[command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/git\.bros\.ninja\/\.extraheader' && git config --local --unset-all 'http.https://git.bros.ninja/.extraheader' || :"
  💬  ::debug::0
  💬  ::debug::
[command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir:
  💬  ::debug::1
  💬  ::debug::
[command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url
  💬  ::debug::0
  💬  ::debug::
  💬  ::debug::Unsetting HOME override
  ✅  Success - Post actions/checkout@v4
Cleaning up container for job lint
🏁  Job failed
Job 'lint' failed
@ai wrote in https://git.bros.ninja/mike/gashy/pulls/31#issuecomment-737: > **6. [IMPORTANT] `renderDashboard` takes 18 positional parameters — `internal/handlers/render.go:48`** > > This function signature is unwieldy and error-prone. All parameters are positional with no compiler help if the order is wrong (e.g., swapping two `string` params compiles but breaks rendering). > > **HUMAN REVIEW:** This is a refactoring candidate. Consider a `DashboardRenderParams` struct to make calls self-documenting. Could be bundled with the v0.6.1 architecture improvements (OP#1705). Good call, this should be bundled into work along with the rest of the follow-up in 0.6.1 from the PR comment. Also there are pipeline errors: ``` fix(js): clean up event listeners on component destroy Commit b4ef568fa9 pushed by Mike Bros #115 Workflow ci.yaml Job lint === Set up job === sao-runner-2(version:v6.4.0) received task 2063 of job lint, be triggered by event: pull_request workflow prepared 🚀 Start image=golang:1.25 🐳 docker pull image=golang:1.25 platform= username= forcePull=false 🐳 docker create image=golang:1.25 platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="docker-shared" 🐳 docker run image=golang:1.25 platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="docker-shared" ☁ git clone 'https://code.forgejo.org/actions/checkout' # ref=v4 ☁ git clone 'https://code.forgejo.org/actions/cache' # ref=v4 ⭐ Run Main Install Node.js 🐳 docker exec cmd=[sh -e /var/run/act/workflow/0.sh] user= workdir= === Install Node.js === debconf: unable to initialize frontend: Dialog debconf: (TERM is not set, so the dialog frontend is not usable.) debconf: falling back to frontend: Readline debconf: unable to initialize frontend: Readline debconf: (This frontend requires a controlling tty.) debconf: falling back to frontend: Teletype debconf: unable to initialize frontend: Teletype debconf: (This frontend requires a controlling tty.) debconf: falling back to frontend: Noninteractive Selecting previously unselected package libcares2:amd64. (Reading database ... (Reading database ... 5% (Reading database ... 10% (Reading database ... 15% (Reading database ... 20% (Reading database ... 25% (Reading database ... 30% (Reading database ... 35% (Reading database ... 40% (Reading database ... 45% (Reading database ... 50% (Reading database ... 55% (Reading database ... 60% (Reading database ... 65% (Reading database ... 70% (Reading database ... 75% (Reading database ... 80% (Reading database ... 85% (Reading database ... 90% (Reading database ... 95% (Reading database ... 100% (Reading database ... 16724 files and directories currently installed.) Preparing to unpack .../00-libcares2_1.34.5-1+deb13u1_amd64.deb ... Unpacking libcares2:amd64 (1.34.5-1+deb13u1) ... Selecting previously unselected package libicu76:amd64. Preparing to unpack .../01-libicu76_76.1-4_amd64.deb ... Unpacking libicu76:amd64 (76.1-4) ... Selecting previously unselected package libuv1t64:amd64. Preparing to unpack .../02-libuv1t64_1.50.0-2_amd64.deb ... Unpacking libuv1t64:amd64 (1.50.0-2) ... Selecting previously unselected package node-xtend. Preparing to unpack .../03-node-xtend_4.0.2-3_all.deb ... Unpacking node-xtend (4.0.2-3) ... Selecting previously unselected package node-corepack. Preparing to unpack .../04-node-corepack_0.24.0-5_all.deb ... Unpacking node-corepack (0.24.0-5) ... Selecting previously unselected package nodejs. Preparing to unpack .../05-nodejs_20.19.2+dfsg-1_amd64.deb ... Unpacking nodejs (20.19.2+dfsg-1) ... Selecting previously unselected package node-acorn. Preparing to unpack .../06-node-acorn_8.8.1+ds+~cs25.17.7-2_all.deb ... Unpacking node-acorn (8.8.1+ds+~cs25.17.7-2) ... Selecting previously unselected package node-cjs-module-lexer. Preparing to unpack .../07-node-cjs-module-lexer_1.2.3+dfsg-1_all.deb ... Unpacking node-cjs-module-lexer (1.2.3+dfsg-1) ... Selecting previously unselected package node-balanced-match. Preparing to unpack .../08-node-balanced-match_2.0.0-1_all.deb ... Unpacking node-balanced-match (2.0.0-1) ... Selecting previously unselected package node-brace-expansion. Preparing to unpack .../09-node-brace-expansion_2.0.1+~1.1.0-2_all.deb ... Unpacking node-brace-expansion (2.0.1+~1.1.0-2) ... Selecting previously unselected package node-minimatch. Preparing to unpack .../10-node-minimatch_9.0.3-6_all.deb ... Unpacking node-minimatch (9.0.3-6) ... Selecting previously unselected package node-undici. Preparing to unpack .../11-node-undici_7.3.0+dfsg1+~cs24.12.11-1_all.deb ... Unpacking node-undici (7.3.0+dfsg1+~cs24.12.11-1) ... Selecting previously unselected package libnode115:amd64. Preparing to unpack .../12-libnode115_20.19.2+dfsg-1_amd64.deb ... Unpacking libnode115:amd64 (20.19.2+dfsg-1) ... Setting up libuv1t64:amd64 (1.50.0-2) ... Setting up node-cjs-module-lexer (1.2.3+dfsg-1) ... Setting up node-balanced-match (2.0.0-1) ... Setting up node-brace-expansion (2.0.1+~1.1.0-2) ... Setting up libcares2:amd64 (1.34.5-1+deb13u1) ... Setting up node-undici (7.3.0+dfsg1+~cs24.12.11-1) ... Setting up libicu76:amd64 (76.1-4) ... Setting up node-minimatch (9.0.3-6) ... Setting up node-xtend (4.0.2-3) ... Setting up node-acorn (8.8.1+ds+~cs25.17.7-2) ... Setting up node-corepack (0.24.0-5) ... Setting up libnode115:amd64 (20.19.2+dfsg-1) ... Setting up nodejs (20.19.2+dfsg-1) ... update-alternatives: using /usr/bin/nodejs to provide /usr/bin/js (js) in auto mode Processing triggers for libc-bin (2.41-12+deb13u1) ... === actions/checkout@v4 === ::add-matcher::/run/act/actions/actions-checkout@v4/dist/problem-matcher.json Syncing repository: mike/gashy Getting Git version info Working directory is '/workspace/mike/gashy' [command]/usr/bin/git version git version 2.47.3 Temporarily overriding HOME='/tmp/894ff524-1965-4fc3-bf42-6660609e30b4' before making global git config changes Adding repository directory to the temporary git global config as a safe directory [command]/usr/bin/git config --global --add safe.directory /workspace/mike/gashy Deleting the contents of '/workspace/mike/gashy' Initializing the repository [command]/usr/bin/git init /workspace/mike/gashy hint: Using 'master' as the name for the initial branch. This default branch name hint: is subject to change. To configure the initial branch name to use in all hint: of your new repositories, which will suppress this warning, call: hint: hint: git config --global init.defaultBranch <name> hint: hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and hint: 'development'. The just-created branch can be renamed via this command: hint: hint: git branch -m <name> Initialized empty Git repository in /workspace/mike/gashy/.git/ [command]/usr/bin/git remote add origin https://git.bros.ninja/mike/gashy Disabling automatic garbage collection [command]/usr/bin/git config --local gc.auto 0 Setting up auth [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :" [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/git\.bros\.ninja\/\.extraheader [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/git\.bros\.ninja\/\.extraheader' && git config --local --unset-all 'http.https://git.bros.ninja/.extraheader' || :" [command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir: [command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url [command]/usr/bin/git config --local http.https://git.bros.ninja/.extraheader AUTHORIZATION: basic *** Fetching the repository [command]/usr/bin/git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +b4ef568fa96be923d84f844a89afab5ba1e244f0:refs/remotes/pull/31/head From https://git.bros.ninja/mike/gashy * [new ref] b4ef568fa96be923d84f844a89afab5ba1e244f0 -> pull/31/head Determining the checkout info [command]/usr/bin/git sparse-checkout disable [command]/usr/bin/git config --local --unset-all extensions.worktreeConfig Checking out the ref [command]/usr/bin/git checkout --progress --force refs/remotes/pull/31/head Note: switching to 'refs/remotes/pull/31/head'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -c with the switch command. Example: git switch -c <new-branch-name> Or undo this operation with: git switch - Turn off this advice by setting config variable advice.detachedHead to false HEAD is now at b4ef568 fix(js): clean up event listeners on component destroy [command]/usr/bin/git log -1 --format=%H b4ef568fa96be923d84f844a89afab5ba1e244f0 ::remove-matcher owner=checkout-git:: === actions/cache@v4 === Cache Size: ~57 MB (59514381 B) [command]/usr/bin/tar -xf /tmp/782dde95-2371-4d0c-bd23-7bd197415d1d/cache.tgz -P -C /workspace/mike/gashy -z Cache restored successfully Cache restored from key: go-2deec8d72462e435c9acc823ee62950ac14a3bccb2bf4a509ffef217c2b1bc49 === Install tools === go: downloading github.com/a-h/templ v0.3.977 go: downloading github.com/fatih/color v1.16.0 go: downloading github.com/natefinch/atomic v1.0.1 go: downloading github.com/cenkalti/backoff/v4 v4.3.0 go: downloading github.com/cli/browser v1.3.0 go: downloading github.com/fsnotify/fsnotify v1.7.0 go: downloading golang.org/x/sync v0.16.0 go: downloading github.com/mattn/go-colorable v0.1.13 go: downloading github.com/mattn/go-isatty v0.0.20 go: downloading golang.org/x/mod v0.26.0 go: downloading github.com/andybalholm/brotli v1.1.0 go: downloading golang.org/x/net v0.42.0 go: downloading github.com/a-h/parse v0.0.0-20250122154542-74294addb73e go: downloading golang.org/x/sys v0.34.0 go: downloading golang.org/x/tools v0.35.0 go: downloading mvdan.cc/gofumpt v0.9.2 go: downloading golang.org/x/mod v0.29.0 go: downloading golang.org/x/sync v0.17.0 go: downloading github.com/google/go-cmp v0.7.0 go: downloading golang.org/x/tools v0.38.0 go: downloading golang.org/x/tools v0.42.0 go: downloading golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 go: downloading golang.org/x/mod v0.33.0 go: downloading golang.org/x/sync v0.19.0 === Generate templ code === (✓) Post-generation event received, processing... [ needsRestart=true needsBrowserReload=true ] (✓) Post-generation event received, processing... [ needsRestart=true needsBrowserReload=true ] (✓) Complete [ updates=127 duration=640.659119ms ] === Install golangci-lint === go: downloading github.com/golangci/golangci-lint v1.64.8 go: downloading github.com/fatih/color v1.18.0 go: downloading github.com/gofrs/flock v0.12.1 go: downloading github.com/hashicorp/go-version v1.7.0 go: downloading github.com/pelletier/go-toml/v2 v2.2.3 go: downloading github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 go: downloading github.com/spf13/cobra v1.9.1 go: downloading github.com/pelletier/go-toml v1.9.5 go: downloading github.com/spf13/pflag v1.0.6 go: downloading github.com/spf13/viper v1.12.0 go: downloading go.uber.org/automaxprocs v1.6.0 go: downloading gopkg.in/yaml.v3 v3.0.1 go: downloading github.com/mattn/go-colorable v0.1.14 go: downloading golang.org/x/tools v0.31.0 go: downloading github.com/go-viper/mapstructure/v2 v2.2.1 go: downloading github.com/ldez/grignotin v0.9.0 go: downloading github.com/mitchellh/go-homedir v1.1.0 go: downloading golang.org/x/mod v0.24.0 go: downloading github.com/golangci/plugin-module-register v0.1.1 go: downloading github.com/sirupsen/logrus v1.9.3 go: downloading github.com/stretchr/testify v1.10.0 go: downloading golang.org/x/sys v0.31.0 go: downloading github.com/go-xmlfmt/xmlfmt v1.1.3 go: downloading github.com/golangci/revgrep v0.8.0 go: downloading golang.org/x/text v0.22.0 go: downloading github.com/fsnotify/fsnotify v1.5.4 go: downloading github.com/mitchellh/mapstructure v1.5.0 go: downloading github.com/spf13/afero v1.12.0 go: downloading github.com/spf13/cast v1.5.0 go: downloading github.com/spf13/jwalterweatherman v1.1.0 go: downloading github.com/rogpeppe/go-internal v1.14.1 go: downloading github.com/alingse/asasalint v0.0.11 go: downloading github.com/tdakkota/asciicheck v0.4.1 go: downloading github.com/breml/bidichk v0.3.2 go: downloading github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 go: downloading github.com/lasiar/canonicalheader v1.1.2 go: downloading github.com/sivchari/containedctx v1.0.3 go: downloading github.com/kkHAIKE/contextcheck v1.1.6 go: downloading github.com/karamaru-alpha/copyloopvar v1.2.1 go: downloading github.com/bkielbasa/cyclop v1.2.3 go: downloading gitlab.com/bosi/decorder v0.4.2 go: downloading github.com/OpenPeeDeeP/depguard/v2 v2.2.1 go: downloading github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 go: downloading github.com/Abirdcfly/dupword v0.1.3 go: downloading github.com/charithe/durationcheck v0.0.10 go: downloading github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 go: downloading github.com/kisielk/errcheck v1.9.0 go: downloading github.com/breml/errchkjson v0.4.0 go: downloading github.com/Antonboom/errname v1.0.0 go: downloading github.com/polyfloyd/go-errorlint v1.7.1 go: downloading github.com/nishanths/exhaustive v0.12.0 go: downloading github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 go: downloading github.com/ldez/exptostd v0.4.2 go: downloading github.com/Crocmagnon/fatcontext v0.7.1 go: downloading github.com/ashanbrown/forbidigo v1.6.0 go: downloading github.com/gostaticanalysis/forcetypeassert v0.2.0 go: downloading github.com/ultraware/funlen v0.2.0 go: downloading github.com/nunnatsa/ginkgolinter v0.19.1 go: downloading 4d63.com/gocheckcompilerdirectives v1.3.0 go: downloading 4d63.com/gochecknoglobals v0.2.2 go: downloading github.com/alecthomas/go-check-sumtype v0.3.1 go: downloading github.com/uudashr/gocognit v1.2.0 go: downloading github.com/jgautheron/goconst v1.7.1 go: downloading github.com/go-critic/go-critic v0.12.0 go: downloading github.com/quasilyte/go-ruleguard/dsl v0.3.22 go: downloading github.com/fzipp/gocyclo v0.6.0 go: downloading github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 go: downloading github.com/tetafro/godot v1.5.0 go: downloading github.com/matoous/godox v1.1.0 go: downloading github.com/denis-tingaikin/go-header v0.5.0 go: downloading github.com/ldez/gomoddirectives v0.6.1 go: downloading github.com/ryancurrah/gomodguard v1.3.5 go: downloading github.com/golangci/go-printf-func-name v0.1.0 go: downloading github.com/securego/gosec/v2 v2.22.2 go: downloading github.com/xen0n/gosmopolitan v1.2.2 go: downloading honnef.co/go/tools v0.6.1 go: downloading github.com/leonklingele/grouper v1.1.2 go: downloading github.com/uudashr/iface v1.3.1 go: downloading github.com/julz/importas v0.2.0 go: downloading github.com/macabu/inamedparam v0.1.3 go: downloading github.com/gordonklaus/ineffassign v0.1.0 go: downloading github.com/sashamelentyev/interfacebloat v1.1.0 go: downloading github.com/ckaznocha/intrange v0.3.0 go: downloading github.com/butuzov/ireturn v0.3.1 go: downloading github.com/timonwong/loggercheck v0.10.1 go: downloading github.com/yagipy/maintidx v1.0.0 go: downloading github.com/ashanbrown/makezero v1.2.0 go: downloading github.com/butuzov/mirror v1.3.0 go: downloading github.com/golangci/misspell v0.6.0 go: downloading github.com/tommy-muehle/go-mnd/v2 v2.5.1 go: downloading go-simpler.org/musttag v0.13.0 go: downloading github.com/alexkohler/nakedret/v2 v2.0.5 go: downloading github.com/nakabonne/nestif v0.3.1 go: downloading github.com/gostaticanalysis/nilerr v0.1.1 go: downloading github.com/alingse/nilnesserr v0.1.2 go: downloading github.com/Antonboom/nilnil v1.0.1 go: downloading github.com/ssgreg/nlreturn/v2 v2.2.1 go: downloading github.com/sonatard/noctx v0.1.0 go: downloading github.com/firefart/nonamedreturns v1.0.5 go: downloading github.com/stbenjam/no-sprintf-host-port v0.2.0 go: downloading github.com/kunwardeep/paralleltest v1.0.10 go: downloading github.com/catenacyber/perfsprint v0.8.2 go: downloading github.com/alexkohler/prealloc v1.0.0 go: downloading github.com/nishanths/predeclared v0.2.2 go: downloading github.com/yeya24/promlinter v0.3.0 go: downloading github.com/ghostiam/protogetter v0.3.9 go: downloading github.com/curioswitch/go-reassign v0.3.0 go: downloading github.com/raeperd/recvcheck v0.2.0 go: downloading github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c go: downloading github.com/mgechev/revive v1.7.0 go: downloading github.com/jingyugao/rowserrcheck v1.1.1 go: downloading go-simpler.org/sloglint v0.9.0 go: downloading github.com/jjti/go-spancheck v0.6.4 go: downloading github.com/ryanrolds/sqlclosecheck v0.5.1 go: downloading github.com/4meepo/tagalign v1.4.2 go: downloading github.com/ldez/tagliatelle v0.7.1 go: downloading github.com/sivchari/tenv v1.12.1 go: downloading github.com/maratori/testableexamples v1.0.0 go: downloading github.com/Antonboom/testifylint v1.5.2 go: downloading github.com/maratori/testpackage v1.1.1 go: downloading github.com/kulti/thelper v0.6.3 go: downloading github.com/moricho/tparallel v0.3.2 go: downloading github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed go: downloading mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f go: downloading github.com/sashamelentyev/usestdlibvars v1.28.0 go: downloading github.com/ldez/usetesting v0.4.2 go: downloading github.com/blizzy78/varnamelen v0.8.0 go: downloading github.com/sanposhiho/wastedassign/v2 v2.1.0 go: downloading github.com/ultraware/whitespace v0.2.0 go: downloading github.com/tomarrell/wrapcheck/v2 v2.10.0 go: downloading github.com/bombsimon/wsl/v4 v4.5.0 go: downloading github.com/ykadowak/zerologlint v0.1.5 go: downloading golang.org/x/sync v0.12.0 go: downloading github.com/davecgh/go-spew v1.1.1 go: downloading github.com/pmezard/go-difflib v1.0.0 go: downloading github.com/stretchr/objx v0.5.2 go: downloading github.com/daixiang0/gci v0.13.5 go: downloading github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d go: downloading mvdan.cc/gofumpt v0.7.0 go: downloading github.com/subosito/gotenv v1.4.1 go: downloading github.com/hashicorp/hcl v1.0.0 go: downloading gopkg.in/ini.v1 v1.67.0 go: downloading github.com/magiconair/properties v1.8.6 go: downloading github.com/sourcegraph/go-diff v0.7.0 go: downloading github.com/gostaticanalysis/analysisutil v0.7.1 go: downloading github.com/go-toolsmith/astcast v1.1.0 go: downloading github.com/gobwas/glob v0.2.3 go: downloading gopkg.in/yaml.v2 v2.4.0 go: downloading github.com/go-toolsmith/astcopy v1.1.0 go: downloading github.com/go-toolsmith/astequal v1.2.0 go: downloading github.com/go-toolsmith/astfmt v1.1.0 go: downloading github.com/go-toolsmith/astp v1.1.0 go: downloading github.com/go-toolsmith/strparse v1.1.0 go: downloading github.com/go-toolsmith/typep v1.1.0 go: downloading github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 go: downloading github.com/Masterminds/semver/v3 v3.3.0 go: downloading github.com/ccojocar/zxcvbn-go v1.0.2 go: downloading github.com/gostaticanalysis/comment v1.5.0 go: downloading github.com/prometheus/client_golang v1.12.1 go: downloading github.com/prometheus/client_model v0.2.0 go: downloading github.com/fatih/structtag v1.2.0 go: downloading github.com/ettle/strcase v0.2.0 go: downloading github.com/hashicorp/go-immutable-radix/v2 v2.1.0 go: downloading go.uber.org/zap v1.24.0 go: downloading github.com/hexops/gotextdiff v1.0.3 go: downloading golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac go: downloading github.com/quasilyte/gogrep v0.5.0 go: downloading golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 go: downloading github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 go: downloading github.com/beorn7/perks v1.0.1 go: downloading github.com/cespare/xxhash/v2 v2.3.0 go: downloading github.com/golang/protobuf v1.5.3 go: downloading github.com/prometheus/common v0.32.1 go: downloading github.com/prometheus/procfs v0.7.3 go: downloading google.golang.org/protobuf v1.36.5 go: downloading github.com/chavacava/garif v0.1.0 go: downloading github.com/olekukonko/tablewriter v0.0.5 go: downloading github.com/hashicorp/golang-lru/v2 v2.0.7 go: downloading go.uber.org/atomic v1.7.0 go: downloading go.uber.org/multierr v1.6.0 go: downloading github.com/matttproud/golang_protobuf_extensions v1.0.1 go: downloading github.com/mattn/go-runewidth v0.0.16 go: downloading github.com/rivo/uniseg v0.4.7 === Lint (golangci-lint) === level=error msg="Timeout exceeded: try increasing it by passing --timeout option" === Complete job === exitcode '4': failure 🐳 docker exec cmd=[node /var/run/act/workflow/hashfiles/index.js] user= workdir= Skipping step 'actions/cache@v4' due to 'success()' ⭐ Run Post actions/checkout@v4 🐳 docker exec cmd=[node /var/run/act/actions/actions-checkout@v4/dist/index.js] user= workdir= 💬 ::debug::Getting git version [command]/usr/bin/git version git version 2.47.3 💬 ::debug::0 💬 ::debug::git version 2.47.3%0A 💬 ::debug::Set git useragent to: git/2.47.3 (github-actions-checkout) ⚙ *** Temporarily overriding HOME='/tmp/0897e64a-ae67-4ab9-98f4-80c3af9a5db1' before making global git config changes Adding repository directory to the temporary git global config as a safe directory [command]/usr/bin/git config --global --add safe.directory /workspace/mike/gashy 💬 ::debug::0 💬 ::debug:: [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand 💬 ::debug::1 💬 ::debug:: [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :" 💬 ::debug::0 💬 ::debug:: [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/git\.bros\.ninja\/\.extraheader http.https://git.bros.ninja/.extraheader 💬 ::debug::0 💬 ::debug::http.https://git.bros.ninja/.extraheader%0A [command]/usr/bin/git config --local --unset-all http.https://git.bros.ninja/.extraheader 💬 ::debug::0 💬 ::debug:: [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/git\.bros\.ninja\/\.extraheader' && git config --local --unset-all 'http.https://git.bros.ninja/.extraheader' || :" 💬 ::debug::0 💬 ::debug:: [command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir: 💬 ::debug::1 💬 ::debug:: [command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url 💬 ::debug::0 💬 ::debug:: 💬 ::debug::Unsetting HOME override ✅ Success - Post actions/checkout@v4 Cleaning up container for job lint 🏁 Job failed Job 'lint' failed ```
fix(ci): increase golangci-lint timeout to 5 minutes
All checks were successful
CI / validate-branch (pull_request) Successful in 1s
CI / validate-release-pr (pull_request) Successful in 4s
CI / build (pull_request) Successful in 46s
CI / security (pull_request) Successful in 2m25s
CI / lint (pull_request) Successful in 5m22s
CI / test (pull_request) Successful in 2m16s
291b5ed870
The lint job was failing in CI with "Timeout exceeded" because the
default 1-minute timeout is insufficient for the growing codebase.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Contributor

PR Review Followup

Responding to review posted on 2026-02-23T02:47:32Z (comment #737) and maintainer feedback in comment #738.

Triage Summary

Severity Count Action
Important (security) 4 Deferred to v0.6.1 (per maintainer)
Important (code quality) 4 Deferred to v0.6.1 (per maintainer)
Important (testing) 1 Deferred to v0.6.1
Important (human review) 1 Bundled into existing OP#1705 (per maintainer)
Minor 7 Deferred to v0.6.1 (4 new WPs + 1 existing updated)
Nitpick 3 Added to existing OP#1711
CI Pipeline 1 Fixed in this branch

Fix Applied

  1. [CI] golangci-lint timeout — fixed in 291b5ed
    • Increased --timeout to 5m in .forgejo/workflows/ci.yaml to resolve lint job failure

Deferred to v0.6.1

Per maintainer direction in comment #738, all review findings are tracked for v0.6.1. Version: v0.6.1 — PR Review Followup — now 19 work packages total.

New work packages created (11):

OP# Type Priority Subject
1712 Bug High fix: atomic token validate-and-consume to prevent TOCTOU race
1713 Bug Normal fix: add missing FK index on users.oidc_provider_id
1714 Bug Normal fix: require authentication for PreviewStart endpoint
1715 Bug Normal fix: implement http.Flusher on gzip response writer
1716 Bug Normal fix: demo handler transaction safety and division-by-zero guard
1717 Task Normal test: add tests for compress, static, and SSE hub components
1718 Bug Normal fix: add CSRF tokens to fetch() POST calls in edit-mode.js
1719 Bug Normal fix: handle SSE write errors on client disconnect
1720 Bug Low fix: prevent email enumeration in auth responses
1721 Task Low fix: encrypt OIDC client_secret at application level
1722 Task Low refactor: extract dashboard user groups and query helpers

Existing work packages updated (3):

OP# Addition
1705 Added renderDashboard 18 positional params finding (per maintainer #738)
1707 Added migration 00023 Down failure with NULL oidc_subject
1711 Added redundant DEFAULT NULL in migration 00029

Human Review Items (Unchanged)

  1. HUMAN REVIEW: renderDashboard 18 positional parameters — bundled into OP#1705 per maintainer decision

Test Results

  • Tests: All passed (go test ./... — 0 failures)
  • go vet: Clean
  • CI fix: golangci-lint timeout resolved (291b5ed)

This followup was generated by the pr-followup skill.

## PR Review Followup Responding to review posted on 2026-02-23T02:47:32Z ([comment #737](https://git.bros.ninja/mike/gashy/pulls/31#issuecomment-737)) and maintainer feedback in [comment #738](https://git.bros.ninja/mike/gashy/pulls/31#issuecomment-738). ### Triage Summary | Severity | Count | Action | | -------- | ----- | ------ | | Important (security) | 4 | Deferred to v0.6.1 (per maintainer) | | Important (code quality) | 4 | Deferred to v0.6.1 (per maintainer) | | Important (testing) | 1 | Deferred to v0.6.1 | | Important (human review) | 1 | Bundled into existing OP#1705 (per maintainer) | | Minor | 7 | Deferred to v0.6.1 (4 new WPs + 1 existing updated) | | Nitpick | 3 | Added to existing OP#1711 | | **CI Pipeline** | **1** | **Fixed in this branch** | ### Fix Applied 1. **[CI]** golangci-lint timeout — fixed in 291b5ed - Increased `--timeout` to 5m in `.forgejo/workflows/ci.yaml` to resolve lint job failure ### Deferred to v0.6.1 Per maintainer direction in [comment #738](https://git.bros.ninja/mike/gashy/pulls/31#issuecomment-738), all review findings are tracked for v0.6.1. Version: [v0.6.1 — PR Review Followup](https://project.bros.ninja/versions/122) — now 19 work packages total. **New work packages created (11):** | OP# | Type | Priority | Subject | |-----|------|----------|---------| | 1712 | Bug | High | fix: atomic token validate-and-consume to prevent TOCTOU race | | 1713 | Bug | Normal | fix: add missing FK index on users.oidc_provider_id | | 1714 | Bug | Normal | fix: require authentication for PreviewStart endpoint | | 1715 | Bug | Normal | fix: implement http.Flusher on gzip response writer | | 1716 | Bug | Normal | fix: demo handler transaction safety and division-by-zero guard | | 1717 | Task | Normal | test: add tests for compress, static, and SSE hub components | | 1718 | Bug | Normal | fix: add CSRF tokens to fetch() POST calls in edit-mode.js | | 1719 | Bug | Normal | fix: handle SSE write errors on client disconnect | | 1720 | Bug | Low | fix: prevent email enumeration in auth responses | | 1721 | Task | Low | fix: encrypt OIDC client_secret at application level | | 1722 | Task | Low | refactor: extract dashboard user groups and query helpers | **Existing work packages updated (3):** | OP# | Addition | |-----|----------| | 1705 | Added `renderDashboard` 18 positional params finding (per maintainer #738) | | 1707 | Added migration 00023 Down failure with NULL oidc_subject | | 1711 | Added redundant `DEFAULT NULL` in migration 00029 | ### Human Review Items (Unchanged) 1. **HUMAN REVIEW:** `renderDashboard` 18 positional parameters — bundled into OP#1705 per maintainer decision ### Test Results - **Tests:** All passed (`go test ./...` — 0 failures) - **go vet:** Clean - **CI fix:** golangci-lint timeout resolved (291b5ed) --- _This followup was generated by the pr-followup skill._
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
mike/gashy!31
No description provided.