172 lines · 5.8 KB
import { first } from "../db";
import { repoDb } from "../scoped-db";
import { CAPS } from "../limits";
import { loadRepoById } from "../repos";
import { detectorName } from "../security/detectors";
import { notifyQuietly, quiet } from "./deliver";
import { repoAdmins } from "./recipients";

/** Notification helpers for security events. Details carry detector names and paths only, never secret values. */

const MAX_PACKAGES_LISTED = 10;

async function repoPath(repoId: string): Promise<string | null> {
  const repo = await loadRepoById(repoId);
  return repo ? `/${repo.owner_name}/${repo.name}` : null;
}

async function notifySecretAlertsUnsafe(
  repoId: string,
  findings: { detector: string; path: string | null; line: number | null }[],
): Promise<void> {
  if (findings.length === 0) return;
  const base = await repoPath(repoId);
  if (!base) return;
  const detectors = [...new Set(findings.map((finding) => detectorName(finding.detector)))];
  const title =
    findings.length === 1
      ? `${detectors[0]} secret detected in ${base.slice(1)}`
      : `${findings.length} secrets detected in ${base.slice(1)}`;
  await notifyQuietly({
    category: "security_alert",
    repoId,
    accountId: null,
    access: "security",
    recipients: await repoAdmins(repoId, true),
    title,
    details: [
      `Detectors: ${detectors.join(", ")}.`,
      ...findings
        .slice(0, 3)