Skip to content

Revoke sessions on CLI logout, audit seeding and MFA auto-locks, enforce forbidigo, add SECURITY.md - #142

Merged
ndbroadbent merged 2 commits into
mainfrom
nathan/security-audit-fixes
Oct 13, 2026
Merged

ndbroadbent merged 2 commits into
mainfrom
nathan/security-audit-fixes

Conversation

@ndbroadbent

Copy link
Copy Markdown
Member

Fixes #127, fixes #128, fixes #129, fixes #130, fixes #132.

CLI logout ends the session (#127)

  • New POST /api/v1/auth/cli/logout: revokes the session sent as the bearer token. Cookies are ignored (so a page
    can't log a browser out) and API tokens get 401. It's audited like web logout: the attempt is written first, and
    the logout is refused with 503 if that write fails.
  • rack-gateway logout calls it before removing the rack. If the gateway can't be reached or refuses, the rack is
    kept and the error suggests --local, which removes the rack without contacting the gateway.

Seeding (#128)

  • Startup seeding only creates the seed lists' missing users (a user in several lists gets each list's role). It
    never changes an existing user, so a role changed in the web UI stays changed.
  • Each created user is recorded as user.create by system in the same transaction. If that can't be written,
    nothing is created and the gateway doesn't start.
  • reset-db no longer seeds; the gateway does it, audited, when it next starts. Removed InitializeAdmin.

MFA auto-lock (#129)

  • The lock after 5 failed MFA attempts in 5 minutes writes user.lock by system in the same transaction. If it
    can't, the user isn't locked, and the error goes to Sentry and fails the health check.
  • Every unlocked admin is emailed ("User locked: {email}") as well as the user. Admins are looked up when it happens.

forbidigo (#130)

  • The patterns ended in \(, but forbidigo matches the identifier (time.Sleep), so none ever fired. They're now
    ^panic$, ^fmt\.Print(f|ln)?$ and ^time\.Sleep$, with fmt.Print* allowed in cmd/ and internal/cli/.
    Checked with a probe file that all three now fire.
  • Removed every time.Sleep and panic:
    • OIDC discovery retries against a deadline.
    • CLI polling waits on a timer with the command's context.
    • The mock Convox log stream doesn't pause.
    • dbtest polls with a retry helper.
    • The rate limiter takes an injectable clock (tests no longer sleep), and DB tests set timestamps.
    • Router setup returns its trusted-proxy error instead of panicking.
    • convoxguard.Run(m) replaces the TestMain panics.
  • Also removed the unused ratelimit.AuthEndpointsOnly.
  • The gateway's own PKCE verifier (gateway ↔ identity provider) was 128 random bytes, or 171 characters, over RFC
    7636's 128-character limit. It's now 32 bytes (43 characters), the RFC's recommended size.

Security policy (#132)

  • SECURITY.md: report privately through GitHub's "Report a vulnerability" or to security@docspring.com; only the
    latest release is supported. Private vulnerability reporting is now enabled on the repository.
  • The docs' security and contributing pages link to it.

Tests

  • Route tests for CLI logout: it revokes the session, ignores cookies and API tokens, and fails closed.
  • CLI tests: logout on 200/401, keeps the rack on 503, and --local.
  • Seed tests: creates and audits missing users, leaves existing users alone, and creates nothing without the audit
    entry.
  • Auto-lock tests: audited, admins emailed, not repeated, and fails closed.
  • The CLI E2E logout check now expects "the session is ended on the gateway".
  • task go:lint and task go:test:unit (1,329 tests) pass locally, and the docs build with no broken links.

… enforce forbidigo

- `rack-gateway logout` ends the session on the gateway (new POST /api/v1/auth/cli/logout, bearer token only,
  audited fail-closed) before removing the rack; `--local` skips the gateway. (#127)
- Startup seeding only creates missing users and never changes existing roles. Each created user is recorded as
  user.create by system in the same transaction, and the gateway won't start if it can't be. reset-db no longer
  seeds. (#128)
- The MFA auto-lock writes user.lock by system in the same transaction as the lock (nothing is locked if it can't),
  and emails every unlocked admin as well as the user. (#129)
- forbidigo patterns now match (they had parentheses, so never fired). Removed every time.Sleep and panic: timers
  with contexts, injectable clocks and explicit timestamps in tests, convoxguard.Run for TestMain. The gateway's
  PKCE verifier is now RFC 7636's 43 characters. Removed the unused ratelimit AuthEndpointsOnly. (#130)
- SECURITY.md, and private vulnerability reporting on GitHub. (#132)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

  • Run on-demand review

This review includes 64 billable files and costs up to $16.00.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 19 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 61 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: e2b88bbf-077d-4d73-ab22-6a711293dec5
📥 Commits

Reviewing files that changed from the base of the PR and between 1d8ae9f and 2609477.

⛔ Files ignored due to path filters (2)
  • internal/gateway/openapi/generated/swagger.json is excluded by !**/generated/**
  • web/src/api/types.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (64)
  • .golangci.yml
  • README.md
  • SECURITY.md
  • cmd/mock-convox/handlers_logs.go
  • docs/src/content/docs/concepts/rbac-principles.mdx
  • docs/src/content/docs/configuration/environment-variables.mdx
  • docs/src/content/docs/deployment/terraform/multi-region.mdx
  • docs/src/content/docs/development/api-reference.mdx
  • docs/src/content/docs/development/contributing.mdx
  • docs/src/content/docs/integrations/email.mdx
  • docs/src/content/docs/integrations/index.mdx
  • docs/src/content/docs/operations/database-maintenance.mdx
  • docs/src/content/docs/operations/troubleshooting.mdx
  • docs/src/content/docs/security/authentication/api-tokens.mdx
  • docs/src/content/docs/security/authentication/index.mdx
  • docs/src/content/docs/security/authentication/sessions.mdx
  • docs/src/content/docs/security/index.mdx
  • docs/src/content/docs/user-guide/cli/authentication.mdx
  • docs/src/content/docs/user-guide/cli/commands.mdx
  • docs/src/content/docs/user-guide/cli/index.mdx
  • docs/src/content/docs/user-guide/cli/multi-rack.mdx
  • internal/cli/CLAUDE.md
  • internal/cli/cli_logout.go
  • internal/cli/cli_logout_test.go
  • internal/cli/deploy_approvals_request.go
  • internal/cli/deploy_approvals_wait.go
  • internal/cli/pause.go
  • internal/gateway/app/app.go
  • internal/gateway/app/setup.go
  • internal/gateway/audit/deploy_approval_expiry.go
  • internal/gateway/audit/main_test.go
  • internal/gateway/audit/system_entries.go
  • internal/gateway/auth/main_test.go
  • internal/gateway/auth/mfa/auto_lock.go
  • internal/gateway/auth/mfa/auto_lock_test.go
  • internal/gateway/auth/mfa/service.go
  • internal/gateway/auth/oauth.go
  • internal/gateway/auth/service_test.go
  • internal/gateway/db/audit_aggregation_merge_test.go
  • internal/gateway/db/database.go
  • internal/gateway/db/database_test.go
  • internal/gateway/db/seeds.go
  • internal/gateway/db/seeds_test.go
  • internal/gateway/db/user_lock.go
  • internal/gateway/handlers/auth_cli_logout.go
  • internal/gateway/handlers/auth_helpers.go
  • internal/gateway/ratelimit/ratelimit.go
  • internal/gateway/ratelimit/ratelimit_test.go
  • internal/gateway/rbac/main_test.go
  • internal/gateway/routes/cli_logout_test.go
  • internal/gateway/routes/main_test.go
  • internal/gateway/routes/route_registration.go
  • internal/gateway/testutil/convoxguard/convoxguard.go
  • internal/gateway/testutil/dbtest/dbtest.go
  • internal/gateway/testutil/dbtest/retry.go
  • internal/gateway/testutil/dbtest/template.go
  • internal/integration/main_test.go
  • main_test.go
  • scripts/lib/cli-e2e/cli_helpers.sh
  • taskfiles/Taskfile.go.yml
  • web/src/api/generated.ts
  • web/src/api/openapi.json
  • web/src/api/schemas/handlersCLILogoutResponse.ts
  • web/src/api/schemas/index.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@deepsource-io

deepsource-io Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 1d8ae9f...2609477 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
JavaScript Oct 13, 2026 1:50a.m. Review ↗
Go Oct 13, 2026 1:50a.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

CI runs golangci-lint on ./..., which includes scripts/test-libfido2.go (a command-line program, so fmt.Print is
its output). task go:lint only linted cmd, internal and tools, so it passed locally. It now lints ./... too, with
node_modules excluded (a web dependency ships Go files), and reruns when the lint config or scripts change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ndbroadbent
ndbroadbent merged commit be5314d into main Oct 13, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment