90 lines · 3.6 KB
import { env } from "cloudflare:workers";
import { z } from "zod";
import { loadRepoById } from "../repos";
import { ZERO_SHA } from "../git/store";
import { MAX_COMMITS_PER_FETCH } from "../git/upload-pack";
import { processSarifUpload } from "./code-scanning";
import { scanDependencies } from "./dependencies";
import { scanPushForSecrets } from "./secret-scan";
import { verifyCommits } from "./signatures";

export const SECURITY_QUEUE = "gitfor-security";
const RETRY_SECONDS = 30;
const MAX_PUSH_SIGNATURES = 50;

const sha = z.string().regex(/^[0-9a-f]{40}$/);
const securityMessage = z.discriminatedUnion("kind", [
  z.object({ kind: z.literal("secrets.push"), repoId: z.string(), ref: z.string(), before: z.string(), after: sha }),
  z.object({ kind: z.literal("deps.scan"), repoId: z.string(), sha }),
  z.object({
    kind: z.literal("signatures.verify"),
    repoId: z.string(),
    shas: z.array(sha).max(MAX_COMMITS_PER_FETCH),
  }),
  z.object({ kind: z.literal("sarif.process"), repoId: z.string(), uploadId: z.string() }),
]);

export type SecurityMessage = z.infer<typeof securityMessage>;

export async function enqueueSecurity(messages: SecurityMessage[]): Promise<void> {
  if (messages.length === 0) return;
  await env.SECURITY_QUEUE.sendBatch(messages.map((body) => ({ body })));
}

const MANIFEST_HINT =
  /(^|\/)(package(-lock)?\.json|npm-shrinkwrap\.json|bun\.lock|yarn\.lock|pnpm-lock\.yaml|requirements(-dev)?\.txt|pyproject\.toml|Pipfile\.lock|go\.mod|Cargo\.(toml|lock)|Gemfile\.lock|composer\.(json|lock))$/;

/** Fans a landed push out to bounded, retryable security jobs on the security queue. */
export async function queuePushSecurity(push: {
  repoId: string;
  ref: string;