Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions deploy/kubernetes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,27 @@ Public staging endpoints:
- `https://atlascloud-mcp.dev.atlascloud.ai/mcp`
- `https://atlascloud-auth.dev.atlascloud.ai/.well-known/openid-configuration`

### Pointing a non-production deployment at a non-production Atlas

Every Atlas call the plugin makes derives from one origin, overridable with:

```text
ATLASCLOUD_API_BASE_URL=https://<non-production atlas api origin>
```

Unset, it is `https://api.atlascloud.ai`. The value must be a bare origin —
the three API paths are appended to it, so a path here silently produces
`/api/v1/api/v1/...` — and must be HTTPS unless the host is loopback.

A `PLUGIN_RELEASE_TIER=production` release refuses any override and fails to
start. A production plugin quietly talking to a different Atlas would validate
credentials against the wrong account universe, and that only surfaces once a
customer sees someone else's data.

Without this override a staging deployment authenticates users against staging
but calls the production API, so only production API keys work there and a test
run bills real accounts.

### ChatGPT and Codex DCR callback policy

Dynamic registration deliberately supports only two exact public-client
Expand Down
69 changes: 65 additions & 4 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,69 @@
// Atlas Cloud API base URLs
export const API_BASE = "https://api.atlascloud.ai/api/v1";
export const LLM_API_BASE = "https://api.atlascloud.ai/v1";
// Atlas Cloud API origin. Every Atlas call the plugin makes is derived from this
// single value so a non-production deployment can be pointed at a non-production
// Atlas without patching call sites.
export const DEFAULT_ATLAS_API_ORIGIN = "https://api.atlascloud.ai";

/**
* Resolves the Atlas API origin from ATLASCLOUD_API_BASE_URL, defaulting to
* production.
*
* The override exists for isolated environments: a staging plugin that still
* called the production API would validate staging credentials against the
* wrong account universe and could bill real accounts from a test run.
*
* A production release refuses any override. Silently talking to a different
* Atlas than the one a production release is supposed to serve is the kind of
* mistake that only surfaces as customers seeing someone else's data.
*/
export function resolveAtlasApiOrigin(
env: NodeJS.ProcessEnv = process.env
): string {
const raw = env.ATLASCLOUD_API_BASE_URL?.trim();
if (!raw) return DEFAULT_ATLAS_API_ORIGIN;

if (env.PLUGIN_RELEASE_TIER === "production" && raw !== DEFAULT_ATLAS_API_ORIGIN) {
throw new Error(
"ATLASCLOUD_API_BASE_URL must not override the Atlas API origin in a production release"
);
}

let parsed: URL;
try {
parsed = new URL(raw);
} catch {
throw new Error("ATLASCLOUD_API_BASE_URL is not a valid URL");
}
// Plain HTTP is acceptable only where traffic cannot traverse the public
// network: loopback, and in-cluster Service DNS. Cluster-internal calls have
// no public exposure, and demanding TLS there pushes people toward the worse
// fix of publishing internal services behind a public ingress.
const hostname = parsed.hostname.toLowerCase();
const isLoopback = ["127.0.0.1", "::1", "localhost"].includes(hostname);
const isClusterLocal = hostname.endsWith(".svc.cluster.local");
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && (isLoopback || isClusterLocal))) {
throw new Error(
"ATLASCLOUD_API_BASE_URL must use https unless the host is loopback or in-cluster (*.svc.cluster.local)"
);
}
if (parsed.username || parsed.password) {
throw new Error("ATLASCLOUD_API_BASE_URL must not contain credentials");
}
// An origin only: the three API paths below are appended to it, so a path here
// would silently produce URLs like `/api/v1/api/v1/...`.
if (parsed.pathname !== "/" || parsed.search || parsed.hash) {
throw new Error(
"ATLASCLOUD_API_BASE_URL must be a bare origin without a path, query, or fragment"
);
}
return parsed.origin;
}

export const ATLAS_API_ORIGIN = resolveAtlasApiOrigin();

export const API_BASE = `${ATLAS_API_ORIGIN}/api/v1`;
export const LLM_API_BASE = `${ATLAS_API_ORIGIN}/v1`;
// Public billing/usage endpoints (balance, usage, costs) use a separate base path
export const PUBLIC_API_BASE = "https://api.atlascloud.ai/public/v1";
export const PUBLIC_API_BASE = `${ATLAS_API_ORIGIN}/public/v1`;

