Dashboard › cli › Session 1OZpyHTtrkv1
1OZpyHTtrkv1BBt7wPerform a read-only adversarial correctness review of the current uncommitted change for getsentry/cli issue #1568. Do not edit files or run destructive commands. Inspect the actual diff and relevant routing/auth code and tests. Focus on getConfiguredSentryUrl's new sntrys_ claim fallback, explicit SENTRY_HOST/SENTRY_URL precedence, all getConfigured callers, import/cycle risk, behavior with malformed tokens, and the regression tests' ability to fail before the fix. Report findings first, ordered severity, citing exact file:line. If none, say PASS and identify residual testing gaps. Return only the review report.
<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`. ...
Error: There is no jj repo in "."
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/test/lib/constants.test.ts M packages/cli/test/lib/security/sntrys-claim-mismatch.test.ts .lore.md | 704 ++++++++++++--------- .../skills/sentry-cli/references/dashboard.md | 2 +- ...
diff --git a/packages/cli/src/lib/constants.ts b/packages/cli/src/lib/constants.ts
index 4e707773a..f86998d9a 100644
--- a/packages/cli/src/lib/constants.ts
+++ b/packages/cli/src/lib/constants.ts
@@ -3,6 +3,7 @@
*/
import { getEnv } from "./env.js";
+import { parseSntrysClaim } from "./token-claims.js";
/** Build-time constant injected by esbuild/bun */
declare const SENTRY_CLI_VERSION: string | undefined;
@@ -64,15 +65,23 @@ 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 or
+ * an org-auth token's embedded URL claim. Checks `SENTRY_HOST` first, then
+ * `SENTRY_URL`, then the `sntrys_` token claim, and otherwise returns undefined. ...
Found 19 matches /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/telemetry.ts: Line 472: const customUrl = getConfiguredSentryUrl(); /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-urls.ts: Line 20: return getConfiguredSentryUrl() ?? DEFAULT_SENTRY_URL; /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-client.ts: Line 685: return getConfiguredSentryUrl() ?? DEFAULT_SENTRY_URL; Line 696: return getConfiguredSentryUrl() ?? DEFAULT_SENTRY_URL; /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/region.ts: Line 109: const baseUrl = ...
Found 12 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/token-claims.ts:
Line 43: export function parseSntrysClaim(
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/constants.ts:
Line 6: import { parseSntrysClaim } from "./token-claims.js";
Line 84: return normalizeUrl(parseSntrysClaim(envToken)?.url);
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentryclirc-import.ts:
Line 58: import { parseSntrysClaim } from "./token-claims.js";
Line 393: const claim = parseSntrysClaim(token);
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-client.ts:
Line 38: import { parseSntrysClaim } from "./token-claims.js";
Line 130: // multiple Sentry instances. ...
<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: import { parseSntrysClaim } from "./token-claims.js";
7:
8: /** Build-time constant injected by esbuild/bun */
9: declare const SENTRY_CLI_VERSION: string | undefined;
10:
11: /**
12: * Build-time debug ID for sourcemap resolution, injected by esbuild.
13: *
14: * During the build, esbuild's `define` replaces this identifier with a
15: * placeholder UUID string literal. After esbuild finishes, the build
16: * script replaces the placeholder with the real debug ID (derived from
17: * the minified JS + sourcemap content hash). The same-length swap keeps
18: * sourcemap character positions valid. ...
<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/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/sentry-client.ts</path>
<type>file</type>
<content>
650: timeoutMs,
651: }: ExecuteAttemptArgs): Promise<AttemptResult> {
652: try {
653: const response = await fetchWithTimeout({
654: input,
655: init,
656: headers,
657: externalSignal: init?.signal,
658: timeoutMs,
659: });
660: return handleResponse(response, headers, isLastAttempt);
661: } catch (error) {
662: return handleFetchError(error, init?.signal, isLastAttempt);
663: }
664: }
665:
666: /** Singleton authenticated fetch instance - reused across all requests */
667: let cachedFetch: typeof fetch | null = null;
668:
669: /**
670: * Get the shared authenticated fetch instance.
671: * Cast to `typeof fetch` for compatibility with @sentry/api SDK options.
672: */
673: function getAuthenticatedFetch(): typeof fetch {
674: if (!cachedFetch) {
675: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/env.ts</path>
<type>file</type>
<content>
1: /**
2: * Environment variable registry for CLI/library isolation.
3: *
4: * CLI mode never calls `setEnv()`, so `getEnv()` returns `process.env`.
5: * Library mode calls `setEnv()` with a merged env copy — the consumer's
6: * `process.env` is never mutated.
7: */
8:
9: let _env: NodeJS.ProcessEnv = process.env;
10:
11: /** Get the active environment. Library mode overrides this; CLI uses process.env. */
12: export function getEnv(): NodeJS.ProcessEnv {
13: return _env;
14: }
15:
16: /** Set the active environment for this invocation. */
17: export function setEnv(env: NodeJS.ProcessEnv): void {
18: _env = env;
19: }
(End of file - total 19 lines)
</content>
<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/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-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/test/lib/constants.test.ts</path>
<type>file</type>
<content>
1: /**
2: * Tests for normalizeUrl and getConfiguredSentryUrl.
3: *
4: * The primary invariant — bare hostnames get `https://` prepended so that
5: * downstream URL construction produces valid URLs — is tested via property-based
6: * tests in constants.property.test.ts. These unit tests cover specific edge
7: * cases and the env-var integration path.
8: */
9:
10: import { afterEach, beforeEach, describe, expect, test } from "vitest";
11: import {
12: getCliEnvironment,
13: getConfiguredSentryUrl,
14: normalizeUrl,
15: } from "../../src/lib/constants.js";
16: import { mintSntrysToken } from "../helpers.js";
17:
18: describe("normalizeUrl", () => {
19: test("returns undefined for undefined", () => {
20: expect(normalizeUrl(undefined)).toBeUndefined();
21: });
22:
23: test("returns ...
<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: } from "../../helpers.js";
18:
19: const ENV_KEYS = [
20: "SENTRY_AUTH_TOKEN",
21: "SENTRY_TOKEN",
22: "SENTRY_HOST",
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>
240:
241: test("sntrys_ claim wins over SENTRY_HOST (immune to env injection)", async () => {
242: // The claim is authoritative for sntrys_ tokens. Even when
243: // SENTRY_HOST is set (legitimately or via CI env injection), the
244: // snapshot uses the claim — it's the only value the token's
245: // issuing server can vouch for.
246: process.env.SENTRY_AUTH_TOKEN = mintSntrysToken({
247: iat: 1_700_000_000,
248: url: "https://sentry.firsthost.com",
249: org: "x",
250: });
251: process.env.SENTRY_HOST = "https://sentry.secondhost.com";
252:
253: const { captureEnvTokenHost, getEnvTokenHost } = await import(
254: "../../../src/lib/env-token-host.js"
255: );
256: captureEnvTokenHost();
257:
258: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/auth.ts</path>
<type>file</type>
<content>
350: let cachedHasStoredCreds: { value: boolean } | undefined;
351:
352: /** Memoized full auth row for {@link refreshToken}. Same wrapper contract as {@link cachedAuthToken}. */
353: let cachedAuthRow: { value: AuthRow | undefined } | undefined;
354:
355: function getCachedAuthRow(): AuthRow | undefined {
356: if (cachedAuthRow !== undefined) {
357: return cachedAuthRow.value;
358: }
359: const row = getAuthRow();
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. ...
Found 20 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/auth.ts:
Line 82: export type AuthSource = "env:SENTRY_AUTH_TOKEN" | "env:SENTRY_TOKEN" | "oauth";
Line 100: export function getRawEnvToken(): string | undefined {
Line 101: const authToken = getEnv().SENTRY_AUTH_TOKEN?.trim();
Line 105: const sentryToken = getEnv().SENTRY_TOKEN?.trim();
Line 114: * `SENTRY_AUTH_TOKEN` takes priority over `SENTRY_TOKEN` (matches legacy sentry-cli).
Line 122: const authToken = getEnv().SENTRY_AUTH_TOKEN?.trim();
Line 124: return { token: authToken, source: "env:SENTRY_AUTH_TOKEN" };
Line 126: const sentryToken = getEnv().SENTRY_TOKEN?.trim();
Line 128: return { token: sentryToken, source: "env:SENTRY_TOKEN" };
Line 143: * Returns the specific variable name (e.g. ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/auth.ts</path>
<type>file</type>
<content>
65: const db = getDatabase();
66: db.query("UPDATE auth SET host = ? WHERE id = 1").run(host);
67: });
68: log.info(`Migrated stored credentials to host-scoped model: ${host}`);
69: } catch {
70: // Non-fatal: if the migration write fails, callers still get a
71: // well-formed host from this function. The migration will retry
72: // on the next access.
73: }
74: row.host = host;
75: return host;
76: }
77:
78: /** Prefix for environment variable auth sources in {@link AuthSource} */
79: export const ENV_SOURCE_PREFIX = "env:";
80:
81: /** Where the auth token originated */
82: export type AuthSource = "env:SENTRY_AUTH_TOKEN" | "env:SENTRY_TOKEN" | "oauth";
83:
84: export type AuthConfig = {
85: token?: string;
86: refreshToken?: string;
87: expiresAt?: number;
88: ...
Found 2 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/helpers.ts:
Line 156: export function useEnvSandbox(keys: readonly string[]): void {
Line 184: export async function resetHostScopingState(): Promise<void> {
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/helpers.ts</path>
<type>file</type>
<content>
130: // Deleting process.env.SENTRY_CONFIG_DIR causes failures in test files
131: // that load after this afterEach runs, because their module-level code
132: // (or beforeEach hooks) may read the env var and get undefined.
133: // Note: preload.ts always sets SENTRY_CONFIG_DIR, so savedConfigDir is
134: // always defined in practice. The else branch is intentionally omitted
135: // to avoid the "delete process.env" anti-pattern.
136: if (savedConfigDir !== undefined) {
137: process.env[CONFIG_DIR_ENV_VAR] = savedConfigDir;
138: }
139: await cleanupTestDir(dir);
140: });
141:
142: return () => dir;
143: }
144:
145: /**
146: * Save/restore a set of `process.env` keys around each test in a `describe`
147: * block. ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/telemetry.ts</path>
<type>file</type>
<content>
430: * Bun does not implement this Node.js API, which the SDK's NodeSystemError
431: * integration uses in its `processEvent` hook. When missing, the hook crashes
432: * during event processing instead of sending the error report (CLI-K1).
433: *
434: * Checked once at module load so the integration filter is a simple boolean.
435: */
436: const hasGetSystemErrorMap = (() => {
437: try {
438: // Dynamic require to avoid bundler issues — the check only matters at runtime
439: const util = _require("node:util") as Record<string, unknown>;
440: return typeof util.getSystemErrorMap === "function";
441: } catch {
442: return false;
443: }
444: })();
445:
446: /** Current beforeExit handler, tracked so it can be replaced on re-init */
447: let currentBeforeExitHandler: (() => void) | null = ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/region.ts</path>
<type>file</type>
<content>
75:
76: const result = await withAuthGuard(async () => {
77: const response = await getOrganization({
78: ...config,
79: path: { organization_id_or_slug: orgSlug },
80: });
81:
82: // Throw SDK errors so withAuthGuard can discriminate:
83: // AuthError propagates, others fall back to default URL
84: if (response.error !== undefined) {
85: throw response.error;
86: }
87:
88: const regionUrl = response.data?.links?.regionUrl ?? baseUrl;
89:
90: // Cache for future use. setOrgRegion also extends the in-process
91: // trust class so the subsequent request to this region passes the
92: // fetch-layer guard without needing a separate registration call. ...
<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/custom-headers.ts</path>
<type>file</type>
<content>
100: }
101:
102: const name = trimmed.slice(0, colonIndex).trim();
103: const value = trimmed.slice(colonIndex + 1).trim();
104:
105: if (!name) {
106: throw new ConfigError(
107: `Invalid header in SENTRY_CUSTOM_HEADERS: empty header name in '${trimmed}'.`
108: );
109: }
110:
111: if (!VALID_HEADER_NAME_RE.test(name)) {
112: throw new ConfigError(
113: `Invalid header name '${name}' in SENTRY_CUSTOM_HEADERS. Header names must contain only alphanumeric characters, hyphens, and RFC 7230 token characters.`
114: );
115: }
116:
117: if (FORBIDDEN_HEADER_NAMES.has(name.toLowerCase())) {
118: throw new ConfigError(
119: `Cannot override reserved header '${name}' in SENTRY_CUSTOM_HEADERS. This header is managed by the CLI.`
120: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/dsn/code-scanner.ts</path>
<type>file</type>
<content>
190: span.setAttribute("dsn.files_scanned", filesScanned);
191: span.setAttribute("dsn.dsns_found", 0);
192: return null;
193: } catch (error) {
194: if (error instanceof ConfigError) {
195: throw error;
196: }
197: span.setStatus({ code: 2, message: "Directory scan failed" });
198: log.debug(`scanCodeForFirstDsn failed: ${String(error)}`);
199: return null;
200: }
201: },
202: {
203: "dsn.scan_dir": cwd,
204: "dsn.stop_on_first": true,
205: "dsn.max_depth": DSN_MAX_DEPTH,
206: }
207: );
208: }
209:
210: function isCommentedLine(trimmedLine: string): boolean {
211: return COMMENT_PREFIXES.some((prefix) => trimmedLine.startsWith(prefix));
212: }
213:
214: /**
215: * Get the expected Sentry ...
Found 4 matches /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-client.ts: Line 574: function createAuthenticatedFetch(): ( Line 675: cachedFetch = createAuthenticatedFetch() as unknown as typeof fetch; /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/api/infrastructure.ts: Line 458: export async function apiRequestToRegion<T>( Line 608: export async function apiRequestToRegionNoContent(
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-client.ts</path>
<type>file</type>
<content>
550:
551: // Unreachable: the last attempt always returns 'done' or 'throw'
552: throw new Error("Exhausted all retry attempts");
553: }
554:
555: /**
556: * Create a fetch function with authentication, timeout, retry, caching, and 401 refresh.
557: *
558: * This wraps the native fetch with:
559: * - **Response caching** for GET requests (checked before hitting the network)
560: * - Auth token injection (Bearer token)
561: * - Request timeout via AbortController
562: * - Automatic retry on transient HTTP errors (408, 429, 5xx)
563: * - 401 handling: force-refreshes the token and retries once
564: * - Exponential backoff between retries
565: * - User-Agent header for API analytics
566: * - Automatic HTTP span tracing for every request
567: *
568: * Cache is checked first — on a hit, auth ...
Found 3 matches /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-client.ts: Line 113: function prepareHeaders( Line 506: async function fetchWithRetry( Line 513: const headers = prepareHeaders(input, init, token);
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-client.ts</path>
<type>file</type>
<content>
490:
491: const body = init?.body;
492: if (body instanceof ReadableStream) {
493: const snapshot = await new Response(body).arrayBuffer();
494: return () => ({ input, init: { ...init, body: snapshot } });
495: }
496:
497: return () => ({ input, init });
498: }
499:
500: /**
501: * Authenticate and execute a request with retry logic.
502: *
503: * Refreshes the auth token, then retries the request up to `MAX_RETRIES` times
504: * with exponential backoff on transient errors. ...
Found 80 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/token-host.test.ts:
Line 7: * cover well (exact edge strings, malformed inputs).
Line 43: test("returns undefined for malformed input", () => {
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/api/traces.test.ts:
Line 754: test("skips spans with no project slug instead of issuing a malformed request", async () => {
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/token-claims.test.ts:
Line 16: import { parseSntrysClaim } from "../../src/lib/token-claims.js";
Line 19: describe("parseSntrysClaim", () => {
Line 27: expect(parseSntrysClaim(token)).toEqual({
Line 40: expect(parseSntrysClaim(token)).toEqual({
Line 48: ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/token-claims.test.ts</path>
<type>file</type>
<content>
180: });
181: expect(parseSntrysClaim(token)?.org).toBeUndefined();
182: });
183:
184: test("does NOT throw on adversarial inputs", () => {
185: // Catch-all: any input must either return undefined or a valid
186: // claim object — never throw.
187: const adversarial = [
188: "sntrys___",
189: "sntrys__",
190: "sntrys_💥_secret",
191: "sntrys_\u0000_secret",
192: "sntrys_/=+_secret",
193: // Base64 of `{` — incomplete JSON
194: `sntrys_${Buffer.from("{", "utf8").toString("base64")}_secret`,
195: ];
196: for (const input of adversarial) {
197: expect(() => parseSntrysClaim(input)).not.toThrow();
198: }
199: });
200:
201: test("treats forged claim same as legitimate (we don't verify signatures)", () => {
202: // ...
(no output)
diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md index 75babca80..96ebe39a6 100644 --- a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md +++ b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md @@ -42,7 +42,7 @@ View a dashboard - `-w, --web - Open in browser` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-r, --refresh <value> - Auto-refresh interval in seconds (default: 60, min: 10)` -- `-t, --period <value> - Time range: "7d", "2026-06-01..2026-07-01", ">=2026-06-01"` +- `-t, --period <value> - Time range: "7d", "2026-08-01..2026-09-01", ">=2026-08-01"` **Examples:** diff --git a/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/event.md b/packages/cli/plugins/sentry-cli/skills/sentry-cli/references/event.md index 9aef2b487..eb286824b 100644 --- ...
<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
Test Files 2 passed (2)
Tests 32 passed (32)
Start at 09:12:43
Duration 3.47s (transform 3.84s, setup 103ms, import 4.35s, tests 1.00s, 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/test/lib/constants.test.ts M packages/cli/test/lib/security/sntrys-claim-mismatch.test.ts
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/token-claims.property.test.ts</path> <type>file</type> <content> 1: /** 2: * Property-based tests for `parseSntrysClaim`. 3: * 4: * Invariants under random input: 5: * 6: * 1. `parseSntrysClaim` never throws. 7: * 2. Round-trip: a token minted with a given url+iat parses back to the 8: * same url. 9: * 3. Forged claims parse identically to legitimate ones (this is by 10: * design — see `token-claims.ts` JSDoc — and the property documents 11: * that the parser is NOT a security primitive). 12: * 4. Adversarial inputs (random strings, near-prefix matches, malformed 13: * base64, JSON injection attempts) always return `undefined` instead 14: * of a partially-trusted result. ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/auth.ts</path>
<type>file</type>
<content>
158: export function getAuthConfig(): AuthConfig | undefined {
159: // When SENTRY_FORCE_ENV_TOKEN is set, check env first (old behavior).
160: // Otherwise, check the DB first — stored OAuth takes priority over env tokens.
161: // This is the core fix for #646: wizard-generated build tokens no longer
162: // silently override the user's interactive login.
163: const forceEnv = getEnv().SENTRY_FORCE_ENV_TOKEN?.trim();
164: if (forceEnv) {
165: const envToken = getEnvToken();
166: if (envToken) {
167: return { token: envToken.token, source: envToken.source };
168: }
169: }
170:
171: const dbConfig = withDbSpan("getAuthConfig", () => {
172: const row = getAuthRow();
173:
174: if (!row?.token) {
175: return;
176: }
177:
178: // Skip expired tokens without a ...
MUST-FIX — ignored env tokens can redirect and break stored-OAuth sessions.
getConfiguredSentryUrl() uses an env sntrys_ claim even when stored OAuth wins token selection (packages/cli/src/lib/constants.ts:83-84). getAuthConfig() deliberately prefers stored OAuth unless forced (packages/cli/src/lib/db/auth.ts:158-201), and the host guard likewise uses the stored token’s host (packages/cli/src/lib/token-host.ts:54-67).
With stored OAuth for sentry.io, an unforced env sntrys_ token claiming self-hosted.example, and no explicit URL, API routing moves to the claim URL while authentication keeps the stored OAuth token. The request then fails host scoping before the wire. Add a regression that stores OAuth, sets an unforced conflicting env claim, and proves routing follows the active stored credential.
MUST-FIX — whitespace SENTRY_HOST suppresses a valid explicit SENTRY_URL, then allows the claim fallback.
normalizeUrl(env.SENTRY_HOST || env.SENTRY_URL) selects whitespace SENTRY_HOST before normalizing it (packages/cli/src/lib/constants.ts:79); normalization then returns undefined for whitespace (packages/cli/src/lib/constants.ts:52-64). The function skips a valid SENTRY_URL and uses the token claim instead (packages/cli/src/lib/constants.ts:83-84).
Test SENTRY_HOST=" ", SENTRY_URL=https://configured.example, and a distinct claim URL. The configured URL must win.
CONCERN — a structurally valid claim with an invalid URL becomes a global configured URL.
The parser accepts any non-empty string as url (packages/cli/src/lib/token-claims.ts:82-90), while normalizeUrl() only adds a scheme and does not validate it (packages/cli/src/lib/constants.ts:52-64). Thus an invalid claim URL propagates through API, OAuth, DSN scanning, web-link, telemetry, region, and custom-header routing callers from packages/cli/src/lib/constants.ts:84. ...
Date: Sep 10, 2026 * 🟡 (09:14) `packages/cli/src/lib/db/auth.ts` `getAuthConfig()` (lines 158-203) reads `SENTRY_FORCE_ENV_TOKEN?.trim()`; when truthy, it returns `getEnvToken()` first. Otherwise it reads `getAuthRow()` in `withDbSpan("getAuthConfig", ...)`, rejects rows without `token` and rows whose `expires_at` is in the past without `refresh_token`, returns stored OAuth `{ token, refreshToke…
Date: Sep 10, 2026 * 🟡 (09:13) Working tree modifications include `.lore.md`; `packages/cli/plugins/sentry-cli/skills/sentry-cli/references/{dashboard.md,event.md,explore.md,feedback.md,issue.md,log.md,replay.md,span.md,trace.md}`; `packages/cli/src/lib/constants.ts`; `packages/cli/test/lib/constants.test.ts`; and `packages/cli/test/lib/security/sntrys-claim-mismatch.test.ts`. * 🔴 (09:13) User …
Date: Sep 10, 2026 * 🟡 (09:11) `packages/cli/src/lib/sentry-client.ts::fetchWithRetry()` at lines 506–553 refreshes the token via `refreshToken()`, prepares headers with `prepareHeaders(input, init, token)`, builds a replayable request factory via `buildAttemptFactory(input, init)`, and resolves `timeoutMs` with `resolveTimeoutMs(fullUrl)` before looping from `attempt = 0` through `attempt <= MA…
Date: Sep 10, 2026 * 🔴 (09:11) User stated the last retry attempt in `packages/cli/src/lib/sentry-client.ts` always returns `"done"` or `"throw"`; therefore `throw new Error("Exhausted all retry attempts")` at lines 551–552 is unreachable. * 🟡 (09:10) `packages/cli/src/lib/dsn/code-scanner.ts::getExpectedHost()` obtains `getConfiguredSentryUrl()`; when configured, parses it with `new URL(sentry…
Date: Sep 10, 2026 * 🔴 (09:10) User stated `preload.ts` always sets `SENTRY_CONFIG_DIR`; therefore `savedConfigDir` is always defined in practice. * 🟡 (09:10) `packages/cli/test/helpers.ts`: test cleanup deliberately does not delete `process.env.SENTRY_CONFIG_DIR`/`CONFIG_DIR_ENV_VAR`; deletion causes later-loaded test files’ module-level code or `beforeEach` hooks to read it as `undefined`. If…
Date: Sep 10, 2026 * 🔴 (09:08) User stated `getSdkConfig()` must set `throwOnError: false` always because errors are handled by the CLI itself. * 🔴 (09:08) User stated endpoints for OAuth, user accounts, and regions are always on the control silo and must use `getControlSdkConfig()`. * 🔴 (09:08) User stated CLI mode never calls `setEnv()`; `getEnv()` returns `process.env` in CLI mode, while li…
Date: Sep 10, 2026 * 🔴 (09:08) User stated that commands must always be checked against the latest `package.json` scripts; commands run from `packages/cli` or from the repository root via `pnpm --filter sentry run <script>`. * 🔴 (09:08) User stated that all packages must be declared in `devDependencies`, never `dependencies`, because everything is bundled at build time via esbuild; CI enforces …
Date: Sep 10, 2026 * 🔴 (09:07) User provided jj workflow guidance stating that `jj` never fails on conflict; after `rebase`, `new`, or `squash`, conflicts are recorded in the resulting commit and `jj st` must be run to identify conflicted files. Conflicts should be resolved manually; `jj resolve` must not be used because it is interactive. * 🟡 (09:07) Repository investigation found no `.jj` rep…
Date: Sep 10, 2026 * 🟡 [requested-code-review] (09:07) User requested a read-only adversarial correctness review of the current uncommitted change for getsentry/cli issue #1568. Constraints: do not edit files or run destructive commands; inspect the actual diff plus relevant routing/auth code and tests; report findings first ordered by severity with exact file:line citations; if no findings, say…