NullShield

Get started
← Back to Blog
·7 min read

Automate Security Scanning in Your CI/CD Pipeline

Step-by-step guide to adding NullShield security scanning to GitHub Actions. Catch vulnerabilities on every deploy, fail builds on critical findings, and post results to Slack.

Security scanning should not be a manual task you remember to run before a release. The best security teams treat vulnerability scanning the same way they treat linting and testing — it runs automatically on every change, and the build fails if something is wrong.

In this guide, you will add NullShield to a GitHub Actions workflow so that every deployment is scanned for security vulnerabilities. If critical or high-severity issues are found, the build fails before the code reaches production.

What you will need

  • A NullShield account (free tier is fine — 10 scans per month).
  • An API key from Settings → API Keys in your NullShield dashboard.
  • A GitHub repository with an existing deployment workflow (Vercel, Netlify, AWS, or any hosting provider).

Step 1: Store your API key as a secret

Go to your GitHub repository → Settings → Secrets and variables → Actions. Click New repository secret and create a secret called NULLSHIELD_API_KEY with your NullShield API key as the value.

Never hardcode API keys in workflow files. GitHub encrypts secrets at rest and masks them in logs automatically.

Step 2: Add the scan step to your workflow

Open your deployment workflow file (typically .github/workflows/deploy.yml) and add the NullShield step after your deploy step completes. The action needs the deployed URL to be live before scanning.

yaml
name: Deploy & Scan

on:
  push:
    branches: [main]

jobs:
  deploy-and-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Your existing deploy steps here
      - name: Deploy to production
        run: npm run deploy
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

      # Add NullShield after deployment
      - name: NullShield Security Scan
        id: security-scan
        uses: nullshield/security-scan@v1
        with:
          api-key: ${{ secrets.NULLSHIELD_API_KEY }}
          target-url: 'https://your-production-url.com'
          fail-on: 'HIGH'

What each input does

  • api-key — authenticates the scan request against your NullShield account and deducts credits.
  • target-url — the live URL to scan. This must be publicly accessible (NullShield scans from external servers).
  • fail-on — the minimum severity that will cause the workflow to fail. Set to CRITICAL for a lenient gate, HIGH for a balanced approach, or MEDIUM for strict enforcement. Use none to run scans without blocking deploys.

Step 3: Use scan results in downstream steps

The action exposes outputs you can reference in later steps: score, grade, findings-count, critical-count, high-count, medium-count, low-count, and scan-url.

Here is an example that posts the scan result as a comment on pull requests:

yaml
- name: Comment scan results on PR
  if: always() && github.event_name == 'pull_request'
  uses: actions/github-script@v7
  with:
    script: |
      const score = '${{ steps.security-scan.outputs.score }}';
      const grade = '${{ steps.security-scan.outputs.grade }}';
      const findings = '${{ steps.security-scan.outputs.findings-count }}';
      const url = '${{ steps.security-scan.outputs.scan-url }}';

      const emoji = grade === 'A' ? 'shield' : grade === 'F' ? 'x' : 'warning';

      await github.rest.issues.createComment({
        owner: context.repo.owner,
        repo: context.repo.repo,
        issue_number: context.issue.number,
        body: [
          `## :${emoji}: NullShield Security Scan`,
          '',
          `**Score:** ${score}/100 (Grade: ${grade})`,
          `**Findings:** ${findings}`,
          '',
          `[View full report](${url})`,
        ].join('\n'),
      });

Step 4: Send results to Slack

If your team uses Slack, you can forward scan results with a webhook. NullShield supports native Slack integration (set it up in your dashboard notification settings), or you can use the action outputs with any Slack GitHub Action:

yaml
- name: Notify Slack
  if: always()
  uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {
        "text": "Security scan: ${{ steps.security-scan.outputs.score }}/100 (${{ steps.security-scan.outputs.grade }}). ${{ steps.security-scan.outputs.findings-count }} findings. <${{ steps.security-scan.outputs.scan-url }}|View report>"
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

Step 5: Scan preview deployments

For teams using Vercel or Netlify preview deployments, you can scan the preview URL before merging:

yaml
on:
  pull_request:
    branches: [main]

jobs:
  preview-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy preview
        id: deploy
        run: |
          # Your preview deploy command
          echo "preview-url=https://preview-abc123.your-app.dev" >> $GITHUB_OUTPUT

      - name: Scan preview
        uses: nullshield/security-scan@v1
        with:
          api-key: ${{ secrets.NULLSHIELD_API_KEY }}
          target-url: ${{ steps.deploy.outputs.preview-url }}
          fail-on: 'MEDIUM'  # Stricter for previews

How the action works internally

  1. Submits a scan — calls POST /api/v1/scan with your target URL and API key.
  2. Polls for results — checks scan status every 5 seconds until completion or timeout (default 5 minutes).
  3. Outputs results — sets GitHub Actions output variables for score, grade, and finding counts.
  4. Annotates findings — each finding appears as a GitHub Actions annotation (error for critical/high, warning for medium, notice for low/info).
  5. Enforces threshold — fails the workflow if any findings meet or exceed the fail-on severity.

Frequently asked questions

How many credits does each scan use?

A surface scan uses 1 credit. Deep scans (authenticated scanning) use 5 credits. The free tier includes 10 credits per month — enough for daily surface scans on one project.

What happens if the scan times out?

If the scan does not complete within the wait-timeout period (default 300 seconds), the action fails with a timeout error. You can increase this for larger sites. No credits are charged for timed-out scans.

Can I use this with GitLab CI or other CI/CD platforms?

The GitHub Action is a convenience wrapper around the NullShield REST API. You can call the API directly from any CI/CD system using curl or any HTTP client. See our integrations page for API documentation.

Does scanning slow down my deployment?

The scan step runs after deployment completes, so it does not delay the deploy itself. A typical surface scan takes 30 to 90 seconds. If you want scanning to be non-blocking, set fail-on: none so the workflow always passes regardless of findings.

Add security scanning to your next deploy

Sign up, grab your API key, and add one YAML step. Your first 10 scans are free every month.

No credit card required