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.
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
CRITICALfor a lenient gate,HIGHfor a balanced approach, orMEDIUMfor strict enforcement. Usenoneto 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:
- 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:
- 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:
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 previewsHow the action works internally
- Submits a scan — calls
POST /api/v1/scanwith your target URL and API key. - Polls for results — checks scan status every 5 seconds until completion or timeout (default 5 minutes).
- Outputs results — sets GitHub Actions output variables for score, grade, and finding counts.
- Annotates findings — each finding appears as a GitHub Actions annotation (error for critical/high, warning for medium, notice for low/info).
- Enforces threshold — fails the workflow if any findings meet or exceed the
fail-onseverity.
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