Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

hosted-loop-secrets.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

hosted-loop-secrets.test.tsBlame65 lines · 1 contributor
4c08073ccantynz-alt1/**
2 * Hosted Claude loops must not hand platform secrets to user code.
3 *
4 * `loop.sourceCode` is arbitrary user-supplied JavaScript. Creating a loop
5 * requires only requireAuth (src/routes/claude-deploy.tsx:54), so any
6 * registered account can run whatever it wants, and the run's stdout is
7 * captured and returned to the caller. Anything placed in the subprocess env
8 * is readable with a single console.log.
9 *
10 * The env used to carry:
11 * - GLUECRON_PAT — the OPERATOR's admin-scoped token
12 * - ANTHROPIC_API_KEY — the platform's billing key
13 *
14 * i.e. any registered user could exfiltrate both.
15 */
16
17import { describe, expect, it } from "bun:test";
18import { readFileSync } from "fs";
19
20const SRC = readFileSync("src/lib/hosted-claude-loop.ts", "utf8");
21const envBlock = SRC.slice(
22 SRC.indexOf("const env: Record<string, string> = {"),
23 SRC.indexOf("const exec = getExecutor();")
24);
25
26describe("operator PAT is never exposed", () => {
27 it("GLUECRON_PAT is not injected into the snippet env", () => {
28 expect(envBlock).not.toMatch(/env\.GLUECRON_PAT\s*=/);
29 expect(envBlock).not.toMatch(/GLUECRON_PAT:/);
30 });
31
32 it("is gone from the whole module, not just relocated", () => {
33 const assignments = SRC.match(/env\.GLUECRON_PAT\s*=/g) ?? [];
34 expect(assignments.length).toBe(0);
35 });
36});
37
38describe("platform Anthropic key is opt-in", () => {
39 it("is not unconditionally placed in the env", () => {
40 // The original bug: a bare property on the object literal.
41 expect(envBlock).not.toMatch(/^\s*ANTHROPIC_API_KEY:/m);
42 });
43
44 it("requires an explicit operator opt-in flag", () => {
45 expect(envBlock).toContain('process.env.HOSTED_LOOPS_PLATFORM_KEY === "1"');
46 });
47
48 it("defaults to withholding the key", () => {
49 // Guard against the flag being inverted later: absence of the env var
50 // must mean no key.
51 const gate = envBlock.slice(envBlock.indexOf("HOSTED_LOOPS_PLATFORM_KEY"));
52 expect(gate).toContain("env.ANTHROPIC_API_KEY =");
53 expect(envBlock.indexOf("HOSTED_LOOPS_PLATFORM_KEY")).toBeLessThan(
54 envBlock.indexOf("env.ANTHROPIC_API_KEY =")
55 );
56 });
57});
58
59describe("what the snippet legitimately still gets", () => {
60 it("keeps the non-secret loop identifiers", () => {
61 expect(envBlock).toContain("GLUECRON_AGENT_ID");
62 expect(envBlock).toContain("GLUECRON_LOOP_ID");
63 expect(envBlock).toContain("GLUECRON_BASE_URL");
64 });
65});