{{ message }}
fix(auth): apply host-trust gate to auto-login#1122
Merged
Conversation
`sentry auth login` refuses to authenticate against an unconfirmed self-hosted host unless `--url` is passed, but the auto-login middleware (triggered when any command hits an auth error in a TTY) called the OAuth device flow with no such gate. So `sentry auth whoami` — and any other command — could start an unconfirmed self-hosted login that `auth login` would have refused (#1121). Since a `.sentryclirc` shim can inject `env.SENTRY_URL`, the bypass also reopened the OAuth-phishing vector the `auth login` gate was added to close. Extract the shared trust logic into `login-host-guard.ts` and call it from both paths. Auto-login is deliberately a touch more lenient than the explicit gate: it also trusts the stored token's host, so an expired self-hosted session can re-auth without forcing `--url` again — but an injected host that differs from the stored host is still refused. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address review: the stored token's host can't anchor expired-session re-auth because both expired paths call clearAuth() before the AuthError reaches the middleware (db/auth.ts refreshToken), so getStoredAuthHost() is always undefined by the time the guard runs — self-hosted re-auth was wrongly refused. Use defaults.url instead: it survives clearAuth() and is only written by explicit, confirmed user actions (auth login --url, cli defaults url, confirmed cli import), never by the .sentryclirc env shim. So expired re-auth against a previously confirmed host proceeds, while an injected host that doesn't match is still refused. Also tidy up: move shouldAutoAuth above its JSDoc so the doc attaches to autoAuthMiddleware, and drop a filler test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Contributor
Codecov Results 📊✅ Patch coverage is 81.25%. Project has 5050 uncovered lines. Files with missing lines (1)Coverage diff@@ Coverage Diff @@
## main #PR +/-##
==========================================
+ Coverage 81.24% 81.26% +0.02%
==========================================
Files 388 390 +2
Lines 26936 26954 +18
Branches 17493 17505 +12
==========================================
+ Hits 21884 21904 +20
- Misses 5052 5050 -2
- Partials 1825 1826 +1Generated by Codecov Action |
Move the auto-login recovery flow out of the cli.ts middleware closure into a dependency-injected module (lib/auto-auth.ts) so the host-trust gate and login/retry behavior are unit-testable — cli.ts itself has no test coverage, which left the #1121 fix's patch coverage at 37.5%. The new module is covered 100%. Also close a second instance of the same bypass flagged in review: scopeRecoveryMiddleware (added by #1032) runs the OAuth device flow on a 403 missing-scope error without the host-trust gate, so a .sentryclirc-injected SENTRY_URL could steer re-auth to an attacker host. Both interactive re-auth paths now call the shared assertAutoLoginHostTrusted(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e2c9654. Configure here.
Address review: isAutoLoginHostTrusted only consulted SaaS, the process anchor, and defaults.url. On a 403 scope-recovery the OAuth token row is still present (clearAuth has not run), and a self-hosted user whose effective host matches their token host but who has no persisted default URL — e.g. defaults.url persistence failed (it's best-effort) or was cleared — was wrongly refused. Also trust the stored token host, which setAuthToken always records. It's the authoritative anchor when a token exists (scope recovery), complementing defaults.url which covers the expired path where the token row is already wiped. isHostTrusted requires an exact host match, so an injected env.SENTRY_URL pointing elsewhere is still refused. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BYK
approved these changes
Jun 23, 2026
This was referenced Jun 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Fixes #1121.
sentry auth loginrefuses to authenticate against an unconfirmed self-hosted host unless--urlconfirms it. But the auto-login middleware (the error-recovery path that runs the OAuth device flow when any command hits an auth error in an interactive TTY) calledrunInteractiveLogin()with no such gate. Sosentry auth whoami— and any other command — could start an unconfirmed self-hosted login thatsentry auth loginwould have refused:Because a
.sentryclircshim can injectenv.SENTRY_URL, this bypass also reopened the OAuth-phishing vector theauth logingate was added to close (seetest/lib/security/login-token-rc-poison.test.ts).Changes
src/lib/login-host-guard.ts— single source of truth for login host trust:resolveEffectiveLoginHost(),isLoginHostTrusted()(explicit-login gate),isAutoLoginHostTrusted()(auto-login gate), andbuildHostRefusalMessage().src/cli.ts— the auto-auth middleware now resolves the effective host and throwsHostScopeErrorwith the standard refusal message before any browser opens, if the host isn't auto-login-trusted. Extracted ashouldAutoAuthtype-guard to keep the function under the complexity limit.src/commands/auth/login.ts—refuseLoginToUntrustedHostandapplyLoginUrlnow reuse the shared helpers (no behavior change; refusal wording preserved).Design note
isAutoLoginHostTrustedis deliberately more lenient than the explicitauth logingate: it additionally trusts the stored token's host. This avoids a regression where a legitimate self-hosted user with an expired session would be forced to re-runauth login --urlon every token expiry. An injected host that differs from the stored host (e.g. poisonedenv.SENTRY_URL→evil.com) is still refused. The reporter's exact case (not_authenticated, no stored token) correctly falls through to refusal, matching the issue's Expected Result.Testing
test/lib/security/auto-login-host-guard.test.ts(15 cases): env host resolution, the explicit-login gate, the auto-login predicate (bug case refused, expired re-auth allowed, injected host refused, anchor/SaaS trusted), and refusal messages.test/lib/security+test/commands/auth+ db host suite: 146 passed. Typecheck and biome clean.🤖 Generated with Claude Code