Files

17 KiB

Self-Service Security Cadence

What you run between our engagements. When something in here surprises you, that's when you call us.

Last updated: June 2026 Produced by: [engagement name / consultant name] For: [client name] — [named admin / IT lead] Next full engagement: [date or "TBD"] Next review of this document: January 2027


What this is

We ran the adversarial validation. We fixed the structural issues we found. The work does not stop when we leave.

This document is your recurring checklist — things you can run yourself, with the tools we set up, on a regular cadence. None of it requires a security background. Most of it takes under an hour per month. The point is to catch drift before it becomes a problem, and to know when to call us before it becomes a crisis.

The most important thing: when something in here produces a result that surprises you, do not sit on it. Log it, screenshot it, and send it to us. The earlier we see a problem the cheaper it is to fix.


Tools you need (all installed during the engagement)

Tool What it does Where to get it
PingCastle Scans Active Directory and produces a security report with a score and specific findings pingcastle.com — free Community edition
Purple Knight Scans Active Directory for indicators of exposure — simpler output than PingCastle, good complement purple-knight.com — free
CAExporter Exports all Conditional Access policies to JSON files you can compare over time github.com/vibecoding/CAExporter
Microsoft Graph PowerShell The PowerShell module for the scripts in this document Install-Module Microsoft.Graph
Microsoft 365 Defender portal alerts.microsoft.com — your alert queue and Secure Score
Microsoft Entra portal entra.microsoft.com — your identity dashboard

The scripts in this document are saved in [location agreed during engagement — e.g., C:\SecurityRunbook\Scripts\].


Monthly checks — 30 to 45 minutes, portal-based

Do these on the first working day of each month. They require no special tools — just a browser logged in as a Global Admin or Security Reader.


M1. Microsoft Secure Score

Where: Microsoft 365 Defender portal > Secure Score

What to do:

  1. Note the current score.
  2. Compare to last month's score (the history graph shows it).
  3. Look at the "Recommended actions" tab — filter to "Not addressed."
  4. Any new items that appeared since last month? Note them.

What you are looking for: Score going down month-over-month without a known reason. New recommended actions you did not create. Completed actions that have reverted to "not addressed" (this means configuration drifted back).

Call us if: Score drops more than 5 points in a month without a documented reason, or if a completed action you remember implementing shows as "not addressed."


M2. Entra ID Recommendations

Where: Entra portal > Overview > Recommendations

What to do:

  1. Look at all open recommendations.
  2. Note any that are new since last month.
  3. Note the impact rating (High / Medium / Low) on new ones.

What you are looking for: New high-impact recommendations that appeared since last month. Specifically watch for anything related to admin accounts, Conditional Access, legacy authentication, or risky sign-ins.

Call us if: Any new High-impact recommendation appears. We will help you assess whether to act immediately or schedule it.


M3. Sign-in risk review

Where: Entra portal > Identity Protection > Risky sign-ins

What to do:

  1. Filter to the last 30 days.
  2. Look at sign-ins with risk level "High" that were not dismissed or remediated.
  3. For any admin account (Global Admin, Exchange Admin, Security Admin) with any risky sign-in event — investigate before dismissing.

What you are looking for: Admin accounts appearing in the risky sign-in list. Any high-risk sign-in that auto-remediated (meaning the user passed an MFA challenge) where the geography or device does not make sense.

Call us if: Any admin account has a risky sign-in event. Any high-risk event that was remediated from an unexpected location.


M4. Alert queue health

Where: Microsoft 365 Defender portal > Incidents & alerts > Alerts

What to do:

  1. Filter to "New" and "In progress" alerts.
  2. How many are sitting open for more than 48 hours?
  3. Are there categories of alert that appear repeatedly? (Recurring alerts on the same user or asset are a pattern, not noise.)

What you are looking for: Alert queue growing over time without being worked. The same alert firing repeatedly on the same account or resource. Any alert tagged as "High severity" that is more than 24 hours old without assignment.

