Dashboard › cli › Session 0tfu9CSyKLUW
0tfu9CSyKLUWWZxuzPerform a strictly read-only security/pentest review of the current uncommitted worktree for GitHub issue getsentry/cli#1568. Focus on the behavioral change in packages/cli/src/lib/sentry-client.ts where getApiBaseUrl() and getControlSiloUrl() now use getConfiguredSentryUrl() ?? getActiveTokenHost() ?? DEFAULT_SENTRY_URL, plus its regression coverage in packages/cli/test/lib/security/sntrys-claim-mismatch.test.ts. Inspect credential host scoping, stored OAuth versus env-token precedence, forged/malformed sntrys claim handling, custom headers, explicit URL overrides, region trust, and possible credential exfiltration. Do not edit any files or run destructive VCS commands. Distinguish vulnerabilities in the issue fix from unrelated broad dirty-worktree changes. Return findings first with severity, exact file:line references, a deterministic exploit/regression for each issue, a concrete fix, residual risks, and final verdict PASS or MUST-FIX.
<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>
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/package.json /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/AGENTS.md /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/CONTRIBUTING.md /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/init-eval/templates/sveltekit-app/package.json /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/init-eval/templates/react-vite-app/package.json /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/init-eval/templates/express-app/package.json /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/init-eval/templates/nextjs-app/...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/AGENTS.md</path> <type>file</type> <content> 1: # AGENTS.md 2: 3: Guidelines for AI agents working in this codebase. 4: 5: ## Project Overview 6: 7: **Sentry CLI** is a command-line interface for [Sentry](https://sentry.io), built with Node.js, [pnpm](https://pnpm.io), and [Stricli](https://bloomberg.github.io/stricli/). It lives in `packages/cli` of a pnpm workspace; the docs site lives in `apps/cli-docs`. ...
Found 4 matches /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/AGENTS.md: Line 25: - **OAuth Device Flow** - Secure authentication without browser redirects Line 178: 1. **Argument parsing** — positional args, flags, URL detection Line 486: ├── DeviceFlowError (exitCode=51 — OAuth flow) Line 913: - Edge cases outside the property generator's range (e.g., self-hosted DSNs when the arbitrary only produces SaaS ones)
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/CONTRIBUTING.md</path> <type>file</type> <content> 1: # Contributing to Sentry CLI 2: 3: This guide documents the patterns and conventions used in this CLI for consistency. 4: 5: ## Command Patterns 6: 7: We follow [gh CLI](https://cli.github.com/) conventions for best-in-class developer experience. 8: 9: ### List Commands 10: 11: List commands use **optional positional arguments** for context with smart auto-detection. 12: 13: ```bash 14: sentry org list [--limit N] [--json] 15: sentry project list [org] [--limit N] [--json] 16: sentry issue list [<org>/<project>] [--json] 17: ``` 18: 19: **Target syntax**: 20: - `<org>/<project>` - Explicit organization and project (e.g., `my-org/frontend`) 21: - `<org>/` - All projects in the specified organization 22: - `<project>` - Search for project by name across all accessible organizations 23: - *(omit)* - Auto-detect ...
<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: ...
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 packages/cli/src/lib/db/regions.ts M packages/cli/src/lib/db/schema.ts M packages/cli/src/lib/oauth.ts M ...
.lore.md | 721 +++++++++++---------- .../skills/sentry-cli/references/dashboard.md | 2 +- .../skills/sentry-cli/references/event.md | 2 +- .../skills/sentry-cli/references/explore.md | 2 +- .../skills/sentry-cli/references/feedback.md | 2 +- .../skills/sentry-cli/references/issue.md | 4 +- .../sentry-cli/skills/sentry-cli/references/log.md | 2 +- .../skills/sentry-cli/references/replay.md | 2 +- .../skills/sentry-cli/references/span.md | 2 +- .../skills/sentry-cli/references/trace.md | 4 +- packages/cli/src/commands/org/list.ts | 3 +- packages/cli/src/commands/project/delete.ts | 3 +- packages/cli/src/lib/api/organizations.ts | 9 +- packages/cli/src/lib/api/projects.ts | 6 +- packages/cli/src/lib/constants.ts | 8 +- packages/cli/src/lib/db/regions.ts | 188 ++++-- ...
{"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, ...
diff --git a/packages/cli/src/lib/sentry-client.ts b/packages/cli/src/lib/sentry-client.ts
index 53dd0cea1..4bdc8a686 100644
--- a/packages/cli/src/lib/sentry-client.ts
+++ b/packages/cli/src/lib/sentry-client.ts
@@ -65,6 +65,9 @@ const ENDPOINT_TIMEOUT_OVERRIDES: TimeoutOverride[] = [
/** Maximum retry attempts for failed requests */
const MAX_RETRIES = 2;
+/** Maximum redirects to follow for one request. Matches native fetch. */
+const MAX_REDIRECTS = 20;
+
/** Maximum backoff delay between retries in milliseconds */
const MAX_BACKOFF_MS = 10_000;
@@ -236,10 +239,140 @@ type FetchWithTimeoutArgs = {
timeoutMs: number;
};
+type FetchWithTimeoutResult = {
+ response: Response;
+ headers: Headers;
+};
+
+function isRedirect(response: Response): boolean {
+ return [301, 302, 303, 307, 308].includes(response.status);
+}
+
+function getRedirectUrl(response: Response, request: Request): URL {
+ const location = response.headers.get("location");
+ if (!location) {
+ ...
<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`). ...
Found 34 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/db/auth.ts:
Line 68: log.info(`Migrated stored credentials to host-scoped model: ${host}`);
Line 96: * when stored OAuth credentials would normally take priority. Used by the HTTP
Line 100: export function getRawEnvToken(): string | undefined {
Line 117: * This function is intentionally pure (no DB access). The "prefer stored OAuth
Line 119: * which check the DB first when `SENTRY_FORCE_ENV_TOKEN` is not set.
Line 158: export function getAuthConfig(): AuthConfig | undefined {
Line 159: // When SENTRY_FORCE_ENV_TOKEN is set, check env first (old behavior).
Line 160: // Otherwise, check the DB first — stored OAuth takes priority over env tokens.
Line 163: const forceEnv = getEnv().SENTRY_FORCE_ENV_TOKEN?.trim();
Line 197: // No stored OAuth — fall back to env token
Line 206: * Read the host the stored OAuth ...
<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 { getEnvTokenHost } 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 lifetime (1 hour) ...
<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. ...
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) ?? ...
<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-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/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: ...
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 ...
diff --git a/packages/cli/src/lib/db/schema.ts b/packages/cli/src/lib/db/schema.ts
index 847fdfdf0..41bede474 100644
--- a/packages/cli/src/lib/db/schema.ts
+++ b/packages/cli/src/lib/db/schema.ts
@@ -19,7 +19,7 @@ import type { Database } from "./sqlite.js";
const _require = createRequire(import.meta.url);
-export const CURRENT_SCHEMA_VERSION = 16;
+export const CURRENT_SCHEMA_VERSION = 17;
/** Environment variable to disable auto-repair */
const NO_AUTO_REPAIR_ENV = "SENTRY_CLI_NO_AUTO_REPAIR";
@@ -168,6 +168,9 @@ export const TABLE_SCHEMAS: Record<string, TableSchema> = {
org_name: { type: "TEXT", addedInVersion: 9 },
org_role: { type: "TEXT", addedInVersion: 10 },
region_url: { type: "TEXT", notNull: true },
+ // Origin that returned this regional URL. Rows only extend trust for the
+ // credential scoped to this control-silo origin. ...
gh: Not Found (HTTP 404) base64: invalid input
[{"path":"src/sentry/utils/security/orgauthtoken_token.py","repository":{"id":"MDEwOlJlcG9zaXRvcnk4NzMzMjg=","isFork":false,"isPrivate":false,"nameWithOwner":"getsentry/sentry","url":"https://github.com/getsentry/sentry"},"url":"https://github.com/getsentry/sentry/blob/337b6f0f1c06821fdced09b61fd416a4e3f81d77/src/sentry/utils/security/orgauthtoken_token.py"},{"path":"tests/sentry/utils/security/test_orgauthtoken_token.py","repository":{"id":"MDEwOlJlcG9zaXRvcnk4NzMzMjg=","isFork":false,"isPrivate":false,"nameWithOwner":"getsentry/sentry","url":"https://github.com/getsentry/sentry"},"url":"https://github.com/getsentry/sentry/blob/337b6f0f1c06821fdced09b61fd416a4e3f81d77/tests/sentry/utils/security/test_orgauthtoken_token.py"},{"path":"tests/sentry/api/endpoints/test_organization_auth_tokens.py","repository":{"id":"MDEwOlJlcG9zaXRvcnk4NzMzMjg=","isFork":false,"isPrivate":false,"nameWithOwner":"getsentry/sentry","url":"https://github.com/getsentry/sentry"},"url":"https://github.com/getsen...
import secrets
from base64 import b64decode, b64encode
from typing import Any
from sentry import options
from sentry.utils import hashlib, json
from sentry.utils.dates import deprecated_utcnow
SENTRY_ORG_AUTH_TOKEN_PREFIX = "sntrys_"
class SystemUrlPrefixMissingException(Exception):
# system.url-prefix is not set. You need to set this to generate a token.
pass
def generate_token(org_slug: str, region_url: str) -> str:
sentry_url = options.get("system.url-prefix")
if sentry_url is None:
raise SystemUrlPrefixMissingException
payload = {
"iat": deprecated_utcnow().timestamp(),
"url": sentry_url,
"region_url": region_url,
"org": org_slug,
}
secret = b64encode(secrets.token_bytes(nbytes=32)).decode("ascii").rstrip("=")
json_str = json.dumps(payload)
payload_encoded = base64_encode_str(json_str)
return f"{SENTRY_ORG_AUTH_TOKEN_PREFIX}{payload_encoded}_{secret}"
def parse_token(token: str) -> ...
<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 { ...
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 84: export function registerLoginTrustAnchor(url: string): void {
/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 or
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/telemetry.ts:
Line 245: // ConfigError, HostScopeError, user 4xx, network failures), it also returns
Line 461: * - Self-hosted: the configured SENTRY_HOST/SENTRY_URL if ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentryclirc.ts</path>
<type>file</type>
<content>
320: throw error;
321: });
322: cache.set(cwd, promise);
323: return promise;
324: }
325:
326: /**
327: * Apply env shim for `.sentryclirc` token and URL fields.
328: *
329: * Maps config file values to environment variables so the existing
330: * auth and URL resolution code picks them up without changes:
331: * - `[auth] token` → `SENTRY_AUTH_TOKEN` (if neither `SENTRY_AUTH_TOKEN` nor `SENTRY_TOKEN` is set)
332: * - `[defaults] url` → `SENTRY_URL` (if both `SENTRY_HOST` and `SENTRY_URL` are unset)
333: *
334: * The URL is applied unconditionally at boot — the trust check is deferred
335: * to {@link assertRcUrlTrusted}, which `buildCommand` calls after Stricli
336: * identifies the command (so the command can opt out via `skipRcUrlCheck`). ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sentry-url-parser.ts</path>
<type>file</type>
<content>
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.
395: *
396: * SaaS URLs proceed (credentials scoped to SaaS are valid for any sentry.io
397: * subdomain). Non-SaaS URLs require the active token's host to match —
398: * otherwise throws `HostScopeError`. ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sdk-invoke.ts</path>
<type>file</type>
<content>
1: /**
2: * Direct command invocation for the typed SDK layer.
3: *
4: * Resolves commands from the Stricli route tree and calls their wrapped
5: * handler functions directly — no string parsing, no route scanning.
6: *
7: * Also provides `buildRunner()` — the variadic `run()` escape hatch
8: * that accepts CLI argument strings and routes them through Stricli.
9: *
10: * Both `buildInvoker` and `buildRunner` share the same env isolation,
11: * telemetry, zero-copy capture, and error wrapping guarantees via the
12: * `executeWithCapture` helper.
13: *
14: * @module
15: */
16:
17: import { homedir } from "node:os";
18: import type { Span } from "@sentry/core";
19: import type { Writer } from "../types/index.js";
20: import { type AsyncChannel, createAsyncChannel } from "./async-channel.js";
21: import ...
Found 9 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sdk-invoke.ts:
Line 12: * `executeWithCapture` helper.
Line 21: import { setEnv } from "./env.js";
Line 393: async function executeWithCapture<T>(
Line 402: setEnv(env);
Line 443: setEnv(process.env);
Line 483: setEnv(env);
Line 531: setEnv(process.env);
Line 570: return executeWithCapture<T>(options, async (ctx, span) => {
Line 612: return executeWithCapture<unknown>(options, async (ctx, span) => {
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sdk-invoke.ts</path>
<type>file</type>
<content>
360: const context = {
361: process: fakeProcess,
362: stdout,
363: stderr,
364: stdin: process.stdin,
365: env,
366: cwd,
367: homeDir: homedir(),
368: configDir: getConfigDir(),
369: };
370:
371: return {
372: context,
373: stdoutChunks,
374: stderrChunks,
375: getCapturedResult: () => {
376: if (capturedResults.length === 0) {
377: return;
378: }
379: return capturedResults.length === 1
380: ? capturedResults[0]
381: : capturedResults;
382: },
383: };
384: }
385:
386: /**
387: * Core execution wrapper shared by buildInvoker and buildRunner.
388: *
389: * Handles env isolation, capture context, telemetry, error wrapping,
390: * and output parsing. ...
<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/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. ...
Found 1 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/region.ts:
Line 111: export function isMultiRegionEnabled(): boolean {
Found 16 matches
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/build/index.ts:
Line 184: /** Deterministic STORE + fixed-mtime options for a normalized ZIP entry. */
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/dif/index.ts:
Line 269: * @param options - Optional behavior flags.
Line 270: * @param options.collectIl2cppSources - When `true`, also include the C# source
Line 281: options?: { collectIl2cppSources?: boolean }
Line 295: if (options?.collectIl2cppSources) {
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/index.ts:
Line 4: * Provides `createSentrySDK()` as the single public API — a typed SDK client
Line 12: * import createSentrySDK from "sentry";
Line 14: * const sdk = createSentrySDK({ token: "sntrys_..." });
Line 31: // index.ts ↔ ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/index.ts</path>
<type>file</type>
<content>
1: /**
2: * Library entry point for programmatic Sentry CLI usage.
3: *
4: * Provides `createSentrySDK()` as the single public API — a typed SDK client
5: * with named methods for every CLI command, plus a `run()` escape hatch for
6: * arbitrary command strings.
7: *
8: * CLI runner is re-exported as `_cli` for the npm bin wrapper (`dist/bin.cjs`).
9: *
10: * @example
11: * ```typescript
12: * import createSentrySDK from "sentry";
13: *
14: * const sdk = createSentrySDK({ token: "sntrys_..." });
15: * const orgs = await sdk.org.list();
16: * const issues = await sdk.issue.list({ orgProject: "acme/frontend" });
17: *
18: * // Escape hatch for arbitrary commands
19: * const version = await sdk.run("--version");
20: * ```
21: *
22: * @module
23: */
24:
25: import { buildInvoker, buildRunner } from ...
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/src/lib/sdk-types.ts</path>
<type>file</type>
<content>
1: /**
2: * Shared types for the SDK layer.
3: *
4: * Lives in `lib/` so both `index.ts` (public API) and `sdk-invoke.ts`
5: * (internal) can import without circular dependencies.
6: *
7: * @module
8: */
9:
10: /** Options for programmatic CLI invocation. */
11: export type SentryOptions = {
12: /**
13: * Auth token override. When omitted, falls back to `SENTRY_AUTH_TOKEN`
14: * or `SENTRY_TOKEN` environment variables, then stored credentials.
15: */
16: token?: string;
17:
18: /**
19: * Sentry instance URL for self-hosted installations.
20: * Defaults to `sentry.io`. Accepts with or without protocol
21: * (e.g., `"sentry.example.com"` or `"https://sentry.example.com"`).
22: */
23: url?: string;
24:
25: /**
26: * Default organization slug. ...
No files found
/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/sdk-invoke.test.ts
No files found
<path>/home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/glowing-cactus/packages/cli/test/lib/sdk-invoke.test.ts</path>
<type>file</type>
<content>
1: /**
2: * Unit tests for the SDK invoke layer.
3: *
4: * Focuses on `applyFlagDefaults` which replicates Stricli's default
5: * application for the SDK direct-invoke path (bypasses Stricli parsing).
6: */
7:
8: import { describe, expect, test } from "vitest";
9: import {
10: applyFlagDefaults,
11: type FlagDef,
12: parseOutput,
13: } from "../../src/lib/sdk-invoke.js";
14:
15: // ---------------------------------------------------------------------------
16: // applyFlagDefaults — parsed flags with defaults
17: // ---------------------------------------------------------------------------
18:
19: describe("applyFlagDefaults: parsed flags with defaults", () => {
20: test("calls parse on string default when flag is missing", () => {
21: const flagDefs: Record<string, FlagDef> = {
22: ...
<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>
Found 7 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/env-token-host.ts:
Line 31: * 1. captureEnvTokenHost() ← this module, env + claim, synchronous
Line 58: export function captureEnvTokenHost(): void {
Line 80: * standard boot path calls `captureEnvTokenHost()` explicitly; this
Line 85: captureEnvTokenHost();
/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();
Date: Sep 10, 2026 * 🔴 (15:55) Project property-testing convention: properties should always hold regardless of input; tests use `fcAssert(property(...), { numRuns: DEFAULT_NUM_RUNS })`, including symmetry and encode/decode round-trip properties. * 🔴 (15:55) Project command architecture uses `buildCommand()` with `docs`, `output`, `parameters`, and an async generator `func`; output may define `…
Date: Sep 10, 2026 * 🔴 [requested-review] (15:54) User requested a strictly read-only security/pentest review of the current uncommitted worktree for GitHub issue `getsentry/cli#1568`; instructed not to edit files or run destructive VCS commands. * 🟡 (15:54) Requested review scope centers on the behavioral change in `packages/cli/src/lib/sentry-client.ts`, where `getApiBaseUrl()` and `getContro…