Skip to main content

Overview

This lab uses Microsoft’s GigaWiper research as a case study for the July 2026 preview of Defender XDR custom detection rules as code. It includes five rule definitions, a disabled deployment canary, GitHub validation, hunting queries, synthetic KQL fixtures, a bounded Windows telemetry generator, and a manual Graph-only fallback after the native preview path returned the result observed July 11–12, 2026.

Blog post: GigaWiper Detection as Code: Testing the Sentinel Repositories Preview

Companion repository: j-dahl7/gigawiper-detection-as-code

Current source-audit boundary (August 13, 2026): the companion repository’s offline Test-Lab.ps1 -Json suite passed against the current source. The July 25 read-only Advanced Hunting synthetic attempt stopped before query execution when Defender returned Account mode is inactive. No fresh live deployment, telemetry, scheduler, alert, or cleanup result is claimed; the live evidence on this page remains dated July 11–14.

Safety boundary

The lab does not download malware, wipe disks, encrypt files, delete boot files, disable recovery, or clear operational Windows logs. Boot/recovery behavior is validated only with synthetic KQL rows.

What the pack defines

IDDetectionDesired stateEvidence path
nls-gw-000-canaryDeployment canaryDisabledExact-ID API read-back
nls-gw-001-onedrive-persistenceOneDrive-lookalike task and registry persistenceEnabledCurrent query re-matched live telemetry; the separate scheduled portal alert used the validation revision
nls-gw-002-recovery-boot-tamperingRecovery and boot tamperingEnabledSynthetic only
nls-gw-003-event-log-destructionWindows event-log clearingEnabledObserved benign custom-log telemetry plus a separate portal-native scheduled alert
nls-gw-004-minio-transfer-stagingUnusual MinIO client stagingEnabledFilename-only telemetry plus a separate portal-native NRT alert
nls-gw-005-candy-rename-burst.candy rename burstEnabledCurrent optimized query matched live telemetry; no saved portal rule or alert

During validation, the six exact IDs were read back in the validation revision’s desired state through the bounded Graph fallback. The disabled canary and five enabled behavior rules had zero automatedActions and zero deprecated responseActions. Alert evidence below belongs only to three separately created portal-native validation rules. The six Graph objects and three portal rules were deleted after evidence capture; follow-up service reads returned 404.

Revision boundary: the current NLS-GW-001 post-correlation deduplication and optimized NLS-GW-005 time-window join were independently re-run read-only against retained live telemetry. Neither current revision was applied to the six validation Graph objects before retirement or to the portal-native validation rules, and no new alert is attributed to either revision.

Quick start

If this is the first subscription in the tenant to activate Defender for Endpoint through Defender for Cloud, allow for service activation and possible telemetry-ingestion delay. Do not generate validation telemetry until the machine API reports the endpoint Onboarded and Active and Advanced Hunting returns DeviceInfo.

git clone https://github.com/j-dahl7/gigawiper-detection-as-code.git
cd gigawiper-detection-as-code
git checkout 643e0f445478b992ecddc4de3a6d8625633b42dc

./scripts/Test-Lab.ps1

The validation workflow compiles all six templates and checks the stable-ID, single-tactic, query-column, fixture, endpoint, fallback, and safety contracts.

In a dedicated lab tenant, fork the lab into a repository you control and connect that fork through Microsoft Sentinel > Content management > Repositories and select Custom Detection Rules to examine the native preview path. In the July 11–12 validation window, that generated path failed all six corrected templates with outer InvalidTemplateDeployment and inner ProviderError: Encountered internal server error. Because the root cause remains unconfirmed, broader role assignments are not a validated workaround.

If you intentionally use the temporary fallback, first follow docs/GRAPH-FALLBACK.md to create your own dedicated Entra application, GitHub OIDC credential, and environment. The identity must have only the Microsoft Graph application permission CustomDetection.ReadWrite.All and no Azure RBAC assignment. Then manually run Deploy custom detections - preview fallback from main, enter the exact confirmation DEPLOY_PREVIEW_FALLBACK, deploy the disabled canary first, and select All only after its exact-ID read-back succeeds. Do not run the fallback concurrently with a working native synchronization path. The current fallback also requires the exact repository ownership marker and zero response actions before any write; it refuses to adopt or update an unmarked same-ID rule.

Run safe telemetry only from an elevated session on a disposable MDE endpoint:

./scripts/Invoke-SafeGigaWiperTelemetry.ps1

Cleanup

./scripts/Invoke-SafeGigaWiperTelemetry.ps1 -CleanupOnly -WhatIf
./scripts/Invoke-SafeGigaWiperTelemetry.ps1 -CleanupOnly

Both telemetry previews perform the same read-only collision and ownership checks as the real operation. Cleanup refuses an unexpected scheduled-task action, registry marker, directory entry, or custom event-log source and does not recursively remove a non-empty directory.

