Claude-code-plugins-plus-skills vercel-policy-guardrails

install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/vercel-pack/skills/vercel-policy-guardrails" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-vercel-policy-guardrails && rm -rf "$T"
manifest: plugins/saas-packs/vercel-pack/skills/vercel-policy-guardrails/SKILL.md
source content

Vercel Policy Guardrails

Overview

Protect against common Vercel failure modes with automated guardrails: ESLint rules preventing secret exposure in client bundles, pre-commit hooks scanning for credentials, CI checks validating vercel.json and edge runtime compatibility, and runtime middleware enforcing auth on protected routes.

Prerequisites

  • ESLint configured in project
  • Git hooks infrastructure (husky or lefthook)
  • CI/CD pipeline (GitHub Actions or similar)
  • TypeScript for type enforcement

Instructions

Step 1: ESLint Rules — Prevent Secret Exposure

// .eslintrc.js — custom rules for Vercel projects
module.exports = {
  rules: {
    // Prevent using NEXT_PUBLIC_ prefix for sensitive variables
    'no-restricted-syntax': [
      'error',
      {
        selector: 'MemberExpression[object.property.name="env"][property.name=/^NEXT_PUBLIC_(SECRET|KEY|TOKEN|PASSWORD|PRIVATE)/]',
        message: 'Do not prefix secrets with NEXT_PUBLIC_ — they will be exposed in the client bundle',
      },
    ],
  },
  overrides: [
    {
      // Edge runtime files — prevent Node.js API usage
      files: ['**/edge-*.ts', '**/middleware.ts'],
      rules: {
        'no-restricted-imports': [
          'error',
          {
            paths: [
              { name: 'fs', message: 'fs is not available in Edge Runtime. Use fetch or Vercel Blob.' },
              { name: 'path', message: 'path is not available in Edge Runtime. Use URL API.' },
              { name: 'crypto', message: 'Use globalThis.crypto (Web Crypto API) in Edge Runtime.' },
              { name: 'child_process', message: 'child_process is not available in Edge Runtime.' },
              { name: 'net', message: 'net is not available in Edge Runtime.' },
            ],
          },
        ],
      },
    },
  ],
};

Step 2: Pre-Commit Hook — Credential Scanning

# Install husky
npm install --save-dev husky
npx husky init
#!/usr/bin/env bash
# .husky/pre-commit
set -euo pipefail

# Scan staged files for credentials
PATTERNS=(
  'VERCEL_TOKEN\s*[:=]\s*\S+'
  'vercel_[a-zA-Z]*_token\s*[:=]\s*\S+'
  'sk_live_[a-zA-Z0-9]+'
  'NEXT_PUBLIC_.*SECRET'
  'api\.vercel\.com.*Bearer\s+[a-zA-Z0-9]+'
)

STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
FOUND=0

for file in $STAGED_FILES; do
  for pattern in "${PATTERNS[@]}"; do
    if grep -qEi "$pattern" "$file" 2>/dev/null; then
      echo "ERROR: Potential credential found in $file"
      echo "  Pattern: $pattern"
      FOUND=1
    fi
  done
done

if [ $FOUND -ne 0 ]; then
  echo ""
  echo "Commit blocked: Remove credentials and use environment variables."
  echo "See: vercel env add <KEY> <environment>"
  exit 1
fi

Step 3: CI Policy Check — vercel.json Validation

# .github/workflows/vercel-policy.yml
name: Vercel Policy Checks
on: [pull_request]

jobs:
  policy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }

      - name: Validate vercel.json schema
        run: |
          if [ -f vercel.json ]; then
            node -e "
              const config = require('./vercel.json');
              const errors = [];

              // Check for deprecated builds property
              if (config.builds) {
                errors.push('vercel.json uses deprecated \"builds\" property — use \"functions\" instead');
              }

              // Check compressHTML (iOS Safari issue)
              if (config.compressHTML === true) {
                errors.push('compressHTML should be disabled — causes iOS Safari rendering issues');
              }

              // Check for hardcoded secrets in headers
              const headerStr = JSON.stringify(config.headers ?? []);
              if (/Bearer\s+[a-zA-Z0-9]{20,}/.test(headerStr)) {
                errors.push('Hardcoded token found in vercel.json headers');
              }

              if (errors.length > 0) {
                console.error('Policy violations:');
                errors.forEach(e => console.error('  - ' + e));
                process.exit(1);
              }
              console.log('vercel.json policy checks passed');
            "
          fi

      - name: Check edge runtime compatibility
        run: |
          # Find files with edge runtime declaration
          for file in $(grep -rl "runtime.*=.*'edge'" src/ api/ 2>/dev/null || true); do
            echo "Checking edge compatibility: $file"
            # Check for Node.js-only imports
            if grep -E "require\(['\"]fs['\"]|from ['\"]fs['\"]|from ['\"]path['\"]|from ['\"]crypto['\"]" "$file"; then
              echo "ERROR: $file uses Node.js APIs incompatible with Edge Runtime"
              exit 1
            fi
          done
          echo "Edge runtime compatibility checks passed"

      - name: Check bundle size budget
        run: |
          npm ci
          npm run build
          # Check output size
          TOTAL=$(du -sb .next/ 2>/dev/null | cut -f1 || echo 0)
          MAX=$((250 * 1024 * 1024))  # 250MB
          if [ "$TOTAL" -gt "$MAX" ]; then
            echo "ERROR: Build output ($TOTAL bytes) exceeds budget ($MAX bytes)"
            exit 1
          fi
          echo "Bundle size within budget: $TOTAL bytes"

