Opinionated, automation-centric rules for building reliable, auditable patch-management workflows on macOS using Bash/Python scripting and Jamf-based MDM pipelines.
If you're still manually coordinating macOS updates across hundreds of devices, clicking through Jamf policies one by one, or worse—relying on users to install critical security patches—you're burning time that could be spent on strategic work. These Cursor Rules transform your patch management from reactive firefighting into a predictable, automated pipeline that handles everything from discovery to compliance verification.
Every macOS admin knows this frustration: A critical CVE drops on Tuesday, you spend Wednesday manually configuring smart groups and policies, Thursday troubleshooting why 30% of devices failed to update, and Friday explaining to security why compliance is still at 73%. Meanwhile, users defer updates indefinitely, pilot deployments require manual coordination, and you have zero visibility into what actually happened during installations.
Traditional approaches break down because they treat patch management as a collection of manual tasks instead of an automated workflow. You end up with:
These Cursor Rules implement a complete automation framework that treats your entire patch lifecycle as code. Instead of clicking through MDM interfaces, you define policies in Git, deploy through CI/CD pipelines, and let automated workflows handle the complexity.
Here's what changes immediately:
Before: Manually creating Jamf policies for each patch, hoping users install updates, scrambling when critical CVEs need immediate deployment.
After: Git commit triggers automated policy creation, staged rollouts deploy to pilot→canary→production with automatic halt on failures, compliance dashboards show real-time status across your entire fleet.
The system automatically prioritizes CVSS 9+ vulnerabilities, schedules installations during off-hours, validates checksums and signatures, maintains full audit trails, and handles rollbacks when things go wrong.
Instead of spending 4-6 hours per patch cycle coordinating deployments, you define policies once in code and let automation handle the rollout. Critical security patches deploy within hours instead of weeks.
Maintain 96%+ compliance automatically through tiered rollouts that catch issues early. No more emergency weekend work when audits reveal compliance gaps.
CVSS 9+ vulnerabilities automatically bypass user deferrals and install within your defined maintenance windows. Security team gets compliance without requiring user cooperation.
Every patch installation, rollback, and policy change is logged to syslog and your SIEM with retention policies that satisfy compliance requirements. Auditors get the data they need without manual report generation.
Old Process: Security team alerts about critical Safari vulnerability → manually create smart group → configure policy → test on pilot devices → manually deploy to production → manually verify compliance.
New Process: Security team creates GitHub issue → PR adds patch to critical updates list → CI validates policy → automated deployment to pilot pool → automatic promotion to production on success → compliance dashboard updates in real-time.
# Automated critical patch deployment
critical_patch_deploy() {
local patch_id="$1" severity="$2"
if [[ "$severity" == "Critical" ]]; then
# Skip user deferral for CVSS 9+
create_policy "$patch_id" --force-install --maintenance-window "22:00-04:00"
deploy_to_group "pilot-pool" "$patch_id"
monitor_success_rate "$patch_id" --threshold 98
fi
}
Old Process: Plan deployment windows → manually configure multiple policies → coordinate with teams → monitor installations manually → troubleshoot failures individually.
New Process: Define rollout parameters in configuration → automated deployment to 5% pilot pool → automatic progression to 20% canary on success → full deployment with automatic rollback on >2% failure rate.
class PatchRollout:
def __init__(self, patch_id: str, rollout_config: dict):
self.patch_id = patch_id
self.stages = rollout_config["stages"] # [5, 20, 100]
def deploy_stage(self, stage_percent: int) -> bool:
success_rate = self.jamf.deploy_to_percent(self.patch_id, stage_percent)
if success_rate < 98.0:
self.rollback_stage(stage_percent)
return False
return True
Old Process: Manually check for Chrome/Firefox updates → download installers → test installations → create deployment packages → configure policies → monitor results.
New Process: Automated discovery checks vendor APIs → validates signatures and checksums → creates deployment packages → tests in staging environment → deploys through standard pipeline.
mkdir -p macos-patching/{scripts,policies,configs}
cd macos-patching
# Create core directory structure
mkdir -p scripts/{pilot,canary,production}
mkdir -p policies/{critical,standard,feature}
mkdir -p configs/{jamf,smart-groups,compliance}
Place these validated, production-ready scripts in your scripts directory:
#!/usr/bin/env bash
# scripts/core/patch-orchestrator.sh
set -euo pipefail
IFS=$'\n\t'
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly LOG_FILE="/var/log/patch-orchestrator.log"
run_patch_cycle() {
local patch_manifest="$1"
# Validate prerequisites
check_disk_space 3 # 3GB minimum
check_battery_level 50 # 50% minimum
# Process each patch in manifest
while IFS= read -r patch_line; do
local patch_id severity group
IFS=',' read -r patch_id severity group <<< "$patch_line"
deploy_patch "$patch_id" "$severity" "$group"
done < "$patch_manifest"
}
deploy_patch() {
local patch_id="$1" severity="$2" group="$3"
printf "Deploying patch %s to %s group\n" "$patch_id" "$group" | tee -a "$LOG_FILE"
if ! create_jamf_policy "$patch_id" "$group"; then
rollback_patch "$patch_id"
return 1
fi
monitor_deployment "$patch_id" "$group"
}
Configure your Jamf API integration with proper error handling and retry logic:
# configs/jamf_client.py
from typing import Dict, Optional
import requests
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
class JamfProClient:
def __init__(self, base_url: str, token: str):
self.base_url = base_url.rstrip('/')
self.session = requests.Session()
# Configure retry strategy
retry_strategy = Retry(
total=3,
backoff_factor=2,
status_forcelist=[429, 500, 502, 503, 504]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
self.session.headers.update({
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
})
def create_patch_policy(self, patch_data: Dict) -> Optional[int]:
"""Create patch policy and return policy ID."""
response = self.session.post(
f'{self.base_url}/JSSResource/policies/id/0',
json=patch_data
)
response.raise_for_status()
return response.json().get('policy', {}).get('id')
Define your smart groups as code for consistent targeting:
<!-- configs/smart-groups/critical-updates-needed.xml -->
<computer_group>
<name>Needs-Critical-Security-Updates</name>
<is_smart>true</is_smart>
<criteria>
<criterion>
<name>Patch Available</name>
<search_type>is</search_type>
<value>true</value>
</criterion>
<criterion>
<name>Patch Severity</name>
<search_type>is</search_type>
<value>Critical</value>
</criterion>
</criteria>
</computer_group>
Add GitHub Actions workflow for automated policy deployment:
# .github/workflows/patch-deployment.yml
name: Patch Policy Deployment
on:
push:
paths: ['policies/**']
jobs:
deploy-policies:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Validate Policy Syntax
run: scripts/validate-policies.sh
- name: Deploy to Jamf Pro
env:
JAMF_URL: ${{ secrets.JAMF_URL }}
JAMF_TOKEN: ${{ secrets.JAMF_TOKEN }}
run: python scripts/deploy-policies.py
The transformation is immediate and measurable. Your patch management evolves from reactive crisis management to predictable, automated operations that scale with your organization's growth.
These Cursor Rules don't just organize your patch management—they transform it into the reliable, automated foundation your security and compliance programs depend on.
You are an expert in macOS patch management, Bash 5+, Python 3.11, Jamf Pro, Munki, and REST-based MDM automation.
Key Principles
- Automate end-to-end: discovery → approval → staged rollout → compliance verification → reporting.
- Critical-updates-first: security & CVE patches always outrank feature upgrades.
- Tiered rollout (Pilot ▶ Canary ▶ Wide): deploy to 5%/20%/100% pools with auto-halt on failure > 2 %.
- Off-hours scheduling (22:00–04:00 local) to minimize user impact; force-install hard deadlines only for CVSS ≥ 9.
- Idempotent scripts: every run reaches the desired state regardless of current state.
- Everything is code: patch policies, smart groups, and approval lists live in Git and travel through PRs + CI.
- Zero trust: every download is checksum-validated and signature-verified.
- Full audit trail: log to syslog + central SIEM; retain 1 year minimum.
Bash (zsh-compatible)
- Always start scripts with:
```bash
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
```
- Use shellcheck-clean syntax; run `shellcheck -xo all` in CI.
- Functions: lower_snake_case; globals in ALL_CAPS; constants are readonly.
- Use `printf` instead of `echo` for predictable output.
- Wrap `softwareupdate`:
```bash
run_updates() {
local label="$1" log_file="/var/log/patch-$label.log"
/usr/sbin/softwareupdate --install "$label" --verbose 2>&1 | tee -a "$log_file"
}
```
- Always write exit-code–aware wrappers for Apple installers:
```bash
sudo installer -pkg "$PKG" -target / || rollback "$PKG"
```
Python 3.11
- Use type hints, `mypy --strict`, `ruff` for linting.
- HTTP calls: `requests` wrapped in 3-retry exponential backoff (`urllib3.Retry`).
- Encapsulate Jamf Pro API calls in a dedicated class:
```python
class JamfAPI: # noqa: D101
_base = "https://jamf.company.com/JSSResource"
def __init__(self, token: str):
self.session = Session()
self.session.headers.update({"Authorization": f"Bearer {token}"})
def get_patch_report(self, id_: int) -> dict: # noqa: D401
r = self.session.get(f"{self._base}/patchreport/id/{id_}")
r.raise_for_status()
return r.json()
```
- CLI entrypoints via `typer` for uniform UX; produce zero exit code on success, non-zero on failure.
Error Handling & Validation
- Fail fast: check network reachability, free disk space (>3 GB), and battery (>50 %) before installation.
- Trap signals in Bash (`trap cleanup EXIT ERR INT`) to ensure temp files removed.
- Maintain rollback catalog: revert using `softwareupdate --ignore` or archived `pkgutil --forget` receipts.
- Alerting:
- Jamf smart group “Patch-Failed > 0” triggers Slack/Webhook.
- Retry logic: 3 attempts max, exponential (5 min → 30 min → 2 h).
Jamf Pro Framework Rules
- Define smart groups:
- `Needs-Critical-Update`: patch_available == true & severity == Critical.
- `Pilot-Pool`: site == "HQ" & department == "IT".
- Patch Policy template:
- Scope: dynamic group.
- Restart: user-deferrable ≤ 72 h, force thereafter.
- Self Service description MUST contain release notes + link to CVE list.
- Use Jamf’s API to version-control policies: export JSON → Git → PR → GitHub Actions → `jamf-upload`.
- Post-install script uploads install log to Jamf Pro’s computer record for audit.
Additional Sections
Testing & Rollout
- Stage patches in a Munki repo titled `testing`: devices opt-in via config profile.
- Canary metrics: target success ≥ 98 %, kernel panics < 0.1 %.
- Use VMware Fusion or AWS EC2 Mac instances for destructive OS beta testing.
Performance & Scheduling
- Throttle bandwidth via `softwareupdate --max-download-size 7000000` (bytes/sec) on VPN users.
- Cache installers on branch-office Mac mini caching servers.
Security
- Validate Apple catalogs’ `trustd` stapled signatures.
- Enforce TLS 1.2+ for all internal artifact downloads.
- Store API tokens in macOS keychain; `security delete-generic-password` on logout hooks.
Compliance & Reporting
- Minimum compliance threshold: 96 % devices patched <30 days.
- Daily job publishes JSON report to Tableau; schema: device_id, patch_level, last_success_ts.
- Keep CIS benchmarks alignment: 1.5.0 §5.9 – "Ensure software updates are installed."
Documentation & Naming
- Patch IDs: `yyyy-mm-dd_appname_version` (e.g., `2024-02-11_chrome_122.0.6261.2`).
- Scripts in repo `macos-patching/scripts/<tier>/<purpose>.sh`.
- README of each patch includes: CVEs fixed, reboot flag, checksum (SHA-256).