From 232909f8452ec19956db53981ea3444607698009 Mon Sep 17 00:00:00 2001 From: Jesse Merhi <79823012+jesse-merhi@users.noreply.github.com> Date: Wed, 16 Sep 2026 09:42:45 +1000 Subject: [PATCH 01/12] feat: queue Endor reachability scans for plugin releases --- .github/workflows/security-scan-codex.yml | 18 +- convex/_generated/api.d.ts | 2 + convex/devSeed.ts | 12 + convex/httpApiV1.handlers.test.ts | 78 ++++ convex/httpApiV1/packagesV1.ts | 3 + convex/httpApiV1/skillsV1.ts | 3 + convex/lib/endorAnalysis.ts | 21 ++ convex/lib/retentionPolicy.ts | 4 +- convex/packageScannerReports.runtime.test.ts | 323 ++++++++++++++++ convex/packages.ts | 132 ++++++- convex/schema.ts | 3 + convex/securityScan.test.ts | 58 ++- convex/securityScan.ts | 162 ++++++-- packages/clawhub/src/schema/packages.ts | 24 ++ packages/schema/dist/packages.d.ts | 40 ++ packages/schema/dist/packages.js | 20 + packages/schema/dist/packages.js.map | 2 +- packages/schema/src/packages.ts | 24 ++ scripts/lib/runWorkerCommand.ts | 105 ++++++ .../run-codex-scan-worker-clawscan.test.ts | 259 ++++++++++++- scripts/security/run-codex-scan-worker.ts | 134 +++---- .../security/run-endor-plugin-scan.test.ts | 209 +++++++++++ scripts/security/run-endor-plugin-scan.ts | 352 ++++++++++++++++++ .../security-scan-worker-workflow.test.ts | 20 +- specs/endor-plugin-scans.md | 58 +++ src/components/EndorSecurityAudit.test.tsx | 63 ++++ src/components/SecurityAuditPage.tsx | 44 +++ src/components/securityAuditModel.ts | 3 + src/lib/packageApi.ts | 2 + src/lib/securityAuditExport.ts | 6 + src/routes/plugins/$name/security-audit.tsx | 1 + 31 files changed, 2041 insertions(+), 144 deletions(-) create mode 100644 convex/lib/endorAnalysis.ts create mode 100644 convex/packageScannerReports.runtime.test.ts create mode 100644 scripts/lib/runWorkerCommand.ts create mode 100644 scripts/security/run-endor-plugin-scan.test.ts create mode 100644 scripts/security/run-endor-plugin-scan.ts create mode 100644 specs/endor-plugin-scans.md create mode 100644 src/components/EndorSecurityAudit.test.tsx diff --git a/.github/workflows/security-scan-codex.yml b/.github/workflows/security-scan-codex.yml index f01cb1de3e..b54d8567e7 100644 --- a/.github/workflows/security-scan-codex.yml +++ b/.github/workflows/security-scan-codex.yml @@ -65,6 +65,9 @@ jobs: CODEX_SECURITY_SCAN_MAX_RUNTIME_MINUTES: ${{ github.event.client_payload.max_runtime_minutes || inputs['max-runtime-minutes'] || '12' }} CODEX_SECURITY_SCAN_LANE: ${{ matrix.lane }} CODEX_SECURITY_SCAN_CLAWSCAN_TIMEOUT_MS: ${{ vars.CODEX_SECURITY_SCAN_CLAWSCAN_TIMEOUT_MS || '900000' }} + CODEX_SECURITY_SCAN_CLAWSCAN_VERSION: ${{ vars.CODEX_SECURITY_SCAN_CLAWSCAN_VERSION || '0.1.8' }} + CODEX_SECURITY_SCAN_ENDOR_ENABLED: ${{ vars.CODEX_SECURITY_SCAN_ENDOR_ENABLED || '0' }} + CODEX_SECURITY_SCAN_ENDOR_IMAGE: ${{ vars.CODEX_SECURITY_SCAN_ENDOR_IMAGE }} # The runner is disposable and installs the reviewed, hash-locked host scanners above. CODEX_SECURITY_SCAN_CLAWSCAN_SANDBOX: "off" CODEX_SECURITY_SCAN_LEASE_MINUTES: "60" @@ -92,9 +95,18 @@ jobs: - name: Install ClawScan CLI run: | set -euo pipefail - npm install -g @openclaw/clawscan@0.1.8 + [[ "$CODEX_SECURITY_SCAN_CLAWSCAN_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo 'ClawScan requires an exact release version'; exit 1; } + npm install -g "@openclaw/clawscan@$CODEX_SECURITY_SCAN_CLAWSCAN_VERSION" clawscan --version + - name: Prepare Endor scanner + if: ${{ env.CODEX_SECURITY_SCAN_ENDOR_ENABLED == '1' }} + run: | + set -euo pipefail + clawscan scanners endor + [[ "$CODEX_SECURITY_SCAN_ENDOR_IMAGE" =~ @sha256:[a-f0-9]{64}$ ]] || { echo 'Endor requires a Docker image pinned by SHA-256 digest'; exit 1; } + docker pull "$CODEX_SECURITY_SCAN_ENDOR_IMAGE" + - name: Install A.I.G scanner run: | set -euo pipefail @@ -129,6 +141,10 @@ jobs: LLM_API_KEY: ${{ secrets.OPENAI_API_KEY || secrets.CODEX_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} SECURITY_SCAN_WORKER_TOKEN: ${{ secrets.SECURITY_SCAN_WORKER_TOKEN }} + ENDOR_NAMESPACE: ${{ vars.ENDOR_NAMESPACE }} + ENDOR_API: ${{ vars.ENDOR_API }} + ENDOR_API_CREDENTIALS_KEY: ${{ secrets.ENDOR_API_CREDENTIALS_KEY }} + ENDOR_API_CREDENTIALS_SECRET: ${{ secrets.ENDOR_API_CREDENTIALS_SECRET }} run: | bun scripts/security/run-codex-scan-worker.ts \ --batch-limit "$CODEX_SECURITY_SCAN_LIMIT" \ diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index 9364b03601..effb67f9e6 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -81,6 +81,7 @@ import type * as lib_emailRendering from "../lib/emailRendering.js"; import type * as lib_emails from "../lib/emails.js"; import type * as lib_embeddingVisibility from "../lib/embeddingVisibility.js"; import type * as lib_embeddings from "../lib/embeddings.js"; +import type * as lib_endorAnalysis from "../lib/endorAnalysis.js"; import type * as lib_experimentalClaws from "../lib/experimentalClaws.js"; import type * as lib_githubAccount from "../lib/githubAccount.js"; import type * as lib_githubActionsOidc from "../lib/githubActionsOidc.js"; @@ -311,6 +312,7 @@ declare const fullApi: ApiFromModules<{ "lib/emails": typeof lib_emails; "lib/embeddingVisibility": typeof lib_embeddingVisibility; "lib/embeddings": typeof lib_embeddings; + "lib/endorAnalysis": typeof lib_endorAnalysis; "lib/experimentalClaws": typeof lib_experimentalClaws; "lib/githubAccount": typeof lib_githubAccount; "lib/githubActionsOidc": typeof lib_githubActionsOidc; diff --git a/convex/devSeed.ts b/convex/devSeed.ts index 67b21fa030..453a7fd88c 100644 --- a/convex/devSeed.ts +++ b/convex/devSeed.ts @@ -3733,6 +3733,18 @@ export async function seedLocalModerationFixturesHandler( }, llmAnalysis: pluginClawScanRiskAnalysis(now), staticScan: scannedStaticScan, + endorAnalysis: { + status: "completed", + checkedAt: now, + reachableFunctionCount: 1, + findings: [ + { + severity: "high", + summary: + "Synthetic reachability fixture: a vulnerable function in fixture-parser@1.0.0 is reachable from the plugin entrypoint.", + }, + ], + }, source: { kind: "github", repo: "openclaw/local-dev-fixture", path: "." }, createdBy: userId, publishActor: { kind: "user", userId }, diff --git a/convex/httpApiV1.handlers.test.ts b/convex/httpApiV1.handlers.test.ts index cebd144b14..c1292d641e 100644 --- a/convex/httpApiV1.handlers.test.ts +++ b/convex/httpApiV1.handlers.test.ts @@ -537,6 +537,63 @@ describe("httpApiV1 handlers", () => { expect(readme).toContain("`clawscan.json`: final ClawScan verdict"); }); + it("includes the bounded Endor summary in stored plugin report downloads", async () => { + vi.mocked(requireApiTokenUser).mockResolvedValue({ + userId: "users:owner", + user: { _id: "users:owner", role: "user" }, + } as never); + const endor = { + status: "completed", + checkedAt: 2, + reachableFunctionCount: 1, + findings: [{ severity: "high", summary: "Reachable vulnerable function" }], + }; + const runQuery = vi.fn(async (_query: unknown, args: Record) => { + if (isRateLimitArgs(args)) return okRate(); + expect(args).toMatchObject({ + actorUserId: "users:owner", + kind: "plugin", + name: "demo-plugin", + version: "1.2.3", + }); + return { + ok: true, + scanId: "plugin:demo-plugin:1.2.3", + status: "succeeded", + sourceKind: "published", + update: false, + writtenBack: true, + artifact: { kind: "plugin", name: "demo-plugin", version: "1.2.3" }, + report: { + clawscan: { status: "clean", checkedAt: 1 }, + endor, + skillspector: null, + staticAnalysis: null, + virustotal: null, + }, + createdAt: 1, + updatedAt: 2, + completedAt: 2, + }; + }); + const response = await __handlers.skillScanGetRouterV1Handler( + makeCtx({ runQuery }), + new Request( + "https://example.com/api/v1/skills/-/scan/download/demo-plugin?version=1.2.3&kind=plugin", + ), + ); + + expect(response.status).toBe(200); + const entries = unzipSync(new Uint8Array(await response.arrayBuffer())); + const endorEntry = entries["endor.json"]; + const readmeEntry = entries["README.md"]; + if (!endorEntry || !readmeEntry) throw new Error("Expected scan report entries missing"); + expect(JSON.parse(strFromU8(endorEntry))).toEqual(endor); + expect(strFromU8(readmeEntry)).toContain( + "a missing or skipped Endor result is not a clean result", + ); + }); + it("forwards the owner namespace when downloading a stored skill report", async () => { vi.mocked(requireApiTokenUser).mockResolvedValue({ userId: "users:owner", @@ -13510,6 +13567,12 @@ describe("httpApiV1 handlers", () => { summary: "Looks safe.", checkedAt: 1, }, + endorAnalysis: { + status: "completed", + checkedAt: 2, + reachableFunctionCount: 2, + findings: [{ severity: "high", summary: "Reachable dependency finding" }], + }, staticScan: { status: "malicious", reasonCodes: ["malicious.static_fixture"], @@ -13547,6 +13610,11 @@ describe("httpApiV1 handlers", () => { status: "clean", verdict: "clean", }, + endorAnalysis: { + status: "completed", + reachableFunctionCount: 2, + findings: [{ severity: "high", summary: "Reachable dependency finding" }], + }, verification: { scanStatus: "clean", }, @@ -13913,6 +13981,11 @@ describe("httpApiV1 handlers", () => { npmTarballName: "demo-plugin-1.0.0.tgz", verification: { scanStatus: "malicious" }, manualModeration: { state: "quarantined", reason: "private reviewer note" }, + endorAnalysis: { + status: "skipped", + checkedAt: 2, + reason: "No supported call graph", + }, }, }; } @@ -13952,6 +14025,11 @@ describe("httpApiV1 handlers", () => { npmShasum: "d".repeat(40), npmTarballName: "demo-plugin-1.0.0.tgz", createdAt: 1, + endorAnalysis: { + status: "skipped", + checkedAt: 2, + reason: "No supported call graph", + }, }, trust: { scanStatus: "malicious", diff --git a/convex/httpApiV1/packagesV1.ts b/convex/httpApiV1/packagesV1.ts index 6f387f7c20..d02ec32313 100644 --- a/convex/httpApiV1/packagesV1.ts +++ b/convex/httpApiV1/packagesV1.ts @@ -558,6 +558,7 @@ type ReleaseLike = { sha256hash?: string; vtAnalysis?: Doc<"packageReleases">["vtAnalysis"]; skillSpectorAnalysis?: Doc<"packageReleases">["skillSpectorAnalysis"]; + endorAnalysis?: Doc<"packageReleases">["endorAnalysis"]; llmAnalysis?: Doc<"packageReleases">["llmAnalysis"]; staticScan?: Doc<"packageReleases">["staticScan"]; manualModeration?: Doc<"packageReleases">["manualModeration"]; @@ -723,6 +724,7 @@ function toPackageReleaseSecurityResponse(params: { ...(params.release.npmShasum ? { npmShasum: params.release.npmShasum } : {}), ...(params.release.npmTarballName ? { npmTarballName: params.release.npmTarballName } : {}), createdAt: params.release.createdAt, + ...(params.release.endorAnalysis ? { endorAnalysis: params.release.endorAnalysis } : {}), }, trust: { scanStatus, @@ -4638,6 +4640,7 @@ export async function packagesGetRouterV1Handler(ctx: ActionCtx, request: Reques sha256hash: result.version.sha256hash ?? null, vtAnalysis: result.version.vtAnalysis ?? null, skillSpectorAnalysis: result.version.skillSpectorAnalysis ?? null, + ...(result.version.endorAnalysis ? { endorAnalysis: result.version.endorAnalysis } : {}), llmAnalysis: result.version.llmAnalysis ?? null, staticScan: result.version.staticScan ?? null, }, diff --git a/convex/httpApiV1/skillsV1.ts b/convex/httpApiV1/skillsV1.ts index ffdf9a5409..a4345d9768 100644 --- a/convex/httpApiV1/skillsV1.ts +++ b/convex/httpApiV1/skillsV1.ts @@ -480,11 +480,13 @@ function buildSkillScanReportZip(status: Record) { "- `clean` means ClawHub did not find blocking security issues in this scan.", "", "VirusTotal results are supporting reputation telemetry. They can help explain a risk signal, but they are not the sole source of ClawHub's final verdict.", + "Endor results summarize reachable dependency findings. They supplement the ClawScan verdict; a missing or skipped Endor result is not a clean result.", "", "## Files", "", "- `manifest.json`: artifact identity, scan status, timestamps, and writeback state.", "- `clawscan.json`: final ClawScan verdict, summary, guidance, and findings.", + "- `endor.json`: bounded Endor reachability summary when available.", "- `skillspector.json`: SkillSpector structure and agentic-risk signals when available.", "- `static-analysis.json`: deterministic scanner findings, reason codes, and static summary.", "- `virustotal.json`: external reputation counts and status when available.", @@ -495,6 +497,7 @@ function buildSkillScanReportZip(status: Record) { return buildDeterministicZip([ { path: "manifest.json", bytes: encodeJsonEntry(manifest) }, { path: "clawscan.json", bytes: encodeJsonEntry(scanReportPart(status, "clawscan")) }, + { path: "endor.json", bytes: encodeJsonEntry(scanReportPart(status, "endor")) }, { path: "skillspector.json", bytes: encodeJsonEntry(scanReportPart(status, "skillspector")) }, { path: "static-analysis.json", diff --git a/convex/lib/endorAnalysis.ts b/convex/lib/endorAnalysis.ts new file mode 100644 index 0000000000..4f8b57d5d9 --- /dev/null +++ b/convex/lib/endorAnalysis.ts @@ -0,0 +1,21 @@ +import { zodToConvex } from "convex-helpers/server/zod4"; +import { z } from "zod"; + +export const endorAnalysisSchema = z.discriminatedUnion("status", [ + z.object({ + status: z.literal("completed"), + checkedAt: z.number().finite(), + reachableFunctionCount: z.number().int().nonnegative(), + findings: z + .array(z.object({ severity: z.string().max(64), summary: z.string().max(2000) })) + .max(50), + }), + z.object({ + status: z.literal("skipped"), + checkedAt: z.number().finite(), + reason: z.string().max(2000), + }), +]); + +export const endorAnalysisValidator = zodToConvex(endorAnalysisSchema); +export type EndorAnalysis = z.infer; diff --git a/convex/lib/retentionPolicy.ts b/convex/lib/retentionPolicy.ts index ef1a31ea2e..a3012f1125 100644 --- a/convex/lib/retentionPolicy.ts +++ b/convex/lib/retentionPolicy.ts @@ -124,7 +124,9 @@ export const RETENTION_POLICIES = { skills: permanent("Canonical skill records."), skillSlugAliases: permanent("Historical slug routing aliases."), packages: permanent("Canonical package records."), - packageReleases: permanent("Canonical package release records."), + packageReleases: permanent( + "Canonical package release records; raw scanner reports follow release deletion and replacement lifecycle.", + ), publishAttempts: ephemeral( "Private staged publish workflow state expires unless later retained by moderation policy.", { diff --git a/convex/packageScannerReports.runtime.test.ts b/convex/packageScannerReports.runtime.test.ts new file mode 100644 index 0000000000..106129ee6c --- /dev/null +++ b/convex/packageScannerReports.runtime.test.ts @@ -0,0 +1,323 @@ +/// +/* @vitest-environment edge-runtime */ +import { register as registerRateLimiter } from "@convex-dev/rate-limiter/test"; +import { convexTest } from "convex-test"; +import { afterEach, expect, it, vi } from "vitest"; +import { api, internal } from "./_generated/api"; +import type { Doc } from "./_generated/dataModel"; +import schema from "./schema"; + +vi.mock("./lib/verifiedClientIp", () => ({ + getVerifiedClientIp: async () => "203.0.113.1", +})); + +const modules = import.meta.glob("./**/*.ts"); +afterEach(() => vi.unstubAllEnvs()); + +async function createPackageScanFixture() { + const t = convexTest(schema, modules); + registerRateLimiter(t); + const fixture = await t.run(async (ctx) => { + const userId = await ctx.db.insert("users", { handle: "endor-owner" }); + const publisherId = await ctx.db.insert("publishers", { + kind: "user", + handle: "endor-owner", + displayName: "Endor owner", + linkedUserId: userId, + createdAt: 1, + updatedAt: 1, + }); + const verification = { + tier: "structural", + scope: "artifact-only", + scanStatus: "pending", + } as const; + const packageId = await ctx.db.insert("packages", { + name: "endor-plugin", + normalizedName: "endor-plugin", + displayName: "Endor plugin", + ownerUserId: userId, + ownerPublisherId: publisherId, + family: "code-plugin", + channel: "community", + isOfficial: false, + tags: {}, + compatibility: {}, + verification, + scanStatus: "pending", + stats: { downloads: 0, installs: 0, stars: 0, versions: 1 }, + createdAt: 1, + updatedAt: 1, + }); + const releaseId = await ctx.db.insert("packageReleases", { + packageId, + version: "1.0.0", + changelog: "Initial release", + distTags: ["latest"], + files: [], + integritySha256: "a".repeat(64), + compatibility: {}, + verification, + createdBy: userId, + publishActor: { kind: "user", userId }, + createdAt: 1, + }); + const latestVersionSummary: NonNullable["latestVersionSummary"]> = { + version: "1.0.0", + createdAt: 1, + changelog: "Initial release", + compatibility: {}, + verification, + }; + await ctx.db.patch(packageId, { + latestReleaseId: releaseId, + tags: { latest: releaseId }, + latestVersionSummary, + }); + const jobId = await ctx.db.insert("securityScanJobs", { + targetKind: "packageRelease", + packageReleaseId: releaseId, + status: "running", + source: "manual", + priority: 1, + hasMaliciousSignal: false, + waitForVtUntil: 0, + nextRunAt: 0, + attempts: 1, + leaseToken: "lease-one", + workerId: "endor-worker", + leaseExpiresAt: Date.now() + 60_000, + createdAt: 1, + updatedAt: 1, + }); + return { packageId, releaseId, jobId }; + }); + return { t, ...fixture }; +} + +it("persists bounded Endor package results with raw reports and replaces stale report storage", async () => { + vi.stubEnv("SECURITY_SCAN_WORKER_TOKEN", "worker-fixture"); + const { t, releaseId, jobId } = await createPackageScanFixture(); + + const hydrated = await t.action(api.securityScan.hydrateCodexScanJob, { + token: "worker-fixture", + jobId, + leaseToken: "lease-one", + workerId: "endor-worker", + }); + expect(hydrated?.scannerReportsUploadUrl).toBeTypeOf("string"); + + const firstRawReport = { + checkedAt: 10, + endor: { + status: "completed", + all_findings: [{ severity: "high", description: "raw finding" }], + blocking_findings: [], + warning_findings: [{ severity: "high", description: "raw finding" }], + preparation: { + sourceRoot: "/tmp/private", + normalizations: ["package-layout"], + }, + }, + skillspector: null, + }; + const firstStorageId = await t.run((ctx) => + ctx.storage.store(new Blob([JSON.stringify(firstRawReport)])), + ); + const firstSummary = { + status: "completed" as const, + checkedAt: 9, + reachableFunctionCount: 4, + findings: [{ severity: "high", summary: "Reachable vulnerable function" }], + }; + const completionPayload = { + token: "worker-fixture", + jobId, + leaseToken: "lease-one", + runId: "run-one", + llmAnalysis: { status: "clean", verdict: "benign", checkedAt: 10 }, + endorAnalysis: firstSummary, + scannerReportsStorageId: firstStorageId, + }; + const concurrentCompletions = await Promise.allSettled([ + t.action(api.securityScan.completeCodexScanJob, completionPayload), + t.action(api.securityScan.completeCodexScanJob, completionPayload), + ]); + expect(concurrentCompletions.filter((result) => result.status === "fulfilled")).toHaveLength(1); + const rejectedCompletion = concurrentCompletions.find((result) => result.status === "rejected"); + expect(rejectedCompletion).toBeDefined(); + if (!rejectedCompletion || rejectedCompletion.status !== "rejected") { + throw new Error("Expected one concurrent completion to lose its lease"); + } + expect( + rejectedCompletion.reason instanceof Error + ? rejectedCompletion.reason.message + : String(rejectedCompletion.reason), + ).toContain("Lease mismatch"); + + expect(await t.run((ctx) => ctx.db.get(releaseId))).toMatchObject({ + endorAnalysis: firstSummary, + scannerReportsStorageId: firstStorageId, + llmAnalysis: { status: "clean", checkedAt: 10 }, + }); + const completedJob = await t.run((ctx) => ctx.db.get(jobId)); + expect(completedJob).toMatchObject({ + status: "succeeded", + runId: "run-one", + }); + expect(completedJob).not.toHaveProperty("leaseToken"); + const storedFirstReport = await t.run(async (ctx) => { + const report = await ctx.storage.get(firstStorageId); + return report ? await report.text() : null; + }); + if (!storedFirstReport) throw new Error("Stored scanner report missing"); + expect(JSON.parse(storedFirstReport)).toEqual(firstRawReport); + + expect(await t.run(async (ctx) => Boolean(await ctx.storage.get(firstStorageId)))).toBe(true); + expect(await t.run((ctx) => ctx.db.get(releaseId))).toMatchObject({ + scannerReportsStorageId: firstStorageId, + endorAnalysis: firstSummary, + }); + expect( + await t.mutation(internal.securityScan.deleteScannerReportIfUnattachedInternal, { + jobId, + storageId: firstStorageId, + }), + ).toEqual({ deleted: false, reason: "attached" }); + expect(await t.run(async (ctx) => Boolean(await ctx.storage.get(firstStorageId)))).toBe(true); + + const unattachedStorageId = await t.run((ctx) => ctx.storage.store(new Blob(["orphaned"]))); + await expect( + t.action(api.securityScan.completeCodexScanJob, { + ...completionPayload, + leaseToken: "lost-lease", + scannerReportsStorageId: unattachedStorageId, + }), + ).rejects.toThrow("Lease mismatch"); + expect(await t.run((ctx) => ctx.storage.get(unattachedStorageId))).toBeNull(); + expect(await t.run(async (ctx) => Boolean(await ctx.storage.get(firstStorageId)))).toBe(true); + + const versionResponse = await t.fetch("/api/v1/packages/endor-plugin/versions/1.0.0"); + expect(versionResponse.status).toBe(200); + const versionBody = await versionResponse.json(); + expect(versionBody.version.endorAnalysis).toEqual(firstSummary); + expect(versionBody.version).not.toHaveProperty("scannerReportsStorageId"); + expect(JSON.stringify(versionBody)).not.toContain("/tmp/private"); + + const securityResponse = await t.fetch("/api/v1/packages/endor-plugin/versions/1.0.0/security"); + expect(securityResponse.status).toBe(200); + const securityBody = await securityResponse.json(); + expect(securityBody.release.endorAnalysis).toEqual(firstSummary); + expect(securityBody.release).not.toHaveProperty("scannerReportsStorageId"); + expect(securityBody.trust.scanStatus).toBe("clean"); + + const secondStorageId = await t.run((ctx) => + ctx.storage.store(new Blob([JSON.stringify({ checkedAt: 20, endor: { scanner: "endor" } })])), + ); + await t.run(async (ctx) => { + await ctx.db.patch(jobId, { + status: "running", + leaseToken: "lease-two", + leaseExpiresAt: Date.now() + 60_000, + }); + }); + await t.action(api.securityScan.completeCodexScanJob, { + token: "worker-fixture", + jobId, + leaseToken: "lease-two", + llmAnalysis: { status: "clean", checkedAt: 20 }, + endorAnalysis: { status: "skipped", checkedAt: 19, reason: "No supported call graph" }, + scannerReportsStorageId: secondStorageId, + }); + expect(await t.run((ctx) => ctx.storage.get(firstStorageId))).toBeNull(); + expect(await t.run((ctx) => ctx.db.get(releaseId))).toMatchObject({ + scannerReportsStorageId: secondStorageId, + endorAnalysis: { status: "skipped", checkedAt: 19 }, + }); +}); + +it("rejects mismatched package targets and leases without attaching Endor results", async () => { + const { t, releaseId, jobId } = await createPackageScanFixture(); + const storageId = await t.run((ctx) => ctx.storage.store(new Blob(["raw report"]))); + const args = { + releaseId, + jobId, + leaseToken: "wrong-lease", + llmAnalysis: { status: "clean", checkedAt: 10 }, + endorAnalysis: { + status: "completed" as const, + checkedAt: 9, + reachableFunctionCount: 1, + findings: [], + }, + scannerReportsStorageId: storageId, + }; + + await expect( + t.mutation(internal.packages.completeReleaseSecurityScanInternal, args), + ).rejects.toThrow("Lease mismatch"); + + await t.run(async (ctx) => { + await ctx.db.patch(jobId, { status: "succeeded" }); + }); + await expect( + t.mutation(internal.packages.completeReleaseSecurityScanInternal, { + ...args, + leaseToken: "lease-one", + }), + ).rejects.toThrow("Lease mismatch"); + await t.run(async (ctx) => { + await ctx.db.patch(jobId, { status: "running" }); + }); + + await expect( + t.mutation(internal.packages.completeReleaseSecurityScanInternal, { + ...args, + leaseToken: "lease-one", + endorAnalysis: { + status: "completed", + checkedAt: 9, + reachableFunctionCount: 51, + findings: Array.from({ length: 51 }, (_, index) => ({ + severity: "high", + summary: `Finding ${index}`, + })), + }, + }), + ).rejects.toThrow(); + + const otherReleaseId = await t.run(async (ctx) => { + const release = await ctx.db.get(releaseId); + if (!release) throw new Error("Fixture release missing"); + const { _id: _releaseId, _creationTime: _createdAt, ...fields } = release; + return await ctx.db.insert("packageReleases", { ...fields, version: "2.0.0" }); + }); + await expect( + t.mutation(internal.packages.completeReleaseSecurityScanInternal, { + ...args, + releaseId: otherReleaseId, + leaseToken: "lease-one", + }), + ).rejects.toThrow("Lease mismatch"); + + expect(await t.run((ctx) => ctx.db.get(releaseId))).not.toMatchObject({ + scannerReportsStorageId: storageId, + }); + expect(await t.run((ctx) => ctx.db.get(otherReleaseId))).not.toHaveProperty("endorAnalysis"); +}); + +it("keeps submitted scanner storage when its job target no longer exists", async () => { + const { t, releaseId, jobId } = await createPackageScanFixture(); + const storageId = await t.run((ctx) => ctx.storage.store(new Blob(["unresolved target"]))); + await t.run(async (ctx) => { + await ctx.db.delete(releaseId); + }); + + const result = await t.mutation(internal.securityScan.deleteScannerReportIfUnattachedInternal, { + jobId, + storageId, + }); + + expect(result).toEqual({ deleted: false, reason: "target-missing" }); + expect(await t.run(async (ctx) => Boolean(await ctx.storage.get(storageId)))).toBe(true); +}); diff --git a/convex/packages.ts b/convex/packages.ts index 9d161a90cf..8fdca2b027 100644 --- a/convex/packages.ts +++ b/convex/packages.ts @@ -66,6 +66,7 @@ import { buildPackageInspectorFindingsEmail, buildPackageInspectorValidationUrl, } from "./lib/emails"; +import { endorAnalysisSchema, endorAnalysisValidator } from "./lib/endorAnalysis"; import { experimentalClawsEnabled, isClawFamilyPubliclyVisible } from "./lib/experimentalClaws"; import { requireGitHubAccountAge } from "./lib/githubAccount"; import { normalizeGitHubRepository } from "./lib/githubActionsOidc"; @@ -1346,6 +1347,7 @@ function toPublicPackageRelease(release: Doc<"packageReleases">, family: Package vtAnalysis: release.vtAnalysis, aigAnalysis: release.aigAnalysis, skillSpectorAnalysis: release.skillSpectorAnalysis, + endorAnalysis: release.endorAnalysis, llmAnalysis: release.llmAnalysis, staticScan: release.staticScan, createdAt: release.createdAt, @@ -1355,6 +1357,7 @@ function toPublicPackageRelease(release: Doc<"packageReleases">, family: Package capabilities: _capabilities, clawManifestSummary: _clawManifestSummary, extractedClawManifest: _extractedClawManifest, + scannerReportsStorageId: _scannerReportsStorageId, ...publicRelease } = release as Doc<"packageReleases"> & { capabilities?: unknown; @@ -3670,6 +3673,7 @@ export const listAuditPage = query({ version: latestRelease.version, createdAt: latestRelease.createdAt, vtAnalysis: latestRelease.vtAnalysis, + endorAnalysis: latestRelease.endorAnalysis, llmAnalysis: latestRelease.llmAnalysis, staticScan: latestRelease.staticScan ? { @@ -5893,7 +5897,7 @@ async function deletePackageModerationEventsForAppeal( } async function hardDeletePackageDoc( - ctx: Pick, + ctx: Pick, pkg: Doc<"packages">, params: { actorUserId: Id<"users">; @@ -5970,6 +5974,11 @@ async function hardDeletePackageDoc( for (const dailyStat of dailyStats) await ctx.db.delete(dailyStat._id); for (const release of releases) await ctx.db.delete(release._id); + await Promise.allSettled( + releases.flatMap((release) => + release.scannerReportsStorageId ? [ctx.storage.delete(release.scannerReportsStorageId)] : [], + ), + ); await ctx.db.delete(pkg._id); await ctx.db.insert("auditLogs", { actorUserId: params.actorUserId, @@ -11354,6 +11363,9 @@ export const discardPendingPackagePublicationInternal = internalMutation({ if (typeof release?.clawpackStorageId === "string") { storageIds.add(release.clawpackStorageId as Id<"_storage">); } + if (release?.scannerReportsStorageId) { + storageIds.add(release.scannerReportsStorageId); + } if (release) await ctx.db.delete(release._id); await Promise.allSettled([...storageIds].map((storageId) => ctx.storage.delete(storageId))); @@ -12394,9 +12406,31 @@ export const updateReleaseAigAnalysisInternal = internalMutation({ }, }); -export const updateReleaseLlmAnalysisInternal = internalMutation({ +type ReleaseLlmAnalysis = NonNullable["llmAnalysis"]>; + +async function applyReleaseLlmAnalysis( + ctx: Pick & Partial>, + release: Doc<"packageReleases">, + llmAnalysis: ReleaseLlmAnalysis, +) { + await ctx.db.patch(release._id, { llmAnalysis }); + const updatedRelease: Doc<"packageReleases"> = { + ...release, + llmAnalysis, + }; + const llmVerdict = (llmAnalysis.verdict ?? llmAnalysis.status).trim().toLowerCase(); + await syncLatestPackageVerification(ctx, updatedRelease, { + quarantineMaliciousLatest: llmVerdict === "malicious", + maliciousTrigger: "malicious.llm_malicious", + }); +} + +export const completeReleaseSecurityScanInternal = internalMutation({ args: { releaseId: v.id("packageReleases"), + jobId: v.id("securityScanJobs"), + leaseToken: v.string(), + runId: v.optional(v.string()), llmAnalysis: v.object({ status: v.string(), verdict: v.optional(v.string()), @@ -12425,20 +12459,96 @@ export const updateReleaseLlmAnalysisInternal = internalMutation({ model: v.optional(v.string()), checkedAt: v.number(), }), + skillSpectorAnalysis: v.optional(skillSpectorAnalysisValidator), + endorAnalysis: v.optional(endorAnalysisValidator), + scannerReportsStorageId: v.optional(v.id("_storage")), }, handler: async (ctx, args) => { + const job = await ctx.db.get(args.jobId); + if ( + !job || + job.status !== "running" || + job.targetKind !== "packageRelease" || + job.packageReleaseId !== args.releaseId || + job.leaseToken !== args.leaseToken + ) { + throw new ConvexError("Lease mismatch"); + } const release = await ctx.db.get(args.releaseId); - if (!isReleaseActive(release)) return; - await ctx.db.patch(args.releaseId, { llmAnalysis: args.llmAnalysis }); - const updatedRelease = { + if (!isReleaseActive(release)) throw new ConvexError("Package release not found"); + const endorAnalysis = args.endorAnalysis + ? endorAnalysisSchema.parse(args.endorAnalysis) + : undefined; + + const previousScannerReportsStorageId = release.scannerReportsStorageId; + const releaseWithScannerResults: Doc<"packageReleases"> = { ...release, - llmAnalysis: args.llmAnalysis, - } as Doc<"packageReleases">; - const llmVerdict = (args.llmAnalysis.verdict ?? args.llmAnalysis.status).trim().toLowerCase(); - await syncLatestPackageVerification(ctx, updatedRelease, { - quarantineMaliciousLatest: llmVerdict === "malicious", - maliciousTrigger: "malicious.llm_malicious", + skillSpectorAnalysis: args.skillSpectorAnalysis, + endorAnalysis, + scannerReportsStorageId: args.scannerReportsStorageId, + }; + await ctx.db.patch(args.releaseId, { + skillSpectorAnalysis: args.skillSpectorAnalysis, + endorAnalysis, + scannerReportsStorageId: args.scannerReportsStorageId, }); + await applyReleaseLlmAnalysis(ctx, releaseWithScannerResults, args.llmAnalysis); + + const now = Date.now(); + await ctx.db.patch(args.jobId, { + status: "succeeded", + runId: args.runId, + completedAt: now, + leaseToken: undefined, + leaseExpiresAt: undefined, + updatedAt: now, + }); + if ( + previousScannerReportsStorageId && + previousScannerReportsStorageId !== args.scannerReportsStorageId + ) { + await ctx.storage.delete(previousScannerReportsStorageId); + } + return { ok: true as const }; + }, +}); + +export const updateReleaseLlmAnalysisInternal = internalMutation({ + args: { + releaseId: v.id("packageReleases"), + llmAnalysis: v.object({ + status: v.string(), + verdict: v.optional(v.string()), + confidence: v.optional(v.string()), + summary: v.optional(v.string()), + dimensions: v.optional( + v.array( + v.object({ + name: v.string(), + label: v.string(), + rating: v.string(), + detail: v.string(), + }), + ), + ), + guidance: v.optional(v.string()), + findings: v.optional(v.string()), + agenticRiskFindings: v.optional(v.array(llmAgenticRiskFindingValidator)), + riskSummary: v.optional( + v.object({ + abnormal_behavior_control: llmRiskSummaryBucketValidator, + permission_boundary: llmRiskSummaryBucketValidator, + sensitive_data_protection: llmRiskSummaryBucketValidator, + }), + ), + model: v.optional(v.string()), + checkedAt: v.number(), + }), + }, + handler: async (ctx, args) => { + const release = await ctx.db.get(args.releaseId); + if (!isReleaseActive(release)) return; + await applyReleaseLlmAnalysis(ctx, release, args.llmAnalysis); }, }); diff --git a/convex/schema.ts b/convex/schema.ts index 81ac6ea2b9..5a66c797e4 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -7,6 +7,7 @@ import { canonicalTrendingSourceRefValidator, } from "./lib/canonicalTrending"; import { EMBEDDING_DIMENSIONS } from "./lib/embeddings"; +import { endorAnalysisValidator } from "./lib/endorAnalysis"; import { pluginCategoryClassificationValidator } from "./lib/pluginCategoryClassification"; const PLATFORM_SKILL_LICENSE = "MIT-0" as const; @@ -1894,9 +1895,11 @@ const packageReleases = defineTable({ verification: packageVerificationValidator, // Deprecated compatibility hash for exact /download ZIP bytes; use artifact.sha256 for installs. sha256hash: v.optional(v.string()), + scannerReportsStorageId: v.optional(v.id("_storage")), vtAnalysis: v.optional(vtAnalysisValidator), aigAnalysis: v.optional(aigAnalysisValidator), skillSpectorAnalysis: v.optional(skillSpectorAnalysisValidator), + endorAnalysis: v.optional(endorAnalysisValidator), llmAnalysis: v.optional( v.object({ status: v.string(), diff --git a/convex/securityScan.test.ts b/convex/securityScan.test.ts index b7195e3217..0a103f8186 100644 --- a/convex/securityScan.test.ts +++ b/convex/securityScan.test.ts @@ -316,6 +316,14 @@ const completeCodexScanJobHandler = ( }>; checkedAt: number; }; + endorAnalysis?: + | { + status: "completed"; + checkedAt: number; + reachableFunctionCount: number; + findings: Array<{ severity: string; summary: string }>; + } + | { status: "skipped"; checkedAt: number; reason: string }; scannerReportsStorageId?: string; runId?: string; }, @@ -583,6 +591,7 @@ const getStoredScanReportForUserInternalHandler = ( artifact: Record; report: { clawscan: Record | null; + endor: Record | null; skillspector: Record | null; staticAnalysis: Record | null; virustotal: Record | null; @@ -2392,6 +2401,12 @@ describe("securityScan", () => { summary: "Runs unexpected shell commands.", checkedAt: 1_700_000_000_000, }, + endorAnalysis: { + status: "completed", + checkedAt: 1_700_000_100_000, + reachableFunctionCount: 2, + findings: [{ severity: "high", summary: "Reachable vulnerable function" }], + }, createdAt: 1_700_000_000_000, }, }, @@ -2418,7 +2433,14 @@ describe("securityScan", () => { status: "malicious", summary: "Runs unexpected shell commands.", }, + endor: { + status: "completed", + checkedAt: 1_700_000_100_000, + reachableFunctionCount: 2, + findings: [{ severity: "high", summary: "Reachable vulnerable function" }], + }, }, + completedAt: 1_700_000_100_000, }); }); @@ -5363,7 +5385,7 @@ describe("securityScan", () => { }, ); - it("deletes a newly stored report when committing the scan verdict fails", async () => { + it("requests atomic cleanup for a newly stored report when committing the verdict fails", async () => { vi.stubEnv("SECURITY_SCAN_WORKER_TOKEN", "worker-secret"); const runQuery = vi.fn(async () => ({ job: { _id: "securityScanJobs:1", targetKind: "skillVersion", leaseToken: "lease-token" }, @@ -5373,10 +5395,9 @@ describe("securityScan", () => { if ("llmAnalysis" in args) throw new Error("commit failed"); return { ok: true }; }); - const storage = { store: vi.fn(async () => "storage:new-report"), delete: vi.fn() }; await expect( completeCodexScanJobHandler( - { runQuery, runMutation, storage }, + { runQuery, runMutation }, { token: "worker-secret", jobId: "securityScanJobs:1", @@ -5387,8 +5408,13 @@ describe("securityScan", () => { }, ), ).rejects.toThrow("commit failed"); - expect(storage.store).not.toHaveBeenCalled(); - expect(storage.delete).toHaveBeenCalledWith("storage:new-report"); + expect(runMutation).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + jobId: "securityScanJobs:1", + storageId: "storage:new-report", + }), + ); }); it("caps SkillSpector findings before storing completed scan results", async () => { @@ -5614,7 +5640,7 @@ describe("securityScan", () => { expect(runMutation).toHaveBeenCalledTimes(2); }); - it("ignores package AIG payloads because plugin scanning is out of scope", async () => { + it("commits package Endor and raw scanner results without persisting package AIG payloads", async () => { vi.stubEnv("SECURITY_SCAN_WORKER_TOKEN", "worker-secret"); const runQuery = vi.fn(async () => ({ job: { @@ -5635,6 +5661,13 @@ describe("securityScan", () => { jobId: "securityScanJobs:plugin", leaseToken: "lease-token", llmAnalysis: { status: "clean", checkedAt: 123 }, + endorAnalysis: { + status: "completed", + checkedAt: 122, + reachableFunctionCount: 3, + findings: [{ severity: "high", summary: "Reachable finding" }], + }, + scannerReportsStorageId: "storage:scanner-reports", aigAnalysis: { status: "error", issueCount: 99, @@ -5644,11 +5677,20 @@ describe("securityScan", () => { }, ); - expect(runMutation).toHaveBeenCalledTimes(4); + expect(runMutation).toHaveBeenCalledTimes(2); const releasePatches = runMutation.mock.calls .map(([, mutationArgs]) => mutationArgs as Record) .filter((mutationArgs) => mutationArgs.releaseId === "packageReleases:plugin"); - expect(releasePatches).toHaveLength(2); + expect(releasePatches).toHaveLength(1); + expect(releasePatches[0]).toMatchObject({ + jobId: "securityScanJobs:plugin", + leaseToken: "lease-token", + endorAnalysis: { + status: "completed", + reachableFunctionCount: 3, + }, + scannerReportsStorageId: "storage:scanner-reports", + }); expect(releasePatches).not.toContainEqual(expect.objectContaining({ aigAnalysis: undefined })); expect(releasePatches.some((patch) => "aigAnalysis" in patch)).toBe(false); }); diff --git a/convex/securityScan.ts b/convex/securityScan.ts index 7047c13f16..ccb1b63ee0 100644 --- a/convex/securityScan.ts +++ b/convex/securityScan.ts @@ -6,6 +6,11 @@ import { action, internalAction, internalMutation, internalQuery, mutation } fro import { applyGitHubSkillVerificationResultHandler } from "./githubSkillSync"; import { assertAdmin, assertModerator, requireUser } from "./lib/access"; import { reusableAigAnalysis } from "./lib/aigAnalysis"; +import { + endorAnalysisSchema, + endorAnalysisValidator, + type EndorAnalysis, +} from "./lib/endorAnalysis"; import { Events, logEvent } from "./lib/observabilityEvents"; import { normalizePackageName } from "./lib/packageRegistry"; import { normalizePackageScanStatus } from "./lib/packageSecurity"; @@ -388,6 +393,7 @@ const catalogScanVerdictValidator = v.union( const internalRefs = internal as unknown as { packages: { + completeReleaseSecurityScanInternal: unknown; getPackageByIdInternal: unknown; getReleaseByIdInternal: unknown; updateReleaseLlmAnalysisInternal: unknown; @@ -407,6 +413,7 @@ const internalRefs = internal as unknown as { listReadySourceJobsForClaimInternal: unknown; recordGitHubSkillScanResultInternal: unknown; completeCatalogSkillScanJobInternal: unknown; + deleteScannerReportIfUnattachedInternal: unknown; recordSkillScanRequestFailedInternal: unknown; recordSkillScanRequestSucceededInternal: unknown; requeueJobLeaseInternal: unknown; @@ -1325,15 +1332,18 @@ function skillScanReportFromRequest(request: Doc<"skillScanRequests">) { }; } -function storedScanReportFromArtifact( - artifact: Pick< - Doc<"skillVersions"> | Doc<"packageReleases">, - "aigAnalysis" | "llmAnalysis" | "skillSpectorAnalysis" | "staticScan" | "vtAnalysis" - >, -) { +type StoredScanArtifact = Pick< + Doc<"skillVersions"> | Doc<"packageReleases">, + "aigAnalysis" | "llmAnalysis" | "skillSpectorAnalysis" | "staticScan" | "vtAnalysis" +> & { + endorAnalysis?: EndorAnalysis; +}; + +function storedScanReportFromArtifact(artifact: StoredScanArtifact) { return { aig: artifact.aigAnalysis ?? null, clawscan: artifact.llmAnalysis ?? null, + endor: artifact.endorAnalysis ?? null, skillspector: artifact.skillSpectorAnalysis ?? null, staticAnalysis: artifact.staticScan ?? null, virustotal: artifact.vtAnalysis @@ -1345,30 +1355,22 @@ function storedScanReportFromArtifact( }; } -function hasStoredScanReport( - artifact: Pick< - Doc<"skillVersions"> | Doc<"packageReleases">, - "aigAnalysis" | "llmAnalysis" | "skillSpectorAnalysis" | "staticScan" | "vtAnalysis" - >, -) { +function hasStoredScanReport(artifact: StoredScanArtifact) { return Boolean( artifact.aigAnalysis || artifact.llmAnalysis || + artifact.endorAnalysis || artifact.skillSpectorAnalysis || artifact.staticScan || artifact.vtAnalysis, ); } -function completedAtFromStoredScanReport( - artifact: Pick< - Doc<"skillVersions"> | Doc<"packageReleases">, - "aigAnalysis" | "llmAnalysis" | "skillSpectorAnalysis" | "staticScan" | "vtAnalysis" - >, -) { +function completedAtFromStoredScanReport(artifact: StoredScanArtifact) { const checkedAtValues = [ artifact.aigAnalysis?.checkedAt, artifact.llmAnalysis?.checkedAt, + artifact.endorAnalysis?.checkedAt, artifact.skillSpectorAnalysis?.checkedAt, artifact.staticScan?.checkedAt, artifact.vtAnalysis?.checkedAt, @@ -3792,6 +3794,55 @@ export const getJobTargetInternal = internalQuery({ }, }); +export const deleteScannerReportIfUnattachedInternal = internalMutation({ + args: { + jobId: v.id("securityScanJobs"), + storageId: v.id("_storage"), + }, + handler: async (ctx, args) => { + const job = await ctx.db.get(args.jobId); + if (!job) return { deleted: false as const, reason: "job-missing" as const }; + + if (job.targetKind === "packageRelease") { + if (!job.packageReleaseId) { + return { deleted: false as const, reason: "target-missing" as const }; + } + const release = await ctx.db.get(job.packageReleaseId); + if (!release) return { deleted: false as const, reason: "target-missing" as const }; + if (release.scannerReportsStorageId === args.storageId) { + return { deleted: false as const, reason: "attached" as const }; + } + } else if (job.targetKind === "skillVersion") { + if (!job.skillVersionId) { + return { deleted: false as const, reason: "target-missing" as const }; + } + const version = await ctx.db.get(job.skillVersionId); + if (!version) return { deleted: false as const, reason: "target-missing" as const }; + if (version.scannerReportsStorageId === args.storageId) { + return { deleted: false as const, reason: "attached" as const }; + } + } else if (job.targetKind === "skillScanRequest") { + if (!job.skillScanRequestId) { + return { deleted: false as const, reason: "target-missing" as const }; + } + const request = await ctx.db.get(job.skillScanRequestId); + if (!request) return { deleted: false as const, reason: "target-missing" as const }; + if (request.skillVersionId) { + const version = await ctx.db.get(request.skillVersionId); + if (!version) return { deleted: false as const, reason: "target-missing" as const }; + if (version.scannerReportsStorageId === args.storageId) { + return { deleted: false as const, reason: "attached" as const }; + } + } + } else { + return { deleted: false as const, reason: "target-missing" as const }; + } + + await ctx.storage.delete(args.storageId); + return { deleted: true as const }; + }, +}); + export const succeedJobInternal = internalMutation({ args: { jobId: v.id("securityScanJobs"), @@ -3989,7 +4040,8 @@ async function hydrateClaimedCodexScanJob( } return { scannerReportsUploadUrl: - version && (job.targetKind === "skillVersion" || scanRequest?.update) + job.targetKind === "packageRelease" || + (version && (job.targetKind === "skillVersion" || scanRequest?.update)) ? await ctx.storage.generateUploadUrl() : null, job, @@ -4120,11 +4172,19 @@ export const completeCodexScanJob = action({ llmAnalysis: llmAnalysisValidator, aigAnalysis: v.optional(aigAnalysisValidator), skillSpectorAnalysis: v.optional(skillSpectorAnalysisValidator), + endorAnalysis: v.optional(endorAnalysisValidator), scannerReportsStorageId: v.optional(v.id("_storage")), runId: v.optional(v.string()), }, handler: async (ctx, args) => { assertWorkerToken(args.token); + async function deleteSubmittedScannerReportIfUnattached() { + if (!args.scannerReportsStorageId) return; + await runMutationRef(ctx, internalRefs.securityScan.deleteScannerReportIfUnattachedInternal, { + jobId: args.jobId, + storageId: args.scannerReportsStorageId, + }); + } const target = await runQueryRef( ctx, internalRefs.securityScan.getJobTargetInternal, @@ -4132,14 +4192,30 @@ export const completeCodexScanJob = action({ jobId: args.jobId, }, ); - if (!target) throw new ConvexError("Job not found"); + if (!target) { + await deleteSubmittedScannerReportIfUnattached(); + throw new ConvexError("Job not found"); + } const isCatalogScanRequest = target.job.targetKind === "skillScanRequest" && target.scanRequest?.sourceKind === "skills-sh-catalog" && Boolean(target.scanRequest.skillsShCatalogAttemptId); if (!isCatalogScanRequest && target.job.leaseToken !== args.leaseToken) { + await deleteSubmittedScannerReportIfUnattached(); throw new ConvexError("Lease mismatch"); } + let endorAnalysis: EndorAnalysis | undefined; + try { + endorAnalysis = args.endorAnalysis + ? endorAnalysisSchema.parse(args.endorAnalysis) + : undefined; + if (endorAnalysis && target.job.targetKind !== "packageRelease") { + throw new ConvexError("Endor analysis is only supported for package release scans"); + } + } catch (error) { + await deleteSubmittedScannerReportIfUnattached(); + throw error; + } const completedAigAnalysis = reusableAigAnalysis(args.aigAnalysis); if ( (target.job.targetKind === "skillVersion" || target.job.targetKind === "skillScanRequest") && @@ -4169,7 +4245,7 @@ export const completeCodexScanJob = action({ scannerReportsStorageId, }); } catch (error) { - if (scannerReportsStorageId) await ctx.storage.delete(scannerReportsStorageId); + await deleteSubmittedScannerReportIfUnattached(); throw error; } } @@ -4177,16 +4253,39 @@ export const completeCodexScanJob = action({ if (target.job.targetKind === "skillVersion" && target.version) { await updateSkillVersion(target.version._id); } else if (target.job.targetKind === "packageRelease" && target.release) { - await runMutationRef(ctx, internalRefs.packages.updateReleaseSkillSpectorAnalysisInternal, { - releaseId: target.release._id, - ...(args.skillSpectorAnalysis - ? { skillSpectorAnalysis: capSkillSpectorAnalysisForStorage(args.skillSpectorAnalysis) } - : {}), - }); - await runMutationRef(ctx, internalRefs.packages.updateReleaseLlmAnalysisInternal, { - releaseId: target.release._id, - llmAnalysis: args.llmAnalysis, - }); + const scannerReportsStorageId = args.scannerReportsStorageId; + let result: { ok: true }; + try { + result = await runMutationRef( + ctx, + internalRefs.packages.completeReleaseSecurityScanInternal, + { + releaseId: target.release._id, + jobId: args.jobId, + leaseToken: args.leaseToken, + runId: args.runId, + llmAnalysis: args.llmAnalysis, + ...(args.skillSpectorAnalysis + ? { + skillSpectorAnalysis: capSkillSpectorAnalysisForStorage( + args.skillSpectorAnalysis, + ), + } + : {}), + endorAnalysis, + scannerReportsStorageId, + }, + ); + } catch (error) { + await deleteSubmittedScannerReportIfUnattached(); + throw error; + } + await runMutationRef( + ctx, + internalRefs.securityScanDispatch.requestSecurityScanDispatchInternal, + {}, + ); + return result; } else if (target.job.targetKind === "skillScanRequest" && target.scanRequest) { let writtenBack = false; if ( @@ -4252,6 +4351,7 @@ export const completeCodexScanJob = action({ writtenBack, }); } else { + await deleteSubmittedScannerReportIfUnattached(); throw new ConvexError("Unsupported security scan target"); } diff --git a/packages/clawhub/src/schema/packages.ts b/packages/clawhub/src/schema/packages.ts index ec2b4d052a..5762c40f9f 100644 --- a/packages/clawhub/src/schema/packages.ts +++ b/packages/clawhub/src/schema/packages.ts @@ -183,6 +183,28 @@ export const PackageSkillSpectorAnalysisSchema = type({ }); export type PackageSkillSpectorAnalysis = (typeof PackageSkillSpectorAnalysisSchema)[inferred]; +const PackageEndorFindingSeveritySchema = type("string").atMostLength(64); +const PackageEndorFindingSummarySchema = type("string").atMostLength(2000); +const PackageEndorFindingSchema = type({ + severity: PackageEndorFindingSeveritySchema, + summary: PackageEndorFindingSummarySchema, +}); +const PackageEndorCompletedAnalysisSchema = type({ + status: '"completed"', + checkedAt: "number", + reachableFunctionCount: "number.integer >= 0", + findings: PackageEndorFindingSchema.array().atMostLength(50), +}); +const PackageEndorSkippedAnalysisSchema = type({ + status: '"skipped"', + checkedAt: "number", + reason: type("string").atMostLength(2000), +}); +export const PackageEndorAnalysisSchema = PackageEndorCompletedAnalysisSchema.or( + PackageEndorSkippedAnalysisSchema, +); +export type PackageEndorAnalysis = (typeof PackageEndorAnalysisSchema)[inferred]; + export const PackageAigFindingSchema = type({ ruleId: "string", level: "string", @@ -521,6 +543,7 @@ export const ApiV1PackageVersionResponseSchema = type({ sha256hash: "string|null?", vtAnalysis: PackageVtAnalysisSchema.or("null").optional(), skillSpectorAnalysis: PackageSkillSpectorAnalysisSchema.or("null").optional(), + endorAnalysis: PackageEndorAnalysisSchema.or("null").optional(), aigAnalysis: PackageAigAnalysisSchema.or("null").optional(), llmAnalysis: PackageLlmAnalysisSchema.or("null").optional(), staticScan: PackageStaticScanSchema.or("null").optional(), @@ -573,6 +596,7 @@ export const ApiV1PackageSecurityResponseSchema = type({ npmShasum: "string?", npmTarballName: "string?", createdAt: "number", + endorAnalysis: PackageEndorAnalysisSchema.or("null").optional(), }), trust: type({ scanStatus: '"clean"|"suspicious"|"malicious"|"pending"|"not-run"', diff --git a/packages/schema/dist/packages.d.ts b/packages/schema/dist/packages.d.ts index e83d6c78aa..bce58e045c 100644 --- a/packages/schema/dist/packages.d.ts +++ b/packages/schema/dist/packages.d.ts @@ -195,6 +195,20 @@ export declare const PackageSkillSpectorAnalysisSchema: import("arktype/internal checkedAt: number; }, {}>; export type PackageSkillSpectorAnalysis = (typeof PackageSkillSpectorAnalysisSchema)[inferred]; +export declare const PackageEndorAnalysisSchema: import("arktype/internal/variants/object.ts").ObjectType<{ + status: "completed"; + checkedAt: number; + reachableFunctionCount: number; + findings: { + severity: string; + summary: string; + }[]; +} | { + status: "skipped"; + checkedAt: number; + reason: string; +}, {}>; +export type PackageEndorAnalysis = (typeof PackageEndorAnalysisSchema)[inferred]; export declare const PackageAigFindingSchema: import("arktype/internal/variants/object.ts").ObjectType<{ ruleId: string; level: string; @@ -840,6 +854,19 @@ export declare const ApiV1PackageVersionResponseSchema: import("arktype/internal error?: string | undefined; checkedAt: number; } | null | undefined; + endorAnalysis?: { + status: "completed"; + checkedAt: number; + reachableFunctionCount: number; + findings: { + severity: string; + summary: string; + }[]; + } | { + status: "skipped"; + checkedAt: number; + reason: string; + } | null | undefined; aigAnalysis?: { status: string; issueCount: number; @@ -940,6 +967,19 @@ export declare const ApiV1PackageSecurityResponseSchema: import("arktype/interna npmShasum?: string | undefined; npmTarballName?: string | undefined; createdAt: number; + endorAnalysis?: { + status: "completed"; + checkedAt: number; + reachableFunctionCount: number; + findings: { + severity: string; + summary: string; + }[]; + } | { + status: "skipped"; + checkedAt: number; + reason: string; + } | null | undefined; }; trust: { scanStatus: "clean" | "malicious" | "not-run" | "pending" | "suspicious"; diff --git a/packages/schema/dist/packages.js b/packages/schema/dist/packages.js index 9a88740c04..bac803c9f9 100644 --- a/packages/schema/dist/packages.js +++ b/packages/schema/dist/packages.js @@ -176,6 +176,24 @@ export const PackageSkillSpectorAnalysisSchema = type({ error: "string?", checkedAt: "number", }); +const PackageEndorFindingSeveritySchema = type("string").atMostLength(64); +const PackageEndorFindingSummarySchema = type("string").atMostLength(2000); +const PackageEndorFindingSchema = type({ + severity: PackageEndorFindingSeveritySchema, + summary: PackageEndorFindingSummarySchema, +}); +const PackageEndorCompletedAnalysisSchema = type({ + status: '"completed"', + checkedAt: "number", + reachableFunctionCount: "number.integer >= 0", + findings: PackageEndorFindingSchema.array().atMostLength(50), +}); +const PackageEndorSkippedAnalysisSchema = type({ + status: '"skipped"', + checkedAt: "number", + reason: type("string").atMostLength(2000), +}); +export const PackageEndorAnalysisSchema = PackageEndorCompletedAnalysisSchema.or(PackageEndorSkippedAnalysisSchema); export const PackageAigFindingSchema = type({ ruleId: "string", level: "string", @@ -470,6 +488,7 @@ export const ApiV1PackageVersionResponseSchema = type({ sha256hash: "string|null?", vtAnalysis: PackageVtAnalysisSchema.or("null").optional(), skillSpectorAnalysis: PackageSkillSpectorAnalysisSchema.or("null").optional(), + endorAnalysis: PackageEndorAnalysisSchema.or("null").optional(), aigAnalysis: PackageAigAnalysisSchema.or("null").optional(), llmAnalysis: PackageLlmAnalysisSchema.or("null").optional(), staticScan: PackageStaticScanSchema.or("null").optional(), @@ -519,6 +538,7 @@ export const ApiV1PackageSecurityResponseSchema = type({ npmShasum: "string?", npmTarballName: "string?", createdAt: "number", + endorAnalysis: PackageEndorAnalysisSchema.or("null").optional(), }), trust: type({ scanStatus: '"clean"|"suspicious"|"malicious"|"pending"|"not-run"', diff --git a/packages/schema/dist/packages.js.map b/packages/schema/dist/packages.js.map index 88cd65816a..f5cb63fa40 100644 --- a/packages/schema/dist/packages.js.map +++ b/packages/schema/dist/packages.js.map @@ -1 +1 @@ -{"version":3,"file":"packages.js","sourceRoot":"","sources":["../src/packages.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"packages.js","sourceRoot":"","sources":["../src/packages.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/schema/src/packages.ts b/packages/schema/src/packages.ts index 9e0813c7e9..83d83e666d 100644 --- a/packages/schema/src/packages.ts +++ b/packages/schema/src/packages.ts @@ -239,6 +239,28 @@ export const PackageSkillSpectorAnalysisSchema = type({ }); export type PackageSkillSpectorAnalysis = (typeof PackageSkillSpectorAnalysisSchema)[inferred]; +const PackageEndorFindingSeveritySchema = type("string").atMostLength(64); +const PackageEndorFindingSummarySchema = type("string").atMostLength(2000); +const PackageEndorFindingSchema = type({ + severity: PackageEndorFindingSeveritySchema, + summary: PackageEndorFindingSummarySchema, +}); +const PackageEndorCompletedAnalysisSchema = type({ + status: '"completed"', + checkedAt: "number", + reachableFunctionCount: "number.integer >= 0", + findings: PackageEndorFindingSchema.array().atMostLength(50), +}); +const PackageEndorSkippedAnalysisSchema = type({ + status: '"skipped"', + checkedAt: "number", + reason: type("string").atMostLength(2000), +}); +export const PackageEndorAnalysisSchema = PackageEndorCompletedAnalysisSchema.or( + PackageEndorSkippedAnalysisSchema, +); +export type PackageEndorAnalysis = (typeof PackageEndorAnalysisSchema)[inferred]; + export const PackageAigFindingSchema = type({ ruleId: "string", level: "string", @@ -609,6 +631,7 @@ export const ApiV1PackageVersionResponseSchema = type({ sha256hash: "string|null?", vtAnalysis: PackageVtAnalysisSchema.or("null").optional(), skillSpectorAnalysis: PackageSkillSpectorAnalysisSchema.or("null").optional(), + endorAnalysis: PackageEndorAnalysisSchema.or("null").optional(), aigAnalysis: PackageAigAnalysisSchema.or("null").optional(), llmAnalysis: PackageLlmAnalysisSchema.or("null").optional(), staticScan: PackageStaticScanSchema.or("null").optional(), @@ -662,6 +685,7 @@ export const ApiV1PackageSecurityResponseSchema = type({ npmShasum: "string?", npmTarballName: "string?", createdAt: "number", + endorAnalysis: PackageEndorAnalysisSchema.or("null").optional(), }), trust: type({ scanStatus: '"clean"|"suspicious"|"malicious"|"pending"|"not-run"', diff --git a/scripts/lib/runWorkerCommand.ts b/scripts/lib/runWorkerCommand.ts new file mode 100644 index 0000000000..1388f686ff --- /dev/null +++ b/scripts/lib/runWorkerCommand.ts @@ -0,0 +1,105 @@ +import { spawn } from "node:child_process"; + +export class CommandFailure extends Error { + exitCode: number | null; + stderr: string; + stdout: string; + timedOut: boolean; + + constructor( + message: string, + exitCode: number | null, + stdout: string, + stderr: string, + timedOut: boolean, + ) { + super(message); + this.name = "CommandFailure"; + this.exitCode = exitCode; + this.stdout = stdout; + this.stderr = stderr; + this.timedOut = timedOut; + } +} + +export async function runWorkerCommand( + command: string, + args: string[], + options: { + commandLabel?: string; + cwd: string; + env: NodeJS.ProcessEnv; + input?: string; + timeoutMs: number; + }, +) { + return await new Promise<{ stdout: string; stderr: string }>((resolvePromise, reject) => { + const child = spawn(command, args, { + cwd: options.cwd, + detached: process.platform !== "win32", + env: options.env, + stdio: ["pipe", "pipe", "pipe"], + }); + let stdout = ""; + let stderr = ""; + let timedOut = false; + let settled = false; + let forceKillTimeout: NodeJS.Timeout | undefined; + const killProcessTree = (signal: NodeJS.Signals) => { + if (process.platform !== "win32" && child.pid) { + try { + process.kill(-child.pid, signal); + return; + } catch { + // The process group may already have exited; fall back to the direct child. + } + } + child.kill(signal); + }; + const timeout = setTimeout(() => { + timedOut = true; + killProcessTree("SIGTERM"); + forceKillTimeout = setTimeout(() => killProcessTree("SIGKILL"), 10_000); + forceKillTimeout.unref(); + }, options.timeoutMs); + const clearTimers = () => { + clearTimeout(timeout); + if (forceKillTimeout) clearTimeout(forceKillTimeout); + }; + child.stdout.on("data", (chunk) => { + stdout += String(chunk); + }); + child.stderr.on("data", (chunk) => { + stderr += String(chunk); + }); + child.on("error", (error) => { + if (settled) return; + settled = true; + clearTimers(); + if (timedOut) killProcessTree("SIGKILL"); + reject(error); + }); + child.on("close", (code) => { + if (settled) return; + settled = true; + clearTimers(); + if (timedOut) killProcessTree("SIGKILL"); + if (code === 0 && !timedOut) { + resolvePromise({ stdout, stderr }); + return; + } + const label = options.commandLabel ?? command; + reject( + new CommandFailure( + `${label} ${timedOut ? "timed out" : `exited ${code}`}; see redacted stdout/stderr diagnostics`, + code, + stdout, + stderr, + timedOut, + ), + ); + }); + if (options.input) child.stdin.end(options.input); + else child.stdin.end(); + }); +} diff --git a/scripts/security/run-codex-scan-worker-clawscan.test.ts b/scripts/security/run-codex-scan-worker-clawscan.test.ts index b16dbfdb08..6432d18e2f 100644 --- a/scripts/security/run-codex-scan-worker-clawscan.test.ts +++ b/scripts/security/run-codex-scan-worker-clawscan.test.ts @@ -261,6 +261,31 @@ function clawScanArtifactJson(options?: { return JSON.stringify(artifact); } +function endorArtifactJson() { + return JSON.stringify({ + completedAt: "2026-09-15T00:00:00Z", + scanners: { + endor: { + status: "completed", + raw: { + all_findings: [ + { + uuid: "reachable-finding", + spec: { + extra_key: "GHSA-reachable", + finding_tags: ["FINDING_TAGS_REACHABLE_FUNCTION"], + level: "FINDING_LEVEL_HIGH", + }, + }, + ], + blocking_findings: [], + warning_findings: [], + }, + }, + }, + }); +} + describe("run-codex-scan-worker clawscan authority", () => { it("drops non-finite and invalid A.I.G SARIF line numbers", () => { const analysis = normalizeAigAnalysis( @@ -296,7 +321,7 @@ describe("run-codex-scan-worker clawscan authority", () => { const environmentLog = join(workspace, "clawscan-environment.log"); await writeFakeClawScanCommand( fakeClawScan, - `printf '%s\\n' "\${DEFAULT_BASE_URL-}" "\${OPENAI_BASE_URL-}" "\${OPENAI_API_KEY-}" "\${SECURITY_SCAN_WORKER_TOKEN-}" > ${JSON.stringify(environmentLog)} + `printf '%s\\n' "\${DEFAULT_BASE_URL-}" "\${OPENAI_BASE_URL-}" "\${OPENAI_API_KEY-}" "\${SECURITY_SCAN_WORKER_TOKEN-}" "\${ENDOR_TOKEN-}" "\${ENDOR_API_CREDENTIALS_KEY-}" "\${ENDOR_API_CREDENTIALS_SECRET-}" > ${JSON.stringify(environmentLog)} out="" while [[ $# -gt 0 ]]; do case "$1" in @@ -320,12 +345,18 @@ JSON`, const previousOpenAiBaseUrl = process.env.OPENAI_BASE_URL; const previousOpenAiApiKey = process.env.OPENAI_API_KEY; const previousWorkerToken = process.env.SECURITY_SCAN_WORKER_TOKEN; + const previousEndorToken = process.env.ENDOR_TOKEN; + const previousEndorKey = process.env.ENDOR_API_CREDENTIALS_KEY; + const previousEndorSecret = process.env.ENDOR_API_CREDENTIALS_SECRET; process.env.CODEX_SECURITY_SCAN_CLAWSCAN_COMMAND = fakeClawScan; process.env.CODEX_SECURITY_SCAN_CLAWSCAN_SANDBOX = "off"; process.env.DEFAULT_BASE_URL = "https://api.openai.com/v1"; process.env.OPENAI_BASE_URL = "https://unapproved.example.invalid/v1"; process.env.OPENAI_API_KEY = "mock-provider-key"; process.env.SECURITY_SCAN_WORKER_TOKEN = "mock-worker-token"; + process.env.ENDOR_TOKEN = "mock-endor-token"; + process.env.ENDOR_API_CREDENTIALS_KEY = "mock-endor-key"; + process.env.ENDOR_API_CREDENTIALS_SECRET = "mock-endor-secret"; try { const onDiagnostic = vi.fn(); @@ -347,6 +378,9 @@ JSON`, "mock-provider-key", "", "", + "", + "", + "", ]); } finally { if (previousCommand === undefined) delete process.env.CODEX_SECURITY_SCAN_CLAWSCAN_COMMAND; @@ -361,6 +395,12 @@ JSON`, else process.env.OPENAI_API_KEY = previousOpenAiApiKey; if (previousWorkerToken === undefined) delete process.env.SECURITY_SCAN_WORKER_TOKEN; else process.env.SECURITY_SCAN_WORKER_TOKEN = previousWorkerToken; + if (previousEndorToken === undefined) delete process.env.ENDOR_TOKEN; + else process.env.ENDOR_TOKEN = previousEndorToken; + if (previousEndorKey === undefined) delete process.env.ENDOR_API_CREDENTIALS_KEY; + else process.env.ENDOR_API_CREDENTIALS_KEY = previousEndorKey; + if (previousEndorSecret === undefined) delete process.env.ENDOR_API_CREDENTIALS_SECRET; + else process.env.ENDOR_API_CREDENTIALS_SECRET = previousEndorSecret; } }); @@ -964,6 +1004,223 @@ JSON`, }, ); + it("waits for the primary scan to settle and retries the queue job when Endor fails", async () => { + const workspace = await tempDir(); + const fakeClawScan = join(workspace, "fake-clawscan"); + const primaryCompleted = join(workspace, "primary-completed"); + await writeFakeClawScanCommand( + fakeClawScan, + `is_endor=false +output="" +while [[ $# -gt 0 ]]; do + case "$1" in + --scanner) + if [[ "$2" == "endor" ]]; then is_endor=true; fi + shift 2 + ;; + --output) + output="$2" + shift 2 + ;; + *) + shift + ;; + esac +done +if [[ "$is_endor" == "true" ]]; then + exit 17 +fi +sleep 0.1 +cat > "$output" <<'JSON' +${clawScanArtifactJson({ verdict: "benign" })} +JSON +touch ${JSON.stringify(primaryCompleted)}`, + ); + const packageJson = '{"name":"fixture-plugin","version":"1.0.0"}\n'; + const pluginManifest = '{"id":"fixture-plugin"}\n'; + const job = claimedJob({ + jobId: "securityScanJobs:endor-retry", + source: "publish", + targetKind: "packageRelease", + target: { + files: [ + { + path: "package/package.json", + sha256: sha256(packageJson), + size: Buffer.byteLength(packageJson), + url: `data:application/json,${encodeURIComponent(packageJson)}`, + }, + { + path: "package/openclaw.plugin.json", + sha256: sha256(pluginManifest), + size: Buffer.byteLength(pluginManifest), + url: `data:application/json,${encodeURIComponent(pluginManifest)}`, + }, + ], + }, + }); + const previousEnv = { + command: process.env.CODEX_SECURITY_SCAN_CLAWSCAN_COMMAND, + enabled: process.env.CODEX_SECURITY_SCAN_ENDOR_ENABLED, + image: process.env.CODEX_SECURITY_SCAN_ENDOR_IMAGE, + namespace: process.env.ENDOR_NAMESPACE, + token: process.env.ENDOR_TOKEN, + }; + process.env.CODEX_SECURITY_SCAN_CLAWSCAN_COMMAND = fakeClawScan; + process.env.CODEX_SECURITY_SCAN_ENDOR_ENABLED = "1"; + process.env.CODEX_SECURITY_SCAN_ENDOR_IMAGE = "clawscan-endor:test"; + process.env.ENDOR_NAMESPACE = "fixture-namespace"; + process.env.ENDOR_TOKEN = "fixture-token"; + let primaryHadSettledAtFailure = false; + + try { + const client = { + action: vi.fn(async (...args: unknown[]) => { + const payload = args[1] as { error?: string } | undefined; + if (!payload?.error) return {}; + primaryHadSettledAtFailure = await readFile(primaryCompleted, "utf8") + .then(() => true) + .catch(() => false); + return { retry: true }; + }), + }; + + await expect(processJob(client, "worker-auth", job, undefined)).resolves.toEqual({ + completed: false, + hardFailed: false, + retryableFailed: true, + }); + expect(primaryHadSettledAtFailure).toBe(true); + expect(client.action).toHaveBeenCalledTimes(1); + expect(client.action.mock.calls[0]?.[1]).toMatchObject({ + error: expect.stringContaining("Endor ClawScan exited 17"), + }); + } finally { + if (previousEnv.command === undefined) + delete process.env.CODEX_SECURITY_SCAN_CLAWSCAN_COMMAND; + else process.env.CODEX_SECURITY_SCAN_CLAWSCAN_COMMAND = previousEnv.command; + if (previousEnv.enabled === undefined) delete process.env.CODEX_SECURITY_SCAN_ENDOR_ENABLED; + else process.env.CODEX_SECURITY_SCAN_ENDOR_ENABLED = previousEnv.enabled; + if (previousEnv.image === undefined) delete process.env.CODEX_SECURITY_SCAN_ENDOR_IMAGE; + else process.env.CODEX_SECURITY_SCAN_ENDOR_IMAGE = previousEnv.image; + if (previousEnv.namespace === undefined) delete process.env.ENDOR_NAMESPACE; + else process.env.ENDOR_NAMESPACE = previousEnv.namespace; + if (previousEnv.token === undefined) delete process.env.ENDOR_TOKEN; + else process.env.ENDOR_TOKEN = previousEnv.token; + } + }); + + it("persists the Endor summary and raw report for a completed package scan", async () => { + const workspace = await tempDir(); + const fakeClawScan = join(workspace, "fake-clawscan"); + await writeFakeClawScanCommand( + fakeClawScan, + `is_endor=false +output="" +while [[ $# -gt 0 ]]; do + case "$1" in + --scanner) + if [[ "$2" == "endor" ]]; then is_endor=true; fi + shift 2 + ;; + --output) + output="$2" + shift 2 + ;; + *) + shift + ;; + esac +done +if [[ "$is_endor" == "true" ]]; then + cat > "$output" <<'JSON' +${endorArtifactJson()} +JSON +else + cat > "$output" <<'JSON' +${clawScanArtifactJson({ verdict: "benign" })} +JSON +fi`, + ); + const packageJson = '{"name":"fixture-plugin","version":"1.0.0"}\n'; + const job: ClaimedJob = { + ...claimedJob({ + jobId: "securityScanJobs:endor-complete", + source: "publish", + targetKind: "packageRelease", + target: { + files: [ + { + path: "package/package.json", + sha256: sha256(packageJson), + size: Buffer.byteLength(packageJson), + url: `data:application/json,${encodeURIComponent(packageJson)}`, + }, + ], + }, + }), + scannerReportsUploadUrl: "https://storage.example/endor-report-upload", + }; + const previousEnv = { + command: process.env.CODEX_SECURITY_SCAN_CLAWSCAN_COMMAND, + enabled: process.env.CODEX_SECURITY_SCAN_ENDOR_ENABLED, + image: process.env.CODEX_SECURITY_SCAN_ENDOR_IMAGE, + namespace: process.env.ENDOR_NAMESPACE, + token: process.env.ENDOR_TOKEN, + }; + process.env.CODEX_SECURITY_SCAN_CLAWSCAN_COMMAND = fakeClawScan; + process.env.CODEX_SECURITY_SCAN_ENDOR_ENABLED = "1"; + process.env.CODEX_SECURITY_SCAN_ENDOR_IMAGE = "clawscan-endor:test"; + process.env.ENDOR_NAMESPACE = "fixture-namespace"; + process.env.ENDOR_TOKEN = "fixture-token"; + const fetchOriginal = globalThis.fetch; + let uploadedReport: unknown; + vi.spyOn(globalThis, "fetch").mockImplementation(async (url, init) => { + if (url !== job.scannerReportsUploadUrl) return fetchOriginal(url, init); + uploadedReport = JSON.parse(String(init?.body)); + return new Response(JSON.stringify({ storageId: "storage:endor-report" })); + }); + + try { + const client = { action: vi.fn(async (..._args: unknown[]) => ({})) }; + await expect(processJob(client, "worker-auth", job, undefined)).resolves.toEqual({ + completed: true, + hardFailed: false, + retryableFailed: false, + }); + expect(client.action).toHaveBeenCalledTimes(1); + expect(client.action.mock.calls[0]?.[1]).toMatchObject({ + endorAnalysis: { + status: "completed", + reachableFunctionCount: 1, + findings: [{ severity: "high", summary: "GHSA-reachable" }], + }, + scannerReportsStorageId: "storage:endor-report", + }); + expect(uploadedReport).toMatchObject({ + endor: { + status: "completed", + all_findings: [{ uuid: "reachable-finding" }], + blocking_findings: [], + warning_findings: [], + preparation: { sourceRoot: "artifact/package", normalizations: [] }, + }, + }); + } finally { + if (previousEnv.command === undefined) + delete process.env.CODEX_SECURITY_SCAN_CLAWSCAN_COMMAND; + else process.env.CODEX_SECURITY_SCAN_CLAWSCAN_COMMAND = previousEnv.command; + if (previousEnv.enabled === undefined) delete process.env.CODEX_SECURITY_SCAN_ENDOR_ENABLED; + else process.env.CODEX_SECURITY_SCAN_ENDOR_ENABLED = previousEnv.enabled; + if (previousEnv.image === undefined) delete process.env.CODEX_SECURITY_SCAN_ENDOR_IMAGE; + else process.env.CODEX_SECURITY_SCAN_ENDOR_IMAGE = previousEnv.image; + if (previousEnv.namespace === undefined) delete process.env.ENDOR_NAMESPACE; + else process.env.ENDOR_NAMESPACE = previousEnv.namespace; + if (previousEnv.token === undefined) delete process.env.ENDOR_TOKEN; + else process.env.ENDOR_TOKEN = previousEnv.token; + } + }); + it("fails the job when SkillSpector scanner status is skipped", async () => { const workspace = await tempDir(); const fakeClawScan = join(workspace, "fake-clawscan"); diff --git a/scripts/security/run-codex-scan-worker.ts b/scripts/security/run-codex-scan-worker.ts index 7a2a650bc0..5a8bbd19f0 100644 --- a/scripts/security/run-codex-scan-worker.ts +++ b/scripts/security/run-codex-scan-worker.ts @@ -1,4 +1,3 @@ -import { spawn } from "node:child_process"; import { mkdirSync, readFileSync } from "node:fs"; import { appendFile, lstat, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; @@ -10,6 +9,7 @@ import type { Id } from "../../convex/_generated/dataModel"; import { parseLlmEvalResponse, type LlmEvalDimension } from "../../convex/lib/securityPrompt"; import { assertCodexWorkerExecutionAllowed, resolveCodexWorkerHome } from "../codex-worker-guard"; import { materializeVerifiedArtifactFiles } from "../lib/artifactMaterialization"; +import { CommandFailure, runWorkerCommand } from "../lib/runWorkerCommand"; import { createWorkerLogger } from "../lib/workerLogger"; import { maskGitHubActionsSecret, @@ -18,6 +18,11 @@ import { redactWorkerPublicText, safeWorkerArtifactPathLabel, } from "../lib/workerRedaction"; +import { + isEndorPluginScanEnabled, + runEndorPluginScan, + type EndorPluginScanResult, +} from "./run-endor-plugin-scan"; import { calculateSecurityScanWorkerHealthSummary, renderSecurityScanWorkerSummaryMarkdown, @@ -883,94 +888,18 @@ function codexEnv(workspace: string) { return env; } -class CommandFailure extends Error { - exitCode: number | null; - stderr: string; - stdout: string; - timedOut: boolean; - - constructor( - message: string, - exitCode: number | null, - stdout: string, - stderr: string, - timedOut: boolean, - ) { - super(message); - this.name = "CommandFailure"; - this.exitCode = exitCode; - this.stdout = stdout; - this.stderr = stderr; - this.timedOut = timedOut; - } -} - async function runCommand( command: string, args: string[], options: { cwd: string; input?: string; omitEnv?: string[]; timeoutMs: number }, ) { - return await new Promise<{ stdout: string; stderr: string }>((resolvePromise, reject) => { - const env = codexEnv(options.cwd); - for (const name of options.omitEnv ?? []) delete env[name]; - const child = spawn(command, args, { - cwd: options.cwd, - detached: process.platform !== "win32", - env, - stdio: ["pipe", "pipe", "pipe"], - }); - let stdout = ""; - let stderr = ""; - let timedOut = false; - let forceKillTimeout: NodeJS.Timeout | undefined; - const killProcessTree = (signal: NodeJS.Signals) => { - if (process.platform !== "win32" && child.pid) { - try { - process.kill(-child.pid, signal); - return; - } catch { - // The process group may already have exited; fall back to the direct child. - } - } - child.kill(signal); - }; - const timeout = setTimeout(() => { - timedOut = true; - killProcessTree("SIGTERM"); - forceKillTimeout = setTimeout(() => killProcessTree("SIGKILL"), 10_000); - forceKillTimeout.unref(); - }, options.timeoutMs); - child.stdout.on("data", (chunk) => { - stdout += String(chunk); - }); - child.stderr.on("data", (chunk) => { - stderr += String(chunk); - }); - child.on("error", (error) => { - clearTimeout(timeout); - if (timedOut) killProcessTree("SIGKILL"); - if (forceKillTimeout) clearTimeout(forceKillTimeout); - reject(error); - }); - child.on("close", (code) => { - clearTimeout(timeout); - if (timedOut) killProcessTree("SIGKILL"); - if (forceKillTimeout) clearTimeout(forceKillTimeout); - if (code === 0) resolvePromise({ stdout, stderr }); - else { - reject( - new CommandFailure( - `${command} ${timedOut ? "timed out" : `exited ${code}`}; see redacted stdout/stderr diagnostics`, - code, - stdout, - stderr, - timedOut, - ), - ); - } - }); - if (options.input) child.stdin.end(options.input); - else child.stdin.end(); + const env = codexEnv(options.cwd); + for (const name of options.omitEnv ?? []) delete env[name]; + return await runWorkerCommand(command, args, { + cwd: options.cwd, + env, + ...(options.input !== undefined ? { input: options.input } : {}), + timeoutMs: options.timeoutMs, }); } @@ -1995,7 +1924,12 @@ export async function runClawScan( try { const output = await runCommand(command, args, { cwd: workspace, - omitEnv: ["VIRUSTOTAL_API_KEY"], + omitEnv: [ + "VIRUSTOTAL_API_KEY", + "ENDOR_TOKEN", + "ENDOR_API_CREDENTIALS_KEY", + "ENDOR_API_CREDENTIALS_SECRET", + ], timeoutMs: clawScanTimeoutMs(), }); onDiagnostic({ @@ -2095,16 +2029,37 @@ export async function processJob( let llmAnalysis: StoredLlmAnalysis | undefined; let aigAnalysis: AigAnalysis | undefined; let skillSpectorAnalysis: SkillSpectorAnalysis | undefined; + let endorAnalysis: EndorPluginScanResult["analysis"] | undefined; let status: JobDiagnosticInput["status"] = "failed"; try { await writeArtifactWorkspace(job, workspace); - const mapped = await runClawScan(job, workspace, (next) => { - Object.assign(clawscan, next); - }); + const shouldRunEndor = job.job.targetKind === "packageRelease" && isEndorPluginScanEnabled(); + const [clawScanResult, endorScanResult] = await Promise.allSettled([ + runClawScan(job, workspace, (next) => { + Object.assign(clawscan, next); + }), + shouldRunEndor + ? runEndorPluginScan({ workspace }) + : Promise.resolve(undefined), + ] as const); + if (clawScanResult.status === "rejected") throw clawScanResult.reason; + if (endorScanResult.status === "rejected") throw endorScanResult.reason; + const mapped = clawScanResult.value; + const endorResult = endorScanResult.value; + endorAnalysis = endorResult?.analysis; llmAnalysis = mapped.llmAnalysis; aigAnalysis = mapped.aigAnalysis; skillSpectorAnalysis = mapped.skillSpectorAnalysis; if (!llmAnalysis) throw new Error("Security scan did not produce llmAnalysis"); + let scannerReportsJson = mapped.scannerReportsJson; + if (endorResult) { + const scannerReports = asRecord(JSON.parse(scannerReportsJson) as unknown); + if (!scannerReports) throw new Error("Security scanner reports were malformed"); + scannerReportsJson = JSON.stringify({ + ...scannerReports, + endor: endorResult.scannerReport, + }); + } // A signed upload URL advertises support after the separately deployed // backend updates. Upload directly to storage to avoid action argument limits. let scannerReportsStorageId: string | undefined; @@ -2112,7 +2067,7 @@ export async function processJob( const uploaded = await fetch(job.scannerReportsUploadUrl, { method: "POST", headers: { "Content-Type": "application/json" }, - body: mapped.scannerReportsJson, + body: scannerReportsJson, }); if (!uploaded.ok) throw new Error(`Scanner report upload failed (${uploaded.status})`); const uploadResult = asRecord(await uploaded.json()); @@ -2128,6 +2083,7 @@ export async function processJob( llmAnalysis, aigAnalysis, skillSpectorAnalysis, + ...(endorAnalysis ? { endorAnalysis } : {}), ...(scannerReportsStorageId ? { scannerReportsStorageId: scannerReportsStorageId as Id<"_storage"> } : {}), diff --git a/scripts/security/run-endor-plugin-scan.test.ts b/scripts/security/run-endor-plugin-scan.test.ts new file mode 100644 index 0000000000..b183c0fea9 --- /dev/null +++ b/scripts/security/run-endor-plugin-scan.test.ts @@ -0,0 +1,209 @@ +/* @vitest-environment node */ +import { chmod, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { runEndorPluginScan } from "./run-endor-plugin-scan"; + +const tempDirs: string[] = []; + +afterEach(async () => { + await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { force: true, recursive: true }))); +}); + +async function tempDir() { + const directory = await mkdtemp(join(tmpdir(), "clawhub-endor-plugin-test-")); + tempDirs.push(directory); + return directory; +} + +async function writeFakeClawScan(path: string, body: string) { + await writeFile(path, `#!/usr/bin/env bash\nset -euo pipefail\n${body}\n`); + await chmod(path, 0o755); +} + +describe("runEndorPluginScan", () => { + it("scans a normalized disposable package copy and retains only exact reachable functions in the summary", async () => { + const workspace = await tempDir(); + const packageRoot = join(workspace, "artifact", "package"); + await mkdir(packageRoot, { recursive: true }); + await writeFile( + join(packageRoot, "package.json"), + `${JSON.stringify({ + name: "fixture-plugin", + dependencies: { "runtime-workspace": "workspace:*", lodash: "4.17.20" }, + devDependencies: { "dev-workspace": "workspace:^", typescript: "6.0.3" }, + })}\n`, + ); + await writeFile(join(packageRoot, "npm-shrinkwrap.json"), '{"lockfileVersion":3}\n'); + + const command = join(workspace, "fake-clawscan"); + const argsLog = join(workspace, "args.log"); + const envLog = join(workspace, "env.log"); + await writeFakeClawScan( + command, + `printf '%s\\n' "$@" > ${JSON.stringify(argsLog)} +printf '%s\\n' "\${ENDOR_NAMESPACE-}" "\${ENDOR_TOKEN-}" "\${OPENAI_API_KEY-}" "\${SECURITY_SCAN_WORKER_TOKEN-}" "\${HOME-}" "\${DOCKER_CONFIG-}" "\${DOCKER_HOST-}" > ${JSON.stringify(envLog)} +output="" +while [[ $# -gt 0 ]]; do + if [[ "$1" == "--output" ]]; then output="$2"; shift 2; else shift; fi +done +cat > "$output" <<'JSON' +{ + "completedAt": "2026-09-15T00:00:00Z", + "scanners": { + "endor": { + "status": "completed", + "raw": { + "all_findings": [ + { + "uuid": "exact", + "meta": {"name": "exact finding"}, + "spec": { + "extra_key": "GHSA-exact", + "finding_tags": ["FINDING_TAGS_REACHABLE_FUNCTION"], + "level": "FINDING_LEVEL_HIGH", + "summary": "Reachable lodash function is vulnerable", + "target_dependency_package_name": "npm://lodash@4.17.20", + "target_dependency_version": "4.17.20" + } + }, + { + "uuid": "potential", + "spec": { + "extra_key": "GHSA-potential", + "finding_tags": ["FINDING_TAGS_POTENTIALLY_REACHABLE_FUNCTION"], + "level": "FINDING_LEVEL_CRITICAL" + } + }, + { + "uuid": "dependency", + "spec": { + "extra_key": "GHSA-dependency", + "finding_tags": ["FINDING_TAGS_REACHABLE_DEPENDENCY"], + "level": "FINDING_LEVEL_MEDIUM" + } + } + ], + "blocking_findings": [{"uuid": "exact"}], + "warning_findings": [{"uuid": "potential"}] + } + } + } +} +JSON`, + ); + + const result = await runEndorPluginScan({ + workspace, + env: { + CODEX_SECURITY_SCAN_CLAWSCAN_COMMAND: command, + CODEX_SECURITY_SCAN_ENDOR_IMAGE: "clawscan-endor:test", + DOCKER_CONFIG: "/tmp/fixture-docker-config", + DOCKER_HOST: "must-not-reach-endor", + ENDOR_NAMESPACE: "fixture-namespace", + ENDOR_TOKEN: "fixture-token", + HOME: "/tmp/fixture-home", + OPENAI_API_KEY: "must-not-reach-endor", + PATH: process.env.PATH, + SECURITY_SCAN_WORKER_TOKEN: "must-not-reach-endor", + }, + }); + + expect(result.analysis).toEqual({ + status: "completed", + checkedAt: Date.parse("2026-09-15T00:00:00Z"), + reachableFunctionCount: 1, + findings: [{ severity: "high", summary: "Reachable lodash function is vulnerable" }], + }); + expect(result.scannerReport).toMatchObject({ + status: "completed", + all_findings: [{ uuid: "exact" }, { uuid: "potential" }, { uuid: "dependency" }], + blocking_findings: [{ uuid: "exact" }], + warning_findings: [{ uuid: "potential" }], + preparation: { + sourceRoot: "artifact/package", + normalizations: [ + { + dependencyNames: ["dev-workspace"], + kind: "omit-workspace-development-dependencies", + packageRoot: ".", + }, + { kind: "npm-shrinkwrap-rename", packageRoot: "." }, + ], + }, + }); + expect( + JSON.parse(await readFile(join(workspace, "endor-artifact", "package.json"), "utf8")), + ).toEqual({ + name: "fixture-plugin", + dependencies: { "runtime-workspace": "workspace:*", lodash: "4.17.20" }, + devDependencies: { typescript: "6.0.3" }, + }); + await expect( + readFile(join(workspace, "endor-artifact", "package-lock.json"), "utf8"), + ).resolves.toContain('"lockfileVersion":3'); + await expect(readFile(join(packageRoot, "npm-shrinkwrap.json"), "utf8")).resolves.toContain( + '"lockfileVersion":3', + ); + expect((await readFile(argsLog, "utf8")).trim().split("\n")).toEqual([ + "./endor-artifact", + "--scanner", + "endor", + "--sandbox", + "docker", + "--sandbox-image", + "clawscan-endor:test", + "--output", + join(workspace, "endor-clawscan-artifact.json"), + ]); + expect((await readFile(envLog, "utf8")).split("\n")).toEqual([ + "fixture-namespace", + "fixture-token", + "", + "", + "/tmp/fixture-home", + "/tmp/fixture-docker-config", + "", + "", + ]); + }); + + it("returns an explicit skipped result when the package artifact has no package.json", async () => { + const workspace = await tempDir(); + await mkdir(join(workspace, "artifact"), { recursive: true }); + + await expect(runEndorPluginScan({ workspace, env: {} })).resolves.toMatchObject({ + analysis: { + status: "skipped", + reason: "Endor requires package.json at the package artifact root.", + }, + scannerReport: { + status: "skipped", + preparation: { normalizations: [] }, + }, + }); + }); + + it("rejects an external artifact symlink before invoking ClawScan", async () => { + const workspace = await tempDir(); + const packageRoot = join(workspace, "artifact", "package"); + const external = join(await tempDir(), "outside.js"); + await mkdir(packageRoot, { recursive: true }); + await writeFile(join(packageRoot, "package.json"), '{"name":"fixture"}\n'); + await writeFile(external, "export const outside = true;\n"); + await symlink(external, join(packageRoot, "outside.js")); + + await expect( + runEndorPluginScan({ + workspace, + env: { + CODEX_SECURITY_SCAN_CLAWSCAN_COMMAND: join(workspace, "missing-clawscan"), + CODEX_SECURITY_SCAN_ENDOR_IMAGE: "clawscan-endor:test", + ENDOR_NAMESPACE: "fixture-namespace", + ENDOR_TOKEN: "fixture-token", + }, + }), + ).rejects.toThrow("Package artifact contains a symlink outside its root"); + }); +}); diff --git a/scripts/security/run-endor-plugin-scan.ts b/scripts/security/run-endor-plugin-scan.ts new file mode 100644 index 0000000000..008f391e14 --- /dev/null +++ b/scripts/security/run-endor-plugin-scan.ts @@ -0,0 +1,352 @@ +import { cp, lstat, readFile, readdir, readlink, rename, writeFile } from "node:fs/promises"; +import { dirname, join, relative, resolve, sep } from "node:path"; +import { z } from "zod"; +import type { EndorAnalysis } from "../../convex/lib/endorAnalysis"; +import { runWorkerCommand } from "../lib/runWorkerCommand"; + +const ENDOR_ENV_KEYS = [ + "ENDOR_API", + "ENDOR_NAMESPACE", + "ENDOR_TOKEN", + "ENDOR_API_CREDENTIALS_KEY", + "ENDOR_API_CREDENTIALS_SECRET", +] as const; +const CHILD_RUNTIME_ENV_KEYS = [ + "PATH", + "LANG", + "LC_ALL", + "LC_CTYPE", + "TZ", + "SYSTEMROOT", + "SystemRoot", + "WINDIR", + "COMSPEC", + "PATHEXT", +] as const; +const DEFAULT_ENDOR_TIMEOUT_MS = 20 * 60 * 1000; +const MAX_SUMMARY_FINDINGS = 50; +const MAX_SUMMARY_TEXT_CHARS = 2_000; +const REACHABLE_FUNCTION_TAG = "FINDING_TAGS_REACHABLE_FUNCTION"; + +const unknownRecordSchema = z.record(z.string(), z.unknown()); +const endorFindingSchema = z + .object({ + meta: z + .object({ + description: z.string().optional(), + name: z.string().optional(), + }) + .passthrough() + .optional(), + spec: z + .object({ + extra_key: z.string().optional(), + finding_tags: z.array(z.string()).optional(), + level: z.string().optional(), + summary: z.string().optional(), + target_dependency_package_name: z.string().optional(), + target_dependency_version: z.string().optional(), + }) + .passthrough(), + }) + .passthrough(); +const endorRawReportSchema = z + .object({ + all_findings: z.array(endorFindingSchema), + blocking_findings: z.array(z.record(z.string(), z.unknown())), + warning_findings: z.array(z.record(z.string(), z.unknown())), + }) + .passthrough(); +const endorArtifactSchema = z + .object({ + completedAt: z.string().min(1), + scanners: z + .object({ + endor: z + .object({ + error: z.string().optional(), + raw: endorRawReportSchema.optional(), + status: z.string(), + }) + .passthrough(), + }) + .passthrough(), + }) + .passthrough(); + +export type EndorPreparationNormalization = { + kind: "npm-shrinkwrap-rename" | "omit-workspace-development-dependencies"; + packageRoot: string; + dependencyNames?: string[]; +}; + +export type EndorScannerReport = + | { + status: "skipped"; + reason: string; + preparation: { + sourceRoot: string; + normalizations: EndorPreparationNormalization[]; + }; + } + | (z.infer & { + status: "completed"; + preparation: { + sourceRoot: string; + normalizations: EndorPreparationNormalization[]; + }; + }); + +export type EndorPluginScanResult = { + analysis: EndorAnalysis; + scannerReport: EndorScannerReport; +}; + +function isPathWithin(root: string, candidate: string) { + const normalizedRoot = resolve(root); + const normalizedCandidate = resolve(candidate); + return ( + normalizedCandidate === normalizedRoot || + normalizedCandidate.startsWith(`${normalizedRoot}${sep}`) + ); +} + +async function assertNoExternalSymlinks(root: string, directory = root): Promise { + for (const entry of await readdir(directory, { withFileTypes: true })) { + const path = join(directory, entry.name); + if (entry.isSymbolicLink()) { + const target = resolve(dirname(path), await readlink(path)); + if (!isPathWithin(root, target)) { + throw new Error("Package artifact contains a symlink outside its root"); + } + continue; + } + if (entry.isDirectory()) await assertNoExternalSymlinks(root, path); + } +} + +async function isRegularFile(path: string) { + return (await lstat(path).catch(() => null))?.isFile() === true; +} + +function packageRootLabel(scanRoot: string, manifestDirectory: string) { + return relative(scanRoot, manifestDirectory).replaceAll("\\", "/") || "."; +} + +async function normalizePackageRoot( + scanRoot: string, + manifestDirectory: string, +): Promise { + const normalizations: EndorPreparationNormalization[] = []; + const packageRoot = packageRootLabel(scanRoot, manifestDirectory); + const manifestPath = join(manifestDirectory, "package.json"); + let parsedManifest: unknown; + try { + parsedManifest = JSON.parse(await readFile(manifestPath, "utf8")); + } catch (error) { + throw new Error(`Endor scan package.json is invalid at ${packageRoot}`, { cause: error }); + } + const manifest = unknownRecordSchema.parse(parsedManifest); + const devDependenciesValue = manifest.devDependencies; + if (devDependenciesValue !== undefined) { + const devDependencies = unknownRecordSchema.safeParse(devDependenciesValue); + if (!devDependencies.success) { + throw new Error(`Endor scan devDependencies is invalid at ${packageRoot}`); + } + const workspaceDependencyNames = Object.entries(devDependencies.data) + .filter(([, version]) => typeof version === "string" && version.startsWith("workspace:")) + .map(([name]) => name) + .sort(); + if (workspaceDependencyNames.length > 0) { + for (const name of workspaceDependencyNames) delete devDependencies.data[name]; + manifest.devDependencies = devDependencies.data; + await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8"); + normalizations.push({ + kind: "omit-workspace-development-dependencies", + packageRoot, + dependencyNames: workspaceDependencyNames, + }); + } + } + + const shrinkwrapPath = join(manifestDirectory, "npm-shrinkwrap.json"); + const packageLockPath = join(manifestDirectory, "package-lock.json"); + if ((await isRegularFile(shrinkwrapPath)) && !(await isRegularFile(packageLockPath))) { + await rename(shrinkwrapPath, packageLockPath); + normalizations.push({ kind: "npm-shrinkwrap-rename", packageRoot }); + } + return normalizations; +} + +async function normalizePackageTree( + scanRoot: string, + directory = scanRoot, +): Promise { + const entries = await readdir(directory, { withFileTypes: true }); + const normalizations = (await isRegularFile(join(directory, "package.json"))) + ? await normalizePackageRoot(scanRoot, directory) + : []; + for (const entry of entries) { + if (!entry.isDirectory() || entry.name === ".git" || entry.name === "node_modules") continue; + normalizations.push(...(await normalizePackageTree(scanRoot, join(directory, entry.name)))); + } + return normalizations; +} + +function endorTimeoutMs(env: NodeJS.ProcessEnv) { + const parsed = Number(env.CODEX_SECURITY_SCAN_ENDOR_TIMEOUT_MS); + return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_ENDOR_TIMEOUT_MS; +} + +function endorCommandEnv(workspace: string, source: NodeJS.ProcessEnv) { + const env: NodeJS.ProcessEnv = { + NO_COLOR: "1", + TEMP: workspace, + TMP: workspace, + TMPDIR: workspace, + }; + for (const key of [...CHILD_RUNTIME_ENV_KEYS, ...ENDOR_ENV_KEYS]) { + const value = source[key]; + if (value !== undefined) env[key] = value; + } + // The host Docker CLI needs its selected context. ClawScan independently + // forwards only the Endor adapter's required and optional env into the container. + for (const key of ["HOME", "DOCKER_CONFIG"] as const) { + const value = source[key]; + if (value !== undefined) env[key] = value; + } + return env; +} + +function normalizeSeverity(value: string | undefined) { + return ( + value + ?.replace(/^FINDING_LEVEL_/, "") + .trim() + .toLowerCase() || "unknown" + ).slice(0, 64); +} + +function normalizeFindingSummary(finding: z.infer) { + const advisory = finding.spec.extra_key?.trim(); + const dependency = finding.spec.target_dependency_package_name?.trim(); + const dependencyVersion = finding.spec.target_dependency_version?.trim(); + const dependencyLabel = + dependency && dependencyVersion && !dependency.endsWith(`@${dependencyVersion}`) + ? `${dependency} ${dependencyVersion}` + : dependency || dependencyVersion; + const reportedSummary = + finding.spec.summary?.trim() || finding.meta?.description?.trim() || finding.meta?.name?.trim(); + return ( + reportedSummary || + [advisory, dependencyLabel].filter(Boolean).join(" in ") || + "Endor reported a reachable vulnerable function" + ).slice(0, MAX_SUMMARY_TEXT_CHARS); +} + +function completedAtMs(value: string) { + const checkedAt = Date.parse(value); + if (!Number.isFinite(checkedAt)) throw new Error("Endor ClawScan completedAt was invalid"); + return checkedAt; +} + +async function resolvePackageArtifactRoot(workspace: string) { + for (const candidate of [join(workspace, "artifact", "package"), join(workspace, "artifact")]) { + if (await isRegularFile(join(candidate, "package.json"))) return candidate; + } + return undefined; +} + +export function isEndorPluginScanEnabled(env: NodeJS.ProcessEnv = process.env) { + return env.CODEX_SECURITY_SCAN_ENDOR_ENABLED === "1"; +} + +export async function runEndorPluginScan(input: { + env?: NodeJS.ProcessEnv; + workspace: string; +}): Promise { + const env = input.env ?? process.env; + const sourceRoot = await resolvePackageArtifactRoot(input.workspace); + if (!sourceRoot) { + const checkedAt = Date.now(); + const reason = "Endor requires package.json at the package artifact root."; + return { + analysis: { status: "skipped", checkedAt, reason }, + scannerReport: { + status: "skipped", + reason, + preparation: { sourceRoot: "artifact", normalizations: [] }, + }, + }; + } + + const image = env.CODEX_SECURITY_SCAN_ENDOR_IMAGE?.trim(); + if (!image) throw new Error("CODEX_SECURITY_SCAN_ENDOR_IMAGE is required when Endor is enabled"); + await assertNoExternalSymlinks(sourceRoot); + const scanRoot = join(input.workspace, "endor-artifact"); + await cp(sourceRoot, scanRoot, { + recursive: true, + errorOnExist: true, + force: false, + verbatimSymlinks: true, + }); + const normalizations = await normalizePackageTree(scanRoot); + const outputPath = join(input.workspace, "endor-clawscan-artifact.json"); + const command = env.CODEX_SECURITY_SCAN_CLAWSCAN_COMMAND?.trim() || "clawscan"; + await runWorkerCommand( + command, + [ + "./endor-artifact", + "--scanner", + "endor", + "--sandbox", + "docker", + "--sandbox-image", + image, + "--output", + outputPath, + ], + { + commandLabel: "Endor ClawScan", + cwd: input.workspace, + env: endorCommandEnv(input.workspace, env), + timeoutMs: endorTimeoutMs(env), + }, + ); + + let parsedArtifact: unknown; + try { + parsedArtifact = JSON.parse(await readFile(outputPath, "utf8")); + } catch (error) { + throw new Error("Endor ClawScan did not emit a valid JSON artifact", { cause: error }); + } + const artifact = endorArtifactSchema.parse(parsedArtifact); + if (artifact.scanners.endor.status !== "completed") { + throw new Error(`Endor ClawScan scanner status was ${artifact.scanners.endor.status}`); + } + if (!artifact.scanners.endor.raw) { + throw new Error("Endor ClawScan scanner output was missing"); + } + const checkedAt = completedAtMs(artifact.completedAt); + const reachableFindings = artifact.scanners.endor.raw.all_findings.filter((finding) => + finding.spec.finding_tags?.includes(REACHABLE_FUNCTION_TAG), + ); + const findings = reachableFindings.slice(0, MAX_SUMMARY_FINDINGS).map((finding) => ({ + severity: normalizeSeverity(finding.spec.level), + summary: normalizeFindingSummary(finding), + })); + const sourceRootLabel = relative(input.workspace, sourceRoot).replaceAll("\\", "/"); + return { + analysis: { + status: "completed", + checkedAt, + reachableFunctionCount: reachableFindings.length, + findings, + }, + scannerReport: { + ...artifact.scanners.endor.raw, + status: "completed", + preparation: { sourceRoot: sourceRootLabel, normalizations }, + }, + }; +} diff --git a/scripts/security/security-scan-worker-workflow.test.ts b/scripts/security/security-scan-worker-workflow.test.ts index 1b94957747..27e86d5488 100644 --- a/scripts/security/security-scan-worker-workflow.test.ts +++ b/scripts/security/security-scan-worker-workflow.test.ts @@ -135,6 +135,10 @@ describe("security-scan-codex workflow", () => { ]); expectSecretStepAllowlist(steps, "LLM_API_KEY", ["Run Codex security worker"]); expectSecretStepAllowlist(steps, "SECURITY_SCAN_WORKER_TOKEN", ["Run Codex security worker"]); + expectSecretStepAllowlist(steps, "ENDOR_API_CREDENTIALS_KEY", ["Run Codex security worker"]); + expectSecretStepAllowlist(steps, "ENDOR_API_CREDENTIALS_SECRET", ["Run Codex security worker"]); + expect(jobEnv).not.toHaveProperty("ENDOR_API_CREDENTIALS_KEY"); + expect(jobEnv).not.toHaveProperty("ENDOR_API_CREDENTIALS_SECRET"); expectSecretStepAllowlist(steps, "VT_API_KEY", []); expect(scanStep?.env ?? {}).not.toHaveProperty("CODEX_API_KEY"); expect(scanStep?.env ?? {}).not.toHaveProperty("OPENAI_API_KEY"); @@ -148,8 +152,18 @@ describe("security-scan-codex workflow", () => { const skillspectorInstall = steps.find((step) => step.name === "Install SkillSpector")?.run; expect(codexInstall).toContain("npm install -g @openai/codex@0.142.3"); expect(codexInstall).not.toContain("@latest"); - expect(clawScanInstall).toContain("npm install -g @openclaw/clawscan@0.1.8"); + expect(jobEnv.CODEX_SECURITY_SCAN_CLAWSCAN_VERSION).toBe( + "${{ vars.CODEX_SECURITY_SCAN_CLAWSCAN_VERSION || '0.1.8' }}", + ); + expect(clawScanInstall).toContain( + 'npm install -g "@openclaw/clawscan@$CODEX_SECURITY_SCAN_CLAWSCAN_VERSION"', + ); expect(clawScanInstall).not.toContain("@latest"); + const endorPrepare = steps.find((step) => step.name === "Prepare Endor scanner"); + expect(endorPrepare?.if).toBe("${{ env.CODEX_SECURITY_SCAN_ENDOR_ENABLED == '1' }}"); + expect(endorPrepare?.run).toContain("clawscan scanners endor"); + expect(endorPrepare?.run).toContain("@sha256:[a-f0-9]{64}$"); + expect(endorPrepare?.run).toContain('docker pull "$CODEX_SECURITY_SCAN_ENDOR_IMAGE"'); expect(aigInstall).toContain( "python -m pip install --require-hashes -r scripts/security/aig-worker-requirements.txt", ); @@ -170,6 +184,10 @@ describe("security-scan-codex workflow", () => { LLM_API_KEY: "${{ secrets.OPENAI_API_KEY || secrets.CODEX_API_KEY }}", OPENAI_API_KEY: "${{ secrets.OPENAI_API_KEY }}", SECURITY_SCAN_WORKER_TOKEN: "${{ secrets.SECURITY_SCAN_WORKER_TOKEN }}", + ENDOR_NAMESPACE: "${{ vars.ENDOR_NAMESPACE }}", + ENDOR_API: "${{ vars.ENDOR_API }}", + ENDOR_API_CREDENTIALS_KEY: "${{ secrets.ENDOR_API_CREDENTIALS_KEY }}", + ENDOR_API_CREDENTIALS_SECRET: "${{ secrets.ENDOR_API_CREDENTIALS_SECRET }}", }); }); }); diff --git a/specs/endor-plugin-scans.md b/specs/endor-plugin-scans.md new file mode 100644 index 0000000000..af50b0cb70 --- /dev/null +++ b/specs/endor-plugin-scans.md @@ -0,0 +1,58 @@ +# Endor in the plugin scan pipeline + +## Intent + +Plugin publication and rescan requests enqueue work and return without waiting for +Endor's dependency and function-reachability analysis. Endor runs in the existing +ClawHub security worker, alongside the existing ClawScan profile. There is one +queue, one lease per release scan, and one completion submission. + +New publications, owner-requested rescans, and admin bulk plugin rescans all use +this path. The existing dispatch watchdog and expired-lease recovery service the +queue. This change does not add a periodic full-catalog LLM rescan. Existing +plugins can be queued through the admin plugin bulk-rescan command, which already +supports paced batches and preserves active jobs. + +## Result contract + +- Apply Endor to package-release jobs. Skill scans keep their existing scanner set. +- Run the native ClawScan `endor` adapter in Docker. The normal ClawHub profile + continues to use its existing execution configuration. +- Keep Endor separate from the ClawScan judge and the existing moderation verdict. + Dependency vulnerabilities are supplemental findings; they do not independently + quarantine a package or change its download policy. +- Display only findings tagged `FINDING_TAGS_REACHABLE_FUNCTION`. A reachable + dependency or a potentially reachable function does not satisfy that filter. +- Store a bounded summary on the exact package release and the complete scanner + report in file storage. Keep the total count when the displayed list is capped. +- An unsupported package is explicitly not analyzed. A scanner failure uses the + existing job failure/retry path and must never become a successful empty report. +- Preserve the last stored result while replacement work is queued or running. + Its check time identifies the analysis being displayed. +- Settle both concurrent scan processes before deleting their workspace. + +The immutable uploaded artifact remains the source of truth. Endor's disposable +copy may normalize an npm shrinkwrap filename and remove unresolved workspace +development dependencies. Runtime dependencies remain unchanged; record those +normalizations with the raw scanner report. + +## Hosted rollout + +The worker is off for Endor until `CODEX_SECURITY_SCAN_ENDOR_ENABLED=1` is set. +Enable it only after the backend result contract is deployed and the following +worker configuration is available: + +- `CODEX_SECURITY_SCAN_CLAWSCAN_VERSION`: an exact released ClawScan version that + includes the `endor` adapter. The current default, `0.1.8`, predates that adapter. +- `CODEX_SECURITY_SCAN_ENDOR_IMAGE`: the Endor scanner Docker image pinned by its + SHA-256 digest. Build it from ClawScan's `docker/clawscan-endor/Dockerfile` with + the chosen Endor CLI binary. +- `ENDOR_NAMESPACE` and the `ENDOR_API_CREDENTIALS_KEY` / + `ENDOR_API_CREDENTIALS_SECRET` secrets. `ENDOR_API` is optional. + +The workflow checks that the adapter exists and pulls the pinned image before +claiming work. Credentials are available only to the worker step; the Endor +subprocess receives its own credentials, and the main scan does not inherit them. + +This integration does not publish a ClawScan release, publish a Docker image, set +hosted secrets, or deploy ClawHub. Those are separate rollout actions. diff --git a/src/components/EndorSecurityAudit.test.tsx b/src/components/EndorSecurityAudit.test.tsx new file mode 100644 index 0000000000..65e10615da --- /dev/null +++ b/src/components/EndorSecurityAudit.test.tsx @@ -0,0 +1,63 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { buildSecurityAuditExportEntries } from "../lib/securityAuditExport"; +import { SecurityAuditPage } from "./SecurityAuditPage"; + +const entity = { + kind: "plugin" as const, + title: "Slack", + name: "@openclaw/slack", + version: "2026.7.1", + detailPath: "/plugins/@openclaw/slack", +}; + +describe("stored Endor plugin scan", () => { + it("shows the stored reachable finding and includes it in the audit export", () => { + const analysis = { + status: "completed" as const, + checkedAt: Date.parse("2026-09-15T00:00:00Z"), + reachableFunctionCount: 1, + findings: [ + { severity: "FINDING_LEVEL_HIGH", summary: "Axios uploads bypass maxBodyLength." }, + ], + }; + render( + , + ); + expect(screen.getByRole("heading", { name: "Endor dependency reachability" })).toBeTruthy(); + expect(screen.getByText("Axios uploads bypass maxBodyLength.", { exact: false })).toBeTruthy(); + expect(screen.getByText("high")).toBeTruthy(); + expect(screen.getByText("Sep 15, 2026 · 12:00 AM UTC")).toBeTruthy(); + const entries = buildSecurityAuditExportEntries({ entity, endorAnalysis: analysis }); + expect(entries.find((entry) => entry.path === "endor.json")?.value).toEqual(analysis); + }); + + it("keeps unsupported analysis distinct from a completed scan with zero findings", () => { + const view = render( + , + ); + expect(screen.getByText("Not analyzed: No package.json")).toBeTruthy(); + expect(screen.queryByText(/Endor found no vulnerabilities/)).toBeNull(); + view.rerender( + , + ); + expect( + screen.getByText("Endor found no vulnerabilities marked as reachable functions."), + ).toBeTruthy(); + }); +}); diff --git a/src/components/SecurityAuditPage.tsx b/src/components/SecurityAuditPage.tsx index ab800c9938..d36d061d16 100644 --- a/src/components/SecurityAuditPage.tsx +++ b/src/components/SecurityAuditPage.tsx @@ -2,6 +2,7 @@ import { getSecurityAuditOverviewCopy } from "clawhub-schema"; import { ArrowLeft, Check, Clock, Download, Info, RefreshCw, TriangleAlert } from "lucide-react"; import { useEffect, useState } from "react"; import type { Id } from "../../convex/_generated/dataModel"; +import type { EndorAnalysis } from "../../convex/lib/endorAnalysis"; import { getRuntimeEnv } from "../lib/runtimeEnv"; import { buildSecurityAuditExportFilename, @@ -56,6 +57,7 @@ type SecurityAuditPageProps = { aigAnalysis?: AigAnalysis | null; llmAnalysis?: LlmAnalysis | null; skillSpectorAnalysis?: SkillSpectorAnalysis | null; + endorAnalysis?: EndorAnalysis | null; skillSpectorApplicable?: boolean; staticScan?: StaticScan | null; source?: Record | null; @@ -1000,6 +1002,48 @@ export function SecurityAuditPage(props: SecurityAuditPageProps) { {orderedScanners.map((kind) => ( ))} + {props.entity.kind === "plugin" && props.endorAnalysis ? ( +
+
+

+ Endor dependency reachability +

+
+
+

Checked {formatAuditSidebarTime(props.endorAnalysis.checkedAt)}

+ {props.endorAnalysis.status === "skipped" ? ( +

Not analyzed: {props.endorAnalysis.reason}

+ ) : ( + <> +

+ {props.endorAnalysis.reachableFunctionCount === 0 + ? "Endor found no vulnerabilities marked as reachable functions." + : `${props.endorAnalysis.reachableFunctionCount} ${props.endorAnalysis.reachableFunctionCount === 1 ? "vulnerability" : "vulnerabilities"} marked as reachable functions by Endor.`} +

+ {props.endorAnalysis.findings.length > 0 ? ( +
    + {props.endorAnalysis.findings.map((finding) => ( +
  • + + {finding.severity.replace("FINDING_LEVEL_", "").toLowerCase()} + {" "} + — {finding.summary} +
  • + ))} +
+ ) : null} + {props.endorAnalysis.reachableFunctionCount > + props.endorAnalysis.findings.length ? ( +

Showing the first {props.endorAnalysis.findings.length} findings.

+ ) : null} + + )} +
+
+ ) : null}