Step 2 · Remediate
Goal
Fix every finding from the security audit in order of severity — applying targeted patches, rotating secrets, and verifying each fix before closing the finding.
Instructions
You are in workflow step 2 of the security-cycle. Work top-down through the audit report from Step 1. Fix findings individually, test each fix, and do not batch unrelated fixes into a single commit.
Tasks to Perform
1. Fix Dependency CVEs
# Node.js — try automated fix first
npm audit fix # safe semver-compat fixes
npm audit fix --force # allow major-version bumps (review manually)
# Review changes before committing
git diff package-lock.json | head -100
# PHP
composer update vendor/package --with-all-dependencies
# or constrain to patch only
composer update vendor/package --prefer-lowest --prefer-stable
# Python
pip install --upgrade vulnerable-package
pip freeze > requirements.txt
# For packages with no fix yet — evaluate workarounds:
# • Disable the vulnerable feature/API path
# • Add a compensating control (input validation, WAF rule)
# • Pin to last safe version and track in TODO.md
2. Rotate Compromised Secrets
# For each secret found in git history:
# 1. Revoke immediately via the service dashboard (do NOT wait)
# . GitHub: Settings > Developer Settings > Tokens
# . AWS: IAM console > Access Keys
# . Stripe: Dashboard > API Keys > Roll keys
# 2. Remove from source and history
git filter-repo --path config/secrets.txt --invert-paths
# or use BFG Repo Cleaner for large repos
java -jar bfg.jar --delete-files secrets.txt .git
# 3. Add to .gitignore
echo "*.env.local" >> .gitignore
echo "config/secrets.php" >> .gitignore
git add .gitignore && git commit -m "chore: ignore secret files"
# 4. Move secrets to environment variables or a secrets manager
# Azure Key Vault, AWS Secrets Manager, HashiCorp Vault, dotenv-vault
3. Fix Injection Vulnerabilities
# SQL — replace concatenation with prepared statements
# BEFORE (vulnerable)
$result = $db->query("SELECT * FROM users WHERE id = " . $_GET['id']);
# AFTER (safe)
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);
# For ORMs:
User::where('id', request('id'))->first(); # Laravel — safe
User.objects.get(id=user_id) # Django ORM — safe
db.execute("SELECT ... WHERE id = ?", [user_id]) # parameterised — safe
# NEVER: db.execute(f"SELECT ... WHERE id = {user_id}")
# XSS — escape on output
echo htmlspecialchars($user_input, ENT_QUOTES, 'UTF-8'); # PHP
{{ variable }} # Blade/Twig auto-escapes
{variable} # React JSX auto-escapes
# Avoid dangerouslySetInnerHTML / v-html / |safe unless sanitising with DOMPurify
# Command injection
# BEFORE
exec("ping " . $_GET['host']);
# AFTER — use argument escaping or avoid shell entirely
$host = escapeshellarg($_GET['host']);
exec("ping " . $host);
# Best: use language-native libraries instead of shell
4. Fix Authentication & Session Issues
# Upgrade password hashing
# PHP: password_hash($password, PASSWORD_ARGON2ID)
# Python: bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
# Node.js: await bcrypt.hash(password, 12)
# Session fixation — regenerate session on login
session_regenerate_id(true); # PHP
request.session.cycle_key() # Django
# Add rate limiting to auth endpoints
# Express (Node.js)
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({ windowMs: 15*60*1000, max: 10 });
app.post('/login', loginLimiter, loginHandler);
# Add brute-force lockout
# After 10 failed attempts → lock account for 15 min, alert by email
5. Fix Insecure Broken Access Control
# Add auth checks to every sensitive route
# Pattern: explicit deny by default, explicit allow by exception
# PHP middleware example
function requireAuth(Request $request): void {
if (!auth()->check()) {
abort(401);
}
}
function requireOwnership(User $user, Resource $resource): void {
if ($user->id !== $resource->user_id && !$user->isAdmin()) {
abort(403);
}
}
# Never trust user-supplied IDs for ownership — verify on the server
# BEFORE (IDOR):
$order = Order::find($_GET['order_id']);
# AFTER:
$order = Order::where('id', $_GET['order_id'])
->where('user_id', auth()->id())
->firstOrFail();
6. Verify Each Fix
# Run the existing test suite
composer test # PHP
npm test # Node.js
pytest # Python
# Re-run the specific scanner that found the issue
npm audit # should show fewer criticals now
gitleaks detect . # should show no leaked secrets
# Add a regression test for each fixed vulnerability
# e.g. for SQL injection: pass ' OR 1=1-- and assert 404/400, not 200
Exit Criteria
- [ ] All CRITICAL findings resolved and verified
- [ ] All HIGH findings resolved and verified
- [ ] Compromised secrets rotated in all environments
- [ ] No new secrets committed (check with
gitleaks detect .) - [ ] Regression tests added for each fixed finding
- [ ] Audit report updated with resolution notes
- [ ] All changes committed with
fix(security):prefix
Next Step
→ Proceed to Step 3 · Harden