{"version":3,"file":"artifact.mjs","names":[],"sources":["../../../../../../../src/astro/routes/api/admin/plugins/registry/artifact.ts"],"sourcesContent":["/**\n * Registry artifact proxy\n *\n * GET /_emdash/api/admin/plugins/registry/artifact?did=&slug=&version=&kind=&index=\n *\n * Proxies an icon / screenshot / banner image referenced by a registry\n * release record so the admin UI can display it without cross-origin\n * requests to arbitrary publisher hosting.\n *\n * Trust model (CRITICAL): the proxy never accepts an artifact URL from the\n * client. The caller addresses an artifact by its coordinates\n * `(did, slug, version, kind, index)`; the server resolves the *declared*\n * URL from the validated release record fetched from the configured\n * aggregator. The proxy can therefore only ever fetch a URL the publisher\n * declared in their signed release — not an arbitrary caller-supplied URL.\n *\n * The publisher-declared URL is still untrusted (an attacker who controls a\n * publisher record, or the aggregator, can point it anywhere), so the\n * resolved URL passes through the SSRF defences (`assertSafeArtifactUrl`,\n * re-validated on every redirect hop) before any fetch, and only allowlisted\n * image content types are served back.\n */\n\nimport type { Did } from \"@atcute/lexicons\";\nimport { evaluateRegistryReleaseWithdrawal } from \"@premium-cms/registry-client/withdrawal\";\nimport type { APIRoute } from \"astro\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError } from \"#api/error.js\";\nimport { verifyChecksum } from \"#api/handlers/registry.js\";\nimport { assertSafeArtifactUrl } from \"#api/index.js\";\n\nimport { fetchRegistryArtifactUrl } from \"../../../../../../registry/artifact-fetch.js\";\nimport { coerceRegistryConfig, validateAggregatorUrl } from \"../../../../../../registry/config.js\";\n\nexport const prerender = false;\n\n/**\n * Image content types the proxy will pass through. Anything else is rejected.\n *\n * SVG is deliberately excluded: it is active content (an `<svg><script>`\n * executes when navigated to as a top-level document), and the publisher\n * supplies the bytes. Rather than serve it behind mitigations, we refuse it\n * end-to-end — the publish CLI rejects SVG artifacts too, so a conforming\n * release never references one. AVIF is included.\n */\nconst ALLOWED_IMAGE_TYPES = new Set([\n\t\"image/png\",\n\t\"image/jpeg\",\n\t\"image/webp\",\n\t\"image/gif\",\n\t\"image/avif\",\n]);\n\n/** Artifact kinds the proxy can resolve. `screenshot` additionally needs `index`. */\nconst ALLOWED_KINDS = new Set([\"icon\", \"banner\", \"screenshot\"]);\n\n/** Loose DID shape (`did:method:id`); the aggregator lexicon is authoritative. */\nconst DID_PATTERN = /^did:[a-z]+:.+/;\n/** Slug grammar: ASCII letter then letters / digits / `-` / `_`. Mirrors the install route. */\nconst SLUG_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;\nconst CID_PATTERN = /^b[a-z2-7]+$/;\n/** Non-negative integer, for the screenshot index param. */\nconst INDEX_PATTERN = /^\\d+$/;\n\n/** Cap proxied images so a hostile host can't stream an unbounded body. */\nconst MAX_IMAGE_BYTES = 5 * 1024 * 1024;\n\n/** Redirect hops to follow, re-validating each target against SSRF rules. */\nconst MAX_REDIRECTS = 5;\n\n/** Wall-clock budget covering connect + headers + body for the artifact fetch. */\nconst FETCH_TIMEOUT_MS = 15_000;\n\n/** Per-aggregator-request timeout and overall budget for release resolution. */\nconst AGGREGATOR_REQUEST_TIMEOUT_MS = 15_000;\nconst AGGREGATOR_TOTAL_BUDGET_MS = 30_000;\n\n/** Bound the version search: 20 pages * 50 per page = 1000 releases worth. */\nconst MAX_LIST_PAGES = 20;\n\n/** Build a fetch that enforces a per-request and per-budget timeout. Mirrors the install handler. */\nfunction timedFetch(totalDeadline: number): typeof fetch {\n\treturn (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => {\n\t\tconst now = Date.now();\n\t\tconst remaining = Math.max(0, totalDeadline - now);\n\t\tif (remaining === 0) {\n\t\t\treturn Promise.reject(new Error(\"Aggregator request budget exhausted\"));\n\t\t}\n\t\tconst timeout = Math.min(AGGREGATOR_REQUEST_TIMEOUT_MS, remaining);\n\t\tconst controller = new AbortController();\n\t\tconst timer = setTimeout(() => controller.abort(), timeout);\n\t\tconst callerSignal = init?.signal;\n\t\tif (callerSignal) {\n\t\t\tif (callerSignal.aborted) controller.abort(callerSignal.reason);\n\t\t\telse callerSignal.addEventListener(\"abort\", () => controller.abort(callerSignal.reason));\n\t\t}\n\t\treturn fetch(input, { ...init, signal: controller.signal }).finally(() => {\n\t\t\tclearTimeout(timer);\n\t\t});\n\t};\n}\n\n/**\n * Narrow one entry of a release's `artifacts` map to a usable image URL.\n *\n * The embedded `release` record is lexicon-validated at the DiscoveryClient\n * boundary, but `artifacts` is an aggregator pass-through typed `unknown`, so\n * the entry's shape is not guaranteed. Returns the `url` string only when the\n * value is an object carrying a non-empty string `url`; everything else\n * (missing key, wrong type, no `url`) yields `null`.\n */\ninterface DeclaredArtifact {\n\turl: string;\n\tchecksum: string;\n}\n\nfunction declaredArtifact(value: unknown): DeclaredArtifact | null {\n\tif (!value || typeof value !== \"object\") return null;\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed to non-null object above; fields checked below\n\tconst entry = value as Record<string, unknown>;\n\tconst url = entry.url;\n\tconst checksum = entry.checksum;\n\tif (\n\t\ttypeof url !== \"string\" ||\n\t\turl.length === 0 ||\n\t\ttypeof checksum !== \"string\" ||\n\t\tchecksum.length === 0\n\t) {\n\t\treturn null;\n\t}\n\treturn { url, checksum };\n}\n\n/**\n * Resolve the declared artifact URL for `(kind, index)` from a release's\n * `artifacts` map. Returns `null` when the requested artifact isn't present\n * or doesn't carry a usable URL.\n */\nfunction resolveDeclaredArtifact(\n\tartifacts: unknown,\n\tkind: string,\n\tindex: number,\n): DeclaredArtifact | null {\n\tif (!artifacts || typeof artifacts !== \"object\") return null;\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- narrowed to non-null object above; each entry shape-narrowed by declaredArtifactUrl\n\tconst map = artifacts as Record<string, unknown>;\n\n\tif (kind === \"icon\") return declaredArtifact(map.icon);\n\tif (kind === \"banner\") return declaredArtifact(map.banner);\n\t// kind === \"screenshot\"\n\tconst screenshots = map.screenshots;\n\tif (!Array.isArray(screenshots)) return null;\n\tif (index < 0 || index >= screenshots.length) return null;\n\treturn declaredArtifact(screenshots[index]);\n}\n\nexport const GET: APIRoute = async ({ url, locals }) => {\n\tconst { emdash, user } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\tconst denied = requirePerm(user, \"plugins:read\");\n\tif (denied) return denied;\n\n\tconst did = url.searchParams.get(\"did\");\n\tconst slug = url.searchParams.get(\"slug\");\n\tconst cid = url.searchParams.get(\"cid\");\n\tconst kind = url.searchParams.get(\"kind\");\n\tconst versionParam = url.searchParams.get(\"version\");\n\tconst indexParam = url.searchParams.get(\"index\");\n\n\tif (!did || !slug || !cid || !kind) {\n\t\treturn apiError(\"INVALID_REQUEST\", \"Missing did, slug, cid, or kind\", 400);\n\t}\n\tif (did.length > 256 || !DID_PATTERN.test(did)) {\n\t\treturn apiError(\"INVALID_REQUEST\", \"Invalid did\", 400);\n\t}\n\tif (slug.length > 64 || !SLUG_PATTERN.test(slug)) {\n\t\treturn apiError(\"INVALID_REQUEST\", \"Invalid slug\", 400);\n\t}\n\tif (cid.length > 256 || !CID_PATTERN.test(cid)) {\n\t\treturn apiError(\"INVALID_REQUEST\", \"Invalid release CID\", 400);\n\t}\n\tif (!ALLOWED_KINDS.has(kind)) {\n\t\treturn apiError(\"INVALID_REQUEST\", \"Invalid kind\", 400);\n\t}\n\n\tlet index = 0;\n\tif (kind === \"screenshot\") {\n\t\tif (indexParam === null) {\n\t\t\treturn apiError(\"INVALID_REQUEST\", \"Missing index for screenshot\", 400);\n\t\t}\n\t\tif (!INDEX_PATTERN.test(indexParam)) {\n\t\t\treturn apiError(\"INVALID_REQUEST\", \"Invalid index\", 400);\n\t\t}\n\t\tindex = Number(indexParam);\n\t\tif (!Number.isSafeInteger(index)) {\n\t\t\treturn apiError(\"INVALID_REQUEST\", \"Invalid index\", 400);\n\t\t}\n\t}\n\n\tlet version: string | undefined;\n\tif (versionParam !== null && versionParam.length > 0) {\n\t\tif (versionParam.length > 64) {\n\t\t\treturn apiError(\"INVALID_REQUEST\", \"Invalid version\", 400);\n\t\t}\n\t\tversion = versionParam;\n\t}\n\n\tconst registryConfig = coerceRegistryConfig(emdash.config.experimental?.registry);\n\tif (!registryConfig) {\n\t\treturn apiError(\"REGISTRY_NOT_CONFIGURED\", \"Registry is not configured\", 400);\n\t}\n\ttry {\n\t\tvalidateAggregatorUrl(registryConfig.aggregatorUrl);\n\t} catch {\n\t\treturn apiError(\"REGISTRY_NOT_CONFIGURED\", \"Registry aggregator URL is invalid\", 500);\n\t}\n\n\t// Resolve the publisher-declared artifact URL from the release record.\n\tlet descriptor: DeclaredArtifact;\n\ttry {\n\t\tconst resolved = await resolveArtifact(registryConfig, did, slug, version, cid, kind, index);\n\t\tif (resolved === null) {\n\t\t\treturn apiError(\"ARTIFACT_NOT_FOUND\", \"Artifact not found\", 404);\n\t\t}\n\t\tdescriptor = resolved;\n\t} catch {\n\t\treturn apiError(\"ARTIFACT_RESOLVE_FAILED\", \"Failed to resolve artifact\", 502);\n\t}\n\n\tconst controller = new AbortController();\n\tconst timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n\ttry {\n\t\t// `assertSafeArtifactUrl` validates scheme / credentials / loopback +\n\t\t// resolves the hostname and rejects private / link-local / metadata\n\t\t// targets (DNS-rebinding defence). It throws a plain Error on any\n\t\t// block, so a rejection here means the URL is unsafe.\n\t\tlet current: URL;\n\t\ttry {\n\t\t\tcurrent = await assertSafeArtifactUrl(descriptor.url);\n\t\t} catch {\n\t\t\treturn apiError(\"ARTIFACT_URL_REJECTED\", \"Artifact URL is not allowed\", 400);\n\t\t}\n\n\t\tlet response: Response;\n\t\tfor (let hop = 0; ; hop++) {\n\t\t\tresponse = await fetchRegistryArtifactUrl(current.href, {\n\t\t\t\tsignal: controller.signal,\n\t\t\t\tmaxResponseBytes: MAX_IMAGE_BYTES,\n\t\t\t});\n\t\t\tif (response.status < 300 || response.status >= 400) break;\n\t\t\tconst location = response.headers.get(\"location\");\n\t\t\tif (!location) break;\n\t\t\tif (hop === MAX_REDIRECTS) {\n\t\t\t\treturn apiError(\"ARTIFACT_URL_REJECTED\", \"Too many redirects\", 502);\n\t\t\t}\n\t\t\tlet next: URL;\n\t\t\ttry {\n\t\t\t\tnext = await assertSafeArtifactUrl(new URL(location, current).href);\n\t\t\t} catch {\n\t\t\t\treturn apiError(\"ARTIFACT_URL_REJECTED\", \"Redirect target is not allowed\", 400);\n\t\t\t}\n\t\t\tcurrent = next;\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\treturn apiError(\"ARTIFACT_FETCH_FAILED\", \"Failed to fetch artifact\", 502);\n\t\t}\n\n\t\t// Content-Type allowlist: only image types are proxied. A non-image\n\t\t// (HTML error page, JSON, octet-stream) is rejected so the admin\n\t\t// never renders publisher-controlled markup from the EmDash origin.\n\t\tconst rawType = response.headers.get(\"content-type\") ?? \"\";\n\t\tconst contentType = rawType.split(\";\", 1)[0]!.trim().toLowerCase();\n\t\tif (!ALLOWED_IMAGE_TYPES.has(contentType)) {\n\t\t\treturn apiError(\"ARTIFACT_NOT_IMAGE\", \"Artifact is not an allowed image type\", 415);\n\t\t}\n\n\t\tconst declaredLength = response.headers.get(\"content-length\");\n\t\tif (declaredLength) {\n\t\t\tconst declared = Number(declaredLength);\n\t\t\tif (Number.isFinite(declared) && declared > MAX_IMAGE_BYTES) {\n\t\t\t\treturn apiError(\"ARTIFACT_TOO_LARGE\", \"Artifact exceeds size limit\", 413);\n\t\t\t}\n\t\t}\n\n\t\tconst bytes = await readCapped(response, MAX_IMAGE_BYTES);\n\t\tif (bytes === null) {\n\t\t\treturn apiError(\"ARTIFACT_TOO_LARGE\", \"Artifact exceeds size limit\", 413);\n\t\t}\n\t\tif (!(await verifyChecksum(bytes, descriptor.checksum))) {\n\t\t\treturn apiError(\n\t\t\t\t\"ARTIFACT_CHECKSUM_MISMATCH\",\n\t\t\t\t\"Artifact bytes do not match the approved release record\",\n\t\t\t\t502,\n\t\t\t);\n\t\t}\n\n\t\t// Only the allowlisted Content-Type is forwarded — never copy other\n\t\t// upstream headers. `private, no-store` keeps publisher images out of\n\t\t// shared caches in the authenticated admin origin.\n\t\t//\n\t\t// SVG is not in the allowlist, so active-content bytes never reach\n\t\t// here. `Content-Disposition: attachment`, the sandbox CSP, and\n\t\t// `nosniff` remain as defence-in-depth: they force a download and\n\t\t// neutralise script/plugins for any image type if a client navigates\n\t\t// directly to the proxy URL.\n\t\treturn new Response(bytes, {\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": contentType,\n\t\t\t\t\"Cache-Control\": \"private, no-store\",\n\t\t\t\t\"X-Content-Type-Options\": \"nosniff\",\n\t\t\t\t\"Content-Disposition\": \"attachment\",\n\t\t\t\t\"Content-Security-Policy\": \"default-src 'none'; sandbox\",\n\t\t\t},\n\t\t});\n\t} catch {\n\t\treturn apiError(\"ARTIFACT_FETCH_FAILED\", \"Failed to fetch artifact\", 502);\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n};\n\n/**\n * Resolve the declared artifact URL for `(did, slug, version, kind, index)`\n * from the aggregator's release record. Mirrors the install handler's release\n * lookup. Returns `null` when the package/release/artifact isn't found.\n *\n * Self-contained to this route: the install/update handlers are intentionally\n * left untouched, so a small amount of resolution-pattern duplication is\n * accepted here.\n */\nasync function resolveArtifact(\n\tregistryConfig: { aggregatorUrl: string; acceptLabelers?: string },\n\tdid: string,\n\tslug: string,\n\tversion: string | undefined,\n\tcid: string,\n\tkind: string,\n\tindex: number,\n): Promise<DeclaredArtifact | null> {\n\t// Lazy-load the discovery client so the `@atcute/client` dependency only\n\t// loads when the registry path is exercised.\n\tconst { DiscoveryClient, registryLabelerPolicy } =\n\t\tawait import(\"@premium-cms/registry-client/discovery\");\n\n\tconst aggregatorDeadline = Date.now() + AGGREGATOR_TOTAL_BUDGET_MS;\n\tconst discovery = new DiscoveryClient({\n\t\taggregatorUrl: registryConfig.aggregatorUrl,\n\t\tacceptLabelers: registryConfig.acceptLabelers,\n\t\tlabelerPolicy: registryLabelerPolicy(registryConfig.acceptLabelers),\n\t\tfetch: timedFetch(aggregatorDeadline),\n\t});\n\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- DID shape validated by the route before this call\n\tconst publisherDid = did as Did;\n\n\tconst releaseView = await (async () => {\n\t\tif (!version) {\n\t\t\treturn discovery.getLatestRelease({ did: publisherDid, package: slug });\n\t\t}\n\t\tlet cursor: string | undefined;\n\t\tconst seenCursors = new Set<string>();\n\t\tfor (let page = 0; page < MAX_LIST_PAGES; page++) {\n\t\t\tif (cursor !== undefined) {\n\t\t\t\tif (seenCursors.has(cursor)) break;\n\t\t\t\tseenCursors.add(cursor);\n\t\t\t}\n\t\t\tconst result = await discovery.listReleases({\n\t\t\t\tdid: publisherDid,\n\t\t\t\tpackage: slug,\n\t\t\t\tcursor,\n\t\t\t\tlimit: 50,\n\t\t\t});\n\t\t\tfor (const r of result.releases) {\n\t\t\t\tif (r.version === version) return r;\n\t\t\t}\n\t\t\tif (!result.cursor) break;\n\t\t\tcursor = result.cursor;\n\t\t}\n\t\treturn undefined;\n\t})();\n\n\tif (\n\t\t!releaseView?.release ||\n\t\treleaseView.cid !== cid ||\n\t\treleaseView.did !== publisherDid ||\n\t\treleaseView.package !== slug ||\n\t\t(version !== undefined && releaseView.version !== version) ||\n\t\treleaseView.release.package !== slug ||\n\t\treleaseView.release.version !== releaseView.version\n\t) {\n\t\treturn null;\n\t}\n\tif (evaluateRegistryReleaseWithdrawal(releaseView, discovery.labelerPolicy).withdrawn) {\n\t\treturn null;\n\t}\n\n\treturn resolveDeclaredArtifact(releaseView.release.artifacts, kind, index);\n}\n\n/**\n * Read a response body into memory, aborting once it exceeds `limit`. Returns\n * `null` when the cap is breached (the streamed body lied about / omitted\n * Content-Length). The cap is the real defence against an unbounded body.\n */\nasync function readCapped(response: Response, limit: number): Promise<Uint8Array | null> {\n\tconst body = response.body;\n\tif (!body) {\n\t\tconst buf = new Uint8Array(await response.arrayBuffer());\n\t\treturn buf.length > limit ? null : buf;\n\t}\n\tconst reader = body.getReader();\n\tconst chunks: Uint8Array[] = [];\n\tlet total = 0;\n\twhile (true) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) break;\n\t\tif (value) {\n\t\t\ttotal += value.length;\n\t\t\tif (total > limit) {\n\t\t\t\tawait reader.cancel();\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tchunks.push(value);\n\t\t}\n\t}\n\tconst combined = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const chunk of chunks) {\n\t\tcombined.set(chunk, offset);\n\t\toffset += chunk.length;\n\t}\n\treturn combined;\n}\n"],"mappings":"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"}