Dashboard › cli › Session 10JCNgfKSjxS
10JCNgfKSjxSeFE1nYour previous security review returned empty output. Continue the same investigation and return a substantive report now. The patch must still hash to d6fb08a6fc3ccdd14c52020f745eb20893148998 using git diff --binary | git hash-object --stdin; verify before and after. Do not edit files or Git state. Review the issue #1568 credential-routing security boundary and the specific prior flaw where legacy NULL-host stored OAuth migration trusted an inactive forged env token claim. You must return either evidence-backed PASS/CONCERN/MUST-FIX findings with exact current file:line citations and residual risks, or BLOCKED with the exact command/error. Empty output is invalid. End exactly MERGE or DO-NOT-MERGE.
605e8318d1f316bd28f53763f71ed716d1869616 d6fb08a6fc3ccdd14c52020f745eb20893148998 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/commands/org/list.ts M packages/cli/src/commands/project/delete.ts M packages/cli/src/lib/api/organizations.ts M packages/cli/src/lib/api/projects.ts M packages/cli/src/lib/constants.ts M ...
diff --git a/packages/cli/src/commands/org/list.ts b/packages/cli/src/commands/org/list.ts
index 75388d34f..b552b2ccf 100644
--- a/packages/cli/src/commands/org/list.ts
+++ b/packages/cli/src/commands/org/list.ts
@@ -19,6 +19,7 @@ import {
FRESH_FLAG,
} from "../../lib/list-command.js";
import { withProgress } from "../../lib/polling.js";
+import { getApiBaseUrl } from "../../lib/sentry-client.js";
import type { SentryOrganization, Writer } from "../../types/index.js";
type ListFlags = {
@@ -141,7 +142,7 @@ export const listCommand = buildCommand({
const limitedOrgs = orgs.slice(0, flags.limit);
// Check if user has orgs in multiple regions
- const orgRegions = getAllOrgRegions();
+ const orgRegions = getAllOrgRegions(getApiBaseUrl());
const uniqueRegions = new Set(orgRegions.values());
const showRegion = uniqueRegions.size > 1;
diff --git a/packages/cli/src/commands/project/delete.ts b/packages/cli/src/commands/project/delete.ts
index ...
<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/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/db/auth.ts</path>
<type>file</type>
<content>
1: /**
2: * Authentication credential storage (single-row table pattern).
3: */
4:
5: import { createHash } from "node:crypto";
6: import { DEFAULT_SENTRY_URL, getConfiguredSentryUrl } from "../constants.js";
7: import { getEnv } from "../env.js";
8: import { getBootConfiguredSentryUrl } from "../env-token-host.js";
9: import { logger } from "../logger.js";
10: import { normalizeOrigin } from "../sentry-urls.js";
11: import { withDbSpan } from "../telemetry.js";
12: import { getDatabase } from "./index.js";
13: import { clearAllIssueOrgCache } from "./issue-org-cache.js";
14: import { clearTrustedHostState } from "./regions.js";
15: import { runUpsert } from "./utils.js";
16:
17: /** Refresh when less than 10% of token lifetime remains */
18: export const REFRESH_THRESHOLD = 0.1;
19:
20: /** Default token ...
<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 { DEFAULT_SENTRY_URL } from "../constants.js";
14: import { normalizeOrigin } from "../sentry-urls.js";
15: import { recordCacheHit } from "../telemetry.js";
16: import { getDatabase } from "./index.js";
17: import { runUpsert } from "./utils.js";
18:
19: const TABLE = "org_regions";
20:
21: /**
22: * Process-local regional ...
<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/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: ...
Found 100 matches (more matches available)
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/commands/trace/view.func.test.ts:
Line 179: setOrgRegion("test-org", DEFAULT_SENTRY_URL);
Line 180: setOrgRegion("my-org", DEFAULT_SENTRY_URL);
Line 402: setOrgRegion("cam-org", DEFAULT_SENTRY_URL);
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/commands/trace/list.test.ts:
Line 130: setOrgRegion("my-org", DEFAULT_SENTRY_URL);
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/commands/team/list.test.ts:
Line 228: setOrgRegion("my-org", DEFAULT_SENTRY_URL);
Line 367: setOrgRegion("my-org", DEFAULT_SENTRY_URL);
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/commands/span/view.test.ts:
...
Found 14 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/token-claims.ts:
Line 11: * modify the token bytes). `captureEnvTokenHost` uses the claim as the
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentryclirc.ts:
Line 13: * The env shim ({@link applySentryCliRcEnvShim}) maps `token` → `SENTRY_AUTH_TOKEN`
Line 340: export async function applySentryCliRcEnvShim(cwd: string): Promise<void> {
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/cli.ts:
Line 28: const { captureEnvTokenHost } = await import("./lib/env-token-host.js");
Line 29: captureEnvTokenHost();
Line 47: const { applySentryCliRcEnvShim } = await import("./lib/sentryclirc.js");
Line 48: await ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/cli.ts</path>
<type>file</type>
<content>
1: /**
2: * CLI runner with fast-path dispatch.
3: *
4: * Shell completion (`__complete`) is dispatched before any heavy imports
5: * to avoid loading `@sentry/node-core` (~280ms). All other commands go through
6: * the full CLI with telemetry, middleware, and error recovery.
7: *
8: * Extracted from `bin.ts` so the logic is testable and reusable without
9: * top-level side effects. `bin.ts` remains a thin wrapper that registers
10: * stream error handlers and calls `startCli()`.
11: */
12:
13: import { getEnv } from "./lib/env.js";
14: import { CliError } from "./lib/errors.js";
15: import { initTimezone } from "./lib/timezone.js";
16:
17: /**
18: * Preload project context: walk up from `cwd` once, finding both the
19: * project root (for DSN detection) and `.sentryclirc` config (for
20: * org/project defaults ...
<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/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/api/infrastructure.ts</path>
<type>file</type>
<content>
1: /**
2: * API Client Infrastructure
3: *
4: * Shared helpers, types, constants, and raw request functions used by
5: * all domain-specific API modules. This is the foundation layer that
6: * other modules in `src/lib/api/` import from.
7: */
8:
9: import { promisify } from "node:util";
10: import { zstdCompress as zstdCompressCb } from "node:zlib";
11: import { parseSentryLinkHeader } from "@sentry/api";
12: // biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import
13: import * as Sentry from "@sentry/node-core/light";
14: import type { z } from "zod";
15:
16: import { extractRequiredScopes } from "../api-scope.js";
17: import { getActiveEnvVarName, isEnvTokenActive } from "../db/auth.js";
18: import { getEnv } from "../env.js";
19: import { ApiError, AuthError, ...
Found 100 matches (more matches available)
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/api/users.ts:
Line 11: import { apiRequestToRegion } from "./infrastructure.js";
Line 21: const { data } = await apiRequestToRegion<SentryUser>(
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/api/trials.ts:
Line 15: import { apiRequestToRegion } from "./infrastructure.js";
Line 30: const { data } = await apiRequestToRegion<CustomerTrialInfo>(
Line 70: await apiRequestToRegion(
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/api/traces.ts:
Line 32: apiRequestToRegion,
Line 119: const { data } = await apiRequestToRegion<TraceSpan[]>(
Line 158: const { data } = await apiRequestToRegion<TraceItemDetail>(
Line 203: const { data } = await ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/api/infrastructure.ts</path>
<type>file</type>
<content>
440: // Safety limit reached — warn and return what we have, no nextCursor
441: logger.warn(
442: `Pagination limit reached (${MAX_PAGINATION_PAGES} pages, ${allRows.length} items). ` +
443: "Results may be incomplete."
444: );
445: return { data: allRows.slice(0, limit) };
446: }
447:
448: /**
449: * Make an authenticated request to a specific Sentry region.
450: * Returns both parsed response data and raw headers for pagination support.
451: * Used for internal endpoints not covered by @sentry/api SDK functions.
452: *
453: * @param regionUrl - The region's base URL (e.g., https://us.sentry.io)
454: * @param endpoint - API endpoint path (e.g., "/users/me/regions/")
455: * @param options - Request options
456: * @returns Parsed data and response headers
457: */
458: export async ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/schema.ts</path>
<type>file</type>
<content>
130: project_aliases: {
131: columns: {
132: alias: { type: "TEXT", primaryKey: true },
133: org_slug: { type: "TEXT", notNull: true },
134: project_slug: { type: "TEXT", notNull: true },
135: dsn_fingerprint: { type: "TEXT" },
136: cached_at: {
137: type: "INTEGER",
138: notNull: true,
139: default: "(unixepoch() * 1000)",
140: },
141: last_accessed: {
142: type: "INTEGER",
143: notNull: true,
144: default: "(unixepoch() * 1000)",
145: },
146: },
147: },
148: pagination_cursors: {
149: columns: {
150: command_key: { type: "TEXT", notNull: true },
151: context: { type: "TEXT", notNull: true },
152: cursor_stack: { type: "TEXT", notNull: true },
153: page_index: { type: "INTEGER", ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/auth.ts</path> <type>file</type> <content> 470: /** Fingerprint returned when no token is present (logged out, no env var). */ 471: export const ANON_IDENTITY = "<anon>"; 472: 473: /** Memoized fingerprint. Identity doesn't change within a single CLI run. */ 474: let cachedFingerprint: string | undefined; 475: 476: /** 477: * Opaque fingerprint of the active bearer identity, used to namespace 478: * response-cache keys so entries never leak across accounts. Mirrors 479: * `getAuthConfig` precedence: forced env token > stored OAuth 480: * (refresh_token preferred for stability across access-token rotation, 481: * falling through expired access-only rows) > env token > anonymous. 482: * 483: * Memoized. ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/db/auth.host.test.ts</path>
<type>file</type>
<content>
1: /**
2: * Tests for host-scoped auth: setAuthToken persistence, getStoredAuthHost,
3: * NULL-host lazy migration, host preservation across refresh-style updates.
4: */
5:
6: import { describe, expect, test } from "vitest";
7: import {
8: getStoredAuthHost,
9: hasUsableStoredToken,
10: setAuthToken,
11: } from "../../../src/lib/db/auth.js";
12: import { getDatabase } from "../../../src/lib/db/index.js";
13: import { useTestConfigDir } from "../../helpers.js";
14:
15: describe("db/auth host scoping", () => {
16: useTestConfigDir("auth-host-test-");
17:
18: test("setAuthToken persists explicit host", () => {
19: setAuthToken("tok-1", undefined, undefined, {
20: host: "https://sentry.acme.com",
21: });
22: expect(getStoredAuthHost()).toBe("https://sentry.acme.com");
23: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/security/refresh-token-poison.test.ts</path>
<type>file</type>
<content>
1: /**
2: * CVE defense-in-depth: OAuth refresh-token credential exfiltration.
3: *
4: * Attack: if something bypasses the entry-point guards and poisons
5: * `env.SENTRY_URL` before the next OAuth refresh fires, the refresh token
6: * would previously be POSTed to the attacker's `/oauth/token/` endpoint.
7: *
8: * Fix: `refreshAccessToken` calls `assertRefreshHostTrusted()` before
9: * building the request body, which throws `CliError` on mismatch.
10: */
11:
12: import { afterEach, beforeEach, describe, expect, test } from "vitest";
13: import { setAuthToken } from "../../../src/lib/db/auth.js";
14: import {
15: captureEnvTokenHost,
16: resetEnvTokenHostForTesting,
17: } from "../../../src/lib/env-token-host.js";
18: import { refreshAccessToken } from ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/sentry-client.test.ts</path>
<type>file</type>
<content>
1: /**
2: * Tests for the authenticated fetch retry + timeout behavior — CLI-1D6
3: * regression coverage.
4: */
5:
6: import { afterEach, beforeEach, describe, expect, test } from "vitest";
7: import { setAuthToken } from "../../src/lib/db/auth.js";
8: import { TimeoutError } from "../../src/lib/errors.js";
9: import {
10: __injectTimeoutOverrideForTests,
11: __resolveRequestTimeoutMsForTests,
12: getSdkConfig,
13: resetAuthenticatedFetch,
14: } from "../../src/lib/sentry-client.js";
15: import { mockFetch, useTestConfigDir } from "../helpers.js";
16:
17: useTestConfigDir("sentry-client-");
18:
19: let originalFetch: typeof globalThis.fetch;
20: const REGION_URL = "https://us.sentry.io";
21:
22: beforeEach(async () => {
23: originalFetch = globalThis.fetch;
24: // Non-expiring token — ...
diff --git a/packages/cli/test/lib/constants.test.ts b/packages/cli/test/lib/constants.test.ts
index 0e6e020f3..7733161da 100644
--- a/packages/cli/test/lib/constants.test.ts
+++ b/packages/cli/test/lib/constants.test.ts
@@ -152,6 +152,13 @@ describe("getConfiguredSentryUrl", () => {
expect(getConfiguredSentryUrl()).toBe("https://host.example.com");
});
+ test("ignores an empty SENTRY_HOST in favor of SENTRY_URL", () => {
+ process.env.SENTRY_HOST = " ";
+ process.env.SENTRY_URL = "https://configured.example.com";
+
+ expect(getConfiguredSentryUrl()).toBe("https://configured.example.com");
+ });
+
test("preserves protocol when already present", () => {
process.env.SENTRY_HOST = "https://sentry.example.com";
expect(getConfiguredSentryUrl()).toBe("https://sentry.example.com");
diff --git a/packages/cli/test/lib/db/auth.host.test.ts b/packages/cli/test/lib/db/auth.host.test.ts
index 1cab411fd..753e55fb2 100644
--- ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/api/organizations.ts</path>
<type>file</type>
<content>
1: /**
2: * Organization API functions
3: *
4: * CRUD operations and region discovery for Sentry organizations.
5: */
6:
7: import {
8: getOrganization as sdkGetOrganization,
9: listOrganizations as sdkListOrganizations,
10: } from "@sentry/api";
11:
12: import {
13: type Region,
14: type SentryOrganization,
15: type UserRegionsResponse,
16: UserRegionsResponseSchema,
17: } from "../../types/index.js";
18:
19: import { ApiError } from "../errors.js";
20: import {
21: getApiBaseUrl,
22: getControlSiloUrl,
23: getSdkConfig,
24: } from "../sentry-client.js";
25:
26: import {
27: API_MAX_PER_PAGE,
28: apiRequestToRegion,
29: autoPaginate,
30: getOrgSdkConfig,
31: MAX_PAGINATION_PAGES,
32: type PaginatedResponse,
33: unwrapPaginatedResult,
34: unwrapResult,
35: } from ...
Found 52 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/telemetry.ts:
Line 709: Sentry.setTag("is_self_hosted", !isSentrySaasUrl(getSentryBaseUrl()));
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/oauth.ts:
Line 15: import { DEFAULT_SENTRY_URL, getConfiguredSentryUrl } from "./constants.js";
Line 51: return getConfiguredSentryUrl() ?? DEFAULT_SENTRY_URL;
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/login-host-guard.ts:
Line 17: import { DEFAULT_SENTRY_URL } from "./constants.js";
Line 36: DEFAULT_SENTRY_URL
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/regions.ts:
Line 13: import { DEFAULT_SENTRY_URL } from "../constants.js";
Line 165: sourceOrigin = ...
Found 29 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/regions.ts:
Line 76: export function registerTrustedRegionUrls(
Line 122: /** When true, getCachedOrganizations() returns empty (forces API fetch). */
Line 163: export function getOrgRegion(
Line 189: export function getOrgByNumericId(
Line 214: export function setOrgRegion(
Line 237: registerTrustedRegionUrls(sourceOrigin, [regionUrl]);
Line 242: * More efficient than calling setOrgRegion() multiple times.
Line 249: export function setOrgRegions(entries: OrgRegionEntry[]): void {
Line 286: registerTrustedRegionUrls(entry.sourceOrigin ?? DEFAULT_SENTRY_URL, [
Line 308: export function getAllOrgRegions(
Line 352: export function getCachedOrganizations(
Line 388: export function ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/org-list.ts</path>
<type>file</type>
<content>
1020: /**
1021: * Behavior when a project-search slug matches a cached organization.
1022: *
1023: * Before cursor validation and handler dispatch, the dispatcher checks
1024: * whether the bare slug matches a cached org. This prevents commands
1025: * from accidentally treating an org slug as a project name.
1026: *
1027: * - `"redirect"` (default): Convert to org-all mode with a warning log.
1028: * The existing org-all handler runs naturally.
1029: * - `"error"`: Throw a {@link ResolutionError} with actionable hints.
1030: * Use this when org-all redirect is inappropriate (e.g., issue list
1031: * has custom per-project query logic that doesn't support org-all). ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/complete.ts</path>
<type>file</type>
<content>
160: ): Completion[] {
161: // Build the command path from preceding words (e.g., "issue list")
162: const cmdPath =
163: precedingWords.length >= 2
164: ? `${precedingWords[0]} ${precedingWords[1]}`
165: : "";
166:
167: if (ORG_PROJECT_COMMANDS.has(cmdPath)) {
168: return completeOrgSlashProject(partial);
169: }
170:
171: if (ORG_ONLY_COMMANDS.has(cmdPath)) {
172: return completeOrgSlugs(partial);
173: }
174:
175: // Not a known command path — no dynamic completions
176: return [];
177: }
178:
179: /**
180: * Complete organization slugs with fuzzy matching.
181: *
182: * Queries the org_regions cache for all known org slugs and matches
183: * them against the partial input. ...
<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: /** ...
Found 2 matches /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/oauth.ts: Line 592: export function refreshAccessToken( /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/auth.ts: Line 606: const tokenResponse = await refreshAccessToken(storedRefreshToken);
Found 13 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/token-host.ts:
Line 71: * Process-local login trust anchor — set by `applyLoginUrl` from `--url` or
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/oauth.ts:
Line 507: export async function performDeviceFlow(
Line 567: * @param tokenResponse - The token response from performDeviceFlow
Line 569: export async function completeOAuthFlow(
Line 587: export async function setApiToken(token: string): Promise<void> {
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/login-host-guard.ts:
Line 29: * precedence as {@link applyLoginUrl}. Self-hosted users export
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/interactive-login.ts:
...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/commands/auth/login.ts</path> <type>file</type> <content> 160: 161: /** 162: * Refuse `auth login` against a host that came from an untrusted channel 163: * (rc-shim bypass wrote env.SENTRY_URL with no matching trust anchor). 164: * 165: * Two distinct attack shapes are blocked here: 166: * 167: * 1. **Token leak (`auth login --token X`)**: without the refusal, login 168: * validation POSTs the user's existing API token to the attacker's 169: * host — direct credential exfiltration. 170: * 171: * 2. **Phishing (`auth login` OAuth device flow)**: the CLI directs the 172: * user's browser to `<attacker-host>/oauth/authorize/...`. A 173: * homograph / look-alike domain plus a Sentry-cloned login page can 174: * capture the user's SSO credentials (Google, GitHub, etc.) — much 175: * worse than a single token leak because it compromises ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/commands/auth/login.ts</path>
<type>file</type>
<content>
440: "Request only read-only OAuth scopes (project:read, org:read, event:read, member:read, team:read). " +
441: "Useful for handing tokens to AI agents or CI jobs that should not be able to mutate Sentry state.",
442: default: false,
443: },
444: scope: {
445: kind: "parsed",
446: parse: String,
447: brief:
448: "Request specific OAuth scopes (repeatable, comma-separated). " +
449: "E.g. --scope project:read --scope org:read. Overrides the default scope set.",
450: variadic: true,
451: optional: true,
452: },
453: },
454: aliases: { s: "scope" },
455: },
456: output: { human: formatLoginResult },
457: async *func(this: SentryContext, flags: LoginFlags) {
458: // Resolve OAuth scopes up ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/commands/auth/login.ts</path>
<type>file</type>
<content>
548: /**
549: * Pre-populate the org + region SQLite cache in the background.
550: *
551: * Called after successful authentication so that the first real command
552: * doesn't pay the cold-start cost of `getUserRegions()` + fan-out to
553: * each region's org list endpoint (~800ms on a typical SaaS account).
554: *
555: * Failures are silently ignored — the cache will be populated lazily
556: * on the next command that needs it.
557: */
558: function warmOrgCache(): void {
559: listOrganizationsUncached().catch(() => {
560: // Best-effort: cache warming failure doesn't affect the login result
561: });
562: }
(End of file - total 562 lines)
</content>
Found 100 matches (more matches available)
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/token-host.ts:
Line 71: * Process-local login trust anchor — set by `applyLoginUrl` from `--url` or
Line 72: * the boot-time env snapshot. Used by {@link isRequestOriginTrustedForCustomHeaders}
Line 129: export function isRequestOriginTrusted(
Line 140: * Like {@link isRequestOriginTrusted}, but anchored on the `sntrys_` claim
Line 153: * Token present → same as {@link isRequestOriginTrusted}. No token but an
Line 158: export function isRequestOriginTrustedForCustomHeaders(
Line 162: return isRequestOriginTrusted(requestInput);
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/token-claims.ts:
Line 10: * (the attacker who can poison `SENTRY_HOST` via `$GITHUB_ENV` can't read ...
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-url-parser.ts
Found 9 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-url-parser.ts:
Line 13: import { HostScopeError } from "./errors.js";
Line 16: import { getActiveTokenHost, isHostTrusted } from "./token-host.js";
Line 398: * otherwise throws `HostScopeError`. Only `sentry auth login --url <url>`
Line 402: * @throws {HostScopeError} On non-SaaS URL that doesn't match the token
Line 404: export function applySentryUrlContext(baseUrl: string): void {
Line 406: // Strict SaaS check (https + default port) matches isHostTrusted
Line 418: const tokenHost = getActiveTokenHost();
Line 419: if (!(tokenHost && isHostTrusted(baseUrl, tokenHost))) {
Line 420: throw new HostScopeError("URL argument", baseUrl, tokenHost);
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-url-parser.ts</path>
<type>file</type>
<content>
370: if (!(input.startsWith("http://") || input.startsWith("https://"))) {
371: return null;
372: }
373:
374: let url: URL;
375: try {
376: url = new URL(input);
377: } catch {
378: return null;
379: }
380:
381: const baseUrl = `${url.protocol}//${url.host}`;
382: const segments = url.pathname.split("/").filter(Boolean);
383:
384: return (
385: matchOrganizationsPath(baseUrl, segments, url.searchParams) ??
386: matchSettingsPath(baseUrl, segments) ??
387: matchSubdomainOrg(baseUrl, url.hostname, segments, url.searchParams) ??
388: matchSharePath(baseUrl, segments)
389: );
390: }
391:
392: /**
393: * Configure `SENTRY_URL` for self-hosted instances detected from a parsed
394: * URL, with a host-scoping trust check. ...
Found 8 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/command.ts:
Line 184: readonly skipRcUrlCheck?: boolean;
Line 500: const skipRcUrlCheck =
Line 501: builderArgs.skipRcUrlCheck === true || builderArgs.auth === "dsn";
Line 757: // Validate rc-sourced URL against the active token's host. Deferred
Line 758: // to here (instead of boot) so commands can opt out via skipRcUrlCheck.
Line 759: if (!skipRcUrlCheck && "cwd" in this) {
Line 760: const { assertRcUrlTrusted } = await import("./sentryclirc.js");
Line 761: await assertRcUrlTrusted(this.cwd as string);
Date: Sep 10, 2026 * 🔴 (16:47) User-provided verification output showed repository revision `605e8318d1f316bd28f53763f71ed716d1869616` and patch hash `d6fb08a6fc3ccdd14c52020f745eb20893148998`, matching the required review hash. * 🟡 (16:47) The reviewed patch comprised 31 modified files with exactly 1,329 insertions and 500 deletions: `.lore.md`; `packages/cli/plugins/sentry-cli/skills/sentry-c…
Date: Sep 10, 2026 * 🔴 [requested-review] (16:47) User stated the previous security review returned empty output and requested continuation of the same investigation with a substantive report. * 🔴 [enforced-workflow] (16:47) User required the patch to hash exactly to `d6fb08a6fc3ccdd14c52020f745eb20893148998` using `git diff --binary | git hash-object --stdin`, with verification both before and…