# yaml-language-server: $schema=https://github.com/benedictjohannes/crobe/releases/latest/download/playbook.schema.json title: "Windows Compliance Playbook" sections: - title: "1. OS Maintenance & Security" description: - "Checks for OS updates, official verification, and authentication security." assertions: - code: OS_RECENTLY_UPDATED title: "OS Recently Updated" description: "Verify the system has been updated within the last 30 days." cmds: - exec: shell: "powershell" script: | $history = (New-Object -ComObject Microsoft.Update.Session).CreateUpdateSearcher().QueryHistory(0,1) if ($history) { [Math]::Floor((New-TimeSpan -Start '1970-01-01' -End $history[0].Date.ToUniversalTime()).TotalSeconds) } else { echo "Unknown" } gather: - key: "OS_LAST_UPDATE_TIME" func: | (stdout) => { const val = parseInt(stdout.trim()); return isNaN(val) ? "Unknown" : new Date(val * 1000).toISOString(); } stdOutRule: func: | (stdout, stderr) => { const lastUpdate = parseInt(stdout.trim()); if (isNaN(lastUpdate)) return -1; const thirtyDaysInSeconds = 30 * 24 * 60 * 60; const nowInSeconds = Math.floor(Date.now() / 1000); return (nowInSeconds - lastUpdate) < thirtyDaysInSeconds ? 1 : -1; } passDescription: "System was updated in the last 30 days." failDescription: "System has not been updated in the last 30 days." - code: OS_INTEGRITY title: "OS Integrity Verification" description: "Checks if the OS is recognized from official sources." cmds: - exec: shell: "powershell" script: "(Get-WmiObject Win32_OperatingSystem).Caption" stdOutRule: regex: "Windows" passDescription: "OS is identified as a recognized distribution." failDescription: "OS distribution is not recognized or is non-standard." - code: DEVICE_INFO title: "Device Identification" description: "Gathering hardware identities (Brand, Model, and Serial)." cmds: - exec: script: | $sys = Get-CimInstance Win32_ComputerSystem $bios = Get-CimInstance Win32_BIOS Write-Output "$($sys.Manufacturer)|||$($sys.Model)|||$($bios.SerialNumber)" gather: - key: "DEVICE_BRAND" func: "(stdout) => stdout.split('|||')[0]" - key: "DEVICE_MODEL" func: "(stdout) => stdout.split('|||')[1]" - key: "DEVICE_SERIAL" func: "(stdout) => stdout.split('|||')[2].trim()" stdOutRule: func: (stdout) => stdout.split('|||').length === 3 passDescription: Device information successfully gathered failDescription: Fail to gather device information - title: "2. Identity & Access Control" description: - "Audits login configurations, screen lock, and administrative privileges." assertions: - code: NO_AUTO_LOGIN title: "No Auto Login" description: "Ensures automatic login is disabled." cmds: - exec: shell: "powershell" script: | $val = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name AutoAdminLogon -ErrorAction SilentlyContinue if ($val) { $val.AutoAdminLogon } else { "0" } stdOutRule: regex: "^0$" passDescription: "Auto login is disabled." failDescription: "Auto login appears to be enabled." - code: SCREEN_LOCK_TIMEOUT title: "Screen Lock Timeout" description: "Verify screen lock timeout is under 30 minutes (1800 seconds)." cmds: - exec: shell: "powershell" script: | $policyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" $policy = Get-ItemProperty -Path $policyPath -Name InactivityTimeoutSecs -ErrorAction SilentlyContinue if ($policy -and $policy.InactivityTimeoutSecs) { Write-Output "POLICY:$($policy.InactivityTimeoutSecs)" exit } $ssPath = "HKCU:\Control Panel\Desktop" $timeout = Get-ItemProperty -Path $ssPath -Name ScreenSaveTimeOut -ErrorAction SilentlyContinue $secure = Get-ItemProperty -Path $ssPath -Name ScreenSaverIsSecure -ErrorAction SilentlyContinue if ($timeout -and $secure.ScreenSaverIsSecure -eq "1") { Write-Output "SCREENSAVER:$($timeout.ScreenSaveTimeOut)" exit } # Modern "Require sign-in" lock detection (DelayLockInterval) $delayLock = Get-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name DelayLockInterval -ErrorAction SilentlyContinue if ($null -ne $delayLock) { $videoIdle = powercfg /q SCHEME_CURRENT SUB_VIDEO VIDEOIDLE $acOff = 0 foreach ($line in $videoIdle) { if ($line -like "*Current AC Power Setting Index:*") { $val = $line.Split(':')[-1].Trim() $acOff = [Convert]::ToInt32($val, 16) } } $totalLock = $acOff + [int]$delayLock.DelayLockInterval Write-Output "MODERN:$totalLock" exit } Write-Output "NOT_CONFIGURED" gather: - key: "SCREEN_LOCK_TIMEOUT_SECONDS" func: | (stdout) => { if (!stdout.includes(":")) return "Unknown"; const val = parseInt(stdout.split(":")[1]); return isNaN(val) ? "Unknown" : val; } stdOutRule: func: | (stdout) => { const out = stdout.trim(); if (out === "NOT_CONFIGURED") return -1; const parts = out.split(":"); if (parts.length !== 2) return -1; const val = parseInt(parts[1]); if (isNaN(val)) return -1; return val <= 1800 ? 1 : -1; } passDescription: "Screen lock timeout is configured within safe limits." failDescription: "Screen lock timeout is too long or disabled." - code: USER_PRIVILEGE title: "User is not running as local administrator" description: "Ensures the current user is not local administrator/root." cmds: - exec: shell: "powershell" script: | try { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $sid = $identity.User.Value $username = $identity.Name # Check for the RID 500 (Built-in Administrator) if ($sid -match "-500$") { Write-Output "BUILTIN_ADMIN|$username|$sid" } else { Write-Output "STANDARD|$username|$sid" } } catch { Write-Output "UNKNOWN" } gather: - key: "CURRENT_USER_ID" func: | (stdout) => { const val = stdout.trim(); if (!val.includes("|")) return val; return val.split("|")[0]; } stdOutRule: func: | (stdout) => { const val = stdout.trim(); if (val === "UNKNOWN") return -1; if (val.startsWith("BUILTIN_ADMIN")) return -1; return 1; } passDescription: "User is operating with standard privileges." failDescription: "User is operating with administrative privileges." - title: "3. Disk Security" description: - "Checks for disk encryption status." assertions: - code: ENCRYPTION_STATUS title: "Disk / Storage is Encrypted" description: "Verify that at least one block device is encrypted." cmds: - exec: shell: "powershell" script: | $ErrorActionPreference = "Stop" try { $volumes = Get-BitLockerVolume if ($volumes) { $encrypted = $volumes | Where-Object { $_.ProtectionStatus -eq 'On' } if ($encrypted) { Write-Output "ENCRYPTED" } else { Write-Output "NOT_ENCRYPTED" } exit } } catch {} try { $reg = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\BitLocker" -ErrorAction Stop if ($reg) { Write-Output "ENCRYPTED" exit } } catch {} Write-Output "INSUFFICIENT_PRIVILEGE" gather: - key: "ENCRYPTED_DEVICES" func: "(stdout) => stdout.trim()" stdOutRule: func: | (stdout) => { const val = stdout.trim(); if (val === "ENCRYPTED") return 1; if (val === "NOT_ENCRYPTED") return -1; if (val === "INSUFFICIENT_PRIVILEGE") return 0; return -1; } passDescription: "Encrypted volumes detected." failDescription: "No encrypted volumes found." - title: "4. Threat and Malware Protection" description: - "Checks for Antivirus installation, update status, and real-time protection." assertions: - code: ANTIVIRUS_ACTIVE title: "Antivirus Active" description: "Verify that antivirus is running." cmds: - exec: shell: "powershell" script: "(Get-MpComputerStatus).RealTimeProtectionEnabled" stdOutRule: regex: "True" passDescription: "Antivirus is active" failDescription: "Antivirus is not active or not installed." - code: ANTIVIRUS_UPDATED title: "Antivirus Definitions Up-to-Date" description: "Check if antivirus definitions were updated in the last 30 days." cmds: - exec: shell: "powershell" script: | $date = (Get-MpComputerStatus).AntivirusSignatureLastUpdated if ($date) { [Math]::Floor((New-TimeSpan -Start '1970-01-01' -End $date.ToUniversalTime()).TotalSeconds) } else { echo "Unknown" } gather: - key: "ANTIVIRUS_LAST_UPDATE" func: | (stdout) => { const val = parseInt(stdout.trim()); return isNaN(val) ? "Unknown" : new Date(val * 1000).toISOString(); } stdOutRule: func: | (stdout) => { const stamp = parseInt(stdout.trim()); if (isNaN(stamp)) return -1; const thirtyDays = 30 * 24 * 60 * 60; const now = Math.floor(Date.now() / 1000); return (now - stamp) < thirtyDays ? 1 : -1; } passDescription: "Definitions are up to date." failDescription: "Definitions are older than 30 days or missing." - title: "5. Network Security" description: - "Checks firewall and network protection status." assertions: - code: FIREWALL_ACTIVE title: "Firewall Status" description: "Verify that a firewall is active." cmds: - exec: shell: "powershell" script: | $profiles = Get-NetFirewallProfile -Profile Domain,Public,Private $active = $profiles | Where-Object {$_.Enabled -eq 'True'} if ($active) { "Active (" + ($active | ForEach-Object {$_.Name} | Out-String).Trim() + ")" } else { "Inactive" } gather: - key: "FIREWALL_STATUS" func: "(stdout) => stdout.trim()" stdOutRule: regex: "Active" passDescription: "Firewall is active." failDescription: "No active firewall detected." - title: "6. Boot Security" description: - "Verification of firmware security settings." assertions: - code: SECURE_BOOT_ENABLED title: "Secure Boot State" description: "Check if UEFI Secure Boot is enabled." cmds: - exec: shell: "powershell" script: | try { $result = Confirm-SecureBootUEFI if ($result -eq $true) { "ENABLED" } else { "DISABLED" } } catch { if ($_.Exception.Message -like "*Access was denied*") { "INSUFFICIENT_PRIVILEGE" } else { "NOT_SUPPORTED" } } gather: [] stdOutRule: func: | (stdout) => { const val = stdout.trim(); if (val === "ENABLED") return 1; if (val === "DISABLED") return -1; if (val === "INSUFFICIENT_PRIVILEGE") return 0; if (val === "NOT_SUPPORTED") return 1; return -1; } passDescription: "Secure Boot is enabled." failDescription: "Secure Boot is disabled." - title: "7. Application Integrity" description: - "Audits browser update status." assertions: - code: BROWSER_UP_TO_DATE title: "Browser Updated" description: "Verify if the primary web browsers has been updated within the last 30 days." cmds: - exec: shell: "powershell" script: | $paths = @( "C:\Program Files\Google\Chrome\Application\chrome.exe", "$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe", "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe", "C:\Program Files\Mozilla Firefox\firefox.exe" ) foreach ($p in $paths) { if (Test-Path $p) { $file = Get-Item $p $time = [int](New-TimeSpan -Start '1970-01-01' -End $file.LastWriteTimeUtc).TotalSeconds Write-Output "$p|$time" } } gather: - key: "FIREFOX_LAST_UPDATE" func: | (stdout) => { const line = stdout.trim().split('\n').find(l => l.includes('firefox')); if (!line) return "Not Installed"; return new Date(parseInt(line.split('|')[1]) * 1000).toISOString(); } - key: "CHROME_LAST_UPDATE" func: | (stdout) => { const line = stdout.trim().split('\n').find(l => l.includes('chrome') || l.includes('chromium')); if (!line) return "Not Installed"; return new Date(parseInt(line.split('|')[1]) * 1000).toISOString(); } stdOutRule: func: | (stdout) => { const lines = stdout.trim().split('\n').filter(Boolean); if (lines.length === 0) return -1; const now = Date.now() / 1000; const thirtyDays = 30 * 24 * 60 * 60; let pass = false; lines.forEach(line => { const parts = line.split('|'); const ts = parseInt(parts[1]); if (!isNaN(ts) && (now - ts) < thirtyDays) { pass = true; } }); return pass ? 1 : -1; } passDescription: "At least one browser appears recently updated." failDescription: "No recently updated browsers found." - title: "8. Monitoring & Logging" description: - "Ensures system event logging is active." assertions: - code: LOGGING_ACTIVE title: "System has active logging service" description: "Verify that system logging service is active." cmds: - exec: shell: "powershell" script: "(Get-Service EventLog).Status" stdOutRule: regex: "Running" passDescription: "System logging is active." failDescription: "System logging service is not running."