SAST, DAST, and SCA in CI: Shift-Left Security Pipeline
What Is Shift-Left Security?
Shift-left security means integrating security testing early in the development lifecycle -- ideally in CI/CD pipelines -- rather than waiting for a penetration test before release. For AI applications, this means running static analysis, dependency scanning, and dynamic testing on every commit, with AI-specific rules that catch vulnerabilities traditional tools miss.
Tool Landscape
| Category | Tool | What It Does | Integration Point | Cost |
|---|---|---|---|---|
| SAST | Semgrep | Pattern-based code analysis | Pre-commit hook, CI | Free (open source rules) |
| SAST | CodeQL | Deep semantic code analysis | GitHub Actions (native) | Free for public repos |
| DAST | ZAP (OWASP) | Active web vulnerability scanning | CI (against staging) | Free/open source |
| DAST | Burp Suite | Comprehensive web security testing | Manual + CI (Enterprise) | Commercial |
| SCA | Snyk | Dependency vulnerability scanning | CI, IDE, registry | Freemium |
| SCA | Dependabot | Automated dependency updates | GitHub native | Free |
| Secrets | GitLeaks | Detect secrets in git history | Pre-commit hook | Free/open source |
| Container | Trivy | Container image vulnerability scanning | CI | Free/open source |
Complete CI Security Pipeline
# .github/workflows/security.yml
name: Security Checks
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Semgrep SAST scan
uses: semgrep/semgrep-action@v1
with:
config: >-
p/owasp-top-ten
p/python
p/typescript
p/secrets
p/ai-security
generateSarif: true
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Snyk dependency scan
uses: snyk/actions/python@master
with:
args: --severity-threshold=high
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect secrets
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
dast:
runs-on: ubuntu-latest
needs: [sast, sca] # only run DAST if static checks pass
steps:
- uses: actions/checkout@v4
- name: Deploy to ephemeral environment
run: ./scripts/deploy-ephemeral.sh
- name: OWASP ZAP full scan
uses: zaproxy/action-full-scan@v0.10.0
with:
target: ${{ env.EPHEMERAL_URL }}
rules_file_name: zap-rules.tsv
cmd_options: '-a -j'
- name: Upload ZAP report
uses: actions/upload-artifact@v4
with:
name: zap-report
path: report_html.html
container-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t app:${{ github.sha }} .
- name: Trivy container scan
uses: aquasecurity/trivy-action@master
with:
image-ref: app:${{ github.sha }}
format: sarif
output: trivy-results.sarif
severity: 'CRITICAL,HIGH'
- name: Upload Trivy SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarif
AI-Specific Semgrep Rules
Standard SAST rules do not catch AI-specific vulnerabilities. Write custom Semgrep rules:
# .semgrep/ai-security-rules.yaml
rules:
- id: llm-output-used-in-sql
pattern: |
$QUERY = f"... {$LLM_RESPONSE} ..."
$DB.execute($QUERY)
message: "LLM output used directly in SQL query. Use parameterized queries."
severity: ERROR
languages: [python]
metadata:
category: security
owasp: "A03:Injection"
ai_specific: true
- id: llm-output-used-in-shell
patterns:
- pattern: os.system(f"... {$LLM_RESPONSE} ...")
- pattern: subprocess.run(f"... {$LLM_RESPONSE} ...", shell=True)
message: "LLM output used in shell command. Sanitize or use allowlist."
severity: ERROR
languages: [python]
metadata:
category: security
owasp: "A03:Injection"
- id: api-key-in-prompt
pattern: |
$PROMPT = f"... $API_KEY ..."
message: "API key may be included in LLM prompt. Remove sensitive data."
severity: WARNING
languages: [python]
- id: no-output-sanitization
pattern: |
$RESPONSE = $LLM.generate(...)
return $RESPONSE
message: "LLM output returned without sanitization. Add output validation."
severity: WARNING
languages: [python]
- id: no-max-tokens-set
pattern: |
$CLIENT.chat.completions.create(
...,
~max_tokens,
...
)
message: "LLM call without max_tokens. Set a limit to prevent cost/DoS issues."
severity: WARNING
languages: [python]
- id: hardcoded-llm-api-key
patterns:
- pattern: '"sk-..."'
- pattern: "'sk-...'"
- pattern: |
$KEY = "sk-$REST"
message: "Hardcoded OpenAI API key detected. Use environment variables."
severity: ERROR
languages: [python, javascript, typescript]
Understanding Each Security Layer
SAST (Static Application Security Testing)
Analyzes source code without executing it. Catches code-level vulnerabilities early.
What it catches in AI apps:
- LLM output used in SQL/shell without sanitization
- API keys hardcoded in prompt templates
- Missing output validation on LLM responses
- Insecure deserialization of model outputs
Limitations: Cannot detect runtime behavior, configuration issues, or logical flaws.
DAST (Dynamic Application Security Testing)
Tests the running application by sending requests and analyzing responses.
What it catches in AI apps:
- XSS via LLM-generated HTML content
- SSRF through AI URL fetching features
- Authentication bypass on AI endpoints
- Information disclosure in error messages
Limitations: Requires a running environment, slower than SAST, cannot see code-level issues.
SCA (Software Composition Analysis)
Scans dependencies for known vulnerabilities (CVEs).
What it catches in AI apps:
- CVEs in PyTorch, TensorFlow, LangChain, Hugging Face libraries
- Vulnerable transitive dependencies in the ML pipeline
- License compliance issues with model dependencies
ML-specific SCA concerns:
- ML libraries are updated less frequently than web frameworks
- Model files from external sources may contain malicious payloads
- The LangChain ecosystem has had several critical CVEs
Pipeline Architecture
Developer Commits Code
|
v
+------------------+
| Pre-commit Hooks | <-- GitLeaks (secrets), Semgrep (quick scan)
| (< 30 seconds) | Runs before code leaves developer machine
+--------+---------+
|
v
+------------------+
| SAST + SCA | <-- Semgrep (full), Snyk (deps), CodeQL (deep)
| (2-5 minutes) | Runs on every PR
+--------+---------+
|
v
+------------------+
| DAST | <-- OWASP ZAP against ephemeral environment
| (10-30 minutes) | Runs after SAST passes (why scan if code is broken?)
+--------+---------+
|
v
+------------------+
| Container Scan | <-- Trivy (image vulnerabilities)
| (2-5 minutes) | Runs on every built image
+--------+---------+
|
v
+------------------+
| AI Security | <-- Prompt injection, jailbreak, data leakage tests
| Tests | Runs when AI code or prompts change
| (5-15 minutes) |
+------------------+
Handling Security Findings
Severity-Based Response
| Severity | SLA | Action |
|---|---|---|
| Critical (e.g., SQL injection, API key leak) | Block merge, fix immediately | PR cannot merge until fixed |
| High (e.g., XSS, vulnerable dependency) | Fix within 3 days | PR can merge with security team approval |
| Medium (e.g., missing headers, info disclosure) | Fix within 2 weeks | PR can merge, ticket created |
| Low (e.g., best practice violation) | Fix in next sprint | No merge block, warning only |
False Positive Management
SAST tools produce false positives. Manage them systematically:
# .semgrep-ignore.yaml
rules:
- id: no-output-sanitization
paths:
# Internal tools that only display to admins
- src/admin/debug_panel.py
comment: "Admin debug panel, no user-facing output"
approved_by: "security-team"
approved_date: "2026-01-15"
Track your false positive rate. If it exceeds 20%, the team will stop trusting the tool. Tune rules to reduce noise while maintaining detection capability.
A security pipeline is not set-and-forget. Review findings weekly, update rules monthly, and re-evaluate tool effectiveness quarterly.