Buckets:
| import fs from "fs/promises" | |
| import path from "path" | |
| import { describe, expect, test } from "bun:test" | |
| import { Effect, Layer } from "effect" | |
| import { HttpClient, HttpClientResponse } from "effect/unstable/http" | |
| import { FSUtil } from "@opencode-ai/core/fs-util" | |
| import { Global } from "@opencode-ai/core/global" | |
| import { SkillDiscovery } from "@opencode-ai/core/skill/discovery" | |
| import { tmpdir } from "./fixture/tmpdir" | |
| const base = "https://skills.example.test/catalog/" | |
| async function pull(skills: unknown[], files: Record<string, string> = {}) { | |
| const tmp = await tmpdir() | |
| const requests: string[] = [] | |
| const http = Layer.succeed( | |
| HttpClient.HttpClient, | |
| HttpClient.make((request) => | |
| Effect.sync(() => requests.push(request.url)).pipe( | |
| Effect.map(() => { | |
| const body = request.url === `${base}index.json` ? JSON.stringify({ skills }) : files[request.url] | |
| return HttpClientResponse.fromWeb( | |
| request, | |
| new Response(body ?? "Not Found", { status: body === undefined ? 404 : 200 }), | |
| ) | |
| }), | |
| ), | |
| ), | |
| ) | |
| const layer = SkillDiscovery.layer.pipe( | |
| Layer.provide(http), | |
| Layer.provide(FSUtil.defaultLayer), | |
| Layer.provide(Global.layerWith({ cache: tmp.path })), | |
| ) | |
| const directories = await Effect.runPromise( | |
| Effect.gen(function* () { | |
| return yield* (yield* SkillDiscovery.Service).pull(base) | |
| }).pipe(Effect.provide(layer)), | |
| ) | |
| return { tmp, requests, directories } | |
| } | |
| describe("SkillDiscovery.pull", () => { | |
| test("rejects skill name traversal without fetching files", async () => { | |
| const result = await pull([{ name: "../outside", files: ["SKILL.md"] }]) | |
| try { | |
| expect(result.directories).toEqual([]) | |
| expect(result.requests).toEqual([`${base}index.json`]) | |
| expect(await fs.readdir(result.tmp.path)).toEqual([]) | |
| } finally { | |
| await result.tmp[Symbol.asyncDispose]() | |
| } | |
| }) | |
| test("rejects file traversal without fetching files", async () => { | |
| const result = await pull([{ name: "deploy", files: ["SKILL.md", "../outside.md"] }]) | |
| try { | |
| expect(result.directories).toEqual([]) | |
| expect(result.requests).toEqual([`${base}index.json`]) | |
| expect(await fs.readdir(result.tmp.path)).toEqual([]) | |
| } finally { | |
| await result.tmp[Symbol.asyncDispose]() | |
| } | |
| }) | |
| test("rejects absolute file paths without fetching files", async () => { | |
| const result = await pull([{ name: "deploy", files: ["SKILL.md", "/tmp/outside.md"] }]) | |
| try { | |
| expect(result.directories).toEqual([]) | |
| expect(result.requests).toEqual([`${base}index.json`]) | |
| expect(await fs.readdir(result.tmp.path)).toEqual([]) | |
| } finally { | |
| await result.tmp[Symbol.asyncDispose]() | |
| } | |
| }) | |
| test("rejects cross-origin file URLs without fetching files", async () => { | |
| const result = await pull([{ name: "deploy", files: ["SKILL.md", "https://evil.example.test/outside.md"] }]) | |
| try { | |
| expect(result.directories).toEqual([]) | |
| expect(result.requests).toEqual([`${base}index.json`]) | |
| expect(await fs.readdir(result.tmp.path)).toEqual([]) | |
| } finally { | |
| await result.tmp[Symbol.asyncDispose]() | |
| } | |
| }) | |
| test("downloads safe nested files under the skill root", async () => { | |
| const result = await pull([{ name: "deploy", files: ["SKILL.md", "references/guide.md"] }], { | |
| [`${base}deploy/SKILL.md`]: "# Deploy", | |
| [`${base}deploy/references/guide.md`]: "# Guide", | |
| }) | |
| try { | |
| expect(result.directories).toHaveLength(1) | |
| expect(result.requests.toSorted()).toEqual( | |
| [`${base}index.json`, `${base}deploy/SKILL.md`, `${base}deploy/references/guide.md`].toSorted(), | |
| ) | |
| expect(await fs.readFile(path.join(result.directories[0], "SKILL.md"), "utf8")).toBe("# Deploy") | |
| expect(await fs.readFile(path.join(result.directories[0], "references", "guide.md"), "utf8")).toBe("# Guide") | |
| } finally { | |
| await result.tmp[Symbol.asyncDispose]() | |
| } | |
| }) | |
| }) | |
Xet Storage Details
- Size:
- 4 kB
- Xet hash:
- ec9b718b33f80c27c079b5dc26e88a7eb00248d0604efebc55aeb02b4ecd1e7c
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.