Spaces:
Paused
Paused
Download src/lib/server/agent/gitProxy.spec.ts from Mike0021/chat-ui-agent-demo: direct link, hf CLI and curl.
- Browser
- Download file 12.9 kB
-
https://huggingface.co/spaces/Mike0021/chat-ui-agent-demo/resolve/main/src/lib/server/agent/gitProxy.spec.ts
- Command line
-
hf download hf://spaces/Mike0021/chat-ui-agent-demo/src/lib/server/agent/gitProxy.spec.ts
-
curl -L -o gitProxy.spec.ts https://huggingface.co/spaces/Mike0021/chat-ui-agent-demo/resolve/main/src/lib/server/agent/gitProxy.spec.ts
12.9 kB
| import { beforeEach, describe, expect, it, vi } from "vitest"; | |
| import { ObjectId } from "mongodb"; | |
| import type { Workspace } from "$lib/types/Workspace"; | |
| const mocks = vi.hoisted(() => ({ | |
| workspaceFindOne: vi.fn(), | |
| sessionFindOne: vi.fn(), | |
| findUser: vi.fn(), | |
| config: { | |
| USE_USER_TOKEN: "false", | |
| OPENAI_API_KEY: "hf_inference_service_must_not_clone", | |
| HF_TOKEN: "hf_jobs_service_must_not_clone", | |
| }, | |
| })); | |
| vi.mock("$lib/server/database", () => ({ | |
| collections: { | |
| workspaces: { findOne: mocks.workspaceFindOne }, | |
| sessions: { findOne: mocks.sessionFindOne }, | |
| }, | |
| })); | |
| vi.mock("$lib/server/config", () => ({ config: mocks.config })); | |
| vi.mock("$lib/server/auth", () => ({ findUser: mocks.findUser })); | |
| import { AGENT_PROXY_MAX_BODY_BYTES, issueAgentProxyCredential } from "./llmProxy"; | |
| import { GET, HEAD, POST } from "../../../routes/api/agent/git/[...path]/+server"; | |
| const NOW = new Date("2026-08-20T00:00:00.000Z"); | |
| const OWNER_HF_TOKEN = "hf_owner_oauth_server_side_only"; | |
| function workspace( | |
| tokenHash: string, | |
| repo: Workspace["repo"] = { kind: "hf", repoType: "space", ref: "owner/private-app" } | |
| ): Workspace { | |
| return { | |
| _id: new ObjectId(), | |
| conversationId: new ObjectId(), | |
| userId: new ObjectId(), | |
| sessionId: "owner-session", | |
| repo, | |
| branch: "main", | |
| flavor: "cpu-basic", | |
| model: "moonshotai/Kimi-K2.7-Code", | |
| thinkingLevel: "medium", | |
| policy: "auto", | |
| sandbox: { | |
| status: "provisioning", | |
| id: "job-agent-git-proxy", | |
| namespace: "service-owner", | |
| baseUrl: "https://job-agent-git-proxy--49983.hf.jobs", | |
| nonce: "nonce-git-proxy", | |
| image: "hf.co/spaces/example/runtime", | |
| flavor: "cpu-basic", | |
| }, | |
| llmProxy: { | |
| tokenHash, | |
| createdAt: NOW, | |
| expiresAt: new Date("2100-01-01T00:00:00Z"), | |
| allowedModels: ["moonshotai/Kimi-K2.7-Code"], | |
| }, | |
| spend: { accruedUsd: 0 }, | |
| createdAt: NOW, | |
| updatedAt: NOW, | |
| }; | |
| } | |
| async function arrange(repo?: Workspace["repo"]) { | |
| const credential = await issueAgentProxyCredential(["moonshotai/Kimi-K2.7-Code"]); | |
| const value = workspace(credential.tokenHash, repo); | |
| mocks.workspaceFindOne.mockResolvedValue(value); | |
| mocks.sessionFindOne.mockResolvedValue({ sessionId: "owner-session" }); | |
| mocks.findUser.mockResolvedValue({ | |
| user: null, | |
| invalidateSession: false, | |
| oauth: { token: { value: OWNER_HF_TOKEN, expiresAt: new Date(Date.now() + 60_000) } }, | |
| }); | |
| return { credential, workspace: value }; | |
| } | |
| function infoRequest(token: string, method = "GET", suffix = "") { | |
| return new Request( | |
| `https://chat.example/api/agent/git/repository.git/info/refs?service=git-upload-pack${suffix}`, | |
| { | |
| method, | |
| headers: { | |
| accept: "application/x-git-upload-pack-advertisement", | |
| authorization: "Bearer attacker-controlled", | |
| cookie: "browser=session", | |
| "git-protocol": "version=2", | |
| "x-agent-session": token, | |
| }, | |
| } | |
| ); | |
| } | |
| function uploadRequest(token: string, body: BodyInit, headers: Record<string, string> = {}) { | |
| return new Request("https://chat.example/api/agent/git/repository.git/git-upload-pack", { | |
| method: "POST", | |
| headers: { | |
| "content-type": "application/x-git-upload-pack-request", | |
| "x-agent-session": token, | |
| ...headers, | |
| }, | |
| body, | |
| }); | |
| } | |
| beforeEach(() => { | |
| vi.restoreAllMocks(); | |
| mocks.workspaceFindOne.mockReset(); | |
| mocks.sessionFindOne.mockReset(); | |
| mocks.findUser.mockReset(); | |
| vi.stubGlobal("fetch", vi.fn()); | |
| }); | |
| describe("agent Hugging Face Git smart-HTTP proxy", () => { | |
| it("derives the exact Space repo and attaches owner OAuth server-side even when inference user tokens are disabled", async () => { | |
| const { credential } = await arrange(); | |
| vi.mocked(fetch).mockResolvedValue( | |
| new Response("001e# service=git-upload-pack\n0000", { | |
| status: 200, | |
| headers: { | |
| "content-type": "application/x-git-upload-pack-advertisement", | |
| "set-cookie": "hub=secret", | |
| location: "https://evil.example/", | |
| "x-reflected-auth": OWNER_HF_TOKEN, | |
| }, | |
| }) | |
| ); | |
| const response = await GET({ | |
| request: infoRequest(credential.token), | |
| params: { path: "repository.git/info/refs" }, | |
| } as never); | |
| expect(response.status).toBe(200); | |
| expect(fetch).toHaveBeenCalledOnce(); | |
| const [url, init] = vi.mocked(fetch).mock.calls[0]; | |
| expect(String(url)).toBe( | |
| "https://huggingface.co/spaces/owner/private-app.git/info/refs?service=git-upload-pack" | |
| ); | |
| const upstreamHeaders = new Headers(init?.headers); | |
| expect(upstreamHeaders.get("authorization")).toBe(`Bearer ${OWNER_HF_TOKEN}`); | |
| expect(upstreamHeaders.get("git-protocol")).toBe("version=2"); | |
| expect(upstreamHeaders.get("x-agent-session")).toBeNull(); | |
| expect(upstreamHeaders.get("cookie")).toBeNull(); | |
| expect(init?.redirect).toBe("manual"); | |
| expect(response.headers.get("set-cookie")).toBeNull(); | |
| expect(response.headers.get("location")).toBeNull(); | |
| expect(response.headers.get("x-reflected-auth")).toBeNull(); | |
| expect(response.headers.get("cache-control")).toBe("no-store"); | |
| expect(await response.text()).not.toContain(OWNER_HF_TOKEN); | |
| expect(mocks.config.USE_USER_TOKEN).toBe("false"); | |
| }); | |
| it.each([ | |
| [ | |
| "model", | |
| "owner/source", | |
| "https://huggingface.co/owner/source.git/info/refs?service=git-upload-pack", | |
| ], | |
| [ | |
| "dataset", | |
| "owner/source", | |
| "https://huggingface.co/datasets/owner/source.git/info/refs?service=git-upload-pack", | |
| ], | |
| ] as const)( | |
| "confines a %s checkout to its persisted repo type and ref", | |
| async (repoType, ref, expected) => { | |
| const { credential } = await arrange({ kind: "hf", repoType, ref }); | |
| vi.mocked(fetch).mockResolvedValue( | |
| new Response("advertisement", { | |
| headers: { "content-type": "application/x-git-upload-pack-advertisement" }, | |
| }) | |
| ); | |
| const response = await GET({ | |
| request: infoRequest(credential.token), | |
| params: { path: "repository.git/info/refs" }, | |
| } as never); | |
| expect(response.status).toBe(200); | |
| expect(String(vi.mocked(fetch).mock.calls[0][0])).toBe(expected); | |
| } | |
| ); | |
| it("uses anonymous Hub access for a public repo instead of falling back to service credentials", async () => { | |
| const { credential } = await arrange(); | |
| mocks.sessionFindOne.mockResolvedValue(null); | |
| vi.mocked(fetch).mockResolvedValue( | |
| new Response("advertisement", { | |
| headers: { "content-type": "application/x-git-upload-pack-advertisement" }, | |
| }) | |
| ); | |
| const response = await GET({ | |
| request: infoRequest(credential.token), | |
| params: { path: "repository.git/info/refs" }, | |
| } as never); | |
| expect(response.status).toBe(200); | |
| const headers = new Headers(vi.mocked(fetch).mock.calls[0][1]?.headers); | |
| expect(headers.get("authorization")).toBeNull(); | |
| expect(JSON.stringify([...headers])).not.toContain(mocks.config.OPENAI_API_KEY); | |
| expect(JSON.stringify([...headers])).not.toContain(mocks.config.HF_TOKEN); | |
| }); | |
| it("streams upload-pack bytes while forwarding only the allowlisted request headers", async () => { | |
| const { credential } = await arrange({ kind: "hf", repoType: "dataset", ref: "owner/data" }); | |
| const result = Uint8Array.from([0, 1, 2, 3, 255]); | |
| vi.mocked(fetch).mockResolvedValue( | |
| new Response(result, { | |
| headers: { "content-type": "application/x-git-upload-pack-result" }, | |
| }) | |
| ); | |
| const requestBody = Buffer.from("000eversion 2\n0000"); | |
| const response = await POST({ | |
| request: uploadRequest(credential.token, requestBody), | |
| params: { path: "repository.git/git-upload-pack" }, | |
| } as never); | |
| expect(response.status).toBe(200); | |
| expect(new Uint8Array(await response.arrayBuffer())).toEqual(result); | |
| const [url, init] = vi.mocked(fetch).mock.calls[0]; | |
| expect(String(url)).toBe("https://huggingface.co/datasets/owner/data.git/git-upload-pack"); | |
| expect(new Uint8Array(init?.body as ArrayBuffer)).toEqual(new Uint8Array(requestBody)); | |
| expect(new Headers(init?.headers).get("authorization")).toBe(`Bearer ${OWNER_HF_TOKEN}`); | |
| }); | |
| it("supports HEAD only for the exact upload-pack advertisement", async () => { | |
| const { credential } = await arrange(); | |
| vi.mocked(fetch).mockResolvedValue( | |
| new Response(null, { | |
| headers: { "content-type": "application/x-git-upload-pack-advertisement" }, | |
| }) | |
| ); | |
| const response = await HEAD({ | |
| request: infoRequest(credential.token, "HEAD"), | |
| params: { path: "repository.git/info/refs" }, | |
| } as never); | |
| expect(response.status).toBe(200); | |
| expect(vi.mocked(fetch).mock.calls[0][1]?.method).toBe("HEAD"); | |
| expect(await response.text()).toBe(""); | |
| }); | |
| it.each([ | |
| "other.git/info/refs", | |
| "repository.git/git-receive-pack", | |
| "repository.git/info/lfs/objects/batch", | |
| "repository.git/../../owner/other.git/info/refs", | |
| "models/expensive-model", | |
| ])( | |
| "rejects cross-repo or operation escalation path %s before any upstream request", | |
| async (path) => { | |
| const { credential } = await arrange(); | |
| const response = await GET({ | |
| request: infoRequest(credential.token), | |
| params: { path }, | |
| } as never); | |
| expect(response.status).toBe(404); | |
| expect(fetch).not.toHaveBeenCalled(); | |
| } | |
| ); | |
| it("rejects extra query controls, non-HF workspaces, and invalid persisted refs", async () => { | |
| let arranged = await arrange(); | |
| let response = await GET({ | |
| request: infoRequest(arranged.credential.token, "GET", "&repo=attacker/other"), | |
| params: { path: "repository.git/info/refs" }, | |
| } as never); | |
| expect(response.status).toBe(404); | |
| arranged = await arrange({ kind: "git", ref: "https://example.com/public.git" }); | |
| response = await GET({ | |
| request: infoRequest(arranged.credential.token), | |
| params: { path: "repository.git/info/refs" }, | |
| } as never); | |
| expect(response.status).toBe(403); | |
| arranged = await arrange({ | |
| kind: "hf", | |
| repoType: "space", | |
| ref: "owner/valid" as string, | |
| }); | |
| arranged.workspace.repo = { | |
| kind: "hf", | |
| repoType: "space", | |
| ref: "owner/../../other", | |
| }; | |
| response = await GET({ | |
| request: infoRequest(arranged.credential.token), | |
| params: { path: "repository.git/info/refs" }, | |
| } as never); | |
| expect(response.status).toBe(403); | |
| expect(fetch).not.toHaveBeenCalled(); | |
| }); | |
| it("rejects unsupported methods and upload-pack media types", async () => { | |
| const { credential } = await arrange(); | |
| let response = await GET({ | |
| request: infoRequest(credential.token, "DELETE"), | |
| params: { path: "repository.git/info/refs" }, | |
| } as never); | |
| expect(response.status).toBe(405); | |
| response = await POST({ | |
| request: uploadRequest(credential.token, "payload", { "content-type": "application/json" }), | |
| params: { path: "repository.git/git-upload-pack" }, | |
| } as never); | |
| expect(response.status).toBe(415); | |
| expect(fetch).not.toHaveBeenCalled(); | |
| }); | |
| it("enforces both declared and streamed 8 MiB upload-pack request limits", async () => { | |
| const { credential } = await arrange(); | |
| let response = await POST({ | |
| request: uploadRequest(credential.token, "x", { | |
| "content-length": String(AGENT_PROXY_MAX_BODY_BYTES + 1), | |
| }), | |
| params: { path: "repository.git/git-upload-pack" }, | |
| } as never); | |
| expect(response.status).toBe(413); | |
| response = await POST({ | |
| request: uploadRequest(credential.token, Buffer.alloc(AGENT_PROXY_MAX_BODY_BYTES + 1)), | |
| params: { path: "repository.git/git-upload-pack" }, | |
| } as never); | |
| expect(response.status).toBe(413); | |
| expect(fetch).not.toHaveBeenCalled(); | |
| }); | |
| it("never reflects upstream errors, redirects, cookies, or the raw owner credential", async () => { | |
| const { credential } = await arrange(); | |
| vi.mocked(fetch).mockResolvedValue( | |
| new Response(`denied: ${OWNER_HF_TOKEN}`, { | |
| status: 401, | |
| headers: { | |
| location: `https://example.com/?token=${OWNER_HF_TOKEN}`, | |
| "set-cookie": `token=${OWNER_HF_TOKEN}`, | |
| "www-authenticate": `Bearer token=${OWNER_HF_TOKEN}`, | |
| }, | |
| }) | |
| ); | |
| const response = await GET({ | |
| request: infoRequest(credential.token), | |
| params: { path: "repository.git/info/refs" }, | |
| } as never); | |
| const serialized = `${JSON.stringify([...response.headers])}\n${await response.text()}`; | |
| expect(response.status).toBe(403); | |
| expect(serialized).toContain("Reconnect Hugging Face"); | |
| expect(serialized).not.toContain(OWNER_HF_TOKEN); | |
| expect(response.headers.get("location")).toBeNull(); | |
| expect(response.headers.get("set-cookie")).toBeNull(); | |
| expect(response.headers.get("www-authenticate")).toBeNull(); | |
| }); | |
| it("returns an actionable non-challenge error when a private repo has no owner OAuth", async () => { | |
| const { credential } = await arrange(); | |
| mocks.sessionFindOne.mockResolvedValue(null); | |
| vi.mocked(fetch).mockResolvedValue( | |
| new Response("private upstream detail", { | |
| status: 401, | |
| headers: { "www-authenticate": "Basic realm=private" }, | |
| }) | |
| ); | |
| const response = await GET({ | |
| request: infoRequest(credential.token), | |
| params: { path: "repository.git/info/refs" }, | |
| } as never); | |
| expect(response.status).toBe(403); | |
| expect(await response.text()).toBe("Sign in with Hugging Face to access this repository\n"); | |
| expect(response.headers.get("www-authenticate")).toBeNull(); | |
| }); | |
| }); | |