Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Merge brokered secrets: a secret a job uses only to call an API can be tied to the hosts it is for, and the job gets a stand-in in its place, which g1t fills in on the way out to those hosts and nowhere else, so the value never enters the sandbox; and a request to an allowed host that fails upstream is retried when it can be, or answered with a 502 that says why, rather than reset. | 1 | /** |
| 2 | * The outbound handler's side of brokered secrets (broker.ts): `egress` | |
| 3 | * (guard.ts) asks `brokered` once for each request to a host the sandbox | |
| 4 | * may reach, before forwarding it itself. | |
| 5 | */ | |
| 6 | import type { OutboundHandlerContext } from "@cloudflare/containers"; | |
| 7 | ||
| 8 | import type { ServiceBinding } from "@g1t/contracts"; | |
| 9 | ||
| 10 | import { type BrokeredJob, brokerFor, fill, scrub, scrubHeaders, scrubbable, standInsIn } from "./broker.ts"; | |
| 11 | import { sandboxNamespace } from "./egress.ts"; | |
| 12 | import { forward } from "./forward.ts"; | |
| 13 | ||
| 14 | /** | |
| 15 | * What the outbound handler is given for brokering: `open` for a job whose | |
| 16 | * network is open but which may be given brokered secrets (every host is | |
| 17 | * reached, as it would be), and for a workflow job, the job, whose | |
| 18 | * stand-ins it fills in. | |
| 19 | */ | |
| 20 | export type BrokeredParams = { open?: boolean; job?: BrokeredJob }; | |
| 21 | ||
| 22 | /** | |
| 23 | * Sends a workflow job's request with its brokered secrets filled in: a | |
| 24 | * stand-in becomes the real value on a request to one of its secret's | |
| 25 | * hosts, and the answer has the value swapped back should it echo it. | |
| 26 | * Null when there is nothing to fill in, and `egress` forwards the | |
| 27 | * request as it is. A filled-in request is not followed through | |
| 28 | * redirects here, so the value never goes wherever a redirect points: the | |
| 29 | * job follows it itself, and that request comes through here again. A | |
| 30 | * stand-in sent to a host it is not for goes as it is, and the job's log | |
| 31 | * says so once. | |
| 32 | */ | |
| 33 | export async function brokered( | |
| 34 | request: Request, | |
| 35 | env: { SANDBOX: DurableObjectNamespace; ACTIONS?: ServiceBinding }, | |
| 36 | ctx: OutboundHandlerContext<BrokeredParams>, | |
| 37 | ): Promise<Response | null> { | |
| 38 | const job = ctx.params?.job; | |
| 39 | if (!job || !env.ACTIONS) return null; | |
| 40 | const wanted = standInsIn(request.url, request.headers); | |
| 41 | if (wanted.length === 0) return null; | |
| 42 | const secrets = await brokerFor(env.ACTIONS, job, wanted); | |
| 43 | const { url, headers, filled, misdirected } = fill(request.url, request.headers, secrets); | |
| 44 | for (const secret of misdirected) { | |
| 45 | try { | |
| 46 | const namespace = sandboxNamespace(env, ctx.className); | |
| 47 | const sandbox = namespace.get(namespace.idFromString(ctx.containerId)) as unknown as { | |
| 48 | noteMisdirected(name: string, host: string): Promise<void>; | |
| 49 | }; | |
| 50 | await sandbox.noteMisdirected(secret.name, new URL(request.url).host); | |
| 51 | } catch (error) { | |
| 52 | console.log("misdirected secret not reported", secret.name, String(error)); | |
| 53 | } | |
| 54 | } | |
| 55 | if (filled.length === 0) return null; | |
| 56 | const response = await forward(new Request(url, { method: request.method, headers, body: request.body, redirect: "manual" })); | |
| 57 | const answered = scrubHeaders(response.headers, filled); | |
| 58 | const init = { status: response.status, statusText: response.statusText, headers: answered }; | |
| 59 | if (!scrubbable(response.headers)) return new Response(response.body, init); | |
| 60 | answered.delete("content-length"); | |
| 61 | return new Response(scrub(await response.text(), filled), init); | |
| 62 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.