---
title: Run recurring security reviews with deepsec on Vercel
description: Learn how to run periodic security scans on your code with deepsec. Set up deepsec to review GitHub repositories and deliver findings to GitHub and Slack using Vercel Connect, Workflows, Sandbox, and AI Gateway.
url: /kb/guide/deepsec-reviews-on-vercel
canonical_url: "https://vercel.com/kb/guide/deepsec-reviews-on-vercel"
published: 2026-09-07
last_updated: 2026-09-07
authors: Anshuman Bhardwaj
related:
  - /docs/sandbox
  - /docs/agent-resources/vercel-plugin
  - /docs/cron-jobs
  - /docs/oidc
  - /docs/ai-gateway/authentication-and-byok/oidc
  - /docs/ai-gateway/observability-and-spend/budgets
  - /docs/workflows
  - /kb/guide/eve-software-factory
  - /docs/sandbox/concepts/persistent-sandboxes
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

Security reviews often begin as a command that someone runs locally. That works for an investigation, but it is difficult to operate across multiple repositories: scans take time, credentials become hard to contain, overlapping runs waste compute, and a successful analysis can still be lost when a notification fails.

The deepsec runner template turns that command into a recurring security-review system on Vercel. It schedules each repository independently, runs untrusted source inside an isolated [Vercel Sandbox](https://vercel.com/docs/sandbox), preserves progress across long reviews, and publishes validated results to GitHub and Slack. You can even configure the frequency, models used, and priority paths for each repository.

This guide explains what deepsec is, how various pieces work together, and how to deploy this template to Vercel. It also walks you through the decisions you can make as you adapt the template.

Deploy the template now, or read on for a deeper look at how the security reviews work.

## Quick start with an AI coding agent

If you're working with an AI coding agent like Claude Code or Cursor, you can use this prompt to have it help you build the agent:

### Agent prompt

```txt
I want to deploy the deepsec runner template for recurring GitHub security reviews. Clone https://github.com/vercel-labs/deepsec-runner-template.git, then read README.md, ARCHITECTURE.md, and AGENTS.md before making changes.

Ask me which GitHub repository, Slack channel, and cron schedule to use before enabling a repository policy. Configure Production-only GitHub and Slack connectors with Vercel Connect, then add the required Production environment variables and a budgeted Vercel AI Gateway key.

Preserve the existing security boundaries: never expose real credentials to the Vercel Sandbox, never grant repository content-write access, and keep GitHub and Slack publication in trusted Workflow steps.

Update src/config/repositories.ts, run pnpm check, and deploy with `vercel deploy --prod`. Trigger one authenticated cron request and stop after the Workflow reaches a terminal state, Slack receives the result, and any findings are published to GitHub.
```

### Vercel Plugin

The [Vercel Plugin](https://vercel.com/docs/agent-resources/vercel-plugin) turns your AI coding agent (e.g., OpenAI Codex, Claude Code, or Cursor) into a Vercel expert. It adds skills, slash commands, and current knowledge of the tools this template uses, including Vercel Connect, Vercel Workflows, Vercel Sandbox, and AI Gateway. The plugin is optional; it isn't required to use deepsec or to follow this guide.

```bash
npx plugins add vercel/vercel-plugin
```

## What is deepsec?

[deepsec](https://deepsec.sh/docs/getting-started) is an AI-powered security agent for your source code. It first learns the shape of a codebase, uses fast scans to identify relevant files and patterns, and then applies a deeper, model-based review where investigation is useful. It keeps its working data under `.deepsec/`, so a review can continue without starting over.

A typical local workflow starts by initializing deepsec, scanning the repository, processing the selected work, and generating a report:

```bash
npx deepsec init
pnpm deepsec scan
pnpm deepsec process
pnpm deepsec report
```

This interactive loop is useful when a developer or security engineer is present to choose the scope and inspect the result. The template addresses a different problem: performing the same kind of review repeatedly, without an operator keeping a terminal open. It supplies the schedule, durable execution, isolation, credentials, incremental state, and result delivery that an unattended scan needs.

deepsec should be treated like a coding agent. Therefore, the template assumes the code under review is untrusted, even when it belongs to your organization.

## Overview

A scheduled review begins with a short HTTP request and continues asynchronously:

1. [Vercel Cron](https://vercel.com/docs/cron-jobs) calls the repository's generated route, which verifies its bearer secret before starting a [Vercel Workflow](https://workflow-sdk.dev/).
   
2. The workflow loads the repository policy, priority paths, models, and claims a deterministic lease for that repository.
   
3. [Vercel Connect](https://vercel.com/connect) issues narrowly scoped GitHub credentials for the workflow to create or resume the repository's named sandbox.
   
4. The sandbox clones or fetches the repository, selects the changed files, and runs the pinned deepsec version through the AI Gateway.
   
5. deepsec records progress in the persistent file system. The runner writes a structured output (`outbox.json`) when the review reaches a terminal result.
   
6. Workflow reads and validates the result, creates a GitHub issue when needed, and sends the notification to Slack.
   
7. After all required deliveries succeed, the runner marks the result as published.
   

This flow separates scheduling from execution and analysis from publication. A cron request does not need to remain open for the duration of a scan, and a sandbox process is never granted permissions for anything beyond its use.

## Prepare and deploy to Vercel

The runner needs one linked Vercel project with Workflows, Sandbox, AI Gateway, and Connect available. The following steps walk you through setting up the project for the first time.

### Step 1: Set up the project

Install dependencies, link the project, and pull its development environment for local development:

```bash
pnpm install
vercel link
```

Deployed code authenticates to Vercel Connect with the project's automatically supplied [OIDC identity](https://vercel.com/docs/oidc). Locally, `vercel env pull` supplies a short-lived `VERCEL_OIDC_TOKEN` for Vercel Connect and sandbox control-plane calls. It is not passed into the analysis sandbox.

### Step 2: Create the GitHub connector

To read the GitHub repository content and create issues in it, you need a GitHub connector. Create a managed GitHub connector from the linked project directory:

```bash
vercel connect create github --name deepsec-scanner
```

The command starts the GitHub App installation and attaches the resulting `github/deepsec-scanner` connector to the linked Vercel project.

In GitHub's installation flow, choose **Only select repositories** and select every repository that this deployment may scan. Confirm that the managed app requests Contents read, Issues read and write, and Metadata read.

If the connector already exists, attach it to this project instead of creating another one:

```bash
vercel connect attach github/deepsec-scanner \
  --environment production \
  --environment development
```

Store its non-secret UID in both environments, then confirm that the linked project can see it:

```bash
vercel env add GITHUB_CONNECTOR production,preview,development \
  --value github/deepsec-scanner --no-sensitive --yes
vercel connect list --type github
```

At runtime, the template requests `contents:read` for cloning or `issues:write` for issue publication, scoped to the configured repository. It never requests repository content-write permission.

The connector UID stored in the `GITHUB_CONNECTOR` environment variable is a non-secret locator. Vercell Connect holds the provider authorization and issues short-lived tokens when trusted code asks for them. The project never stores a GitHub App private key.

### Step 3: Create the Slack connector

To notify your team about the deepsec scans’ progress and reports, you need to create a Slack connector. Create the managed Slack connector from the same linked project directory:

```bash
vercel connect create slack --name deepsec-notifier
```

The command starts Slack authorization, installs the managed app, and attaches `slack/deepsec-notifier` to the linked project. The template requests only `chat:write` when it sends a notification. If you are reusing an existing connector, attach it explicitly:

```bash
vercel connect attach slack/deepsec-notifier \
  --environment production \
  --environment development
```

From the destination Slack channel, invite the managed app:

```plaintext
/invite @deepsec-notifier
```

Configure the channel by ID, such as `C0123456789` in the environment variables:

```bash
vercel env add SLACK_CONNECTOR production,preview,development \
  --value slack/deepsec-notifier --no-sensitive --yes
vercel env add SLACK_CHANNEL_ID production,preview,development \
  --value C0123456789 --no-sensitive --yes
vercel connect list --type slack
```

### Step 4: Configure AI Gateway

[AI Gateway supports OIDC](https://vercel.com/docs/ai-gateway/authentication-and-byok/oidc) for model calls made directly from Vercel deployments. This template uses a different boundary: the model request originates inside a sandbox, therefore it’s better to use a revocable `AI_GATEWAY_API_KEY` with a [project or key budget](https://vercel.com/docs/ai-gateway/observability-and-spend/budgets).

The key remains in the trusted workflow and sandbox network control plane. The sandbox receives a placeholder, which is replaced only when an approved request is sent to the AI Gateway.

Add the AI Gateway key through the CLI prompt so it does not appear in shell history. Generate the trigger and publication secrets directly into their environment variable commands:

```bash
vercel env add AI_GATEWAY_API_KEY production,preview
openssl rand -hex 32 | vercel env add DEEPSEC_MARKER_SECRET production,preview --yes
openssl rand -hex 32 | vercel env add CRON_SECRET production,preview --yes
```

These commands apply the values to Production and Preview so the complete flow can be verified locally. Use separate values for local development to ensure strict environment isolation.

The secrets have separate jobs: `CRON_SECRET` authenticates scheduled routes and `DEEPSEC_MARKER_SECRET` creates unguessable GitHub issue markers for retry-safe publication. Rotating the marker secret begins a new issue-deduplication namespace.

### Step 5: Define what each repository reviews

Repository policies in `src/config/repositories.ts` are the source of truth for what scans run. They connect a repository's security intent to its schedule, model, publication mode, and sandbox resources.

```typescript
{
  id: "payments-api",
  enabled: true,
  repository: "acme/payments-api",
  cloneAccess: "github-app",
  issuePublication: "required",
  frequency: "0 7 * * 1",
  model: "gpt-5.5",
  thinkingLevel: "high",
  batchSize: 2,
  priorityPaths: ["src/auth/", "src/billing/"],
  ignorePaths: ["**/dist/**"],
  context: "Treat requests and webhooks as attacker-controlled.",
  deepsecVersion: "2.3.7",
  sandbox: { vcpus: 4, timeoutMs: 2_700_000 },
}
```

`frequency` becomes the cron schedule; `id` identifies the workflow lease and persistent sandbox; the model settings control deepsec through AI Gateway; and `issuePublication` determines how workflow delivers findings.

Use `context` to describe assets, attacker-controlled inputs, trust boundaries, and invariants. Put your critical application paths, such as authentication, authorization, billing, parsers, and deployment boundaries in `priorityPaths`, and keep `ignorePaths` narrow. Pin the deepsec version so recurring behavior changes only after review.

Use `cloneAccess: "github-app"` for private source and reserve `public` for anonymous public clones. Choose `issuePublication: "slack-only"` when findings must not be written to GitHub. Smaller batches isolate slow investigations, while larger batches reduce orchestration overhead. Sandbox vCPUs and timeout must stay within the target Vercel plan.

The live targeted run after deployment verifies the Production connector attachments, GitHub permissions, Slack channel membership, AI Gateway credentials, and sandbox execution together.

In `vercel.ts`, the project generates one entry for each enabled repository, making the schedule reviewable alongside the repository configuration. Changing a repository or its frequency requires re-deploying the project.

```typescript
import type { VercelConfig } from "@vercel/config/v1";
import { repositories } from "./src/config/repositories";
import { buildCronJobs } from "./src/lib/cron";

export const config = {
  crons: buildCronJobs(repositories),
} satisfies VercelConfig;
```

Push the policy change to the configured production branch for an automatic Production Deployment, or deploy it directly with `vercel deploy --prod`. The successful deployment applies the cron schedules.

To test the first deployment, you can manually trigger the cron job from the deployment summary section or the project settings on the Vercel Dashboard.

After the scan finishes, you’ll receive a message on Slack:

The Slack message also links to the GitHub issue containing an in-depth review report:

## In-depth architecture breakdown

Now that you have learned how to run this project on Vercel. It’s time to understand how every piece of the template fits together, why it’s needed, and how you can tune it to your specific requirements.

### Long scans durable with Vercel Workflow

[Vercel Workflows](https://vercel.com/docs/workflows) resolves the repository from a static policy, validates the runtime configuration, and claims a deterministic lease before creating compute.

```typescript
using repositoryLease = createHook({ token: `deepsec:${config.id}` });
const conflict = await repositoryLease.getConflict();
if (conflict) {
  const completedAt = await currentTimestamp();
  await notifySlack(config, {
    runId,
    repository: config.repository,
    status: "already-running",
    runMode: "incremental",
    activeRunId: conflict.runId,
    completedAt,
  });
  return {
    status: "already-running" as const,
    sandboxName,
    activeRunId: conflict.runId,
  };
}
```

The lease stays outside the untrusted file system, so an overlapping run reports `already-running` before consuming sandbox resources. An admitted review can span three sandbox sessions: workflow runs deepsec as a detached command, polls its lifecycle, stops before the session deadline, and resumes the named sandbox. Permanent failures remain visible; transient provider failures retry in trusted steps.

### Broker access with Vercel Connect

With the lease held, the workflow asks Vercel Connect for a short-lived GitHub token scoped to one repository and operation. The connectors ensure that correct but limited access is given securely to the request.

```typescript
return await getToken(
  connector(),
  {
    subject: { type: "app" },
    authorizationDetails: [{
      type: "github_app_installation",
      repositories: [repository],
      permissions,
    }],
  },
  process.env.VERCEL_TOKEN
    ? { vercelToken: process.env.VERCEL_TOKEN }
    : undefined,
);
```

### Isolate analysis in Vercel Sandbox

Vercel Sandbox is the next handoff. It’s named, persistent microVM retains the checkout, pinned deepsec installation, checkpoints, and pending result, but everything on that file system remains untrusted. The workflow reapplies a deny-by-default network policy whenever it creates or resumes the sandbox. The AI Gateway rule, for example, accepts a placeholder only on `POST /v1/*` and injects the real key at egress.

```typescript
"ai-gateway.vercel.sh": [{
  match: {
    method: ["POST"],
    path: { startsWith: "/v1/" },
    headers: [{
      key: { exact: "authorization" },
      value: { exact: `Bearer ${GATEWAY_PLACEHOLDER}` },
    }],
  },
  transform: [{
    headers: { authorization: `Bearer ${gatewayCredential}` },
  }],
}],
```

Git cloning follows the same pattern but also matches the configured owner, repository, and smart-HTTP path. Real GitHub, Slack, Gateway, OIDC, trigger, and marker secrets never enter the sandbox. It can analyze a source and propose a result, but it cannot choose another repository, change code, or publish findings.

### Route model traffic through Vercel AI Gateway

Vercel AI Gateway gives deepsec a consistent model endpoint while keeping model selection and spend controls in the Vercel project. Each repository policy can select an agent, model, and thinking level according to the codebase's risk and the depth of review required.

Set a project or key [budget](https://vercel.com/docs/ai-gateway/observability-and-spend/budgets) before enabling recurring schedules. A scan fleet converts schedule frequency, repository change volume, batch size, and reasoning depth into recurring model usage. AI Gateway gives you one place to monitor that usage and revoke or rotate access without rebuilding the analysis image.

### Validate and publish results

For completed analysis, the sandbox writes a `clean`, `findings`, or `no-changes` result to its outbox. The workflow treats the entire outbox as untrusted, checks its schema and repository identity, and constructs provider-specific messages only then. It produces `failed` and `timed-out` outcomes themselves when analysis does not reach a valid outcome.

For `findings`, the default policy creates or updates a GitHub issue and then sends Slack a summary with counts, severities, and the issue link. A `slack-only` policy avoids writing findings to GitHub and sends a bounded report instead. Clean, no-change, failure, timeout, and overlap outcomes still produce terminal Slack notifications, so silence is not mistaken for success.

Delivery is designed for retries. GitHub issues include an HMAC-protected marker derived from trusted run data, allowing a repeated publication step to find and update the same issue without trusting a guessable label. Slack messages use a deterministic `client_msg_id`. If GitHub succeeds but Slack fails, the outbox remains pending and a later run replays delivery rather than creating another issue.

This is also why scanned repositories remain read-only. An issue is a reporting artifact with repository visibility; it is not a code change. Automated remediation should be a separate, approval-gated workflow with its own connector and write permissions.

### Preserve incremental and delivery state separately

The first complete incremental run analyzes all tracked files that are not ignored. Later runs compare the target commit with `lastSuccessfulSha` and select added, modified, renamed, and copied files. A targeted run uses only its requested tracked files and never advances this baseline.

The runner deliberately separates `lastSuccessfulSha` from `lastPublishedSha`. The first records that analysis completed for a commit. The second records that every required external delivery completed. If analysis succeeds but GitHub or Slack is unavailable, the next run can replay the existing outbox even when the repository has no new changes.

Combining these values would create an unsafe choice: either rescan completed work after every notification failure or advance the baseline, risking the loss of a result. The split makes analysis progress and delivery progress independently recoverable.

Vercel’s Workflow and Sandbox dashboard views remain the source for operational status, retries, duration, and resource use. Raw sandbox logs should not be copied to public destinations because they may contain private source context or security evidence.

### Operate the runner safely

Before enabling schedules, verify connector attachments, repository selection, Slack channel membership, required secrets, generated cron configuration, and AI Gateway budget. Begin with a targeted run against a few security-sensitive files, then establish the full incremental baseline.

For ongoing operation, watch for outcomes rather than only findings. `already-running` may indicate a schedule that is too frequent for the repository. Repeated timeouts may call for a smaller batch, more vCPUs, or a longer session. Publication failures point to connector permissions or destination configuration, while model failures may indicate budget, key, or provider issues.

Pin the deepsec version so recurring behavior does not change without review. Start with a small batch size when investigations may be slow, then increase it if overhead dominates. Set sandbox vCPUs and session timeout within the target Vercel plan. Use a stable lowercase policy ID because routes, workflow leases, sandbox identity, and state all depend on it.

Keep logs sanitized and rotate AI Gateway, trigger, and marker secrets through Vercel environment controls. GitHub issues inherit repository visibility; do not use ordinary issues for embargoed vulnerabilities unless that visibility matches your disclosure policy.

## Extend the template

The template stops at a reliable security review runner. You can add broader control-plane features for your use cases. Here are some ideas for where to take the template:

### Add Postgres for fleet-level scale

Postgres becomes useful when you need a central repository catalog, historical coverage, ownership, security SLAs, fleet search, organization quotas, or cost attribution. Keep the database in trusted routes and workflow steps. The sandbox should still exchange results through a validated outbox and should not receive a general database credential.

A central store can record run identity, repository, target SHA, outcome, finding metadata, delivery status, model usage, and timestamps.

### Add other notifiers

Discord, Microsoft Teams, Linear, Jira, or an internal vulnerability platform can follow the same publication pattern as Slack. Add the provider connection to the trusted control plane, translate only validated outbox fields, and use a deterministic provider idempotency key.

Different outcomes may need different routes. Findings can go to a vulnerability system, failures to an operations channel, and clean or no-change results to a lower-volume audit sink. Preserve a terminal record for every run so that the absence of a finding can be distinguished from a broken notification path.

### Add sensitive disclosure or remediation

For embargoed findings, replace standard GitHub issues with private vulnerability reporting, security advisories, or an internal system with restricted access. Keep the same outbox validation and retry rules.

If you later add [automated fixes](https://vercel.com/kb/guide/eve-software-factory), build them as a separate approval-gated workflow. Give that workflow its own connector, narrowly scoped content-write permissions, and review policy. The analysis runner should remain read-only, so that untrusted sources and model outputs cannot turn a security review into an unapproved code change.

* * *

Start with one repository and one notification path. Once the behavior, cost, and findings fit your security process, expand the static fleet or introduce Postgres and additional destinations where centralized operations justify the extra control plane.

## Next steps

- Start with the [deepsec getting started guide](https://deepsec.sh/docs/getting-started) to run your first security review.
  
- Explore [deepsec architecture](https://deepsec.sh/docs/architecture) to understand its scan, processing, revalidation, and reporting pipeline.
  
- Learn how [persistent Sandboxes](https://vercel.com/docs/sandbox/concepts/persistent-sandboxes) preserve files and resume sessions.
  
- Read about [Workflows and Steps](https://workflow-sdk.dev/docs/foundations/workflows-and-steps) to understand durable orchestration and retryable operations.
  
- Review [Workflow idempotency](https://workflow-sdk.dev/docs/foundations/idempotency) to make retried scans and external writes safe.
  
- Learn how to build a [software factory](https://vercel.com/kb/guide/eve-software-factory) to automatically fix security issues.