Skip to content

Commit

Merge branch 'runner-egress-reset'

syntaqxcommitted Parentsf663ec03039e06Browse files
4 files+34−110/4 viewed
+4−2
606606 the toolchains' download sites (`nodejs.org`, `go.dev`,
607607 `static.rust-lang.org`), and the public container registries (Docker Hub,
608608 GitHub's, Quay, and `mirror.gcr.io`, the mirror of Docker Hub that a job's
609−Engine asks first). A request anywhere else gets `403` with
610−the reason. To reach another host, someone with the Maintain [role](/guides/access-and-roles/) or
609+Engine asks first). A request anywhere else gets `403`, and its status
610+line names the host (`403 Forbidden by g1t guardrails: example.com is not
611+an allowed domain`), so even a tool that reports only the status says which
612+host to allow. To reach another host, someone with the Maintain [role](/guides/access-and-roles/) or
611613 higher adds it to the project's
612614 allowed domains under **Settings → Guardrails**; a project whose guardrails
613615 turn the network restriction off runs its jobs with an open network.
+8−2
7070 and HTTPS (443) has any route out: SSH, raw TCP and UDP connections fail.
7171 - Every HTTP and HTTPS request it makes is handed to g1t's runner Worker
7272 before it leaves. The Worker forwards requests to allowed hosts and
73− answers every other with `403` and a line saying the host is not allowed
74− and where to allow it.
73+ answers every other with `403`. The refusal names the host three times
74+ over, since tools differ in what they show of a failed request: in its
75+ status line (`403 Forbidden by g1t guardrails: example.com is not an
76+ allowed domain`, which is what a tool that prints only the status shows),
77+ in an `x-g1t-blocked-host` header beside `x-g1t-guardrails: blocked`, and
78+ in a plain-text body saying where to allow it: **Allowed domains** for
79+ every sandbox, or **Workflow-only domains** for the jobs of one workflow
80+ and environment.
7581 - To see the host of an HTTPS request, the connection is re-encrypted with
7682 a certificate the sandbox is given when it starts, which g1t adds to the
7783 sandbox's trusted certificates (and points Node.js, Python, curl, Cargo
+6−2
5050 const response = refusal("Evil.com:443");
5151 assert.equal(response.status, 403);
5252 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");
5356 const text = await response.text();
54− assert.match(text, /evil\.com is not on this project's allowed domains/);
55− assert.match(text, /Settings, Guardrails/);
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/);
5660 assert.equal(blockedStep("Evil.com"), "Blocked: evil.com (not an allowed domain)");
5761 });
5862
+16−5
7474 return [...hosts];
7575 }
7676
77−/** What a refused request gets back, so the agent knows why. */
77+/**
78+ * What a refused request gets back. Many tools show only the status line
79+ * of a failed request (a GraphQL client prints its reason phrase, a
80+ * package manager "HTTP 403: Forbidden"), so the reason phrase names the
81+ * host too, and the body and a header say the rest.
82+ */
7883 export function refusal(host: string): Response {
79− const name = normalizeHost(host);
84+ // A reason phrase is printable ASCII; a host from a URL already is.
85+ const name = normalizeHost(host).replace(/[^\x21-\x7e]/g, "");
8086 return new Response(
81− `g1t guardrails: ${name} is not on this project's allowed domains, so this sandbox cannot reach it. ` +
82− "A member can allow it under the project's Settings, Guardrails.\n",
83− { status: 403, headers: { "content-type": "text/plain; charset=utf-8", "x-g1t-guardrails": "blocked" } },
87+ `g1t guardrails refused ${name}: it is not on this project's allowed domains, so this sandbox cannot reach it. ` +
88+ "A member can allow it in the project's Settings, Guardrails: under Allowed domains for every sandbox, " +
89+ "or under Workflow-only domains for the jobs of one workflow and environment.\n",
90+ {
91+ status: 403,
92+ statusText: `Forbidden by g1t guardrails: ${name} is not an allowed domain`,
93+ headers: { "content-type": "text/plain; charset=utf-8", "x-g1t-guardrails": "blocked", "x-g1t-blocked-host": name },
94+ },
8495 );
8596 }
8697