Patching Strategy


Introduction

Patching is the most fundamental security practice, yet organizations consistently struggle with timely patch deployment. The challenge is balancing speed against stability — deploying patches too slowly leaves systems vulnerable, while deploying too quickly risks breaking critical applications. A mature patching strategy addresses both sides of this equation.

Vulnerability Prioritization

Not all vulnerabilities are equal. Prioritize based on exploitability, asset criticality, and threat intelligence.




from dataclasses import dataclass


from enum import Enum




class Severity(Enum):


CRITICAL = 1


HIGH = 2


MEDIUM = 3


LOW = 4




@dataclass


class Vulnerability:


cve_id: str


cvss_score: float


asset_criticality: int # 1-5


exploit_available: bool


in_wild: bool


affected_systems: int




def prioritize(vuln: Vulnerability) -> int:


score = vuln.cvss_score




# Asset criticality multiplier


score *= (vuln.asset_criticality / 3)




# Exploit availability


if vuln.exploit_available:


score *= 1.5


if vuln.in_wild:


score *= 2.0




# Reachability


if vuln.affected_systems > 100:


score *= 1.3




return round(score, 1)





Prioritization Matrix




patch_priority:


critical:


- cvss_score: ">= 9.0"


- exploit_in_wild: true


- sla_hours: 24


- process: emergency_change




high:


- cvss_score: "7.0 - 8.9"


- exploit_available: true


- sla_days: 7


- process: standard_change_fast_track




medium:


- cvss_score: "4.0 - 6.9"


- sla_days: 30


- process: standard_change




low:


- cvss_score: "< 4.0"


- sla_days: 90


- process: next_maintenance_window





Patch Testing Pipeline

Test patches in a progressive environment chain before production deployment.




patch_pipeline:


stages:


- environment: development


validation:


- automated_tests_pass


- integration_tests_pass


rollback: immediate_redeploy




- environment: staging


validation:


- regression_tests_pass


- performance_tests_pass


- security_scan_pass


duration: 24_hours




- environment: canary


validation:


- error_rate_below_baseline


- latency_below_baseline


- no_critical_alerts


duration: 4_hours


percentage: 10%




- environment: production


strategy: rolling_update


max_surge: 25%


max_unavailable: 0








#!/bin/bash


# Automated patch testing script


set -euo pipefail




PATCH_ID="$1"


ENVIRONMENTS=("dev" "staging" "canary" "prod")




for env in "${ENVIRONMENTS[@]}"; do


echo "=== Deploying patch $PATCH_ID to $env ==="




# Apply patch


if ! ansible-playbook -i inventories/$env patch-playbook.yml \


-e patch_id=$PATCH_ID; then


echo "FAILED: Patch deployment to $env"


rollback_patch $env $PATCH_ID


exit 1


fi




# Run validation


if ! run_validation_tests $env; then


echo "FAILED: Validation in $env"


rollback_patch $env $PATCH_ID


exit 1


fi




# Monitor for issues


sleep $([ "$env" == "production" ] && echo 3600 || echo 300)




if check_alerts $env; then


echo "ALERTS detected in $env after patch"


rollback_patch $env $PATCH_ID


exit 1


fi




echo "=== Patch $PATCH_ID passed in $env ==="


done





Emergency Patches

Zero-day exploits require immediate action, bypassing normal testing cycles.




emergency_patch_workflow:


trigger: active_exploitation_of_critical_vulnerability




steps:


- phase: assessment


duration: 1_hour


actions:


- confirm_exploit_activity


- identify_affected_systems


- assess_business_impact




- phase: decision


duration: 30_minutes


actions:


- ciso_approval


- change_advisory_board_notification


- communication_plan_activation




- phase: deployment


duration: 2_hours


actions:


- deploy_workaround_virtual_patch


- deploy_vendor_patch_to_staging


- fast_track_testing


- deploy_to_production


- monitor_for_side_effects




- phase: recovery


duration: 24_hours


actions:


- continuous_monitoring


- post_incident_review


- remove_virtual_patches_if_applicable








# Virtual patching via WAF as immediate stopgap


# Deploy WAF rule to block exploit attempts


cat > /etc/modsecurity/crs/custom/emergency-sqli.conf << 'EOF'


SecRule REQUEST_FILENAME "@contains /api/endpoint" \


"id:9999999,phase:2,deny,status:403,\


msg:'Emergency patch: CVE-2026-1234 exploitation attempt blocked'"


EOF


nginx -t && systemctl reload nginx





Rollback Planning

Every patch must have a tested rollback procedure.




class PatchRollbackPlan:


def __init__(self, patch_id, system, version_before, version_after):


self.patch_id = patch_id


self.system = system


self.version_before = version_before


self.version_after = version_after


self.rollback_commands = []


self.validation_commands = []




def add_rollback_step(self, command):


self.rollback_commands.append(command)




def execute_rollback(self):


"""Execute rollback if patch causes issues."""


for cmd in self.rollback_commands:


result = subprocess.run(cmd, shell=True, capture_output=True)


if result.returncode != 0:


raise Exception(f"Rollback failed: {cmd}")


return False


return True




def validate_rollback(self):


"""Verify system is back to pre-patch state."""


for cmd in self.validation_commands:


result = subprocess.run(cmd, shell=True, capture_output=True)


if not self._check_expected(result):


return False


return True





Conclusion

A mature patching strategy prioritizes based on exploitability and asset criticality, tests patches in progressive environments, maintains emergency procedures for zero-day vulnerabilities, and always plans for rollback. Automate as much of the pipeline as possible — manual patching does not scale and is prone to error in high-pressure situations.