Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Patch management is the systematic process of identifying, testing, deploying, and verifying software updates to remediate vulnerabilities across an organization's IT infrastructure. An effective patc
.claude/skills/implementing-patch-management-workflow/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-20 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-08 | ✗→✓ | ▲ Improved | — | — |
| case-15 | ✗→✓ | ▲ Improved | — | — |
| case-16 | ✗→✓ | ▲ Improved | — | — |
Patch management is the systematic process of identifying, testing, deploying, and verifying software updates to remediate vulnerabilities across an organization's IT infrastructure. An effective patch management workflow reduces the attack surface while minimizing operational disruption through structured testing, approval gates, and phased rollouts.
| Ring | Environment | % of Fleet | Soak Time | Purpose | |------|------------|------------|-----------|---------| | Ring 0 | Lab/Test | N/A | 24-48 hrs | Functional validation | | Ring 1 | IT Early Adopters | 5% | 48-72 hrs | Real-world pilot | | Ring 2 | Business Pilot | 15% | 5-7 days | Broader compatibility | | Ring 3 | General Deployment | 50% | 7-14 days | Main rollout | | Ring 4 | Mission Critical | 30% | After Ring 3 | Final deployment |
bash# WSUS (Windows Server Update Services) # Configure WSUS server to sync with Microsoft Update # Via PowerShell on WSUS server: Install-WindowsFeature -Name UpdateServices -IncludeManagementTools & "C:\Program Files\Update Services\Tools\WsusUtil.exe" postinstall CONTENT_DIR=D:\WSUS # Configure GPO for WSUS clients # Computer Configuration > Administrative Templates > Windows Components > Windows Update # Specify intranet Microsoft update service location: http://wsus-server:8530
yaml# Ansible: Configure patch repositories for Linux # roles/patch-management/tasks/configure_repos.yml --- - name: Configure RHEL patch repository yum_repository: name: rhel-patches description: RHEL Security Patches baseurl: https://satellite.corp.local/pulp/repos/patches gpgcheck: yes gpgkey: file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release enabled: yes - name: Configure Ubuntu patch sources apt_repository: repo: "deb https://apt-mirror.corp.local/ubuntu {{ ansible_distribution_release }}-security main" state: present when: ansible_os_family == "Debian"
python# patch_assessment.py - Correlate vulnerability scans with available patches import subprocess import platform import json def get_windows_pending_patches(): """Query Windows Update for pending patches via PowerShell.""" ps_cmd = """ $Session = New-Object -ComObject Microsoft.Update.Session $Searcher = $Session.CreateUpdateSearcher() $Results = $Searcher.Search("IsInstalled=0 AND Type='Software'") $Results.Updates | ForEach-Object { [PSCustomObject]@{ Title = $_.Title KB = ($_.KBArticleIDs -join ',') Severity = $_.MsrcSeverity Size = [math]::Round($_.MaxDownloadSize / 1MB, 2) Published = $_.LastDeploymentChangeTime.ToString('yyyy-MM-dd') CVE = ($_.CveIDs -join ',') } } | ConvertTo-Json """ result = subprocess.run( ["powershell", "-Command", ps_cmd], capture_output=True, text=True, timeout=120 ) return json.loads(result.stdout) if result.stdout.strip() else [] def get_linux_pending_patches(): """Query package manager for available security updates.""" if platform.system() != "Linux": return [] # Try apt (Debian/Ubuntu) try: result = subprocess.run( ["apt", "list", "--upgradable"], capture_output=True, text=True, timeout=60 ) packages = [] for line in result.stdout.strip().split("\n")[1:]: if line: parts = line.split("/") packages.append({ "package": parts[0], "available_version": parts[1].split()[0] if len(parts) > 1 else "", "source": "apt" }) return packages except FileNotFoundError: pass # Try yum/dnf (RHEL/CentOS) try: result = subprocess.run( ["dnf", "updateinfo", "list", "security", "--available"], capture_output=True, text=True, timeout=60 ) packages = [] for line in result.stdout.strip().split("\n"): parts = line.split() if len(parts) >= 3: packages.append({ "advisory": parts[0], "severity": parts[1], "package": parts[2], "source": "dnf" }) return packages except FileNotFoundError: return []
yaml# Ansible playbook: test_patches.yml --- - name: Test Patches in Lab Environment hosts: test_servers become: yes vars: rollback_snapshot: "pre-patch-{{ ansible_date_time.date }}" tasks: - name: Create VM snapshot before patching community.vmware.vmware_guest_snapshot: hostname: "{{ vcenter_host }}" username: "{{ vcenter_user }}" password: "{{ vcenter_pass }}" datacenter: "{{ datacenter }}" name: "{{ inventory_hostname }}" snapshot_name: "{{ rollback_snapshot }}" state: present delegate_to: localhost - name: Apply security patches (RHEL/CentOS) dnf: name: "*" state: latest security: yes update_cache: yes when: ansible_os_family == "RedHat" register: patch_result - name: Apply security patches (Ubuntu/Debian) apt: upgrade: dist update_cache: yes only_upgrade: yes when: ansible_os_family == "Debian" register: patch_result - name: Reboot if required reboot: reboot_timeout: 600 msg: "Rebooting for patch installation" when: patch_result.changed - name: Run post-patch validation include_tasks: validate_services.yml - name: Report patch results debug: msg: "Patching {{ 'succeeded' if patch_result.changed else 'no updates' }} on {{ inventory_hostname }}"
yaml# deploy_patches.yml - Phased production rollout --- - name: Ring 1 - IT Early Adopters hosts: ring1_hosts serial: "25%" max_fail_percentage: 10 become: yes tasks: - import_tasks: apply_patches.yml - import_tasks: validate_services.yml - name: Wait for soak period pause: hours: 48 run_once: true - name: Ring 2 - Business Pilot hosts: ring2_hosts serial: "20%" max_fail_percentage: 5 become: yes tasks: - import_tasks: apply_patches.yml - import_tasks: validate_services.yml - name: Ring 3 - General Deployment hosts: ring3_hosts serial: "10%" max_fail_percentage: 3 become: yes tasks: - import_tasks: apply_patches.yml - import_tasks: validate_services.yml
Run a post-patch vulnerability scan to confirm patch installation:
bash# Trigger post-patch verification scan curl -k -X POST "https://nessus:8834/scans/$VERIFY_SCAN_ID/launch" \ -H "X-Cookie: token=$TOKEN" # Compare pre-patch and post-patch results # Expecting reduction in vulnerabilities matching deployed patches
| Severity | SLA (Internet-Facing) | SLA (Internal) | SLA (Air-Gapped) | |----------|----------------------|----------------|-------------------| | Critical (CVSS 9+) | 48 hours | 7 days | 14 days | | High (CVSS 7-8.9) | 7 days | 14 days | 30 days | | Medium (CVSS 4-6.9) | 30 days | 30 days | 60 days | | Low (CVSS 0.1-3.9) | 90 days | 90 days | 90 days |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +39 percentage points is the difference between those two pass rates over the 23 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.