Call us if: A High-severity alert is more than 24 hours old and you do not know what to do with it. Or if the same alert keeps firing on the same account.


M5. New admin assignments

Where: Entra portal > Identity > Roles & admins > All roles > Global Administrator > Assignments

What to do:

  1. Check the current member list against last month's.
  2. Any new members? Were they expected?
  3. Check at minimum: Global Administrator, Exchange Administrator, Security Administrator, SharePoint Administrator.

What you are looking for: Anyone in a privileged role who should not be, or who appeared without a formal request.

Call us if: Any new privileged role assignment you did not authorize or do not recognize.


M6. Break-glass confirmation (30 seconds)

What to do:

  1. Confirm the break-glass account credentials are still in the agreed storage location.
  2. Confirm the contact for "break-glass alert fired" is still the right person.

Do not log in to the break-glass account during this check — any sign-in triggers an alert. Just confirm the credentials are accessible.

Call us if: Credentials cannot be found. Or if the break-glass alert fires without a drill scheduled.


Quarterly checks — 2 to 3 hours, tools required

Do these in the first week of each quarter (January, April, July, October). These require running the installed tools and saving the output.


Q1. PingCastle AD scan

How to run:

  1. Log in to the domain controller (or any domain-joined machine) as a Domain Admin.
  2. Run PingCastle.exe --healthcheck --server <your-domain-FQDN>.
  3. It produces an HTML report. Save it to [agreed location] with the date in the filename: PingCastle-2026-Q3.html.
  4. Open the report and note the score and any findings marked "Critical" or "High."
  5. Compare to the previous quarter's report — is the score going up or down?

What you are looking for: Score trending down quarter-over-quarter. New Critical or High findings that were not present last quarter. Specifically watch the "Stale Objects" section (accounts nobody uses) and the "Privileged Access" section.

Call us if: The score drops more than 10 points since last quarter. Any new Critical finding. Any finding in the "Privileged Access" category that was clean last quarter.


Q2. Purple Knight AD scan

How to run:

  1. Download and run Purple Knight on a domain-joined machine with Domain Admin credentials.
  2. It is a GUI tool — click through the scan, wait for it to finish.
  3. Save the PDF report with the date: PurpleKnight-2026-Q3.pdf.
  4. Look at the "Identity Security Indicators" with status "Exposed" or "Critical."
  5. Compare to the previous quarter.

What you are looking for: New exposed indicators that did not appear last quarter. Any indicator flagged as Critical. The tool is organized by MITRE ATT&CK category — pay particular attention to "Credential Access" and "Privilege Escalation."

Call us if: Any new Critical indicator. Or if the same Medium indicators keep appearing quarter after quarter without being resolved (this means the fix did not stick).


Q3. KRBTGT and AZUREADSSOACC age check

How to run: Open PowerShell as Domain Admin and run the following:

Write-Host "=== KRBTGT ===" -ForegroundColor Cyan
Get-ADUser krbtgt -Properties PasswordLastSet |
  Select-Object @{N="Account";E={"krbtgt"}},
               PasswordLastSet,
               @{N="AgeDays";E={((Get-Date) - $_.PasswordLastSet).Days}}

Write-Host "=== AZUREADSSOACC ===" -ForegroundColor Cyan
Get-ADComputer AZUREADSSOACC -Properties PasswordLastSet -ErrorAction SilentlyContinue |
  Select-Object @{N="Account";E={"AZUREADSSOACC"}},
               PasswordLastSet,
               @{N="AgeDays";E={((Get-Date) - $_.PasswordLastSet).Days}}

Record the age in days in your tracking spreadsheet.

What you are looking for: KRBTGT older than 365 days = P1 (schedule rotation with us). KRBTGT older than 180 days = note and plan. AZUREADSSOACC never rotated since initial sync setup = note.

