Dashboard › cli › Session 0J9wwhqeJUQB
0J9wwhqeJUQBb27ccPerform an independent, read-only security review of the FINAL current worktree patch for getsentry/cli issue #1568. Do not edit files. Inspect the actual diff and relevant auth, request, token claim, host trust, region-resolution flows. Assess credential leakage, host-scoping bypass, inactive environment token influence, SENTRY_FORCE_ENV_TOKEN, malformed claims, custom-header behavior, and any denial-of-service regression. Report findings first, ordered by severity with exact file:line. If no blocking security defect, state that plainly and list only concrete residual test gaps. End exactly PASS or MUST-FIX.
<skill_content name="jj-guide"> # Skill: jj-guide # jj Guide for AI Agents Jujutsu (jj) is a Git-compatible VCS with mutable commits, automatic snapshotting, no staging area, and first-class conflicts. This skill teaches you how to use it safely from a non-interactive agent environment. If `.jj/` exists in the repo root, this is a jj repo. **Use `jj` commands, not `git`.** In a colocated repo (`.jj/` *and* `.git/`), git tools can read the state, but mutations should go through `jj` so the operation log stays consistent. ## Critical Rules - **NEVER** use interactive flags (`-i`, `--interactive`). TUI prompts hang in agent environments. This applies to `jj split -i`, `jj squash -i`, `jj commit -i`, `jj resolve`, `jj diffedit`, etc. - **ALWAYS** pass `-m "msg"` when describing/committing. Without `-m`, an editor opens and hangs. - **VERIFY** mutations with `jj st` and `jj log` after `squash`, `abandon`, `rebase`, `restore`, `commit`. ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus</path> <type>directory</type> <entries> .craft.yml .git .github/ .gitignore .lore.md .nojekyll .npmrc .opencode/ .plans/ .ruff_cache/ AGENTS.md apps/ CHANGELOG.md codemods/ coverage/ dist-bin/ dist/ docs/ LICENSE.md node_modules/ package.json packages/ pnpm-lock.yaml pnpm-workspace.yaml README.md test-results.junit.xml (26 entries) </entries>
Error: There is no jj repo in "."
...output truncated... Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_08ac3f67a001zxnS93GHCd3ZHe feat/replay-list-view 60f66a40c [origin/feat/replay-list-view: gone] docs: add missing JSDoc to replay search, explore, and view internals feat/require-conventional-pr-title a91117f21 [origin/feat/require-conventional-pr-title: gone] ci: require conventional commit PR titles via changelog-preview feat/response-cache 639261f95 [origin/feat/response-cache: gone] fix: add trace ID validation to trace view + UUID dash-stripping feat/return-based-output 96e858ea3 [origin/feat/return-based-output: gone] feat: return-based output with OutputConfig on buildCommand feat/runtime-metrics b21f6c4aa [origin/feat/runtime-metrics: gone] feat(telemetry): upgrade Sentry SDK to 10.47.0 and enable runtime metrics feat/schema-command ...
diff --git a/packages/cli/src/lib/constants.ts b/packages/cli/src/lib/constants.ts
index 4e707773a..843b7dcb1 100644
--- a/packages/cli/src/lib/constants.ts
+++ b/packages/cli/src/lib/constants.ts
@@ -64,15 +64,15 @@ export function normalizeUrl(url: string | undefined): string | undefined {
}
/**
- * Resolve the Sentry instance URL from environment variables.
- * Checks SENTRY_HOST first, then SENTRY_URL, then falls back to undefined.
+ * Resolve the Sentry instance URL from explicit environment configuration.
+ * Checks `SENTRY_HOST` first, then `SENTRY_URL`, and otherwise returns undefined.
*
* Bare hostnames (e.g., `sentry.example.com`) are automatically prefixed
* with `https://` to prevent invalid URL construction downstream.
*/
export function getConfiguredSentryUrl(): string | undefined {
- const raw = getEnv().SENTRY_HOST || getEnv().SENTRY_URL || undefined;
- return normalizeUrl(raw);
+ const env = getEnv();
+ return normalizeUrl(env.SENTRY_HOST) ?? ...
Found 100 matches (more matches available)
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/token-type.ts:
Line 17: /** `sntrys_...` — organization-scoped auth token, not tied to a user. */
Line 33: if (token.startsWith("sntrys_")) {
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/command.ts:
Line 757: // Validate rc-sourced URL against the active token's host. Deferred
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/jvm-bundle.ts:
Line 277: async function claimDir(dir: string): Promise<string | null> {
Line 323: if ((await claimDir(dir)) === null) {
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/auth-hint.ts:
Line 17: * - Gated behind `!SENTRY_FORCE_ENV_TOKEN`: when the ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-client.ts</path>
<type>file</type>
<content>
1: /**
2: * Sentry API Client Configuration
3: *
4: * Provides request configuration for @sentry/api SDK functions,
5: * including authentication, retry logic, timeout, and multi-region support.
6: *
7: * Instead of managing client instances, we pass configuration per-request
8: * through the SDK function options (baseUrl, fetch, headers).
9: */
10:
11: import { setTimeout as sleepMs } from "node:timers/promises";
12: import { getTraceData } from "@sentry/node-core/light";
13: import { maybeWarnEnvTokenIgnored } from "./auth-hint.js";
14: import { computeInvalidationPrefixes } from "./cache-keys.js";
15: import {
16: DEFAULT_SENTRY_URL,
17: getConfiguredSentryUrl,
18: getUserAgent,
19: } from "./constants.js";
20: import {
21: buildTlsErrorDetail,
22: getCustomTlsOptions,
23: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/token-host.ts</path> <type>file</type> <content> 1: /** 2: * Host-Scoped Token Trust Model 3: * 4: * Tokens (env or stored OAuth) are bound to a specific Sentry host. The fetch 5: * layer (and the `.sentryclirc` / URL-arg entry points) check each request's 6: * destination against the token's recorded host and refuse to attach 7: * credentials when they don't match — so untrusted routing inputs can't leak 8: * credentials to an attacker's host. 9: * 10: * Host equivalence: 11: * - Exact origin match (scheme + host + explicit port). 12: * - SaaS equivalence class: a token scoped to `https://sentry.io` is valid for 13: * any `*.sentry.io` subdomain. Non-SaaS hosts match exactly — no subdomain 14: * suffix matching (a `sentry.acme.com` token does NOT match 15: * `sentry.acme.evil.com`). ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/env-token-host.ts</path> <type>file</type> <content> 1: /** 2: * Env-Token Host Snapshot 3: * 4: * Captures the host an env-var auth token (`SENTRY_AUTH_TOKEN` / 5: * `SENTRY_TOKEN`) is scoped to, BEFORE any post-boot code path can mutate 6: * `env.SENTRY_HOST`/`env.SENTRY_URL` (specifically before 7: * `applySentryCliRcEnvShim` writes from a `.sentryclirc` file). 8: * 9: * Trust model for the snapshot source: 10: * 11: * - `SENTRY_HOST`/`SENTRY_URL` from env are NOT unconditionally trusted. 12: * In layered CI environments (e.g. GitHub Actions `$GITHUB_ENV`), a 13: * low-privilege step can write env vars that a later high-privilege step 14: * inherits — without having read access to `SENTRY_AUTH_TOKEN`. So 15: * env-host and env-token may have different integrity levels. ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/token-claims.ts</path>
<type>file</type>
<content>
1: /**
2: * Sentry Org-Auth-Token (`sntrys_`) Claim Extraction
3: *
4: * Format: `sntrys_<base64(JSON{iat, url, region_url, org})>_<random-secret>`
5: * (server-side: getsentry/sentry `orgauthtoken_token.py`).
6: *
7: * The claim is **NOT signed** — anyone can forge a `sntrys_` string with any
8: * `url`. However, for legitimate tokens the claim IS authoritative: the real
9: * server wrote it at issuance time, and it's immune to env-injection attacks
10: * (the attacker who can poison `SENTRY_HOST` via `$GITHUB_ENV` can't read or
11: * modify the token bytes). `captureEnvTokenHost` uses the claim as the
12: * primary trust source for `sntrys_` tokens, ahead of env vars. ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/region.ts</path>
<type>file</type>
<content>
1: /**
2: * Region resolution for multi-region Sentry support.
3: *
4: * Provides utilities to resolve the correct region URL for an organization,
5: * using cached data when available or fetching from the API when needed.
6: */
7:
8: import { getOrganization } from "@sentry/api";
9: import { getConfiguredSentryUrl } from "./constants.js";
10: import { getOrgByNumericId, getOrgRegion, setOrgRegion } from "./db/regions.js";
11: import { stripDsnOrgPrefix } from "./dsn/index.js";
12: import { withAuthGuard } from "./errors.js";
13: import { getApiBaseUrl, getSdkConfig } from "./sentry-client.js";
14: import { isSentrySaasUrl } from "./sentry-urls.js";
15:
16: /**
17: * Promise cache for org region resolution, keyed by orgSlug. ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/custom-headers.ts</path>
<type>file</type>
<content>
1: /**
2: * Custom Headers for Self-Hosted Sentry
3: *
4: * Parses `SENTRY_CUSTOM_HEADERS` env var (or `defaults.headers` from SQLite)
5: * and injects user-specified HTTP headers into all requests to self-hosted
6: * Sentry instances. Designed for environments behind reverse proxies
7: * (e.g., Google IAP, Cloudflare Access) that require extra headers.
8: *
9: * Format: semicolon-separated `Name: Value` pairs (newlines also accepted).
10: *
11: * @example
12: * ```bash
13: * # Single header
14: * SENTRY_CUSTOM_HEADERS="X-IAP-Token: abc123"
15: *
16: * # Multiple headers
17: * SENTRY_CUSTOM_HEADERS="X-IAP-Token: abc123; X-Forwarded-For: 10.0.0.1"
18: *
19: * # Via defaults command
20: * sentry cli defaults headers "X-IAP-Token: abc123"
21: * ```
22: */
23:
24: import { ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/auth.ts</path>
<type>file</type>
<content>
100: export function getRawEnvToken(): string | undefined {
101: const authToken = getEnv().SENTRY_AUTH_TOKEN?.trim();
102: if (authToken) {
103: return authToken;
104: }
105: const sentryToken = getEnv().SENTRY_TOKEN?.trim();
106: if (sentryToken) {
107: return sentryToken;
108: }
109: return;
110: }
111:
112: /**
113: * Read token from environment variables.
114: * `SENTRY_AUTH_TOKEN` takes priority over `SENTRY_TOKEN` (matches legacy sentry-cli).
115: * Empty or whitespace-only values are treated as unset.
116: *
117: * This function is intentionally pure (no DB access). The "prefer stored OAuth
118: * over env token" logic lives in {@link getAuthToken} and {@link getAuthConfig}
119: * which check the DB first when `SENTRY_FORCE_ENV_TOKEN` is not set. ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-urls.ts</path>
<type>file</type>
<content>
1: /**
2: * Sentry URL Utilities
3: *
4: * Utilities for constructing Sentry web URLs.
5: * Supports self-hosted instances via SENTRY_URL environment variable.
6: */
7:
8: import {
9: DEFAULT_SENTRY_HOST,
10: DEFAULT_SENTRY_URL,
11: getConfiguredSentryUrl,
12: normalizeUrl,
13: } from "./constants.js";
14:
15: /**
16: * Get the Sentry web base URL.
17: * Supports self-hosted instances via SENTRY_URL env var.
18: */
19: export function getSentryBaseUrl(): string {
20: return getConfiguredSentryUrl() ?? DEFAULT_SENTRY_URL;
21: }
22:
23: /**
24: * Build the org-scoped base URL using the subdomain pattern.
25: * E.g. "https://sentry.io" + "my-org" → "https://my-org.sentry.io"
26: *
27: * @param orgSlug - Organization slug
28: * @returns Origin URL with org as subdomain
29: */
30: export ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/regions.ts</path>
<type>file</type>
<content>
1: /**
2: * Organization region cache for multi-region support.
3: *
4: * Sentry has multiple regions (US, EU, etc.) and organizations are bound
5: * to a specific region. This module caches the organization-to-region
6: * mapping to avoid repeated lookups.
7: *
8: * The `org_id` column (added in schema v8) enables offline resolution
9: * of numeric org IDs extracted from DSN hosts (e.g., `o1081365` →
10: * look up by `org_id = '1081365'` → get the slug).
11: */
12:
13: import { normalizeOrigin } from "../sentry-urls.js";
14: import { recordCacheHit } from "../telemetry.js";
15: import { getDatabase } from "./index.js";
16: import { runUpsert } from "./utils.js";
17:
18: const TABLE = "org_regions";
19:
20: /**
21: * Process-local trust extension: origins that were vouched for by the
22: * active ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/constants.ts</path>
<type>file</type>
<content>
1: /**
2: * Runtime constants for the CLI.
3: */
4:
5: import { getEnv } from "./env.js";
6:
7: /** Build-time constant injected by esbuild/bun */
8: declare const SENTRY_CLI_VERSION: string | undefined;
9:
10: /**
11: * Build-time debug ID for sourcemap resolution, injected by esbuild.
12: *
13: * During the build, esbuild's `define` replaces this identifier with a
14: * placeholder UUID string literal. After esbuild finishes, the build
15: * script replaces the placeholder with the real debug ID (derived from
16: * the minified JS + sourcemap content hash). The same-length swap keeps
17: * sourcemap character positions valid.
18: */
19: declare const __SENTRY_DEBUG_ID__: string | undefined;
20:
21: /** Default Sentry SaaS hostname */
22: export const DEFAULT_SENTRY_HOST = "sentry.io";
23:
24: /** ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-client.ts</path>
<type>file</type>
<content>
600: async (span) => {
601: const fullUrl = extractFullUrl(input);
602: const startTime = performance.now();
603:
604: // Check cache before auth/retry for GET requests.
605: // Uses current token (no refresh) so lookups are fast but Vary-correct.
606: const cached = await tryCacheHit(
607: method,
608: fullUrl,
609: authHeaders(getAuthToken())
610: );
611: if (cached) {
612: span.setAttribute("http.response.status_code", cached.status);
613: log.debug(
614: `${method} ${urlPath} → ${cached.status} (cache hit, ${(performance.now() - startTime).toFixed(0)}ms)`
615: );
616: return cached;
617: }
618:
619: const response = await fetchWithRetry(input, init, ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-client.ts</path>
<type>file</type>
<content>
190: }
191: } catch (error) {
192: log.debug("Token refresh failed after 401", error);
193: }
194: return false;
195: }
196:
197: /** Link an external abort signal to an AbortController */
198: function linkAbortSignal(
199: signal: AbortSignal | undefined | null,
200: controller: AbortController
201: ): void {
202: if (!signal) {
203: return;
204: }
205: if (signal.aborted) {
206: controller.abort();
207: return;
208: }
209: signal.addEventListener("abort", () => controller.abort(), { once: true });
210: }
211:
212: /** Resolve the per-request timeout for a URL via {@link ENDPOINT_TIMEOUT_OVERRIDES}. ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/auth.ts</path>
<type>file</type>
<content>
560: *
561: * Memoized within the process. Reset on {@link setAuthToken} and
562: * {@link clearAuth} mutations. Tests call {@link resetHasStoredCredsCache}
563: * between cases.
564: *
565: * Used by the login command to decide whether to prompt for re-authentication
566: * when an env token is present.
567: */
568: export function hasStoredAuthCredentials(): boolean {
569: if (cachedHasStoredCreds !== undefined) {
570: return cachedHasStoredCreds.value;
571: }
572: const row = getAuthRow();
573: let result = false;
574: if (row?.token) {
575: // Non-expired token
576: if (!row.expires_at || Date.now() <= row.expires_at) {
577: result = true;
578: } else {
579: // Expired but has refresh token — will be refreshed on next use
580: result = !!row.refresh_token;
581: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/auth.ts</path>
<type>file</type>
<content>
360: cachedAuthRow = { value: row };
361: return row;
362: }
363:
364: /** Reset the memoized auth row. Tests only — call between auth-state mutations. */
365: export function resetAuthRowCache(): void {
366: cachedAuthRow = undefined;
367: }
368:
369: /** Reset the memoized stored-credentials flag. Tests only — call between auth-state mutations. */
370: export function resetHasStoredCredsCache(): void {
371: cachedHasStoredCreds = undefined;
372: }
373:
374: /**
375: * Options for persisting a token.
376: *
377: * @property host - Origin URL the token was issued against. When omitted on
378: * an update (e.g., access-token refresh), the existing row's host is
379: * preserved. ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/security/sntrys-claim-mismatch.test.ts</path>
<type>file</type>
<content>
1: /**
2: * Defense-in-depth: `sntrys_` token claim vs request-origin mismatch.
3: *
4: * The fetch-layer guard refuses to attach a `sntrys_` token when its
5: * embedded `url` claim disagrees with the request origin. Defends users
6: * with access to multiple Sentry instances against routing one
7: * instance's token to another. Claim is unsigned (see token-claims.ts),
8: * so this catches honest misconfigurations more than malicious attacks.
9: */
10:
11: import { afterEach, beforeEach, describe, expect, test } from "vitest";
12: import {
13: extractFetchUrl,
14: mintSntrysToken,
15: resetHostScopingState,
16: useEnvSandbox,
17: useTestConfigDir,
18: } from "../../helpers.js";
19:
20: const ENV_KEYS = [
21: "SENTRY_AUTH_TOKEN",
22: "SENTRY_TOKEN",
23: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/security/sntrys-claim-mismatch.test.ts</path>
<type>file</type>
<content>
361: captureEnvTokenHost();
362:
363: expect(getEnvTokenHost()).toBe("https://sentry.firsthost.com");
364: });
365:
366: test("non-sntrys_ token + no SENTRY_HOST → snapshot falls back to SaaS default", async () => {
367: process.env.SENTRY_AUTH_TOKEN = "sntryu_opaque-user-token";
368: // No SENTRY_HOST.
369:
370: const { captureEnvTokenHost, getEnvTokenHost } = await import(
371: "../../../src/lib/env-token-host.js"
372: );
373: captureEnvTokenHost();
374:
375: expect(getEnvTokenHost()).toBe("https://sentry.io");
376: });
377:
378: test("forged claim url is captured (claim is NOT a security primitive)", async () => {
379: // Documents the trust contract: the snapshot picks up whatever the
380: // claim says, even if forged. ...
Found 30 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-client.ts:
Line 447: const prefixes = computeInvalidationPrefixes(fullUrl, getApiBaseUrl());
Line 685: export function getApiBaseUrl(): string {
Line 696: export function getControlSiloUrl(): string {
Line 734: export function getDefaultSdkConfig() {
Line 735: return getSdkConfig(getApiBaseUrl());
Line 743: return getSdkConfig(getControlSiloUrl());
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/api/users.ts:
Line 9: import { getControlSiloUrl } from "../sentry-client.js";
Line 22: getControlSiloUrl(),
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/api/trials.ts:
Line 13: import { getControlSiloUrl } from "../sentry-client.js";
Line 31: getControlSiloUrl(),
...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/oauth.ts</path>
<type>file</type>
<content>
450: let pollInterval = interval;
451:
452: // Step 2: Poll for token
453: while (Date.now() < timeoutAt) {
454: await sleep(pollInterval * 1000);
455:
456: const result = await attemptPoll(device_code);
457:
458: switch (result.status) {
459: case "success":
460: return result.token;
461: case "pending":
462: continue;
463: case "slow_down":
464: pollInterval += 5;
465: continue;
466: case "error":
467: throw new DeviceFlowError("authorization_failed", result.message);
468: default:
469: throw new DeviceFlowError("unexpected_error", "Unexpected poll result");
470: }
471: }
472:
473: throw new DeviceFlowError(
474: "expired_token",
475: "Authentication timed out. Please try again."
476: );
477: }
478: ...
Found 3 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/oauth.ts:
Line 44: function getSentryUrl(): string {
Line 246: function assertRefreshHostTrusted(): void {
Line 513: assertRefreshHostTrusted();
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/oauth.ts</path>
<type>file</type>
<content>
1: /**
2: * OAuth Authentication
3: *
4: * Implements RFC 8628 Device Authorization Grant for Sentry OAuth.
5: * https://datatracker.ietf.org/doc/html/rfc8628
6: */
7:
8: import type { TokenResponse } from "../types/index.js";
9: import {
10: DeviceCodeResponseSchema,
11: TokenErrorResponseSchema,
12: TokenResponseSchema,
13: } from "../types/index.js";
14: import { SENTRY_SCOPES } from "./api-scope.js";
15: import { DEFAULT_SENTRY_URL, getConfiguredSentryUrl } from "./constants.js";
16: import {
17: buildTlsErrorDetail,
18: getCustomTlsOptions,
19: isTlsCertError,
20: warnIfSaasWithEnvCa,
21: } from "./custom-ca.js";
22: import { applyCustomHeaders } from "./custom-headers.js";
23: import { setAuthToken } from "./db/auth.js";
24: import { getEnv } from "./env.js";
25: import {
26: ApiError,
27: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/oauth.ts</path>
<type>file</type>
<content>
220: 0,
221: buildTlsErrorDetail(error)
222: );
223: }
224:
225: const isConnectionError =
226: error.message.includes("ECONNREFUSED") ||
227: error.message.includes("fetch failed") ||
228: error.message.includes("network");
229:
230: if (isConnectionError) {
231: throw new ApiError(
232: `Cannot connect to Sentry at ${getSentryUrl()}`,
233: 0,
234: "Check your network connection and SENTRY_URL configuration"
235: );
236: }
237: throw error;
238: }
239: }
240:
241: /**
242: * Refuse to POST a refresh token to a host that doesn't match the active
243: * token's scope. Defense-in-depth for the rare case where SENTRY_HOST/URL
244: * was mutated without going through the URL-arg / rc-shim guards. ...
Found 5 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/commands/auth/logout.ts:
Line 62: await clearAuth();
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/commands/auth/login.ts:
Line 365: await clearAuth();
Line 386: await clearAuth();
Line 494: await setAuthToken(flags.token, undefined, undefined, {
Line 531: // OAuth device flow (host scope recorded via completeOAuthFlow → setAuthToken)
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/commands/auth/login.ts</path>
<type>file</type>
<content>
335: if (!hasStoredAuthCredentials()) {
336: return true;
337: }
338: // Fall through to the re-auth confirmation logic below
339: }
340:
341: if (!force) {
342: // Non-interactive (piped, CI): print message and block
343: if (!isatty(0)) {
344: log.info(
345: "You are already authenticated. Use '--force' or 'sentry auth logout' first to re-authenticate."
346: );
347: return false;
348: }
349:
350: // Interactive TTY: prompt user to confirm re-authentication
351: const userInfo = getUserInfo();
352: const identity = userInfo ? formatUserIdentity(userInfo) : "current user";
353: const confirmed = await log.prompt(
354: `Already authenticated as ${identity}. Re-authenticate?`,
355: { type: "confirm", initial: false }
356: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/package.json</path>
<type>file</type>
<content>
1: {
2: "name": "sentry",
3: "version": "0.40.0-dev.0",
4: "description": "Sentry CLI - A command-line interface for using Sentry built by robots and humans for robots and humans",
5: "repository": {
6: "type": "git",
7: "url": "git+https://github.com/getsentry/cli.git"
8: },
9: "main": "./dist/index.cjs",
10: "type": "module",
11: "types": "./dist/index.d.cts",
12: "exports": {
13: ".": {
14: "import": {
15: "types": "./dist/index.d.mts",
16: "default": "./dist/index.mjs"
17: },
18: "require": {
19: "types": "./dist/index.d.cts",
20: "default": "./dist/index.cjs"
21: }
22: }
23: },
24: "bin": {
25: "sentry": "./dist/bin.cjs"
26: },
27: "files": [
28: "dist/bin.cjs",
29: "dist/index.cjs",
30: ...
DEPRECATED `test.poolOptions` was removed in Vitest 4. All previous `poolOptions` are now top-level options. Please, refer to the migration guide: https://vitest.dev/guide/migration#pool-rework
RUN v4.1.10 /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli
[info] [auth] Detected SENTRY_AUTH_TOKEN env var but using stored login for stored OAuth user.
Set SENTRY_FORCE_ENV_TOKEN=1 to prefer the env var.
Test Files 2 passed (2)
Tests 35 passed (35)
Start at 10:05:55
Duration 3.70s (transform 2.58s, setup 176ms, import 2.26s, tests 1.25s, environment 0ms)
M .lore.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/event.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/explore.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/feedback.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/issue.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/log.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/replay.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/span.md M packages/cli/plugins/sentry-cli/skills/sentry-cli/references/trace.md M packages/cli/src/lib/constants.ts M packages/cli/src/lib/region.ts M packages/cli/src/lib/sentry-client.ts M packages/cli/test/lib/constants.test.ts M packages/cli/test/lib/security/sntrys-claim-mismatch.test.ts 92efc96c44aab439dc60b5785f3e29dce1d55c69 605e8318d1f316bd28f53763f71ed716d1869616 M .lore.md M ...
{"body":"When only `SENTRY_AUTH_TOKEN` is set (an `sntrys_` org-auth token embedding a custom instance URL, e.g. `http://localhost:8000`) and no `SENTRY_URL`/`SENTRY_HOST`/`--url` is provided, commands such as `sentry debug-files upload` fail with:\n\n```\nError: Credentials: https://sentry.io\nRefusing to route requests here because it doesn't match the host your Sentry credentials are for (http://localhost:8000).\nTo use this host, run: sentry auth login --url https://sentry.io\nTo keep using your current credentials, remove this URL override.\n```\n\nRunning `sentry auth logout` first does not help; the same error persists.\n\nRoot cause (source-verified):\n- `getConfiguredSentryUrl()` in `packages/cli/src/lib/constants.ts` (used by `getApiBaseUrl()`/`getControlSiloUrl()` in `lib/sentry-client.ts`) only reads `SENTRY_HOST`/`SENTRY_URL` and otherwise falls back to `DEFAULT_SENTRY_URL` (`https://sentry.io`). It never consults the `sntrys_` token's embedded `url` claim.\n- Separately, ...
Found 100 matches (more matches available)
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/token-type.property.test.ts:
Line 15: test("any string starting with sntrys_ classifies as org-auth-token", () => {
Line 18: expect(classifySentryToken(`sntrys_${suffix}`)).toBe(
Line 26: test("bare sntrys_ prefix classifies as org-auth-token", () => {
Line 27: expect(classifySentryToken("sntrys_")).toBe("org-auth-token");
Line 50: // Reject anything that could happen to start with sntrys_ or sntryu_.
Line 54: if (value.startsWith("sntrys_") || value.startsWith("sntryu_")) {
Line 69: // the sntrys_/sntryu_ prefix.
Line 99: expect(classifySentryToken("xsntrys_abc")).toBe("oauth-or-legacy");
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/token-claims.test.ts:
Line 2: * Unit ...
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/security/custom-headers-leak.test.ts /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/custom-headers.test.ts /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/custom-headers.property.test.ts
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/security/custom-headers-leak.test.ts</path>
<type>file</type>
<content>
1: /**
2: * CVE regression: custom-headers leak via share URL and via the
3: * `auth login` rc-URL bypass.
4: *
5: * Tests `applyCustomHeaders` trust scoping. Two attack shapes:
6: * 1. Share URL: `getSharedIssue(https://evil.com, ...)` — headers must
7: * not attach to URLs that don't match the active token.
8: * 2. auth login bypass: when `env.SENTRY_URL` is rc-poisoned and no
9: * token is active yet, headers must fail closed.
10: *
11: * See also `fetch-layer-guard.test.ts` for the Bearer-token path.
12: */
13:
14: import { afterEach, beforeEach, describe, expect, test } from "vitest";
15: import { getSharedIssue } from "../../../src/lib/api/issues.js";
16: import {
17: _resetCustomHeadersCache,
18: applyCustomHeaders,
19: } from ...
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/security/fetch-layer-guard.test.ts
DEPRECATED `test.poolOptions` was removed in Vitest 4. All previous `poolOptions` are now top-level options. Please, refer to the migration guide: https://vitest.dev/guide/migration#pool-rework RUN v4.1.10 /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli [warn] [custom-headers] Skipping custom headers for request to untrusted host. If this is legitimate, run 'sentry auth login --url <url>' against the intended instance. [warn] [custom-headers] Skipping custom headers for request to untrusted host. If this is legitimate, run 'sentry auth login --url <url>' against the intended instance. [warn] [custom-headers] Skipping custom headers for request to untrusted host. If this is legitimate, run 'sentry auth login --url <url>' against the intended instance. [warn] [custom-headers] Skipping custom headers for request to untrusted host. If this is legitimate, run 'sentry auth login --url <url>' against the intended instance. ...
605e8318d1f316bd28f53763f71ed716d1869616 92efc96c44aab439dc60b5785f3e29dce1d55c69
{"authorization":null,"iap":"custom-secret"}
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/api/preprod-artifacts.ts</path>
<type>file</type>
<content>
510: snapshotId: string,
511: onBuildStarted?: () => void
512: ): Promise<void> {
513: if (await getSnapshotArchiveReady(org, snapshotId)) {
514: return;
515: }
516: await triggerSnapshotArchiveBuild(org, snapshotId);
517: onBuildStarted?.();
518:
519: const deadline = Date.now() + SNAPSHOT_ARCHIVE_TIMEOUT_MS;
520: while (!(await getSnapshotArchiveReady(org, snapshotId))) {
521: if (Date.now() >= deadline) {
522: throw new TimeoutError(
523: `Snapshot archive was not ready after ${
524: SNAPSHOT_ARCHIVE_TIMEOUT_MS / 1000
525: }s. The build may still be running; try again shortly.`
526: );
527: }
528: await new Promise((r) => setTimeout(r, SNAPSHOT_ARCHIVE_POLL_MS));
529: }
530: }
531:
532: /**
533: * Open a snapshot's archive ZIP ...
HIGH — cross-origin redirects leak custom credentials
packages/cli/src/lib/constants.ts:73-75, packages/cli/src/lib/sentry-client.ts:268-273, packages/cli/src/lib/custom-headers.ts:225-238
With whitespace SENTRY_HOST, a self-hosted SENTRY_URL, and SENTRY_CUSTOM_HEADERS, the patch now routes and attaches custom headers. Native fetch() then follows cross-origin redirects without re-running the trust check. Node strips Authorization, but retains arbitrary headers such as X-IAP-Token.
I reproduced this: the redirected origin received X-IAP-Token: custom-secret.
Use manual redirect handling, validate every redirect origin, and reapply custom headers only after that validation. Add a two-origin regression test covering this whitespace-host fallback.
MUST-FIX