Step 4: Env Var Documentation Guard

#!/usr/bin/env bash
# scripts/check-env-docs.sh — ensure .env.example stays in sync
set -euo pipefail

# Extract env vars used in code
CODE_VARS=$(grep -roh 'process\.env\.\w\+' src/ api/ 2>/dev/null \
  | sed 's/process\.env\.//' \
  | sort -u)

# Extract vars documented in .env.example
if [ ! -f .env.example ]; then
  echo "ERROR: .env.example file missing"
  exit 1
fi

DOC_VARS=$(grep -oE '^\w+=' .env.example | sed 's/=//' | sort -u)

# Find undocumented vars
MISSING=$(comm -23 <(echo "$CODE_VARS") <(echo "$DOC_VARS"))
if [ -n "$MISSING" ]; then
  echo "ERROR: Undocumented environment variables:"
  echo "$MISSING" | sed 's/^/  /'
  echo "Add these to .env.example"
  exit 1
fi

echo "All environment variables documented"

Step 5: Runtime Auth Middleware Guard

// middleware.ts — enforce that protected routes always require auth
import { NextRequest, NextResponse } from 'next/server';

// Routes that MUST require authentication
const PROTECTED_PATTERNS = [
  /^\/api\/admin/,
  /^\/api\/users/,
  /^\/dashboard/,
  /^\/settings/,
];

// Routes explicitly allowed without auth
const PUBLIC_PATTERNS = [
  /^\/api\/health/,
  /^\/api\/webhooks/,
  /^\/$/, // homepage
  /^\/login/,
  /^\/signup/,
];

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  const isProtected = PROTECTED_PATTERNS.some(p => p.test(pathname));
  const isPublic = PUBLIC_PATTERNS.some(p => p.test(pathname));

  if (isProtected && !isPublic) {
    const token = request.cookies.get('session')?.value;
    if (!token) {
      if (pathname.startsWith('/api/')) {
        return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
      }
      return NextResponse.redirect(new URL('/login', request.url));
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

Step 6: Deployment Freeze Guard

#!/usr/bin/env bash
# scripts/check-deploy-freeze.sh — prevent production deploys during freeze windows
set -euo pipefail

# Check if we're in a deployment freeze window
HOUR=$(date -u +%H)
DAY=$(date -u +%u)  # 1=Monday, 7=Sunday

# No deploys on weekends
if [ "$DAY" -gt 5 ]; then
  echo "BLOCKED: No production deploys on weekends"
  exit 1
fi

# No deploys after 4pm UTC (Friday especially)
if [ "$DAY" -eq 5 ] && [ "$HOUR" -ge 16 ]; then
  echo "BLOCKED: No production deploys after 4pm UTC on Fridays"
  exit 1
fi

echo "Deploy allowed"

Guardrails Summary

GuardrailEnforcement PointPrevents
Secret prefix lintESLint (editor + CI)Client bundle secret exposure
Edge runtime lintESLint (editor + CI)Node.js APIs in edge functions
Credential scanPre-commit hookSecrets in version control
vercel.json validationCIDeprecated config, hardcoded tokens
Env var documentationCIMissing .env.example entries
Auth middlewareRuntimeUnprotected routes
Deploy freezeCIWeekend/late deploys

Output

  • ESLint rules preventing secret exposure and edge runtime violations
  • Pre-commit hooks blocking credentials from entering git
  • CI policy checks validating configuration and compatibility
  • Runtime middleware enforcing authentication on protected routes
  • Deployment freeze windows preventing risky deploys

Error Handling

ErrorCauseSolution
ESLint rule false positiveVariable name matches patternAdd
// eslint-disable-next-line
with justification
Pre-commit hook blocks valid commitPattern too broadNarrow the regex or add allowlist
CI edge check false positiveDead importRemove unused import
Deploy freeze too restrictiveUrgent hotfix neededUse
--force
flag with team approval

Resources

Next Steps

For architecture variants, see

vercel-architecture-variants
.