Call us if: KRBTGT is over 365 days old and there is no scheduled rotation. Or if either account shows a password age younger than expected (meaning someone rotated it without telling you — that is a finding too).


Q4. Cloud-only Global Admins check

How to run:

Connect-MgGraph -Scopes "Directory.Read.All"

$gaRoleId = (Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'").Id
$gaMembers = Get-MgDirectoryRoleMember -DirectoryRoleId $gaRoleId

Write-Host "=== Global Admins ===" -ForegroundColor Cyan
$gaMembers | ForEach-Object {
  $user = Get-MgUser -UserId $_.Id -Property DisplayName,UserPrincipalName,OnPremisesSyncEnabled
  [PSCustomObject]@{
    Name            = $user.DisplayName
    UPN             = $user.UserPrincipalName
    SyncedFromAD    = $user.OnPremisesSyncEnabled
  }
} | Format-Table -AutoSize

Any row where SyncedFromAD is True is a P0 — call us immediately.

What you are looking for: Any Global Admin that is synced from on-prem AD. Any new GA you did not create.

Call us if: Any synced GA appears. Any GA you do not recognize.


Q5. Service principal secrets check — expiring and never-expiring

How to run:

Connect-MgGraph -Scopes "Application.Read.All"

$today = Get-Date
$warningDays = 60

Write-Host "=== Non-expiring secrets ===" -ForegroundColor Red
Get-MgApplication -All | ForEach-Object {
  $app = $_
  $app.PasswordCredentials | Where-Object { $_.EndDateTime -eq $null } | ForEach-Object {
    [PSCustomObject]@{ App = $app.DisplayName; Secret = $_.DisplayName; Expires = "NEVER" }
  }
} | Format-Table

Write-Host "=== Secrets expiring within $warningDays days ===" -ForegroundColor Yellow
Get-MgApplication -All | ForEach-Object {
  $app = $_
  $app.PasswordCredentials | Where-Object {
    $_.EndDateTime -ne $null -and $_.EndDateTime -lt $today.AddDays($warningDays)
  } | ForEach-Object {
    [PSCustomObject]@{ App = $app.DisplayName; Secret = $_.DisplayName; Expires = $_.EndDateTime }
  }
} | Sort-Object Expires | Format-Table

What you are looking for: Non-expiring secrets on any app registration. Secrets about to expire (these will break an application if not rotated — but they also need reviewing: is the app still needed?).

Call us if: You find a non-expiring secret on an app you do not recognize. Or if you find an expiring secret and do not know which application or service it belongs to.


Q6. Stale guest review

How to run:

Connect-MgGraph -Scopes "User.Read.All", "AuditLog.Read.All"

$cutoff = (Get-Date).AddDays(-90)

Get-MgUser -Filter "userType eq 'Guest'" -All -Property DisplayName,Mail,CreatedDateTime,SignInActivity |
  ForEach-Object {
    $lastSignIn = $_.SignInActivity.LastSignInDateTime
    [PSCustomObject]@{
      Name        = $_.DisplayName
      Email       = $_.Mail
      Created     = $_.CreatedDateTime
      LastSignIn  = $lastSignIn
      DaysSinceSignIn = if ($lastSignIn) { ((Get-Date) - $lastSignIn).Days } else { "Never" }
    }
  } |
  Sort-Object DaysSinceSignIn -Descending |
  Format-Table -AutoSize

What you are looking for: Guests who have not signed in for 90+ days. Guests you do not recognize (external parties from concluded projects or former vendors).

Call us if: The count of stale guests is growing quarter-over-quarter and nobody is pruning them. Or if a guest account appears that belongs to an external party from a concluded engagement and still has active access.


How to run: Connect using PnP PowerShell (installed during engagement):

Connect-PnPOnline -Url "https://[tenant]-admin.sharepoint.com" -Interactive

$sites = Get-PnPTenantSite -IncludeOneDriveSites

$anonLinks = foreach ($site in $sites) {
  Connect-PnPOnline -Url $site.Url -Interactive
  Get-PnPSharingLinks | Where-Object { $_.SharingLinkType -eq "Anonymous" } |
    ForEach-Object { [PSCustomObject]@{ Site = $site.Url; Link = $_.ShareLink; Expires = $_.ExpirationDateTime } }
}

Write-Host "Total anonymous links: $($anonLinks.Count)" -ForegroundColor Yellow
$anonLinks | Sort-Object Site | Format-Table

Record the count. Save the export.

What you are looking for: Count increasing quarter-over-quarter (means new anonymous links are being created despite the policy). Links with no expiration date.

Call us if: Count is increasing despite the restriction we put in place. Or if you find anonymous links on sites that hold sensitive data (HR, Finance, M&A).


Q8. CA policy diff — detect drift

How to run:

# CAExporter is set up from the engagement — run from its directory
.\CAExporter.ps1 -ExportPath "C:\SecurityRunbook\CA-Exports\CA-$(Get-Date -Format 'yyyy-MM-dd')"

Then compare this quarter's export folder to last quarter's using any file diff tool (WinMerge, VS Code with the "compare folders" extension, or simply Compare-Object in PowerShell):

$old = Get-ChildItem "C:\SecurityRunbook\CA-Exports\CA-2026-04-01" -File | Select-Object -ExpandProperty Name
$new = Get-ChildItem "C:\SecurityRunbook\CA-Exports\CA-2026-07-01" -File | Select-Object -ExpandProperty Name

Compare-Object $old $new

Then for any policy that changed, open the JSON files and compare manually. The changed lines are the configuration drift.

What you are looking for: Policies deleted since last quarter. Policies whose parameters changed (exclusions added, scope narrowed, MFA grant changed to "grant without controls"). New policies in report-only mode that should have been enabled.

Call us if: Any CA policy has changed without a corresponding change record. A policy that was enforcing is now in report-only mode. A new exclusion was added to a critical policy (legacy auth block, admin MFA, device compliance).


"Call us" trigger list

These are the situations where you stop, take a screenshot, and contact us — even outside a scheduled check:

What you see How urgent What to do first
Break-glass alert fires unexpectedly Immediate Disable any active sessions for the break-glass account, then call us
New Global Admin you did not create Immediate Do not remove it yet — screenshot first, then call us
Synced account in Global Admin role Same day Do not change anything — screenshot and call us
DCSync alert from Defender for Identity Immediate Isolate the source host from the network if possible, then call us
External auto-forward rule found on any executive mailbox Same day Disable the rule, check for mail forwarded, call us
PingCastle score drops more than 10 points Within 48 hours Send us the report alongside the previous quarter's
Any alert sitting at High severity for more than 24 hours you do not know how to triage Within 24 hours Screenshot, note what the alert says, call us
Backup restore fails or produces corrupt data Same day Do not delete anything — call us
Something that feels wrong but is not on this list Use your judgement A wrong feeling is data. Document what you noticed and send it. We will tell you if it is nothing.

Tracking spreadsheet columns

Keep a simple spreadsheet (Excel or SharePoint list) with one row per check per quarter:

Date Check Result / Count vs. Last Quarter Action taken Escalated to consultant?

The trend matters more than any individual value. A metric that is consistently getting worse is a finding even if no single value crosses a threshold.


When to schedule the next full engagement

Use this as a rule of thumb:

  • Annual: Full adversarial validation (the engagement that produced this document). Recommended even if the monthly and quarterly checks are clean — they catch drift, not adversarial paths.
  • Triggered: Any time a "call us immediately" event fires, or PingCastle / Purple Knight produces a new Critical finding.
  • Project-triggered: Before any major change to the estate — AD migration, new cloud service onboarding, M365 license change, acquisition or merger, significant IT staff change.

Self-service cadence for [client name]. Produced June 2026. Review and update January 2027 alongside the field guide update.