Dashboard › cli › Distillation
9a7a358a-b88a-4d64-85f8-18828c659f22["lore_tm_v1_PYVzCjOKI_gAiSWNjaSdO6YN2sjwOizTXuSq-cezv4Y","lore_tm_v1_aXEKKpqUpSMyDg4a9WKe7Tj2hBO0ZCUXY6IyYAjdTtk","lore_tm_v1_aAsNSn77QxGckBvm3zUYat_4N5IAgE_jV2K93o-zbWE","lore_tm_v1_MvvAqVxVNFaJ_Bvzw8WmS1C_nk3eyi2gcVCvqmmubhs"]
Date: Sep 10, 2026
packages/cli/test/lib/security/auto-login-host-guard.test.ts regression-tests getsentry/cli#1121: auto-login error recovery/device flow previously accepted unconfirmed self-hosted env.SENTRY_HOST/SENTRY_URL, including values injected through .sentryclirc, bypassing the explicit sentry auth login host-trust gate and reopening an OAuth-phishing vector.resolveEffectiveLoginHost() tests establish precedence/fallback: no SENTRY_HOST/SENTRY_URL → https://sentry.io; SENTRY_HOST="https://sentry.example.com" → that origin; absent SENTRY_HOST with SENTRY_URL="https://sentry.example.com/" → normalized https://sentry.example.com.isLoginHostTrusted() explicit auth login gate trusts SaaS (https://sentry.io) and a self-hosted host only with a matching registerLoginTrustAnchor(); a persisted defaults.url alone intentionally does not relax explicit-login trust, so self-hosted login still requires explicit --url.isAutoLoginHostTrusted() allows SaaS, self-hosted hosts matching a persisted default URL, hosts matching the stored token host even without a default URL during 403 scope recovery, and matching login trust anchors. It refuses unconfirmed self-hosted hosts and injected hosts differing from either the confirmed default or stored token host.clearAuth() before AuthError reaches middleware, removing the stored token row; defaults.url survives, allowing re-authentication against the formerly confirmed self-hosted host.buildHostRefusalMessage("https://sentry.example.com") tells users: Refusing to log in against https://sentry.example.com — --url was not provided. and instructs sentry auth login --url https://sentry.example.com. With { tokenFlag: true, rcSource: "/repo/.sentryclirc" }, it says the URL was read from .sentryclirc (/repo/.sentryclirc) but is unconfirmed and gives sentry auth login --url https://sentry.example.com --token <your-token>.packages/cli/test/lib/constants.test.ts establishes normalizeUrl() behavior: undefined, empty, and whitespace-only inputs return undefined; bare hostnames—including sentry.io and ports—gain https://; whitespace is trimmed; supplied http://, https://, case-insensitive HTTP:///HTTPS://, paths, and trailing slashes are preserved.getCliEnvironment() maps 0.0.0-dev (including uninjected development version) to development, versions such as 0.24.0-dev.1740000000 and 1.0.0-dev.1700000000 to nightly, and stable versions such as 0.20.0, 1.0.0, and 0.23.0 to production.getConfiguredSentryUrl() returns undefined absent SENTRY_HOST/SENTRY_URL; normalizes either bare variable; gives SENTRY_HOST precedence over SENTRY_URL; otherwise uses a sntrys_ SENTRY_AUTH_TOKEN URL claim (tested with http://localhost:8000); explicit host configuration overrides a token URL claim; and preserves preexisting protocols including local-development http://localhost:8000.packages/cli/src/commands/auth/login.ts, persistLoginUrlAsDefault(flagUrl, effectiveHost) persists a non-SaaS host via setDefaultUrl(effectiveHost) only when --url was explicitly passed; DB persistence failure is non-fatal and logs guidance to set SENTRY_HOST or run sentry cli defaults url ${effectiveHost}.applyLoginUrl(url) writes explicit --url to both env.SENTRY_HOST and env.SENTRY_URL, registers it with registerLoginTrustAnchor(url), and returns it. Without --url, it returns resolveEffectiveLoginHost() and does not register an env/.sentryclirc-derived host as trusted.handleExistingAuth(force) behavior: active environment token warns that OAuth credentials are stored separately; if no stored credentials exist, login proceeds; otherwise --force clears credentials via clearAuth() and proceeds, interactive TTY prompts Already authenticated as ${identity}. Re-authenticate? with default false, and non-interactive operation without --force logs an instruction to use --force or sentry auth logout and blocks.handleTokenValidationError(error) clears authentication and throws new AuthError("invalid", "Invalid API token. Please check your token and try again.") only for ApiError status 401 or 403; network, 5xx, parse, and other errors preserve the token and rethrow the original error.loginCommand documentation describes OAuth device-code and --token login; for self-hosted instances it requires --url <url> as the only way to trust a new host, rejecting URL arguments/config-file hosts that do not match the currently authenticated host.packages/cli/src/lib/custom-headers.ts parses SENTRY_CUSTOM_HEADERS or SQLite defaults.headers for self-hosted reverse-proxy authentication use cases such as Google IAP and Cloudflare Access. Format is semicolon- or newline-separated Name: Value pairs; environment value takes precedence over SQLite defaults.parseCustomHeaders(raw) skips empty segments; strips trailing \r; validates names against VALID_HEADER_NAME_RE = /^[!#$%&'*+\-.^_\|~\w]+$/; preserves declaration order; and rejects malformed entries, empty names, invalid names, and reserved names: authorization, host, content-type, content-length, user-agent, sentry-trace, and baggage`.getCustomHeaders() returns no headers when none are configured or when target routing is SaaS/no custom URL; it warns once in the latter case. It identifies self-hosted targets as configured URLs for which isSentrySaasUrl(configured) is false, caches parsed results by raw source string, and re-evaluates host class on each call because URL parsing can dynamically set SENTRY_HOST.applyCustomHeaders(headers, requestUrl) only injects custom headers when isRequestOriginTrustedForCustomHeaders(requestUrl) approves the destination based on the active token’s trust class or an explicit --url login anchor during no-token bootstrap. It fails closed with neither token nor anchor and logs once when skipping an untrusted destination, recommending sentry auth login --url <url>.