// Upload timeout (60s for larger files)
export const UPLOAD_TIMEOUT_MS = 60000;
Expand Down
92 changes: 92 additions & 0 deletions test/atlas-api-origin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import assert from "node:assert/strict";
import test from "node:test";
import { DEFAULT_ATLAS_API_ORIGIN, resolveAtlasApiOrigin } from "../src/constants.js";

test("atlas API origin defaults to production when unset or empty", () => {
assert.equal(resolveAtlasApiOrigin({}), DEFAULT_ATLAS_API_ORIGIN);
assert.equal(resolveAtlasApiOrigin({ ATLASCLOUD_API_BASE_URL: " " }), DEFAULT_ATLAS_API_ORIGIN);
});

test("atlas API origin accepts an https origin for an isolated environment", () => {
assert.equal(
resolveAtlasApiOrigin({ ATLASCLOUD_API_BASE_URL: "https://api.dev.atlascloud.ai" }),
"https://api.dev.atlascloud.ai"
);
// 末尾斜杠是常见写法,不该因此被拒。
assert.equal(
resolveAtlasApiOrigin({ ATLASCLOUD_API_BASE_URL: "https://api.dev.atlascloud.ai/" }),
"https://api.dev.atlascloud.ai"
);
});

test("atlas API origin allows http only for loopback and in-cluster DNS", () => {
assert.equal(
resolveAtlasApiOrigin({ ATLASCLOUD_API_BASE_URL: "http://127.0.0.1:9099" }),
"http://127.0.0.1:9099"
);
// 集群内 Service DNS 不经过公网,允许明文;这是 dev 指向自己 Atlas 的路径。
assert.equal(
resolveAtlasApiOrigin({
ATLASCLOUD_API_BASE_URL: "http://backend.atlascloud-dev.svc.cluster.local:9099",
}),
"http://backend.atlascloud-dev.svc.cluster.local:9099"
);
// 公网域名依然强制 https,不能借道这个例外。
assert.throws(
() => resolveAtlasApiOrigin({ ATLASCLOUD_API_BASE_URL: "http://api.dev.atlascloud.ai" }),
/must use https/
);
assert.throws(
() => resolveAtlasApiOrigin({ ATLASCLOUD_API_BASE_URL: "http://evil.svc.cluster.local.example.com" }),
/must use https/
);
});

// 生产版本指向另一个 Atlas,只会在客户看到别人的数据时才暴露,所以直接拒绝。
test("a production release refuses to override the Atlas API origin", () => {
assert.throws(
() =>
resolveAtlasApiOrigin({
PLUGIN_RELEASE_TIER: "production",
ATLASCLOUD_API_BASE_URL: "https://api.dev.atlascloud.ai",
}),
/must not override/
);
// 显式填成默认值不算覆盖,允许。
assert.equal(
resolveAtlasApiOrigin({
PLUGIN_RELEASE_TIER: "production",
ATLASCLOUD_API_BASE_URL: DEFAULT_ATLAS_API_ORIGIN,
}),
DEFAULT_ATLAS_API_ORIGIN
);
});

test("atlas API origin rejects paths, credentials, and malformed values", () => {
// 带 path 会拼成 /api/v1/api/v1/...,必须挡住。
assert.throws(
() => resolveAtlasApiOrigin({ ATLASCLOUD_API_BASE_URL: "https://api.dev.atlascloud.ai/api/v1" }),
/bare origin/
);
assert.throws(
() => resolveAtlasApiOrigin({ ATLASCLOUD_API_BASE_URL: "https://api.dev.atlascloud.ai?x=1" }),
/bare origin/
);
assert.throws(
() => resolveAtlasApiOrigin({ ATLASCLOUD_API_BASE_URL: "https://user:pw@api.dev.atlascloud.ai" }),
/credentials/
);
assert.throws(
() => resolveAtlasApiOrigin({ ATLASCLOUD_API_BASE_URL: "not-a-url" }),
/not a valid URL/
);
});

test("derived API bases all come from the same origin", async () => {
const { API_BASE, LLM_API_BASE, PUBLIC_API_BASE, ATLAS_API_ORIGIN } = await import(
"../src/constants.js"
);
assert.equal(API_BASE, `${ATLAS_API_ORIGIN}/api/v1`);
assert.equal(LLM_API_BASE, `${ATLAS_API_ORIGIN}/v1`);
assert.equal(PUBLIC_API_BASE, `${ATLAS_API_ORIGIN}/public/v1`);
});