Skip to content
Docs

Run recurring security reviews with deepsec on Vercel

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.

Anshuman BhardwajContent Engineer

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
  • Preserves progress across long reviews
  • Publishes validated results to GitHub and Slack

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.

deepsec Runner Template

Schedule recurring deepsec reviews across GitHub repositories and get detailed reports delivered to Slack and GitHub.

Deploy Template

Copy link to headingQuick 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

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.

Copy link to headingVercel Plugin

The 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.

Terminal
npx plugins add vercel/vercel-plugin

Copy link to headingWhat is deepsec?

deepsec 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.

The model you choose affects review quality, cost, and runtime. DeepSecBench compares models on application-code vulnerability discovery, helping you choose which models and reasoning levels to evaluate for your repositories.

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

Terminal
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.

Copy link to headingOverview

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

  1. Vercel Cron calls the repository's generated route, which verifies its bearer secret before starting a Vercel Workflow.
  2. The workflow loads the repository policy, priority paths, models, and claims a deterministic lease for that repository.
  3. Vercel 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.

Copy link to headingPrepare 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.

Copy link to headingStep 1: Set up the project

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

Terminal
pnpm install
vercel link

Deployed code authenticates to Vercel Connect with the project's automatically supplied OIDC identity. 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.

Copy link to headingStep 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:

Terminal
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:

Terminal
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:

Terminal
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. Vercel 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.

Copy link to headingStep 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:

Terminal
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:

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

From the destination Slack channel, invite the managed app:

/invite @deepsec-notifier

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

Terminal
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

Copy link to headingStep 4: Configure AI Gateway

AI Gateway supports 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.

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:

Terminal
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.

Copy link to headingStep 5: Choose models and schedules by repository

Different repositories need different review policies. Authentication services, payment systems, and applications that handle sensitive data warrant more capable analysis than a low-risk internal tool. Change volume also matters: a frequently updated service may benefit from daily reviews, while a stable repository may need a less frequent schedule.

Use DeepSecBench to compare model performance, reasoning levels, cost, and runtime. Check recall and precision alongside the overall score: missed vulnerabilities affect coverage, while false positives add work for the people reviewing findings.

The following examples show how a team might distribute its scanning budget:

Copy link to heading

Team and repositoryExample model and reasoningScheduleWhy
Startup: customer-facing application with authentication and billingOpenAI GPT-6 Sol, xhighDaily at 02:00 UTC: 0 2 * * *Prioritize higher-capability analysis for changes affecting customer accounts and payments.
Startup: less frequently updated internal service with limited access to sensitive dataOpenAI GPT-6 Luna, xhighMonday at 03:00 UTC: 0 3 * * 1Combine a lower-cost model with a weekly cadence for a lower-risk repository.
Enterprise: identity or payments service using the Claude harnessAnthropic Claude Opus 5, maxDaily at 02:00 UTC: 0 2 * * *Allocate more analysis budget to sensitive services using the team's selected model provider.
Enterprise: actively maintained, lower-risk internal applications using an open-weight modelMoonshot AI Kimi K3, xhighDaily at 04:00 UTC: 0 4 * * *Run recurring incremental reviews across internal repositories with an open-weight model available through AI Gateway.

These examples demonstrate different provider and budget choices. Compare the latest DeepSecBench results before selecting a model; they do not imply that each model is the best-performing or cheapest option for its scenario.

Use these repository policy settings:

ModelagentmodelthinkingLevel
GPT-6 Sol"codex""gpt-6-sol""xhigh"
GPT-6 Luna"codex""gpt-6-luna""xhigh"
Claude Opus 5"claude""claude-opus-5""xhigh"
Kimi K3"pi""moonshotai/kimi-k3""xhigh"

For Claude, deepsec maps thinkingLevel: "xhigh" to the provider's max effort setting. See the deepsec model configuration documentation. Verify model support in your pinned deepsec version before enabling each policy.

Copy link to headingCombine recurring reviews with deeper audits

The DeepSecBench launch post describes a startup using inexpensive scans on every merge and deeper audits at milestone releases. An enterprise can apply frontier models to critical services, lower reasoning levels to time-sensitive reviews, and cheaper models across the rest of its repositories.

This template implements the scheduled part of that program. Its first successful run establishes a baseline; subsequent runs review changed files. Scanning on every merge requires an additional trigger. A full audit at a release milestone or after adopting a stronger model must explicitly revisit unchanged files; changing the model or schedule alone does not turn an incremental review into a full audit.

Copy link to headingMeasure cost before expanding coverage

Start with one repository and measure the initial baseline separately from subsequent incremental runs. Benchmark costs describe the benchmark workload, so use your own model usage and infrastructure costs to estimate recurring spend.

Set an AI Gateway budget, review scan duration and finding quality, and adjust the policy before enabling more repositories. Revisit the latest DeepSecBench results when evaluating a new model.

Copy link to headingStep 6: Configure the repository policy

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.

src/config/repositories.ts
{
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.

vercel.ts
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 that also links to the GitHub issue containing an in-depth review report.

Copy link to headingIn-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.

Copy link to headingLong scans durable with Vercel Workflow

Vercel Workflows resolves the repository from a static policy, validates the runtime configuration, and claims a deterministic lease before creating compute.

workflows/deepsec-repository.ts
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.

Copy link to headingBroker 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.

src/lib/github.ts
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,
);

Copy link to headingIsolate 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.

workflows/deepsec-repository.ts
"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.

Copy link to headingRoute 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 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.

Copy link to headingValidate 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.

Copy link to headingPreserve 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.

Copy link to headingOperate 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.

Copy link to headingExtend 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:

Copy link to headingAdd 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.

Copy link to headingAdd 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.

Copy link to headingAdd 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, 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.

Copy link to headingNext steps

More Vercel Workflows guides