Skip to content

Continuous Drift Detection and Alerts

Keep the image hardened

A server is hardened at deployment — but configuration changes over time, from updates, administrators, other tooling or misconfiguration. Drift detection watches for those changes and tells you the moment your hardened state regresses, so it can be reviewed, remediated or rolled back.

   Secure at deployment
   Configuration changes  (update · admin · tooling · misconfig)
     Drift detected        (a control regresses PASS -> FAIL)
     Alert generated       (event log · log · optional email)
   Review, remediate or roll back

After hardening, apply writes an expected-state Baseline.json. Drift monitoring re-audits against it and alerts only on regressions (a control that was PASS at deployment and is now FAIL).

.\CloudInfraSecure.ps1 drift enable     # register a daily scheduled task
.\CloudInfraSecure.ps1 drift check      # run now
.\CloudInfraSecure.ps1 drift disable    # remove the task

What counts as drift?

Drift is deliberately about regression from the deployed baseline, not any change in the abstract:

Situation Counts as drift?
A control that was PASS at deployment is now FAIL Yes — this is the core signal (exit 4, alert raised)
A control moves from PASS to WARNING Surfaced in the drift report, but not treated as a hard regression
A control that was already FAIL at deployment is still FAIL No — it never regressed; it was never in the hardened set
A new control added by a product update Evaluated on the next run; a new FAIL is reported so you can decide to apply it
A setting managed by Group Policy overrides the hardened value Yes — it regresses to FAIL like any other control (GPO is a common drift source)
A control becomes NOT APPLICABLE (e.g. host left the domain) No — excluded from scoring, reported as N/A rather than as drift

drift check returns exit code 4 when drift is detected.

Detection

The scheduled task (drift enable) re-audits the deployed baseline daily at drift.time. drift check runs the same audit on demand. On drift it writes an HTML drift report to the reports path and records the regressed controls.

Notification

When drift is detected, CloudInfra Secure raises alerts through several channels at once:

  • A Windows Event Log entry (id 3000) — pick it up with your SIEM or Azure Monitor.
  • A local log line.
  • An HTML drift report saved to the reports path.
  • If configured, an email alert (see below).

Remediation

Drift can be corrected automatically. It is off by default, and when enabled it re-applies only the regressed controls (snapshotting first, so it stays reversible).

One-off, on demand

Run an elevated check with -Remediate to detect and fix in one step:

.\CloudInfraSecure.ps1 drift check -Remediate

Enable automatic remediation (self-healing scheduled check)

  1. Register the scheduled drift task (if you haven't already):
    .\CloudInfraSecure.ps1 drift enable
    
  2. Open the config file in an editor (elevated): C:\ProgramData\Cloud Infrastructure Services\CloudInfra Secure\config.json
  3. In the drift block, set autoRemediate to true:
    "drift": {
      "enabled": true,
      "autoRemediate": true,
      "time": "03:00",
      "attachReport": "csv"
    }
    
  4. Save. From now on the scheduled check re-applies any regressed control automatically, logs it (Event ID 3001), and — if email is configured — the alert states it was auto-remediated.

Confirm it works without waiting for the daily run — trigger the task, then verify:

Start-ScheduledTask -TaskName 'CloudInfraSecure-DriftCheck'
Start-Sleep 20
.\CloudInfraSecure.ps1 verify        # expect drift=PASS

Email alerts

Two transports are supported; choose per environment. When graph.enabled is true it is used, otherwise smtp.

Graph sends over HTTPS as an app (OAuth2 client-credentials), so it works with Security Defaults / MFA enabled, needs no port 25, and is not affected by IP-reputation blocklists.

One-time Entra app registration (entra.microsoft.com → Identity → Applications → App registrations):

  1. New registration → name it (e.g. "CloudInfra Secure Mailer").
  2. API permissions → Add → Microsoft Graph → Application permissions → Mail.SendGrant admin consent (green tick). Application, not Delegated.
  3. Certificates & secrets → New client secret → copy the Value.
  4. From Overview copy the Application (client) ID and Directory (tenant) ID; choose a licensed mailbox as sender.

Edit the config file at C:\ProgramData\Cloud Infrastructure Services\CloudInfra Secure\config.json and set the graph block:

"graph": {
  "enabled":  true,
  "tenantId": "<tenant-guid>",
  "clientId": "<app-client-id>",
  "sender":   "alerts@yourdomain.com",
  "to":       ["secops@yourdomain.com"]
}

Then store the client secret (DPAPI-encrypted) and send a test message:

.\CloudInfraSecure.ps1 config set-graph-secret     # prompts for the client secret
.\Tools\Send-TestEmail.ps1                          # test delivery

Order doesn't matter

config set-graph-secret only stores the encrypted secret — it preserves the rest of your config, so you can edit the graph block before or after running it. (Run each command in its own step; the secret is written straight to config.json and never appears in plaintext.)

SMTP / Direct Send

For non-Microsoft relays or on-prem SMTP. Authenticated relays store the password DPAPI-encrypted via config set-smtp-password <user>. For Direct Send (anonymous, internal recipients only) leave username blank and point server at your tenant's *.mail.protection.outlook.com endpoint on port 25.

Office 365 + basic SMTP

Microsoft 365 tenants with Security Defaults block basic-auth SMTP, and cloud-VM IPs are often blocklisted for Direct Send. Use Graph on those environments.

Alert attachment

The drift email attaches a benign CSV by default (drift.attachReport: "csv"). HTML attachments are commonly flagged/quarantined by antivirus and mail gateways (inline JavaScript + embedded images look like "HTML smuggling"), so the full interactive HTML report is not attached — it is always saved on the host, and its path is shown in the email. Options: "csv" (default), "none", "html".

Change attribution — "who changed it"

Drift reports show when drift was detected and the baseline was established. To also capture who changed a setting, enable registry object-access auditing once (elevated):

.\Tools\Enable-CisChangeAuditing.ps1            # enable (after an apply, so keys exist)
.\Tools\Enable-CisChangeAuditing.ps1 -Disable   # remove later

This enables the Registry audit subcategory and sets audit SACLs on the controlled keys, so changes raise Security event 4657, which drift check correlates to an account. Attribution applies to registry-backed controls.