Skip to content

Commit 3c4cc3a

Browse files
committed
fix(env): surface CLERK_PLATFORM_API_URL credential mismatch
CLERK_PLATFORM_API_URL redirects API traffic to an arbitrary host, but credentials are keyed by environment name, not by URL, so the active env's token is sent to the override host with no isolation. Warn about this in human mode (agent/scripted output stays clean), and report the active environment and API URL in clerk doctor so the mismatch is visible. Refs #329
1 parent 41ad30e commit 3c4cc3a

5 files changed

Lines changed: 78 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"clerk": patch
3+
---
4+
5+
Warn (in human mode) when `CLERK_PLATFORM_API_URL` routes requests to a host that differs from the active environment's URL, since credentials are keyed by environment name and not by URL. `clerk doctor` now also reports the active environment and its API URL so the mismatch is visible.

packages/cli-core/src/cli-program.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
getCurrentEnvName,
2727
getAvailableEnvs,
2828
getPlapiBaseUrl,
29+
warnIfPlatformApiUrlOverride,
2930
} from "./lib/environment.ts";
3031
import {
3132
CliError,
@@ -129,6 +130,8 @@ export function createProgram(): Program {
129130
if (activeEnv !== "production") {
130131
process.stderr.write(`[${activeEnv.toUpperCase()}]\n`);
131132
}
133+
134+
warnIfPlatformApiUrlOverride();
132135
});
133136

134137
// Show update notification after each command, except for commands that

packages/cli-core/src/commands/doctor/checks.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
formatChannelLabel,
1717
} from "../../lib/update-check.ts";
1818
import { formatHostStateProbeFailures, getAgentHostStateProbe } from "../../lib/host-execution.ts";
19+
import { getCurrentEnvName, getPlapiBaseUrl } from "../../lib/environment.ts";
1920
import { isAgent } from "../../mode.ts";
2021
import type { CheckResult, DoctorContext, FixAction } from "./types.ts";
2122

@@ -74,7 +75,7 @@ export async function checkLoggedIn(ctx: DoctorContext): Promise<CheckResult> {
7475
remedy: "Run `clerk auth login` to authenticate.",
7576
});
7677
}
77-
return check.pass("Logged in (token found in credential store)");
78+
return check.pass(`Logged in — environment "${getCurrentEnvName()}", API ${getPlapiBaseUrl()}`);
7879
}
7980

8081
export async function checkHostExecution(): Promise<CheckResult> {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { test, expect, describe, beforeEach, afterEach } from "bun:test";
2+
import { getPlapiBaseUrl, warnIfPlatformApiUrlOverride } from "./environment.ts";
3+
import { setMode } from "../mode.ts";
4+
import { useCaptureLog } from "../test/lib/stubs.ts";
5+
6+
describe("warnIfPlatformApiUrlOverride", () => {
7+
const captured = useCaptureLog();
8+
const original = process.env.CLERK_PLATFORM_API_URL;
9+
10+
beforeEach(() => {
11+
setMode("human");
12+
delete process.env.CLERK_PLATFORM_API_URL;
13+
});
14+
15+
afterEach(() => {
16+
setMode("human");
17+
if (original === undefined) delete process.env.CLERK_PLATFORM_API_URL;
18+
else process.env.CLERK_PLATFORM_API_URL = original;
19+
});
20+
21+
test("warns in human mode when the override differs from the active env URL", () => {
22+
process.env.CLERK_PLATFORM_API_URL = "https://api.staging.example.com";
23+
warnIfPlatformApiUrlOverride();
24+
expect(captured.err).toContain("CLERK_PLATFORM_API_URL");
25+
expect(captured.err).toContain("production");
26+
});
27+
28+
test("does not warn when no override is set", () => {
29+
warnIfPlatformApiUrlOverride();
30+
expect(captured.err).not.toContain("CLERK_PLATFORM_API_URL");
31+
});
32+
33+
test("does not warn when the override equals the active env URL", () => {
34+
const profileUrl = getPlapiBaseUrl(); // no override set → active env URL
35+
process.env.CLERK_PLATFORM_API_URL = profileUrl;
36+
warnIfPlatformApiUrlOverride();
37+
expect(captured.err).not.toContain("CLERK_PLATFORM_API_URL");
38+
});
39+
40+
test("stays silent in agent mode to avoid corrupting machine-readable output", () => {
41+
setMode("agent");
42+
process.env.CLERK_PLATFORM_API_URL = "https://api.staging.example.com";
43+
warnIfPlatformApiUrlOverride();
44+
expect(captured.err).not.toContain("CLERK_PLATFORM_API_URL");
45+
});
46+
});

packages/cli-core/src/lib/environment.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import { readFileSync } from "node:fs";
1313
import { join } from "node:path";
14+
import { isHuman } from "../mode.ts";
1415
import { log } from "./log.ts";
1516

1617
export interface EnvProfileConfig {
@@ -136,6 +137,27 @@ export function getPlapiBaseUrl(): string {
136137
return process.env.CLERK_PLATFORM_API_URL ?? getCurrentEnv().platformApiUrl;
137138
}
138139

140+
/**
141+
* Warn when CLERK_PLATFORM_API_URL redirects requests to a host that differs
142+
* from the active environment's platform URL. Credentials are keyed by
143+
* environment name, not by URL, so the active env's token is what gets sent to
144+
* the override host — surface that so it isn't a silent surprise.
145+
*
146+
* Human mode only: a per-command warning line would corrupt the machine-readable
147+
* stderr that agent mode emits. Agent/scripted callers get the same information
148+
* from the `clerk doctor` environment report instead.
149+
*/
150+
export function warnIfPlatformApiUrlOverride(): void {
151+
if (!isHuman()) return;
152+
const override = process.env.CLERK_PLATFORM_API_URL;
153+
if (!override) return;
154+
const envName = getCurrentEnvName();
155+
if (override === getCurrentEnv().platformApiUrl) return;
156+
log.warn(
157+
`CLERK_PLATFORM_API_URL is routing requests to ${override}, but credentials stay keyed to the "${envName}" environment — the "${envName}" token will be sent to that host.`,
158+
);
159+
}
160+
139161
export function getBapiBaseUrl(): string {
140162
return process.env.CLERK_BACKEND_API_URL ?? getCurrentEnv().backendApiUrl;
141163
}

0 commit comments

Comments
 (0)