diff --git a/src/infra/clawhub.test.ts b/src/infra/clawhub.test.ts index 4da5b677d37..19e6697c518 100644 --- a/src/infra/clawhub.test.ts +++ b/src/infra/clawhub.test.ts @@ -9,6 +9,7 @@ import { downloadClawHubSkillArchive, fetchClawHubPackageArtifact, fetchClawHubPackageReadiness, + fetchClawHubPackageSecurity, normalizeClawHubSha256Integrity, normalizeClawHubSha256Hex, parseClawHubPluginSpec, @@ -292,6 +293,40 @@ describe("clawhub helpers", () => { ); }); + it("fetches typed package security reports", async () => { + let requestedUrl = ""; + await expect( + fetchClawHubPackageSecurity({ + name: "@openclaw/diagnostics-otel", + version: "2026.3.22", + fetchImpl: async (input) => { + requestedUrl = input instanceof Request ? input.url : String(input); + return new Response( + JSON.stringify({ + releaseId: "rel_demo", + state: "approved", + reasonCode: "clean", + createdAt: 1774256733107, + scanState: "clean", + moderationState: "approved", + }), + { status: 200, headers: { "content-type": "application/json" } }, + ); + }, + }), + ).resolves.toEqual({ + releaseId: "rel_demo", + state: "approved", + reasonCode: "clean", + createdAt: 1774256733107, + scanState: "clean", + moderationState: "approved", + }); + expect(new URL(requestedUrl).pathname).toBe( + "/api/v1/packages/%40openclaw%2Fdiagnostics-otel/versions/2026.3.22/security", + ); + }); + it("downloads package archives to sanitized temp paths and cleans them up", async () => { const archive = await downloadClawHubPackageArchive({ name: "@hyf/zai-external-alpha", diff --git a/src/infra/clawhub.ts b/src/infra/clawhub.ts index 74025101374..5bf5cd7e5d0 100644 --- a/src/infra/clawhub.ts +++ b/src/infra/clawhub.ts @@ -62,6 +62,14 @@ export type ClawHubArtifactScanState = | "not-run" | (string & {}); export type ClawHubArtifactModerationState = "approved" | "quarantined" | "revoked" | (string & {}); +export type ClawHubPackageSecurityState = + | "pending" + | "approved" + | "limited" + | "quarantined" + | "rejected" + | "revoked" + | (string & {}); export type ClawHubResolvedArtifact = | { source: "clawhub"; @@ -90,6 +98,17 @@ export type ClawHubPackageArtifactResolverResponse = { version?: { version?: string | null } | string | null; artifact?: ClawHubResolvedArtifact | null; }; +export type ClawHubPackageSecurityResponse = { + packageId?: string | null; + releaseId?: string | null; + state: ClawHubPackageSecurityState; + reasonCode?: string | null; + moderatorNote?: string | null; + actorId?: string | null; + createdAt?: number | null; + scanState?: ClawHubArtifactScanState | null; + moderationState?: ClawHubArtifactModerationState | null; +}; export type ClawHubPackageClawPackSummary = { available: boolean; specVersion?: number | null; @@ -674,6 +693,25 @@ export async function fetchClawHubPackageArtifact(params: { }); } +export async function fetchClawHubPackageSecurity(params: { + name: string; + version: string; + baseUrl?: string; + token?: string; + timeoutMs?: number; + fetchImpl?: FetchLike; +}): Promise { + return await fetchJson({ + baseUrl: params.baseUrl, + path: `/api/v1/packages/${encodeURIComponent(params.name)}/versions/${encodeURIComponent( + params.version, + )}/security`, + token: params.token, + timeoutMs: params.timeoutMs, + fetchImpl: params.fetchImpl, + }); +} + export async function fetchClawHubPackageReadiness(params: { name: string; baseUrl?: string;