Dashboard › cli › Distillation
f3cb7fdc-e893-40fb-8b82-cbbde14d1eee["lore_tm_v1_VmUhsW-GHpS7mu_nNwG86yy8FUW2ahJ5sCZ30v0gtwg","lore_tm_v1_zctLeeQziXDbT2Mjky5y1B7dbNhO3Q_iusBcId7D7Tk","lore_tm_v1_z7H3HQYg7OFJCWTb5-MQ36xwohfFSP7mfCbkTd2OHQ8","lore_tm_v1_y5yN6KrXnAKTJK-_aPwf2skOaPMl4yZCBoyf3EIJQVo","lore_tm_v1_2ZlWvnm7pDQk6CCV2iL1nAcmCLpx1rqcqYbK0pxvcWc","lore_tm_v1_X1UclwX6F9821asQ-0as0hYIDTk-_DAgG1v6i7nVUE0","lore_tm_v1_ss-KhyGZLh9QdJj_mE-_Hi_D0xqIv_2vrOVIUKCg0b8","lore_tm_v1_IA6zZTRVSz4akjG3ELcVr6t8-JBsrWpa1sdfcU7aEXc","lore_tm_v1_-wYFSBly-BJzcf5k2uhNeaumx3E5dSif-GMKnXqNhr8"]
🔴 (15:57) User stated the retry loop in packages/cli/src/lib/sentry-client.ts cannot fall through because the last attempt always returns "done" or "throw".
🔴 (15:57) User stated SDK configuration sets throwOnError to always be false because errors are handled by the CLI itself.
🔴 (15:57) User stated getControlSdkConfig() is used for endpoints that are always on the control silo, including OAuth, user accounts, and regions.
🔴 (15:57) User showed createAuthenticatedFetch() in packages/cli/src/lib/sentry-client.ts checks response caching for GET requests before authentication/retry, records http.response.status_code on spans for cached and network responses, and keeps local-only commands such as sentry help quiet.
🔴 (15:57) User showed retry request-body handling in packages/cli/src/lib/sentry-client.ts: buildAttemptFactory() preserves/clones request inputs so retries after timeouts, 5xx responses, or auth refresh have an unconsumed body; it specifically accounts for ReadableStream and headers that fetch derives from FormData bodies such as sourcemap chunk uploads.
🔴 (15:57) User showed retry handling in packages/cli/src/lib/sentry-client.ts uses getAuthToken() rather than the originally captured token after a 401 refresh.
🔴 (15:57) User provided the host-scoped token trust model in packages/cli/src/lib/token-host.ts: tokens from environment variables or stored OAuth are bound to a Sentry host, and request destinations are checked before credentials are attached.
🔴 (15:57) User stated isHostTrusted(candidate, trusted) normalizes both origins and returns false if either fails to parse; exact origin matches are trusted, while SaaS equivalence requires both origins to satisfy strict isSaaSTrustOrigin() checks.
🔴 (15:57) User stated SaaS host equivalence permits a token scoped to https://sentry.io on any valid *.sentry.io subdomain, with strict HTTPS and default-port requirements; non-SaaS hosts require exact origin matching, so sentry.acme.com does not trust sentry.acme.evil.com, and http://sentry.io or an explicit :8443 does not inherit SaaS trust.
🔴 (15:57) User stated getActiveTokenHost() mirrors getAuthConfig() precedence: stored OAuth wins over an environment token unless a nonempty SENTRY_FORCE_ENV_TOKEN is set and an environment token exists; it uses getUsableStoredTokenHost() for stored OAuth and otherwise getEnvTokenHost().
🔴 (15:57) User stated registerLoginTrustAnchor(url) stores a normalized, process-local login trust anchor sourced from explicit --url or the boot-time environment snapshot; .sentryclirc does not register this anchor.
🔴 (15:57) User stated shell arguments and boot environment values are at the same trust boundary as SENTRY_AUTH_TOKEN, allowing the login trust anchor to authorize SENTRY_CUSTOM_HEADERS during the no-token OAuth bootstrap window for IAP-protected self-hosted instances.
🔴 (15:57) User stated isLoginTrustAnchorFor(host) must compare the requested host against the stored anchor rather than merely checking that an anchor exists; this prevents a stale anchor from a prior auth login --url <other-host> in library/test mode from authorizing login to a different host.
🔴 (15:57) User showed resetLoginTrustAnchorForTesting() clears the process-local loginTrustAnchor.
🔴 (15:57) User stated isOriginTrustedFor(requestInput, anchorHost) trusts either a direct host-scope match or a dynamically discovered regional silo accepted by isTrustedRegionOrigin(requestOrigin, anchorHost).
🔴 (15:57) User stated isRequestOriginTrusted() returns true when no token is active because there are no credentials to protect; otherwise it validates against the active token host.
🔴 (15:57) User stated isHostTrustedForClaim() applies the same origin-and-region trust logic but anchors it on the sntrys_ token claim URL rather than getActiveTokenHost().
🔴 (15:57) User stated isRequestOriginTrustedForCustomHeaders() follows three cases: with an active token, use isRequestOriginTrusted(); without a token but with a login trust anchor, require isHostTrusted(requestInput, loginTrustAnchor); with neither, fail closed.
🔴 (15:58) User provided constants in packages/cli/src/lib/constants.ts: DEFAULT_SENTRY_HOST = "sentry.io", DEFAULT_SENTRY_URL = "https://sentry.io", NODE_MODULES_DIRNAME = "node_modules", and HAS_PROTOCOL_RE = /^https?:\/\//i.
🔴 (15:58) User stated normalizeUrl(url) in packages/cli/src/lib/constants.ts trims input, returns undefined for empty/whitespace input, preserves strings beginning with http:// or https://, and otherwise prepends https://; this prevents bare values such as SENTRY_HOST=sentry.example.com from causing TypeError: Failed to construct 'Request': Invalid URL.
🔴 (15:58) User stated getConfiguredSentryUrl() checks SENTRY_HOST first, then SENTRY_URL, normalizing either through normalizeUrl().
🔴 (15:58) User stated CLI_VERSION uses the build-time SENTRY_CLI_VERSION when defined and otherwise defaults to "0.0.0-dev".
🔴 (15:58) User stated the build injects __SENTRY_DEBUG_ID__ as a placeholder UUID and later replaces it with the real debug ID derived from the minified JavaScript and sourcemap content hash; the replacement has the same length to preserve sourcemap character positions.
🔴 (15:58) User stated getCliEnvironment(version = CLI_VERSION) maps "0.0.0-dev" to "development", versions containing "-dev." to "nightly", and stable versions to "production"; this replaced process.env.NODE_ENV because esbuild cannot statically replace dynamic access through getEnv().NODE_ENV.
🔴 (15:58) User stated getUserAgent() formats API user agents as sentry-cli/<version> (<os>-<arch>) <runtime>/<version>, with examples "sentry-cli/0.5.0 (linux-x64) bun/1.3.13" and "sentry-cli/0.5.0 (darwin-arm64) node/22.12.0".
🔴 (15:58) User provided packages/cli/src/lib/env-token-host.ts, which snapshots the host associated with SENTRY_AUTH_TOKEN or SENTRY_TOKEN before post-boot code can mutate env.SENTRY_HOST or env.SENTRY_URL.
🔴 (15:58) User stated environment host variables are not unconditionally trusted because in layered CI environments such as GitHub Actions $GITHUB_ENV, a low-privilege step may inject host variables inherited by a later high-privilege step without being able to read SENTRY_AUTH_TOKEN.
🔴 (15:58) User stated that for sntrys_ org-auth tokens, the embedded url claim is authoritative and wins over environment host configuration because it was written by the issuing Sentry server and cannot be overridden through environment injection.
🔴 (15:58) User stated non-sntrys_ tokens lack a claim, so environment configuration is the only host signal; the remaining layered-CI risk is treated as a workflow-design concern, and sntrys_ tokens are recommended for CI.
🔴 (15:58) User stated .sentryclirc files are never consulted by packages/cli/src/lib/env-token-host.ts because they have weaker integrity than environment values or token claims.
🔴 (15:58) User provided the exact boot order in src/cli.ts::preloadProjectContext: 1. captureEnvTokenHost() synchronously captures environment/claim state; 2. findProjectRoot populates the .sentryclirc cache; 3. applySentryCliRcEnvShim may write env.SENTRY_URL; 4. getDefaultUrl() fallback may write env.SENTRY_URL.
🔴 (15:58) User stated captureEnvTokenHost() is idempotent and resolves the pinned host in this order: 1. normalized parseSntrysClaim(getRawEnvToken())?.url; 2. normalized SENTRY_HOST or SENTRY_URL; 3. DEFAULT_SENTRY_URL.
🔴 (15:58) User stated getEnvTokenHost() auto-invokes captureEnvTokenHost() for library-mode callers that bypass normal boot and ultimately falls back to DEFAULT_SENTRY_URL; resetEnvTokenHostForTesting() clears the pinned host.
🔴 (15:58) User stated getRawEnvToken() in packages/cli/src/lib/db/auth.ts always returns an environment token when set, even if stored OAuth would normally take precedence; it checks trimmed SENTRY_AUTH_TOKEN first, then trimmed SENTRY_TOKEN.
🔴 (15:58) User stated getRawEnvToken() is used to determine whether an environment token was provided independently of whether it is effective, and by the per-endpoint permission cache.
🔴 (15:58) User stated getEnvToken() is intentionally pure and performs no database access; SENTRY_AUTH_TOKEN takes precedence over SENTRY_TOKEN, and empty or whitespace-only values count as unset.
🔴 (15:58) User stated getAuthConfig() defaults to stored OAuth before environment tokens, fixing issue #646 where wizard-generated build tokens silently overrode interactive login; with nonempty SENTRY_FORCE_ENV_TOKEN, it checks the environment token first.
🔴 (15:58) User stated stored OAuth rows with expired access tokens and no refresh token are unusable, while expired rows that have a refresh token remain usable for the OAuth refresh flow.
🔴 (15:58) User stated getStoredAuthHost() lazy-migrates pre-schema-v16 rows with NULL hosts to the currently configured host, returns undefined if no stored token exists, and tolerates database failures by returning undefined.
🔴 (15:58) User stated hasUsableStoredToken() requires a bearer token and accepts a row with no expiry, a nonexpired token, or an expired token with a refresh token; database failures return false.
🔴 (15:58) User stated getUsableStoredTokenHost() atomically checks stored-token usability and reads/migrates the stored host in one database operation, avoiding an interleaving clearAuth() race that could otherwise produce a “usable but undefined host” result.
🔴 (15:58) User stated getAuthToken() memoizes its result while distinguishing “not cached” from “cached as undefined”; by default stored OAuth wins and environment variables are fallback, while SENTRY_FORCE_ENV_TOKEN=1 restores environment-first behavior.
🔴 (15:59) User provided GitHub issue getsentry/cli#1568, titled "Env org-auth token host claim ignored when picking request base URL", state OPEN, URL https://github.com/getsentry/cli/issues/1568, reported by Daniel Szoke.
🔴 (15:59) User stated issue #1568 occurs when only SENTRY_AUTH_TOKEN is set to an sntrys_ org-auth token embedding a custom instance URL such as http://localhost:8000, with no SENTRY_URL, SENTRY_HOST, or --url; commands such as sentry debug-files upload then fail with a credentials host-scope mismatch between https://sentry.io and http://localhost:8000.
🔴 (15:59) User provided the exact issue #1568 failure text: Error: Credentials: https://sentry.io, followed by refusal to route because it does not match the credentials host http://localhost:8000, with suggestions to run sentry auth login --url https://sentry.io or remove the URL override.
🔴 (15:59) User stated running sentry auth logout before retrying does not resolve issue #1568; the same error persists.
🔴 (15:59) User stated the source-verified root cause of issue #1568: getConfiguredSentryUrl() in packages/cli/src/lib/constants.ts, used by getApiBaseUrl() and getControlSiloUrl() in packages/cli/src/lib/sentry-client.ts, reads only SENTRY_HOST/SENTRY_URL and otherwise defaults requests to DEFAULT_SENTRY_URL (https://sentry.io), without consulting the sntrys_ token’s embedded url claim.
🔴 (15:59) User stated the other half of issue #1568 is that getActiveTokenHost() via packages/cli/src/lib/env-token-host.ts and packages/cli/src/lib/token-host.ts does treat the sntrys_ claim URL as authoritative, causing request routing to select https://sentry.io while credential trust selects http://localhost:8000.
🔴 (15:59) User stated issue #1568 trips the credential-leak protection in packages/cli/src/lib/token-host.ts and packages/cli/src/lib/errors.ts, producing a confusing “Refusing to route requests” error despite the user not intentionally specifying a host override.
🔴 (15:59) User stated the legacy Rust sentry-cli does not have this host-scoping guard and therefore does not exhibit issue #1568 with the same credentials.