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