The published validation environment’s control-plane retirement is complete. For a new run, repeat these exact-scope cleanup checks:

  • The validation Sentinel Repository connection, its identities, assignments, secrets, and branch-scoped GitHub OIDC grants were removed. Deselecting Custom Detection Rules only stops that content type and is not identity cleanup.
  • All six exact nls-gw-* custom detection IDs were deleted through an exact-ID operation, and follow-up reads returned 404. The fallback intentionally has no delete or prune mode; future cleanup requires a separately authorized path.
  • Portal rules NLS-GW-LIVE-001, NLS-GW-LIVE-003, and NLS-GW-LIVE-004 were deleted separately. A hard refresh showed zero matching rules, and their service reads returned 404. There was never a LIVE-005 object.
  • The dedicated fallback app registration, service principal, federated credential, Graph grant, and GitHub environment variables were removed after the rule reads confirmed retirement.
  • The credential-less nls-gigawiper-validation-20260711 diagnostic app, service principal, and tenant-wide Graph grants were removed after the final audit.
  • The dedicated disposable Azure resource group used for endpoint validation was deleted after evidence capture. For a new run, supply an explicitly reviewed image version to New-LabEndpoint.ps1; it writes a local manifest and unique Azure ownership tags before creation. Reuse is allowed only for an empty manifest-owned group still in the planned state; clean up and recreate after a partial or completed deployment. Preview and run Remove-LabEndpoint.ps1 -ConfirmResourceGroup <exact-name> for cleanup. The helper refuses deletion unless the manifest, tenant, subscription, Azure ID, and both unique tags all match, and it removes the manifest only after Azure reports the group absent. Use the exact Az module versions documented by the companion repository for both lifecycle helpers.

Current validation status

Live tenant validation ran July 11–12, 2026; repository hardening and CI were revalidated July 13, cleanup state was reverified July 14, and the current source-only audit passed August 13 with the July 25 live API limitation stated above:

  • The Bicep pack, endpoint template, five positive plus five negative exact-query fixture contracts, fallback controls, and safety tests pass locally. The separate read-only synthetic runner’s July 12 historical run passed all ten rows through Advanced Hunting; the July 25 attempt did not execute because Defender returned the inactive account-mode response stated above.
  • During validation, the native Repository connection targeted main, used the CustomDetection content type, and generated its expected OIDC workflow. Its identity held the documented Graph permission and could read a rule by exact ID. The validation connection was subsequently removed; the tenant currently has no Sentinel Repository connection for this lab.
  • The connection-generated native workflow and helper are retained outside .github/workflows as non-reusable evidence artifacts. They cannot run from the public repository. A native retest requires a new Repository connection, review of its freshly generated files, and a deliberate ownership transfer away from the Graph fallback.
  • Native synchronization returned outer InvalidTemplateDeployment and inner ProviderError for all six corrected templates. That non-specific native-path response is distinct from the earlier Graph InvalidInput - Only one tactic is currently supported contract error, which was corrected and added to CI.
  • Manual Graph-only GitHub OIDC fallback run 29180593038 succeeded. All six validation-revision IDs returned HTTP 200 and passed semantic read-back: five rules enabled, the canary disabled, and zero response actions on every rule. The later current NLS-GW-001 and NLS-GW-005 revisions were not applied to those validation objects before they were deleted.
  • During validation, the fallback identity had only CustomDetection.ReadWrite.All, no stored secret, and zero Azure RBAC assignments. The workflow operates only on the exact six IDs, uses GET plus POST or PATCH, and has no delete or prune path. The fallback application, service principal, federated credential, Graph grant, and environment variables were removed after exact rule retirement was verified.
  • During validation, the disposable endpoint had default inbound deny and no custom inbound NSG rules. The Defender machine API reported it Onboarded and Active, and Advanced Hunting received DeviceInfo. Its dedicated Azure resource group was deleted after evidence capture on July 14, 2026; the retained telemetry and screenshots remain historical evidence.
  • The four bounded telemetry jobs exited successfully. The exact checked-in KQL produced live benign matches for NLS-GW-001, NLS-GW-003, NLS-GW-004, and NLS-GW-005. Seven inert .candy rename rows satisfied the burst query after an ingestion delay. NLS-GW-002 remains synthetic-only by design.
  • Three separately created portal-native rulesβ€”NLS-GW-LIVE-001, NLS-GW-LIVE-003, and NLS-GW-LIVE-004β€”generated three Custom detection / Microsoft Defender for Endpoint alerts on nls-gw-lab. Incident 628 is High with Active alerts 3/3, and all three alert pages record that no response actions were taken.
  • LIVE-001 and LIVE-003 alerted during their initial scheduled evaluation over prior benign telemetry. The LIVE-004 NRT alert incorporated a post-rule safe marker as its last activity; the evidence does not establish that marker as the alert’s sole cause.
  • NLS-GW-005 matched live telemetry, but repeated portal wizards stopped at Supported entities could not be loaded; no 005 portal rule or alert exists or is claimed. NLS-GW-002 remains synthetic-only.
  • A separate built-in Defender alert, System executable renamed and launched, remains outside the three custom-alert claim.

The companion article’s evidence set includes the native-failure capture, Graph fallback and scheduler output, Advanced Hunting results, the LIVE-004 no-actions page, a historical one-alert incident view, and a tightly cropped final incident 628 Alerts tab. The final capture shows the High, Active incident with exactly LIVE-004, LIVE-003, and LIVE-001 on nls-gw-lab. The crop removes the global portal header and account or tenant identifiers while preserving the incident and alert rows. Before the LIVE rules were created, an exact NLS-GW filter returned zero rows despite successful Graph read-back. A follow-up filter then returned exactly the three LIVE rules and still none of the six Graph-fallback objects. During validation, that lack of portal visibility was a separate unresolved observation; the cause was unconfirmed, and no alert attribution was assigned to those objects. The native Repository attempt remains historical evidence of the observed Failed state; the validation connection itself was subsequently removed. Retirement then deleted all six Graph objects and all three LIVE rules; exact service reads returned 404, while incident 628 retained the historical three-alert record.