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
| ID | Detection | Desired state | Evidence path |
|---|---|---|---|
nls-gw-000-canary | Deployment canary | Disabled | Exact-ID API read-back |
nls-gw-001-onedrive-persistence | OneDrive-lookalike task and registry persistence | Enabled | Current query re-matched live telemetry; the separate scheduled portal alert used the validation revision |
nls-gw-002-recovery-boot-tampering | Recovery and boot tampering | Enabled | Synthetic only |
nls-gw-003-event-log-destruction | Windows event-log clearing | Enabled | Observed benign custom-log telemetry plus a separate portal-native scheduled alert |
nls-gw-004-minio-transfer-staging | Unusual MinIO client staging | Enabled | Filename-only telemetry plus a separate portal-native NRT alert |
nls-gw-005-candy-rename-burst | .candy rename burst | Enabled | Current 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, andNLS-GW-LIVE-004were 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-20260711diagnostic 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 theplannedstate; clean up and recreate after a partial or completed deployment. Preview and runRemove-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 theCustomDetectioncontent 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/workflowsas 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
InvalidTemplateDeploymentand innerProviderErrorfor all six corrected templates. That non-specific native-path response is distinct from the earlier GraphInvalidInput - Only one tactic is currently supportedcontract error, which was corrected and added to CI. - Manual Graph-only GitHub OIDC fallback run
29180593038succeeded. 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
OnboardedandActive, and Advanced Hunting receivedDeviceInfo. 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
.candyrename 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, andNLS-GW-LIVE-004βgenerated three Custom detection / Microsoft Defender for Endpoint alerts onnls-gw-lab. Incident628is 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.
