Files
promptfoo--promptfoo/site/docs/model-audit/ci-cd.md
T
wehub-resource-sync 0d3cb498a3
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

641 lines
18 KiB
Markdown

---
sidebar_label: CI/CD
description: Integrate ModelAudit into GitHub Actions, GitLab CI, Jenkins, and CircleCI pipelines for automated ML model security scanning with SARIF output and deployment gates
keywords:
[
modelaudit ci cd,
github actions model scanning,
gitlab ci model security,
jenkins model scanning,
circleci model security,
automated model scanning,
sarif integration,
model security automation,
production deployment gates,
scheduled security scans,
strict mode validation,
exit codes,
security pipeline integration,
github code scanning,
model scan workflow,
continuous security,
ml security automation,
automated vulnerability scanning,
]
---
# CI/CD Integration
ModelAudit integrates into CI/CD pipelines to automatically scan ML model files for security vulnerabilities before deployment.
## Exit Codes
ModelAudit uses specific exit codes for CI/CD automation:
- **0**: No security issues found ✅
- **1**: Security issues detected (warnings or critical findings) 🟡
- **2**: Operational errors or inconclusive scans (file access, installation, timeouts, or no scanned files) 🔴
In CI/CD pipelines, exit code 1 indicates findings that should be reviewed. Only exit code 2 represents actual scan failures.
## GitHub Actions
### Scan Changed Model Files
Scan model files modified in pull requests:
```yaml
name: Model Security Scan
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
scan-models:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Promptfoo and ModelAudit
run: |
npm install -g promptfoo
pip install modelaudit
- name: Get changed model files
id: changed-files
run: |
MODEL_EXTENSIONS='pkl|pickle|dill|pth|pt|ckpt|checkpoint|orbax-checkpoint|h5|hdf5|keras|onnx|pb|tflite|safetensors|gguf|ggml|ggmf|ggjt|ggla|ggsa|bin|engine|plan|msgpack|flax|orbax|jax|joblib|skops|npy|npz|pmml|zip|tar|7z|json|yaml|yml|xml|toml|config|jinja|j2|template|bst|model|ubj'
CHANGED=$(git diff --name-only --diff-filter=ACM \
${{ github.event.pull_request.base.sha }} ${{ github.sha }} | \
grep -Ei "\.(${MODEL_EXTENSIONS})$" || true)
if [ -z "$CHANGED" ]; then
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "$CHANGED"
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "$CHANGED" > changed_files.txt
fi
- name: Scan changed models
if: steps.changed-files.outputs.has_changes == 'true'
run: |
mkdir -p scan_results
while IFS= read -r file; do
if [ -f "$file" ]; then
echo "Scanning: $file"
report_id=$(printf '%s' "$file" | sha256sum | cut -d ' ' -f1)
promptfoo scan-model "$file" \
--format json \
--output "scan_results/${report_id}.json" || true
fi
done < changed_files.txt
- name: Upload scan results
if: steps.changed-files.outputs.has_changes == 'true'
uses: actions/upload-artifact@v4
with:
name: model-scan-results
path: scan_results/
- name: Check for critical issues
if: steps.changed-files.outputs.has_changes == 'true'
run: |
jq -es '
all(.[];
type == "object" and
(.issues | type == "array") and
(.checks | type == "array") and
(.has_errors | type == "boolean") and
(.failed_checks | type == "number") and
(.has_errors == false) and
(.failed_checks == 0) and
([.checks[]? | select(.status == "failed")] | length == 0) and
([.issues[]? | select(.severity == "critical" or .severity == "error")] | length == 0)
)
' scan_results/*.json >/dev/null
WARNING_COUNT=$(jq -es '[.[] | .issues[]? | select(.severity == "warning")] | length' scan_results/*.json)
if [ "$WARNING_COUNT" -gt 0 ]; then
echo "⚠️ Found warnings in $WARNING_COUNT file(s)"
exit 0
else
echo "✅ No security issues detected"
fi
```
### Upload to GitHub Advanced Security
Use SARIF format to integrate with GitHub Code Scanning:
```yaml
name: Model Security Scan with SARIF
on:
push:
branches: [main]
paths:
- '**/*.pkl'
- '**/*.pth'
- '**/*.h5'
- '**/*.onnx'
pull_request:
branches: [main]
jobs:
scan-models:
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install tools
run: |
npm install -g promptfoo
pip install modelaudit
- name: Scan models directory
id: scan
continue-on-error: true
run: |
promptfoo scan-model models/ \
--no-write \
--format sarif \
--output modelaudit.sarif
- name: Upload SARIF to GitHub
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: modelaudit.sarif
category: model-security
- name: Fail if scan found issues or errors
if: steps.scan.outcome == 'failure'
run: exit 1
```
### Scheduled Security Scans
Run periodic scans on all models:
```yaml
name: Weekly Model Security Audit
on:
schedule:
- cron: '0 2 * * 0' # Sundays at 2 AM UTC
workflow_dispatch:
jobs:
comprehensive-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install tools
run: |
npm install -g promptfoo
pip install modelaudit[all]
- name: Comprehensive model scan
continue-on-error: true
run: |
promptfoo scan-model models/ \
--format json \
--output scan_results.json \
--strict \
--sbom sbom.json
- name: Generate SBOM
run: |
echo "Software Bill of Materials generated: sbom.json"
- name: Check for critical issues
id: check-issues
run: |
if ! jq -e '
type == "object" and
(.issues | type == "array") and
(.checks | type == "array") and
(.has_errors | type == "boolean") and
(.failed_checks | type == "number") and
(.has_errors == false) and
(.failed_checks == 0) and
([.checks[]? | select(.status == "failed")] | length == 0) and
([.issues[]? | select(.severity == "critical" or .severity == "error")] | length == 0)
' scan_results.json >/dev/null; then
echo "critical=true" >> $GITHUB_OUTPUT
exit 1
fi
- name: Create issue if scan fails closed
if: failure() && steps.check-issues.outputs.critical == 'true'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const results = JSON.parse(fs.readFileSync('scan_results.json', 'utf8'));
const issues = Array.isArray(results.issues) ? results.issues : [];
const criticalIssues = issues
.filter(i => i.severity === 'critical' || i.severity === 'error')
.map(i => `- ${i.message}`)
.join('\n');
const failedCheckCount = results.failed_checks || 0;
const findingsSummary = criticalIssues || `- Failed checks: ${failedCheckCount}`;
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: '🚨 Critical Model Security Issues Detected',
body: `Weekly security scan found non-clean results:\n\n${findingsSummary}`,
labels: ['security', 'critical', 'model-audit']
});
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: weekly-audit-results
path: |
scan_results.json
sbom.json
```
### Strict Mode for Production
Block deployments on any security findings:
```yaml
name: Production Model Validation
on:
push:
branches: [main]
paths:
- 'models/production/**'
jobs:
strict-validation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install tools
run: |
npm install -g promptfoo
pip install modelaudit[all]
- name: Strict security scan
run: |
promptfoo scan-model models/production/ \
--strict \
--format json \
--output results.json
```
The scan step fails if strict mode finds warnings or critical issues.
## GitLab CI
Example `.gitlab-ci.yml`:
```yaml
model-security-scan:
stage: test
image: node:24
before_script:
- apt-get update && apt-get install -y python3 python3-pip
- npm install -g promptfoo
- pip3 install modelaudit
script:
- |
MODEL_EXTENSIONS='pkl|pickle|dill|pth|pt|ckpt|checkpoint|orbax-checkpoint|h5|hdf5|keras|onnx|pb|tflite|safetensors|gguf|ggml|ggmf|ggjt|ggla|ggsa|bin|engine|plan|msgpack|flax|orbax|jax|joblib|skops|npy|npz|pmml|zip|tar|7z|json|yaml|yml|xml|toml|config|jinja|j2|template|bst|model|ubj'
CHANGED=$(git diff --name-only --diff-filter=ACM $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA | \
grep -Ei "\.(${MODEL_EXTENSIONS})$" || true)
if [ -n "$CHANGED" ]; then
mkdir -p scan_results
printf '%s\n' "$CHANGED" > changed_files.txt
scan_status=0
while IFS= read -r file; do
if [ -f "$file" ]; then
report_id=$(printf '%s' "$file" | sha256sum | cut -d ' ' -f1)
file_status=0
promptfoo scan-model "$file" \
--format json \
--output "scan_results/${report_id}.json" || file_status=$?
if [ "$file_status" -gt "$scan_status" ]; then scan_status=$file_status; fi
fi
done < changed_files.txt
exit "$scan_status"
else
echo "No model files changed"
fi
artifacts:
paths:
- scan_results/
when: always
expire_in: 30 days
only:
- merge_requests
- main
```
## Jenkins
Example Jenkinsfile:
```groovy
pipeline {
agent any
stages {
stage('Setup') {
steps {
sh '''
npm install -g promptfoo
pip install modelaudit
'''
}
}
stage('Scan Models') {
steps {
script {
def changed = sh(
script: '''
MODEL_EXTENSIONS='pkl|pickle|dill|pth|pt|ckpt|checkpoint|orbax-checkpoint|h5|hdf5|keras|onnx|pb|tflite|safetensors|gguf|ggml|ggmf|ggjt|ggla|ggsa|bin|engine|plan|msgpack|flax|orbax|jax|joblib|skops|npy|npz|pmml|zip|tar|7z|json|yaml|yml|xml|toml|config|jinja|j2|template|bst|model|ubj'
git diff --name-only HEAD~1 HEAD | \
grep -Ei "\\.(${MODEL_EXTENSIONS})$" || true
''',
returnStdout: true
).trim()
if (changed) {
changed.split('\n').each { file ->
def reportFile = "scan_${file.bytes.encodeHex().toString()}.json"
withEnv(["MODEL_FILE=${file}", "REPORT_FILE=${reportFile}"]) {
sh '''
promptfoo scan-model "$MODEL_FILE" \
--format json \
--output "$REPORT_FILE"
'''
}
}
// Check for critical issues
def critical = sh(
script: '''
jq -es '
all(.[];
type == "object" and
(.issues | type == "array") and
(.checks | type == "array") and
(.has_errors | type == "boolean") and
(.failed_checks | type == "number") and
(.has_errors == false) and
(.failed_checks == 0) and
([.checks[]? | select(.status == "failed")] | length == 0) and
([.issues[]? | select(.severity == "critical" or .severity == "error")] | length == 0)
)
' scan_*.json >/dev/null
''',
returnStatus: true
)
if (critical != 0) {
error("Found model scan failures or critical security issues")
}
}
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'scan_*.json', allowEmptyArchive: true
}
}
}
```
## CircleCI
Example `.circleci/config.yml`:
```yaml
version: 2.1
jobs:
model-scan:
docker:
- image: cimg/node:lts
steps:
- checkout
- run:
name: Install tools
command: |
npm install -g promptfoo
pip install modelaudit
- run:
name: Scan changed models
command: |
MODEL_EXTENSIONS='pkl|pickle|dill|pth|pt|ckpt|checkpoint|orbax-checkpoint|h5|hdf5|keras|onnx|pb|tflite|safetensors|gguf|ggml|ggmf|ggjt|ggla|ggsa|bin|engine|plan|msgpack|flax|orbax|jax|joblib|skops|npy|npz|pmml|zip|tar|7z|json|yaml|yml|xml|toml|config|jinja|j2|template|bst|model|ubj'
CHANGED=$(git diff --name-only origin/main...HEAD | \
grep -Ei "\.(${MODEL_EXTENSIONS})$" || true)
if [ -n "$CHANGED" ]; then
echo "$CHANGED" | while read -r file; do
if [ -f "$file" ]; then
promptfoo scan-model "$file" --format json >> scan_results.json
fi
done
fi
- store_artifacts:
path: scan_results.json
destination: model-scan-results
workflows:
security:
jobs:
- model-scan:
filters:
branches:
only:
- main
- develop
```
## Best Practices
### Scan Strategy
- **Pull Requests**: Scan only changed files for fast feedback
- **Main Branch**: Run comprehensive scans on merge
- **Scheduled Scans**: Weekly or daily full audits
- **Production Gate**: Use `--strict` mode to block deployments
### Performance
```yaml
# Cache dependencies
- name: Cache npm and pip
uses: actions/cache@v3
with:
path: |
~/.npm
~/.cache/pip
key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json', '**/requirements.txt') }}
# Parallel scanning
- name: Parallel scan
run: |
find models/ -name "*.pkl" -print0 | \
xargs -0 -P 4 -I {} promptfoo scan-model {} --format json --output {}.json
```
### Security
- Store credentials as encrypted secrets
- Use read-only tokens when possible
- Rotate credentials regularly
- Audit access to scan results
### Timeout Configuration
For large models (8GB+):
```yaml
- name: Scan large model
run: |
promptfoo scan-model large_model.bin \
--timeout 1800 \
--verbose \
--format json \
--output results.json
```
### Retry Logic
```yaml
- name: Scan with retry
uses: nick-fields/retry@v2
with:
timeout_minutes: 10
max_attempts: 3
command: promptfoo scan-model models/ --format json --output results.json
```
### Notifications
```yaml
- name: Notify on critical issues
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "🚨 Critical model security issues detected!",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Model Security Scan Failed*\nRepository: ${{ github.repository }}\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Details>"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
```
## Troubleshooting
### Verbose Output
```bash
promptfoo scan-model models/ --verbose
```
### File Size Limits
```bash
# Set maximum file size
promptfoo scan-model models/ --max-size 1GB
```
### Dry Run
```bash
# Preview scan without processing
promptfoo scan-model models/ --dry-run
```
## Next Steps
- [Scanner Reference](./scanners.md) - Security checks performed
- [Advanced Usage](./usage.md) - Cloud storage, authentication, remote sources
- [Overview](./index.md) - Getting started with ModelAudit