Skip to content

fix(security): defence-in-depth AuthUser check in Router.Route#84

Merged
cristim merged 1 commit into
feat/multicloud-web-frontendfrom
fix/router-authuser-enforcement
Apr 28, 2026
Merged

fix(security): defence-in-depth AuthUser check in Router.Route#84
cristim merged 1 commit into
feat/multicloud-web-frontendfrom
fix/router-authuser-enforcement

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented Apr 25, 2026

Summary

Fixes #60. Router.Route only enforced authentication when the matched route had Auth == AuthAdmin. Routes declared as Auth: AuthUser (/api/auth/logout, /api/auth/me, /api/auth/profile, /api/auth/change-password, /api/api-keys, /api/federation/iac) fell through with no router-level check, relying entirely on the validateSecurity → authenticate middleware in handler.go.

That middleware does protect these routes today (they're not listed in isPublicEndpoint), so this isn't an exploitable gap right now — but it's a fragile single-point-of-failure. A future refactor that reorders middleware, or a new code path that bypasses validateSecurity, would silently expose every AuthUser endpoint.

Audit

Aspect Finding
Bug confirmed? Yes — internal/api/router.go:213-226 only checks AuthAdmin.
Currently exploitable? No. validateSecurity (handler.go:272) → authenticate (middleware.go:285) covers all AuthUser paths today via the isPublicEndpoint allow-list.
Latent risk? Yes. Single layer of defence; any change to middleware ordering or route registration could expose every AuthUser endpoint with zero compile-time signal.

Fix

  • Add requireAuth(ctx, req) helper in middleware.go — reuses the existing h.authenticate (admin API key, user API key, or session bearer token), returns a 401 ClientError on rejection.
  • Convert the AuthAdmin-only if in Router.Route to a switch on route.Auth:
    • AuthAdminrequireAdmin (unchanged)
    • AuthUserrequireAuth (new)
    • AuthPublic → no-op (still gated by isPublicEndpoint in middleware)
  • Tighten the AuthLevel doc comments to spell out where each level is enforced — addresses the issue's second ask.

This is a defence-in-depth change: the middleware still runs first; the router check is a redundant safety net that ensures the route's declared auth level is honoured at dispatch time regardless of upstream middleware decisions.

Test plan

  • Unit tests in internal/api/router_authuser_test.go cover:
    • AuthUser route rejects when no credential is presented (401 ClientError)
    • AuthUser route rejects when bearer token is invalid (401 ClientError)
    • AuthUser route accepts a valid non-admin user session (no regression)
    • AuthPublic route still dispatches with no credentials (no regression)
    • requireAuth helper unit tests for admin API key / user session / no cred
  • go build ./... passes
  • go test ./internal/api/... passes (existing suite + new tests)
  • go vet ./... clean
  • Pre-commit hooks all pass (gosec, trivy, etc.)
  • CodeRabbit review
  • CI green
  • Manual smoke against deployed Lambda after merge (merge-watch handles this)

Router.Route previously enforced authentication only when the matched
Route had Auth == AuthAdmin. Routes declared with Auth: AuthUser
(/api/auth/logout, /api/auth/me, /api/auth/profile,
/api/auth/change-password, /api/api-keys, /api/federation/iac) fell
through with no router-level check, relying entirely on the
validateSecurity → authenticate middleware in handler.go. That worked
today, but a future refactor that reordered middleware or added a route
that bypassed validateSecurity would silently expose every AuthUser
endpoint.

Add a switch on route.Auth in Router.Route that calls a new
h.requireAuth(ctx, req) helper for AuthUser routes. requireAuth reuses
the existing h.authenticate path (admin API key, user API key, or
session bearer token), so the accepted credentials are identical to the
middleware — this is purely a belt-and-braces re-check at dispatch time.
AuthPublic remains a no-op (still gated by isPublicEndpoint in the
middleware).

Also tighten the AuthLevel doc comments to spell out where each level is
enforced (Router.Route via requireAdmin/requireAuth, isPublicEndpoint
for AuthPublic), per the issue's second ask.

Tests in router_authuser_test.go cover:
- AuthUser route rejects when no credential is presented (401)
- AuthUser route rejects when bearer token is invalid (401)
- AuthUser route accepts a valid non-admin user session
- AuthPublic route still dispatches with no credentials (no regression)
- requireAuth unit tests for admin API key / user session / no cred

Closes #60
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 25, 2026

Warning

Rate limit exceeded

@cristim has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 15 minutes and 7 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 15 minutes and 7 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 52e7578c-4b68-4c85-b279-3657037c40d4

📥 Commits

Reviewing files that changed from the base of the PR and between 2e33c88 and 4adaf1c.

📒 Files selected for processing (3)
  • internal/api/middleware.go
  • internal/api/router.go
  • internal/api/router_authuser_test.go
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/router-authuser-enforcement

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@cristim
Copy link
Copy Markdown
Member Author

cristim commented Apr 25, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 25, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim cristim added triaged Item has been triaged priority/p1 Next up; this sprint severity/high Significant harm urgency/this-sprint Within the current sprint impact/all-users Affects every user effort/s Hours type/security Security finding labels Apr 28, 2026
@cristim
Copy link
Copy Markdown
Member Author

cristim commented Apr 28, 2026

P1 — defence-in-depth security fix: router silently bypassed AuthUser enforcement, leaving a fragile single layer of protection. CI green, MERGEABLE, CodeRabbit passed. Ready to merge. (triage agent wave2-E)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/all-users Affects every user priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/security Security finding urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant