forked from bai/curriculum-project-hub
feat: secure organization provider credentials
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
import { createServer } from "node:http";
|
||||
import { once } from "node:events";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { openProviderProxyLease, type AgentProviderLease } from "../../src/connections/providerProxy.js";
|
||||
|
||||
const leases: AgentProviderLease[] = [];
|
||||
const upstreamServers: ReturnType<typeof createServer>[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(leases.splice(0).map((lease) => lease.close()));
|
||||
await Promise.all(upstreamServers.splice(0).map(async (server) => {
|
||||
server.closeAllConnections();
|
||||
if (server.listening) {
|
||||
server.close();
|
||||
await once(server, "close");
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
describe("run-scoped provider proxy", () => {
|
||||
it("keeps the Organization provider credential out of the Agent environment", async () => {
|
||||
const observed: Array<{ authorization: string | undefined; apiKey: string | undefined; body: string }> = [];
|
||||
const upstream = createServer((request, response) => {
|
||||
const chunks: Buffer[] = [];
|
||||
request.on("data", (chunk: Buffer) => chunks.push(chunk));
|
||||
request.on("end", () => {
|
||||
observed.push({
|
||||
authorization: request.headers.authorization,
|
||||
apiKey: header(request.headers["x-api-key"]),
|
||||
body: Buffer.concat(chunks).toString("utf8"),
|
||||
});
|
||||
response.writeHead(200, { "content-type": "application/json" });
|
||||
response.end(JSON.stringify({ ok: true }));
|
||||
});
|
||||
});
|
||||
upstreamServers.push(upstream);
|
||||
upstream.listen(0, "127.0.0.1");
|
||||
await once(upstream, "listening");
|
||||
const address = upstream.address();
|
||||
if (address === null || typeof address === "string") throw new Error("expected upstream TCP address");
|
||||
|
||||
const lease = await openProviderProxyLease({
|
||||
baseUrl: `http://127.0.0.1:${address.port}/api`,
|
||||
authToken: "customer-provider-auth-token",
|
||||
anthropicApiKey: "customer-provider-api-key",
|
||||
});
|
||||
leases.push(lease);
|
||||
|
||||
const serializedAgentEnv = JSON.stringify(lease.sdkEnv);
|
||||
expect(serializedAgentEnv).not.toContain("customer-provider-auth-token");
|
||||
expect(serializedAgentEnv).not.toContain("customer-provider-api-key");
|
||||
expect(serializedAgentEnv).not.toContain(String(address.port));
|
||||
expect(lease.sdkEnv).toMatchObject({
|
||||
ANTHROPIC_BASE_URL: expect.stringMatching(/^http:\/\/127\.0\.0\.1:\d+$/),
|
||||
ANTHROPIC_AUTH_TOKEN: expect.any(String),
|
||||
ANTHROPIC_API_KEY: "",
|
||||
});
|
||||
|
||||
const response = await fetch(`${lease.sdkEnv.ANTHROPIC_BASE_URL}/v1/messages`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
authorization: `Bearer ${lease.sdkEnv.ANTHROPIC_AUTH_TOKEN}`,
|
||||
"content-type": "application/json",
|
||||
"anthropic-version": "2023-06-01",
|
||||
},
|
||||
body: JSON.stringify({ model: "test" }),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
await expect(response.json()).resolves.toEqual({ ok: true });
|
||||
expect(observed).toEqual([{
|
||||
authorization: "Bearer customer-provider-auth-token",
|
||||
apiKey: "customer-provider-api-key",
|
||||
body: JSON.stringify({ model: "test" }),
|
||||
}]);
|
||||
});
|
||||
|
||||
it("rejects requests without the run capability before contacting upstream", async () => {
|
||||
let upstreamRequests = 0;
|
||||
const diagnostics: unknown[] = [];
|
||||
const upstream = createServer((_request, response) => {
|
||||
upstreamRequests += 1;
|
||||
response.end("unexpected");
|
||||
});
|
||||
upstreamServers.push(upstream);
|
||||
upstream.listen(0, "127.0.0.1");
|
||||
await once(upstream, "listening");
|
||||
const address = upstream.address();
|
||||
if (address === null || typeof address === "string") throw new Error("expected upstream TCP address");
|
||||
const lease = await openProviderProxyLease(
|
||||
{
|
||||
baseUrl: `http://127.0.0.1:${address.port}`,
|
||||
authToken: "customer-secret",
|
||||
anthropicApiKey: "",
|
||||
},
|
||||
{ onDiagnostic: (diagnostic) => diagnostics.push(diagnostic) },
|
||||
);
|
||||
leases.push(lease);
|
||||
|
||||
const response = await fetch(`${lease.sdkEnv.ANTHROPIC_BASE_URL}/v1/messages`, {
|
||||
method: "POST",
|
||||
body: "{}",
|
||||
});
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
expect(upstreamRequests).toBe(0);
|
||||
expect(diagnostics).toEqual([{ code: "provider_proxy_unauthorized", category: "authorization" }]);
|
||||
});
|
||||
|
||||
it("does not forward provider credentials across an upstream redirect", async () => {
|
||||
let redirectedRequests = 0;
|
||||
const diagnostics: unknown[] = [];
|
||||
const redirectTarget = createServer((_request, response) => {
|
||||
redirectedRequests += 1;
|
||||
response.end("leaked");
|
||||
});
|
||||
upstreamServers.push(redirectTarget);
|
||||
redirectTarget.listen(0, "127.0.0.1");
|
||||
await once(redirectTarget, "listening");
|
||||
const targetAddress = redirectTarget.address();
|
||||
if (targetAddress === null || typeof targetAddress === "string") throw new Error("expected redirect target address");
|
||||
|
||||
const upstream = createServer((_request, response) => {
|
||||
response.writeHead(307, { location: `http://127.0.0.1:${targetAddress.port}/capture` });
|
||||
response.end();
|
||||
});
|
||||
upstreamServers.push(upstream);
|
||||
upstream.listen(0, "127.0.0.1");
|
||||
await once(upstream, "listening");
|
||||
const upstreamAddress = upstream.address();
|
||||
if (upstreamAddress === null || typeof upstreamAddress === "string") throw new Error("expected upstream address");
|
||||
const lease = await openProviderProxyLease(
|
||||
{
|
||||
baseUrl: `http://127.0.0.1:${upstreamAddress.port}`,
|
||||
authToken: "customer-secret",
|
||||
anthropicApiKey: "",
|
||||
},
|
||||
{ onDiagnostic: (diagnostic) => diagnostics.push(diagnostic) },
|
||||
);
|
||||
leases.push(lease);
|
||||
|
||||
const response = await fetch(`${lease.sdkEnv.ANTHROPIC_BASE_URL}/v1/messages`, {
|
||||
headers: { authorization: `Bearer ${lease.sdkEnv.ANTHROPIC_AUTH_TOKEN}` },
|
||||
redirect: "manual",
|
||||
});
|
||||
|
||||
expect(response.status).toBe(502);
|
||||
await expect(response.text()).resolves.toBe("provider redirect refused");
|
||||
expect(redirectedRequests).toBe(0);
|
||||
expect(diagnostics).toEqual([{ code: "provider_proxy_redirect_refused", category: "redirect" }]);
|
||||
});
|
||||
});
|
||||
|
||||
function header(value: string | string[] | undefined): string | undefined {
|
||||
return Array.isArray(value) ? value[0] : value;
|
||||
}
|
||||
Reference in New Issue
Block a user