Fork Horse repository, listed by its owner. Publisher snapshot.

packages/forkhorse-runner/src/git.ts

Snapshot at 34e3175

Containing directory

1,545 bytes · typescriptView raw
1/** Validate an authority-selected HTTPS remote; credentials never belong in this URL. */
2export function remoteUrl(remote: string): URL {
3  const url = new URL(remote);
4  if (url.protocol !== "https:" || url.username || url.password || url.port ||
5      url.search || url.hash || url.pathname === "/" || url.pathname.endsWith("/") ||
6      url.href !== remote) throw new Error("Invalid fork remote");
7  return url;
8}
9
10/** Restrict egress to this fork's Git read protocol. Never follow a credential-bearing redirect. */
11export async function forwardGitRead(
12  request: Request, remote: string, forward: typeof fetch = fetch,
13): Promise<Response> {
14  const repo = remoteUrl(remote);
15  const url = new URL(request.url);
16  const allowed = url.origin === repo.origin && (
17    (request.method === "GET" && url.pathname === `${repo.pathname}/info/refs` &&
18      url.search === "?service=git-upload-pack") ||
19    (request.method === "POST" && url.pathname === `${repo.pathname}/git-upload-pack` && !url.search)
20  );
21  if (!allowed) return new Response("Forbidden", { status: 403 });
22  const headers = new Headers(request.headers);
23  headers.set("User-Agent", "git/2.47.0");
24  // Only Git protocol headers and the repo-scoped read authorization cross the boundary.
25  const safe = new Headers();
26  for (const name of ["authorization", "content-type", "accept", "git-protocol", "user-agent"]) {
27    const value = headers.get(name);
28    if (value) safe.set(name, value);
29  }
30  return forward(new Request(request, { headers: safe, redirect: "manual" }));
31}
32