10 KiB
Vulnerability Remediation Patterns
Common patterns for remediating dependency vulnerabilities detected by Grype.
Table of Contents
- General Remediation Strategies
- Package Update Patterns
- Base Image Updates
- Dependency Pinning
- Compensating Controls
- Language-Specific Patterns
General Remediation Strategies
Strategy 1: Direct Dependency Update
When to use: Vulnerability in a directly declared dependency
Pattern:
- Identify fixed version from Grype output
- Update dependency version in manifest file
- Test application compatibility
- Re-scan to verify fix
- Deploy updated application
Example:
# Grype reports: lodash@4.17.15 has CVE-2020-8203, fixed in 4.17.19
# Update package.json
npm install lodash@4.17.19
npm test
grype dir:. --only-fixed
Strategy 2: Transitive Dependency Update
When to use: Vulnerability in an indirect dependency
Pattern:
- Identify which direct dependency includes the vulnerable package
- Check if direct dependency has an update that resolves the issue
- Update direct dependency or use dependency override mechanism
- Re-scan to verify fix
Example (npm):
// package.json - Override transitive dependency
{
"overrides": {
"lodash": "^4.17.21"
}
}
Example (pip):
# constraints.txt
lodash>=4.17.21
Strategy 3: Base Image Update
When to use: Vulnerability in OS packages from container base image
Pattern:
- Identify vulnerable OS package and fixed version
- Update to newer base image tag or rebuild with package updates
- Re-scan updated image
- Test application on new base image
Example:
# Before: Alpine 3.14 with vulnerable openssl
FROM alpine:3.14
# After: Alpine 3.19 with fixed openssl
FROM alpine:3.19
# Or: Explicit package update
FROM alpine:3.14
RUN apk upgrade --no-cache openssl
Strategy 4: Patch or Backport
When to use: No fixed version available or update breaks compatibility
Pattern:
- Research if security patch exists separately from full version update
- Apply patch using package manager's patching mechanism
- Consider backporting fix if feasible
- Document patch and establish review schedule
Example (npm postinstall):
{
"scripts": {
"postinstall": "patch-package"
}
}
Strategy 5: Compensating Controls
When to use: Fix not available and risk must be accepted
Pattern:
- Document vulnerability and risk acceptance
- Implement network, application, or operational controls
- Enhance monitoring and detection
- Schedule regular review (quarterly)
- Track for future remediation when fix becomes available
Package Update Patterns
Pattern: Semantic Versioning Updates
Minor/Patch Updates (Generally Safe):
# Python: Update to latest patch version
pip install --upgrade 'package>=1.2.0,<1.3.0'
# Node.js: Update to latest minor version
npm update package
# Go: Update to latest patch
go get -u=patch github.com/org/package
Major Updates (Breaking Changes):
# Review changelog before updating
npm show package versions
pip index versions package
# Update and test thoroughly
npm install package@3.0.0
npm test
Pattern: Lock File Management
Update specific package:
# npm
npm install package@latest
npm install # Update lock file
# pip
pip install --upgrade package
pip freeze > requirements.txt
# Go
go get -u github.com/org/package
go mod tidy
Update all dependencies:
# npm (interactive)
npm-check-updates --interactive
# pip
pip list --outdated | cut -d ' ' -f1 | xargs -n1 pip install -U
# Go
go get -u ./...
go mod tidy
Base Image Updates
Pattern: Minimal Base Images
Reduce attack surface with minimal images:
# ❌ Large attack surface
FROM ubuntu:22.04
# ✅ Minimal attack surface
FROM alpine:3.19
# or
FROM gcr.io/distroless/base-debian12
# ✅ Minimal for specific language
FROM python:3.11-slim
FROM node:20-alpine
Benefits:
- Fewer packages = fewer vulnerabilities
- Smaller image size
- Faster scans
Pattern: Multi-Stage Builds
Separate build dependencies from runtime:
# Build stage with full toolchain
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage with minimal image
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/index.js"]
Benefits:
- Build tools not present in final image
- Reduced vulnerability exposure
- Smaller production image
Pattern: Regular Base Image Updates
Automate base image updates:
# Dependabot config for Dockerfile
version: 2
updates:
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
Manual update process:
# Check for newer base image versions
docker pull alpine:3.19
docker images alpine
# Update Dockerfile
sed -i 's/FROM alpine:3.18/FROM alpine:3.19/' Dockerfile
# Rebuild and scan
docker build -t myapp:latest .
grype myapp:latest
Dependency Pinning
Pattern: Pin to Secure Versions
Lock to known-good versions:
# ✅ Pin specific versions
FROM alpine:3.19.0@sha256:abc123...
# Install specific package versions
RUN apk add --no-cache \
ca-certificates=20240226-r0 \
openssl=3.1.4-r0
// package.json - Exact versions
{
"dependencies": {
"express": "4.18.2",
"lodash": "4.17.21"
}
}
Benefits:
- Reproducible builds
- Controlled updates
- Prevent automatic vulnerability introduction
Drawbacks:
- Manual update effort
- May miss security patches
- Requires active maintenance
Pattern: Range-Based Pinning
Allow patch updates, lock major/minor:
// package.json - Allow patch updates
{
"dependencies": {
"express": "~4.18.2", // Allow 4.18.x
"lodash": "^4.17.21" // Allow 4.x.x
}
}
# requirements.txt - Compatible releases
express>=4.18.2,<5.0.0
lodash>=4.17.21,<5.0.0
Compensating Controls
Pattern: Network Segmentation
Isolate vulnerable systems:
# Docker Compose network isolation
services:
vulnerable-service:
image: myapp:vulnerable
networks:
- internal
# No external port exposure
gateway:
image: nginx:alpine
ports:
- "80:80"
networks:
- internal
- external
networks:
internal:
internal: true
external:
Benefits:
- Limits attack surface
- Contains potential breaches
- Buys time for proper remediation
Pattern: Web Application Firewall (WAF)
Block exploit attempts at perimeter:
# ModSecurity/OWASP Core Rule Set
location / {
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
proxy_pass http://vulnerable-backend;
}
Virtual Patching:
- Create WAF rules for specific CVEs
- Block known exploit patterns
- Monitor for exploitation attempts
Pattern: Runtime Application Self-Protection (RASP)
Detect and prevent exploitation at runtime:
# Example: Add input validation
def process_user_input(data):
# Validate against known exploit patterns
if contains_sql_injection(data):
log_security_event("SQL injection attempt blocked")
raise SecurityException("Invalid input")
return sanitize_input(data)
Language-Specific Patterns
Python
Update vulnerable package:
# Check for vulnerabilities
grype dir:/path/to/project -o json
# Update package
pip install --upgrade vulnerable-package
# Freeze updated dependencies
pip freeze > requirements.txt
# Verify fix
grype dir:/path/to/project
Use constraints files:
# constraints.txt
vulnerable-package>=1.2.3 # CVE-2024-XXXX fixed
# Install with constraints
pip install -r requirements.txt -c constraints.txt
Node.js
Update vulnerable package:
# Check for vulnerabilities
npm audit
grype dir:. -o json
# Fix automatically (if possible)
npm audit fix
# Manual update
npm install package@version
# Verify fix
npm audit
grype dir:.
Override transitive dependencies:
{
"overrides": {
"vulnerable-package": "^2.0.0"
}
}
Go
Update vulnerable module:
# Check for vulnerabilities
go list -m all | grype
# Update specific module
go get -u github.com/org/vulnerable-module
# Update all modules
go get -u ./...
# Verify and tidy
go mod tidy
grype dir:.
Java/Maven
Update vulnerable dependency:
<!-- pom.xml - Update version -->
<dependency>
<groupId>org.example</groupId>
<artifactId>vulnerable-lib</artifactId>
<version>2.0.0</version> <!-- Updated from 1.0.0 -->
</dependency>
Force dependency version:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>vulnerable-lib</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
</dependencyManagement>
Rust
Update vulnerable crate:
# Check for vulnerabilities
cargo audit
grype dir:. -o json
# Update specific crate
cargo update -p vulnerable-crate
# Update all crates
cargo update
# Verify fix
cargo audit
grype dir:.
Verification Workflow
After applying any remediation:
Progress: [ ] 1. Re-scan: Run Grype scan to verify vulnerability resolved [ ] 2. Test: Execute test suite to ensure no functionality broken [ ] 3. Document: Record CVE, fix applied, and verification results [ ] 4. Deploy: Roll out fix to affected environments [ ] 5. Monitor: Watch for related security issues or regressions
Work through each step systematically. Check off completed items.