Skip to content
104 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { allows, blockedStep, harnessEnv, newlyBlocked, normalizeHost, refusal, sandboxHosts, timeCapMessage } from "./egress.ts";
5
6const policy = ["g1t.sh", "api.g1t.sh", "registry.npmjs.org", "*.example.com"];
7
8test("listed hosts are allowed and nothing else is", () => {
9 assert.equal(allows(policy, "registry.npmjs.org"), true);
10 assert.equal(allows(policy, "api.g1t.sh"), true);
11 assert.equal(allows(policy, "evil.com"), false);
12 assert.equal(allows(policy, "registry.npmjs.org.evil.com"), false);
13 assert.equal(allows(policy, "npmjs.org"), false);
14 assert.equal(allows(policy, ""), false);
15 assert.equal(allows([], "api.g1t.sh"), false);
16});
17
18test("hosts are compared without case, port or trailing dot", () => {
19 assert.equal(allows(policy, "Registry.NPMJS.org:443"), true);
20 assert.equal(allows(policy, "registry.npmjs.org."), true);
21 assert.equal(normalizeHost("[::1]:8080"), "[::1]");
22});
23
24test("a wildcard covers subdomains at any depth, not the domain itself", () => {
25 assert.equal(allows(policy, "api.example.com"), true);
26 assert.equal(allows(policy, "a.b.example.com"), true);
27 assert.equal(allows(policy, "example.com"), false);
28 assert.equal(allows(policy, "badexample.com"), false);
29});
30
31test("the model and g1t's tools are always reachable", () => {
32 const hosts = sandboxHosts(
33 ["registry.npmjs.org"],
34 { MODELS_URL: "https://models.g1t.sh" },
35 { G1T_API: "https://api.g1t.sh", GIT_REMOTE: "https://g1t.sh/acme/site.git", ANTHROPIC_BASE_URL: "https://models.g1t.sh/anthropic" },
36 );
37 for (const host of ["registry.npmjs.org", "models.g1t.sh", "api.g1t.sh", "g1t.sh"]) {
38 assert.ok(hosts.includes(host), host);
39 }
40 assert.ok(!hosts.includes("gateway.ai.cloudflare.com"));
41});
42
43test("without the model proxy, the gateway or the provider is added", () => {
44 assert.ok(sandboxHosts([], { AI_GATEWAY_ID: "g1t" }, {}).includes("gateway.ai.cloudflare.com"));
45 assert.ok(sandboxHosts([], { ANTHROPIC_API_KEY: "k" }, {}).includes("api.anthropic.com"));
46 assert.deepEqual(sandboxHosts([], {}, { G1T_API: "not a url" }), []);
47});
48
49test("a refusal says why and how to allow it", async () => {
50 const response = refusal("Evil.com:443");
51 assert.equal(response.status, 403);
52 assert.equal(response.headers.get("x-g1t-guardrails"), "blocked");
53 assert.equal(response.headers.get("x-g1t-blocked-host"), "evil.com");
54 // Tools that print only the status line still name the host.
55 assert.equal(response.statusText, "Forbidden by g1t guardrails: evil.com is not an allowed domain");
56 const text = await response.text();
57 assert.match(text, /^g1t guardrails refused evil\.com: it is not on this project's allowed domains/);
58 assert.match(text, /Settings, Guardrails: under Allowed domains/);
59 assert.match(text, /Workflow-only domains/);
60 assert.equal(blockedStep("Evil.com"), "Blocked: evil.com (not an allowed domain)");
61});
62
63test("the harness is told the run's rules, caps and branch", () => {
64 const guard = {
65 policy: {
66 restrictNetwork: true,
67 registries: [],
68 domains: [],
69 hosts: [],
70 rules: { sudo: true },
71 deny: ["Bash(kubectl:*)"],
72 budgetUsd: 5,
73 minutes: { implement: 90 },
74 },
75 minutes: 90,
76 };
77 const restricted = harnessEnv(guard, { UPSTREAM_BRANCH: "trunk" }, true);
78 assert.deepEqual(JSON.parse(restricted.GUARDRAILS), {
79 rules: { sudo: true },
80 deny: ["Bash(kubectl:*)"],
81 budgetUsd: 5,
82 minutes: 90,
83 restrictNetwork: true,
84 defaultBranch: "trunk",
85 });
86 assert.equal(restricted.NODE_EXTRA_CA_CERTS, "/etc/cloudflare/certs/cloudflare-containers-ca.crt");
87 // Tools that would report usage home are told not to: it would be refused.
88 assert.equal(restricted.WRANGLER_SEND_METRICS, "false");
89 assert.equal(restricted.DO_NOT_TRACK, "1");
90 // Open, as the operator's switch makes it: no certificate to trust.
91 const open = harnessEnv(guard, {}, false);
92 assert.equal(JSON.parse(open.GUARDRAILS).restrictNetwork, false);
93 assert.equal(open.NODE_EXTRA_CA_CERTS, undefined);
94 assert.equal(open.DO_NOT_TRACK, undefined);
95});
96
97test("each refused host is one step, up to a limit", () => {
98 const first = newlyBlocked([], "evil.com");
99 assert.deepEqual(first, { step: "Blocked: evil.com (not an allowed domain)", seen: ["Blocked: evil.com (not an allowed domain)"] });
100 assert.equal(newlyBlocked(first!.seen, "EVIL.com:443"), null);
101 const full = Array.from({ length: 25 }, (_, i) => `Blocked: h${i}.com (not an allowed domain)`);
102 assert.equal(newlyBlocked(full, "another.com"), null);
103 assert.equal(timeCapMessage(1), "Stopped: it reached its time cap of 1 minute.");
104});