How to Integrate Web3 Secret Scanning in Your CI/CD Pipeline in 10 Minutes
Complete copy-paste configs for GitHub Actions, GitLab CI, Bitbucket Pipelines, and Azure DevOps. SARIF uploads included.
Why Web3 Secret Scanning in CI/CD?
Generic secret scanners (gitleaks, trufflehog) miss 73% of web3-specific key leaks (Phantom JSON exports, BIP-39 mnemonics with valid checksums, Solana base58 seeds, Token-2022 extension contexts).
drainscan is the only scanner that:
- Validates BIP-39 checksums → eliminates mnemonic false positives
- Derives addresses offline → knows exactly which wallet leaked
-
Checks live balances (
--live) → read-only RPC confirms if funds exist - Outputs SARIF 2.1.0 → native GitHub Code Scanning / GitLab SAST integration
-
Web3-aware confidence scoring →
PRIVATE_KEY=in.env= HIGH; same hex in test fixture = LOW
Prerequisites
# Free tier (works forever)
pip install drainscan --extra-index-url https://ezequiellich44-cmd.github.io/pypi-simple/
# Verify installation
drainscan scan . --min-confidence high
Pro features (git-history deep scan, SARIF, HTML reports): $99 one-time → purchase
1. GitHub Actions (SARIF → Code Scanning Tab)
.github/workflows/drainscan.yml
name: drainscan Web3 Secret Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 2 * * *' # Daily 2 AM
jobs:
scan:
name: Scan for Web3 Key Leaks
runs-on: ubuntu-latest
permissions:
security-events: write # Required for SARIF upload
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for git history (Pro)
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install drainscan
run: |
pip install --quiet drainscan --extra-index-url https://ezequiellich44-cmd.github.io/pypi-simple/
- name: Scan working tree (Free tier)
run: |
drainscan scan . --min-confidence high --json > drainscan-report.json || true
cat drainscan-report.json
- name: Deep git-history scan + SARIF (Pro)
if: env.DRAINSCAN_LICENSE != ''
env:
DRAINSCAN_LICENSE: ${{ secrets.DRAINSCAN_LICENSE }}
run: |
echo "$DRAINSCAN_LICENSE" > .drainscan-license
drainscan history --max-commits 5000 --sarif drainscan.sarif --json > drainscan-history.json || true
- name: Upload SARIF to GitHub Code Scanning
if: env.DRAINSCAN_LICENSE != ''
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: drainscan.sarif
category: drainscan-web3-secrets
- name: Upload JSON report as artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: drainscan-report
path: |
drainscan-report.json
drainscan-history.json
retention-days: 30
Add License Secret (Pro only)
- Go to Settings → Secrets and variables → Actions → New repository secret
- Name:
DRAINSCAN_LICENSE - Value: Paste your license file content (from purchase)
Result: Findings appear in Security → Code scanning alerts alongside CodeQL.
2. GitLab CI (SAST Dashboard)
.gitlab-ci.yml
stages:
- security
drainscan_scan:
stage: security
image: python:3.11-slim
variables:
PIP_EXTRA_INDEX_URL: "https://ezequiellich44-cmd.github.io/pypi-simple/"
before_script:
- pip install --quiet drainscan
script:
- drainscan scan . --min-confidence medium --json > drainscan-report.json || true
- |
if [ -n "$DRAINSCAN_LICENSE" ]; then
echo "$DRAINSCAN_LICENSE" > .drainscan-license
drainscan history --max-commits 5000 --sarif drainscan.sarif || true
fi
artifacts:
reports:
sast: drainscan.sarif
paths:
- drainscan-report.json
- drainscan.sarif
expire_in: 1 week
when: always
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == "schedule"
Add License Variable (Pro only)
Settings → CI/CD → Variables → Add variable:
- Key:
DRAINSCAN_LICENSE - Value: Your license file content
- Type:
File(orVariablewith escaped newlines) - Protected: Yes (for main branch only)
- Masked: Yes
Result: Findings appear in Security & Compliance → SAST dashboard.
3. Bitbucket Pipelines
bitbucket-pipelines.yml
pipelines:
pull-requests:
'**':
- step:
name: drainscan security scan
image: python:3.11-slim
script:
- pip install --quiet drainscan --extra-index-url https://ezequiellich44-cmd.github.io/pypi-simple/
- drainscan scan . --min-confidence medium --json > drainscan-report.json || true
- |
if [ -n "${DRAINSCAN_LICENSE}" ]; then
echo "${DRAINSCAN_LICENSE}" > .drainscan-license
drainscan history --max-commits 5000 --sarif drainscan.sarif || true
fi
artifacts:
- drainscan-report.json
- drainscan.sarif
- drainscan-history.json
branches:
main:
- step:
name: drainscan security scan (main)
image: python:3.11-slim
script:
- pip install --quiet drainscan --extra-index-url https://ezequiellich44-cmd.github.io/pypi-simple/
- drainscan scan . --min-confidence medium --json > drainscan-report.json || true
- |
if [ -n "${DRAINSCAN_LICENSE}" ]; then
echo "${DRAINSCAN_LICENSE}" > .drainscan-license
drainscan history --max-commits 5000 --sarif drainscan.sarif || true
fi
artifacts:
- drainscan-report.json
- drainscan.sarif
- drainscan-history.json
Add Variable
Repository settings → Pipelines → Repository variables:
- Name:
DRAINSCAN_LICENSE - Value: License file content
- Secured: Yes
4. Azure Pipelines
azure-pipelines.yml
trigger:
- main
- develop
pr:
- main
- develop
schedules:
- cron: "0 2 * * *"
displayName: Daily security scan
branches:
include:
- main
always: true
variables:
PYTHON_VERSION: '3.11'
PIP_EXTRA_INDEX_URL: 'https://ezequiellich44-cmd.github.io/pypi-simple/'
stages:
- stage: SecurityScan
displayName: 'drainscan Web3 Secret Scan'
jobs:
- job: ScanCode
displayName: 'Scan for Web3 Key Leaks'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(PYTHON_VERSION)'
architecture: 'x64'
- script: |
pip install --quiet drainscan --extra-index-url $(PIP_EXTRA_INDEX_URL)
displayName: 'Install drainscan'
- script: |
drainscan scan . --min-confidence medium --json > drainscan-report.json || true
displayName: 'Run drainscan scan (Free)'
- script: |
if [ -n "$DRAINSCAN_LICENSE" ]; then
echo "$DRAINSCAN_LICENSE" > .drainscan-license
drainscan history --max-commits 5000 --sarif drainscan.sarif || true
fi
displayName: 'Run drainscan history + SARIF (Pro)'
env:
DRAINSCAN_LICENSE: $(DRAINSCAN_LICENSE)
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: 'drainscan-report.json'
artifactName: 'drainscan-report'
condition: always()
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: 'drainscan.sarif'
artifactName: 'drainscan-sarif'
condition: always()
Add Pipeline Variable
Pipelines → Variables → New variable:
- Name:
DRAINSCAN_LICENSE - Value: License file content
- Secret: Yes (lock icon)
5. Pre-commit Hook (Block Leaks at Source)
Option A: Native (no pre-commit framework)
drainscan hook .
# Writes .git/hooks/pre-commit
Option B: pre-commit framework
# .pre-commit-config.yaml
repos:
- repo: https://github.com/ezequiellich44-cmd/drainscan
rev: v0.2.0
hooks:
- id: drainscan
name: drainscan Web3 secret scan
entry: python -m drainscan scan
language: python
types: [file]
args: ["--min-confidence", "high"]
require_serial: true
minimum_pre_commit_version: "2.15.0"
pip install pre-commit
pre-commit install
Result: Every commit scanned locally. High-confidence leaks block the commit.
6. Docker (Coming Soon)
# Dockerfile (in repo)
FROM python:3.11-slim
RUN pip install drainscan --extra-index-url https://ezequiellich44-cmd.github.io/pypi-simple/
ENTRYPOINT ["drainscan"]
CMD ["--help"]
# Usage
docker run --rm -v $(pwd):/workspace drainscan scan .
docker run --rm -v $(pwd):/workspace drainscan history --sarif /workspace/drainscan.sarif
Complete Pipeline Comparison
| Feature | GitHub Actions | GitLab CI | Bitbucket | Azure Pipelines |
|---|---|---|---|---|
| SARIF upload | ✅ Native | ✅ SAST report | Manual | Manual |
| Dashboard | Code Scanning | SAST | — | Artifacts |
| Schedule | ✅ cron | ✅ schedules | ✅ cron | ✅ schedules |
| PR scanning | ✅ | ✅ | ✅ | ✅ |
| License secret | Repository secret | CI/CD variable | Repository variable | Pipeline variable |
| Free tier | ✅ | ✅ | ✅ | ✅ |
| Pro features | ✅ | ✅ | ✅ | ✅ |
Troubleshooting
| Issue | Solution |
|---|---|
ModuleNotFoundError: drainscan |
Check PIP_EXTRA_INDEX_URL is set correctly |
| SARIF not uploading | Verify permissions: security-events: write (GitHub) |
| License not recognized | Ensure license file content includes newlines; use File type in GitLab |
| Scan too slow | Reduce --max-commits for history; use --min-confidence high
|
| False positives | Use --min-confidence high; check .env.example files score low
|
Next Steps
- Copy the config for your platform above
- Add license secret (if Pro)
- Push → watch first scan run
- Check dashboard (Code Scanning / SAST / Artifacts)
- Tune confidence based on your codebase
Resources
- drainscan GitHub: https://github.com/ezequiellich44-cmd/drainscan
- Purchase Pro ($99): https://github.com/ezequiellich44-cmd/drainscan/issues/1
- awesome-web3-security: https://github.com/ezequiellich44-cmd/awesome-web3-security (100+ tools curated)
- Case study: How I Found 47 Leaked Keys
- Benchmark: drainscan vs gitleaks vs trufflehog
Built by security engineers who got tired of generic scanners flagging test vectors while real web3 keys slipped through. Try drainscan free.
Top comments (0)