Merge branch 'feat/367-native-channel-access' into feat/371-ad-object-sacl

# Conflicts:
#	CHANGELOG-Japanese.md
#	CHANGELOG.md
#	WELA.ps1
#	scripts/Configuration.ps1
#	website/docs/resources/changelog.ja.md
#	website/docs/resources/changelog.md
This commit is contained in:
Shirofune-Security committed 2026-09-19 06:54:08 +09:00
commit 9d993a2a7e
26 files changed
+2286 -4

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
# Native AppLocker readiness
`applocker-readiness` reports local and GP effective policy XML, each of the five rule collections, enforcement modes, rule counts, Application Identity (`AppIDSvc`) state/start mode and relevant AppLocker channel observations. A host with enabled channels but no rules reports `MissingGpPolicy`. Stopped/disabled services, missing channels, unavailable cmdlets and read errors remain explicit. `NotConfigured` with rules is treated as potential enforcement, never as disabled.
```powershell
./WELA.ps1 applocker-readiness -ResultsPath applocker.json
./WELA.ps1 applocker-readiness -AppLockerAction Plan -AppLockerPolicyPath operator-audit.xml -ResultsPath plan.json
./WELA.ps1 applocker-readiness -AppLockerAction Import -AppLockerPolicyPath operator-audit.xml -DryRun
./WELA.ps1 applocker-readiness -AppLockerAction Import -AppLockerPolicyPath operator-audit.xml -BackupPath C:\WelaBackups\applocker-001 -ResultsPath imported.json
```
The default is read-only Audit. Windows 11 clients and member servers running Server 2016 or later are candidates; availability is checked through the actual native cmdlets and service. Edition names alone do not establish capability. Import requires a 64-bit elevated session. Ordinary `configure` does not invoke this workflow. No service, channel, application control enforcement or forwarding settings are automatically changed.
## Scope and import safeguards
AppLocker-specific options are rejected on unrelated commands; `-Profile` and `-Baseline` do not select AppLocker policy.
Import accepts an **operator-supplied** native XML policy. Every included collection must explicitly be AuditOnly and contain rules. XML DTDs, namespaces, unknown collection types, duplicate IDs and policy extensions are rejected. The prepared file is created without overwriting existing files, locked against writes, and compared byte-for-byte (length and SHA-256) with the reviewed in-memory XML before native validation. The native `Test-AppLockerPolicy` cmdlet validates that same locked file before it can be installed; it does not execute the test file. There are no generated blanket allow rules or default policy assumptions.
Import only initializes an empty local/GP policy, or verifies an identical previously imported policy. Existing configured collections, existing enforcement (including NotConfigured collections with rules), unreadable policy, domain membership, observed enrollment/provider entries or unknown management state block import. Use the organization's policy authority to manage those hosts. The workflow uses `Set-AppLockerPolicy -Merge`, retains original policy XML in the recovery journal, rechecks state before writing, and verifies local collection content again after writing and at completion. It does not replace an existing policy. An import failure is reported with a nonzero exit code. Dry-run makes no policy or recovery-file changes.
An omitted **unused** collection and an empty `NotConfigured` placeholder are equivalent for initialization/readback comparisons. A placeholder must have exactly the unqualified `Type` and `EnforcementMode` attributes and no content except whitespace/comments. Empty `Enabled` or `AuditOnly` collections, rules, extensions, unknown attributes/elements/text and namespaces are **not** ignored. Unknown policy-level attributes/content also block import. Raw collections and XML remain in assessments and recovery journals, with `IsEmptyPlaceholder` and `EmptyPlaceholderCount` identifying only the recognized empty shells. Imported collections can therefore be verified alongside unused placeholders without false collection-count failures. Raw XML changes between recovery and the native write still stop the import.
**An empty `NotConfigured` collection targeted for new rules remains a pre-import blocker.** Microsoft documents that a [merge into this shape can retain NotConfigured and start enforcing newly added rules](https://github.com/MicrosoftDocs/memdocs/blob/main/intune/device-configuration/endpoint-security/manage-app-control.md). WELA does not remove that collection, change its mode or assume that a serializer placeholder is safe to merge into. Review it through the existing policy authority before importing. For example, an Exe-only import can coexist with empty Dll/Msi/Script/Appx placeholders, but an existing empty Exe placeholder blocks that import. This precaution is separate from post-import readback, where unused placeholders cannot turn a verified AuditOnly Exe collection into a failure.
Microsoft's [Get-AppLockerPolicy documentation](https://learn.microsoft.com/en-us/powershell/module/applocker/get-applockerpolicy) limits that cmdlet to GP policies: **CSP policies are invisible**. Enrollment/provider observations are conservative blockers, not proof that CSP policy is absent. `CspPolicyState=Unknown` remains in every assessment; review other management mechanisms before choosing local import. The [merge semantics](https://learn.microsoft.com/en-us/powershell/module/applocker/set-applockerpolicy) preserve existing enforcement mode. Concurrent policy administration is not an atomic transaction with this workflow; keep the deployment window isolated and review the final readback. No automatic rollback overwrites newer policy.
Recovery: keep the backup directory outside temporary folders. `before.jsonl` contains the original local and GP policy XML, service/channel/management observations and desired policy. The prepared imported XML is retained as `appLocker-audit-import.xml`. Compare them with a fresh audit before recovery; use the existing policy authority or Local Security Policy to remove only the policy created by this run. Do not blindly restore stale effective domain policy or remove someone else's new rules. Use an isolated machine snapshot for integration tests.
## What readiness means
`Conditional` means GP rules, a running service and enabled channels were observed. All collections still report `GenerationReadiness=Unverified` and zero usable-rule credit. A policy can omit rule collections, contain rules that do not match the relevant user/application, or be superseded later. Missing GP rules do not prove no CSP rules exist. A successful import verifies local policy content only; it does not start the service, validate an actual executable/script event or verify collector ingestion.
[Microsoft WEF guidance](https://learn.microsoft.com/en-us/windows/security/operating-system-security/device-management/use-windows-event-forwarding-to-assist-in-intrusion-detection) recommends at least an audit-only policy. See Microsoft's [audit-only configuration](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/applocker/configure-an-applocker-policy-for-audit-only), [requirements](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/applocker/requirements-to-use-applocker) and [rule enforcement behavior](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/applocker/working-with-applocker-rules). Native Windows functionality only; Sysmon is out of scope.
Before closing issue #381, on an isolated patched Windows 11/member-server snapshot, export policy/service/channel state, import a reviewed audit-only policy, explicitly configure required service prerequisites, run a benign executable and script, and match the expected AppLocker event XML to their paths/user/rule collection. Confirm an enforced policy stays unchanged when this importer refuses it. Repeat for managed hosts and validate forwarding where required. CI only uses mocked mutations and actual read-only native policy/schema observations; it does not establish event generation or production deployment safety.
Representation regressions cover initialization, readback, idempotence, recovery exports and final drift with empty placeholders. Windows CI additionally reads both equivalent XML shapes through `Test-AppLockerPolicy` without importing them. These tests resolve the collection-count ambiguity; they do not establish how every Windows build serializes a live merge, nor claim that a live policy import or event-generation lab has been performed.
+46
View File
@@ -0,0 +1,46 @@
# Native channel settings and CAPI2 access
`channel-settings` audits, plans and optionally applies the native channel examples in Microsoft's WEF Appendix C. Its separate query inventory identifies the channels required by the selected Appendix E/F queries. This is an opt-in command; ordinary `configure` does not change channel ACLs.
```powershell
.\WELA.ps1 channel-settings -ChannelAction Audit -WefQuerySet Baseline -ResultsPath channels.json
.\WELA.ps1 channel-settings -ChannelAction Plan -WefQuerySet Both -GrantEventLogReaders -ResultsPath plan.json
.\WELA.ps1 channel-settings -ChannelAction Configure -GrantEventLogReaders -DryRun -ResultsPath preview.json
# Elevated Windows shell, after reviewing the plan; prompts unless -Auto is supplied:
.\WELA.ps1 channel-settings -ChannelAction Configure -GrantEventLogReaders -BackupPath C:\WELA-Recovery\channels-run1 -ResultsPath result.json
```
The named `-ChannelProfile microsoft-wef-appendix-c` is the only profile. `-WefQuerySet Baseline|Suspect|Both` selects **inventory**, not which Appendix C controls are applied. Audit and Plan never modify Windows. Configure always requests the three declared enable/size controls; adding the CAPI2 reader ACE additionally requires `-GrantEventLogReaders`. Without it, the existing descriptor is preserved and a missing read grant remains an unmet prerequisite. JSON exports include the full current/proposed descriptor, source bytes, native read failures, query IDs and unverified prerequisites. Access failures stay unknown; unregistered channels stay not installed and require role/query review.
| Channel | Enabled setting | Source example bytes | Rounded minimum applied | Access request |
|---|---|---:|---:|---|
| Microsoft-Windows-CAPI2/Operational | Enable | 102432768 | 102432768 | Event Log Readers read, explicit opt-in |
| Microsoft-Windows-AppLocker/EXE and DLL | Preserve | 102432768 | 102432768 | Preserve |
| Microsoft-Windows-DriverFrameworks-UserMode/Operational | Enable | 52432896 | 52494336 | Preserve |
The source examples are **not** 100 MiB and 50 MiB. Larger existing limits and retention modes are preserved. These are channel buffer examples, not promised retention duration. Source: [Microsoft WEF Appendix C](https://learn.microsoft.com/en-us/windows/security/operating-system-security/device-management/use-windows-event-forwarding-to-assist-in-intrusion-detection#appendix-c---event-channel-settings-enable-and-channel-access-methods). Applied limits round upward to a 64 KiB unit as required by [wevtutil](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/wevtutil).
## Permission and mutation boundaries
The appended ACE grants SID `S-1-5-32-573` (Event Log Readers) **read only**, access mask `0x1`. Existing ACEs are not broadened or removed; even an existing write-only grant is retained and a separate read ACE is appended. WELA does not copy Microsoft's complete example descriptor over the host descriptor. Event Log read, write and clear are separate [Windows access constants](https://learn.microsoft.com/en-us/windows/win32/wes/windows-event-log-constants).
The planner uses [RawSecurityDescriptor](https://learn.microsoft.com/en-us/dotnet/api/system.security.accesscontrol.rawsecuritydescriptor?view=netframework-4.8.1), clones its binary representation, inserts an explicit allow before the first inherited ACE and verifies an exact binary round trip through the proposed SDDL. Owner, group, SACL, control flags and every existing ACE byte/order must survive. No ACL canonicalization occurs. Absent/null DACLs, any applicable read-deny ACE, unknown ACEs and descriptors that cannot round-trip losslessly require manual review and are left unchanged. Recognized object/callback ACEs are retained only if lossless serialization succeeds. This conservative rule may decline descriptors that an administrator can safely edit manually.
`GrantPresent` describes an unconditional group read ACE in the descriptor. It **does not establish effective read access** for any user or service token. Group membership, denied groups, privileges, actual event reads and forwarding remain separate. Read permission also does not establish AppLocker policy, provider generation readiness, or Sigma rule usability.
The shared configuration runner writes `before.jsonl` before each native mutation, capturing the original enabled state, exact size, full descriptor and retention mode. A fresh read must match both the plan and the journal snapshot before `wevtutil sl` executes. Only changed `/e:true`, `/ms:...` and explicitly authorized `/ca:...` arguments are sent. Native failure, failed readback, descriptor mismatch and final drift produce a nonzero result. There is no atomic Windows compare-and-set; another writer can still race the final check. Re-run after policy refresh to check persistence. No automatic rollback occurs.
Recovery is manual: review each journal `Before` against the current settings, identify the affected channel, and restore only the intended previous values using `wevtutil sl "CHANNEL" /e:true|false /ms:ORIGINAL_BYTES /ca:"ORIGINAL_SDDL"`. Pass the descriptor as one argument in PowerShell, for example `& wevtutil.exe sl $entry.Target.Channel ("/ca:" + $entry.Before.SecurityDescriptor)` after loading and reviewing the relevant JSONL entry. Restoring a smaller limit can discard events. Existing retention is not intentionally modified; investigate any changed mode before choosing recovery actions. Use a new backup directory for each run.
## Native WEF prerequisites and validation
The checked-in inventory maps source query IDs to 12 baseline channels and 8 suspect channels (18 unique combined). Baseline queries 12 (EMET) and 39 (Sysmon) are explicitly excluded. Native-only scope excludes external agents; channel settings do not create subscriptions, add service identities to groups or configure WinRM/collectors. The Microsoft [source prerequisite guidance and sample queries](https://learn.microsoft.com/en-us/windows/security/operating-system-security/device-management/use-windows-event-forwarding-to-assist-in-intrusion-detection) remain a starting point for reviewing role applicability. The report always lists token/group membership, producer configuration, representative event generation and forwarding/ingestion as unverified; required disabled or missing channels remain visible. Other inventoried channels are not automatically enabled.
Safe tests exercise the actual command/JSON/runner with mocked Windows setters. Windows PowerShell 5.1 and PowerShell 7 CI additionally exercise real descriptor serialization and read-only CLI inspection. Release packaging already includes the whole `config`, `modules` and `scripts` directories.
**Isolated Windows acceptance evidence is still pending; related to issue #367, not sufficient to close it.** On patched Windows 11, member server, DC and ADCS snapshots where the channels exist:
1. Save the plan, channel metadata, descriptor and policy context. Review capacity and the intended forwarding identity. Capture the actual identity/token memberships separately.
2. Apply the opt-in profile, retain the journal/results, then independently read enablement, exact bytes and full SDDL. Compare all original ACEs plus owner/group/SACL/flags and repeat after policy refresh.
3. Using the intended forwarding identity's actual token, read CAPI2 event records. An administrator's successful query or a matching group ACE is insufficient evidence. Record denied/missing cases explicitly.
4. Generate a benign native event appropriate to the isolated role, retain its XML and verify matching collector ingestion under the intended subscription. WELA does not perform this test or claim any measured Sigma coverage increase.
+77
View File
@@ -0,0 +1,77 @@
# Optional WMI namespace auditing
`wmi-auditing` appends reviewed success-audit entries to explicitly selected **local** WMI namespace SACLs. It does not run during ordinary `configure`, change namespace access permissions, create namespaces, enable remote WMI access, change audit policy, install a forwarding subscription, or grant rule-coverage credit. PowerShell 5.1 and PowerShell 7 on Windows use the same `System.Management` provider methods.
```powershell
./WELA.ps1 wmi-auditing -WmiAction List
./WELA.ps1 wmi-auditing -WmiAction Audit -WmiNamespace 'root\cimv2' -ResultsPath wmi-before.json
./WELA.ps1 wmi-auditing -WmiAction Plan -WmiNamespace 'root\cimv2','root\subscription' -ResultsPath wmi-plan.json
./WELA.ps1 wmi-auditing -WmiAction Configure -WmiNamespace 'root\cimv2' -DryRun
./WELA.ps1 wmi-auditing -WmiAction Configure -WmiNamespace 'root\cimv2' -BackupPath C:\WelaBackups\wmi-change-001 -ResultsPath wmi-result.json
# Explicitly opt in to the reference script's descendant inheritance:
./WELA.ps1 wmi-auditing -WmiAction Configure -WmiNamespace 'root\subscription' -WmiIncludeChildren
```
The default action is read-only `List`. Audit/Plan/Configure require exact namespace selections; wildcards, remote paths, unreviewed namespaces and empty selections are rejected. `-Auto` skips per-namespace confirmation after the operator has selected the scope. `-DryRun` is supported only with Configure, and calls no setter or journal writer. `-Profile` and `-Baseline` do not select WMI SACLs.
## Reference entries and scope
The entries come from the [ASD WMI script pinned at 59041b5](https://github.com/AustralianCyberSecurityCentre/windows_event_logging/blob/59041b5d4586789a751171fb752be1624ad5e3b4/events/wmi_auditing/wmi_auditing.ps1). Every new ACE has type 2 (system audit), success only. Existing failure entries and unfamiliar ACEs are retained.
| Namespace | Principal | Mask | Audited namespace rights | ASD flags |
| --- | --- | --- | --- | --- |
| `root\cimv2` | Everyone `S-1-1-0` | `0x40002` (262146) | Execute Methods, Edit Security | 64 |
| `root\cimv2` | Interactive `S-1-5-4` | `0x1` | Enable Account / read | 64 |
| `root\cimv2` | Network `S-1-5-2` | `0x1` | Enable Account / read | 64 |
| `root\cimv2` | Batch `S-1-5-3` | `0x1` | Enable Account / read | 64 |
| `root\SecurityCenter` | Everyone | `0x40001` (262145) | Enable Account / read, Edit Security | 66 |
| `root\SecurityCenter2` | Everyone | `0x40001` (262145) | Enable Account / read, Edit Security | 66 |
| `root\subscription` | Everyone | `0x4001E` (262174) | Execute Methods, Full Write, Partial Write, Provider Write, Edit Security | 66 |
| `root\default` | Everyone | `0x4001F` (262175) | Read plus all preceding rights | 66 |
The numeric subscription mask includes Execute Methods even though the reference script's comment omits it. WELA uses the actual numeric mask. Flags 64 mean success on this namespace; 66 add container inheritance. **By default WELA uses 64 for every selection**, limiting new entries to that namespace. `-WmiIncludeChildren` enables the reference's flag 66 for the four applicable namespaces. This may propagate audit ACEs to inheriting descendants, including existing and future child namespaces; it does not grant access. Child ACL propagation is not enumerated, backed up or verified by this command, and is an explicit additional scope requiring a lab review. Existing inherited entries are retained in either mode. SecurityCenter namespaces are commonly absent on servers; absence is reported rather than treated as successful configuration.
## Privileges, preservation and results
Run elevated with **SeSecurityPrivilege assigned** for Audit/Plan/Configure. WELA enables this privilege in its process while accessing the descriptor, restores the previous token state afterward, and requests privileges for the local WMI connection. Without it a provider can return a DACL while omitting the SACL; WELA refuses that ambiguous read. List only enumerates the supported root child namespaces and reports Present, NotInstalled or Unknown.
Each GetSecurityDescriptor and SetSecurityDescriptor return code must be explicitly zero. Exceptions, denied/missing namespaces, incomplete descriptors, nonzero return codes, ineffective writes and failed read-back are failures. The journal stores the complete provider descriptor as JSON and MOF strings before the setter is called; nested entries cannot be truncated by the outer result serializer. Native objects are cloned rather than rebuilt from a shortened permission list. The native setter request clears `SE_DACL_PRESENT` and leaves DACL, owner and group null: the [documented provider contract](https://learn.microsoft.com/en-us/windows/win32/wmisdk/setsecuritydescriptor-method-in-class---systemsecurity) preserves those access fields rather than rewriting them. `SE_SACL_PRESENT` requests the SACL update. Full read-back still verifies DACL order, owner, group, other control flags and every original audit entry against the complete recovery snapshot. Unknown entries are never deliberately simplified or discarded.
Privilege restoration runs even if connection disposal fails. Both enabling and restoring the token privilege check the API return value and last-error code; [AdjustTokenPrivileges](https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-adjusttokenprivileges) can return success while reporting an unassigned privilege. A restoration error is reported as a failed operation, not silently treated as restored state.
Immediately before writing, WELA reads the full descriptor again and refuses to overwrite a changed snapshot. Read-back checks all original fields/ACE multiplicities and every requested exact audit entry. The final check detects descriptor drift after verification. This is not an atomic transaction with other administrators or management software: changes between the last read and the provider write remain possible. No automatic rollback overwrites concurrent changes.
An exact existing entry is not duplicated. Different masks, audit outcomes, inheritance, object-specific ACEs or inherited ACEs are preserved and do not suppress the explicit requested entry. Result statuses use the shared configuration contract: Applied/AlreadyCompliant indicate observed SACL compliance, Skipped includes dry-run or declined changes, and Failed/Overridden produce exit code 1. Exit code 0 alone is not evidence of a write or successful event generation.
## Audit prerequisites and local/remote evidence
WELA separately observes the effective **Other Object Access Events** audit policy (success bit) without changing it. A missing/unknown prerequisite is visible in `Prerequisite`; a successful SACL update alone does not establish event readiness. Review audit precedence and the effective policy using the separate audit-policy workflow.
[Microsoft documents namespace auditing](https://learn.microsoft.com/en-us/windows/win32/wmisdk/access-to-wmi-namespaces) as Security event **4662** for matching namespace access checks. It does not establish whether the subsequent provider operation succeeded. Interactive/Network/Batch SIDs select token membership, not a universal local/remote classification: validate the logon type, user SID, namespace and access mask in observed XML. Remote Enable (`0x20`) is not added to the DACL or the new audit mask. WMI-Activity/Operational telemetry is a separate evidence source and is not made equivalent to namespace Security events.
## Recovery and remaining lab verification
Keep the new backup directory and result JSON outside temporary folders. `before.jsonl` contains each selected namespace's original `DescriptorJson` and `DescriptorMof`, namespace name and proposed entries. Compare these with a fresh Audit export before making any recovery change. In an elevated WMI Control (`wmimgmt.msc`), select the exact namespace, Security > Advanced > Auditing, and remove only entries that this run added after confirming they were absent from the original descriptor. Restore changed audit flags/masks from the original export if necessary; retain unrelated owner/group/DACL and newer administrative changes. WELA deliberately provides no blind whole-descriptor restore. An existing matching ACE was not created by this run and must not be removed. If descendant inheritance was enabled, inspect affected child namespaces independently and use a pre-change machine snapshot if a complete rollback is needed.
CI uses synthetic descriptors, a real privileged read of root\cimv2, an in-memory native writer adapter and a read-only dry-run on Windows PowerShell 5.1/7. A separate integration job performs real SACL writes only on uniquely created temporary namespaces on disposable Server 2022/2025 runners, verifies read-back/idempotence and deletes its own namespaces. It never changes the SACL of an existing namespace. **Writes to the five production target namespaces, Windows 11 behavior, benign local/remote event generation, child propagation and forwarding have not been verified by these tests.** Before deployment, use isolated patched snapshots of Windows 11, member server, domain controller and AD CS hosts; record descriptors/effective audit policy before and after, repeat configuration for idempotence, issue benign local and remote calls with known tokens, capture Security 4662 XML, and test the chosen WEF subscription and collector receipt. Validate namespace `ObjectName` and access masks, not EventID alone. These are pending acceptance labs, not claimed Sigma uplift.
Additional primary references: [SetSecurityDescriptor and preservation flags](https://learn.microsoft.com/en-us/windows/win32/wmisdk/setsecuritydescriptor-method-in-class---systemsecurity), [namespace access masks](https://learn.microsoft.com/en-us/windows/win32/wmisdk/namespace-access-rights-constants), [namespace inheritance flags](https://learn.microsoft.com/en-us/windows/win32/wmisdk/namespace-ace-flag-constants).
## Native control-flag readback evidence
The disposable-namespace test addresses [review comment 4052822447](https://github.com/Yamato-Security/WELA/pull/399#discussion_r4052822447) without weakening preservation checks. [The verified CI run](https://github.com/Yamato-Security/WELA/actions/runs/35436825928) used the real production SACL writer and configuration runner against eight fresh namespaces (two ACE flag modes, two PowerShell versions and two operating systems). Each started without a SACL, retained owner/group/DACL, passed first-write readback and an idempotent repeat, then was deleted. Full descriptor snapshots and cleanup results are in the job logs.
| Host build | PowerShell | ACE flags tested | ControlFlags before | ControlFlags after |
| --- | --- | --- | --- | --- |
| Server 2022 / 20348 | 5.1.20348.5622, 7.6.6 | 64 and 66 | 32772 / `0x8004` | 32788 / `0x8014` |
| Server 2025 / 26100 | 5.1.26100.33296, 7.6.5 | 64 and 66 | 32772 / `0x8004` | 32788 / `0x8014` |
No extra auto-inherited/defaulted bit appeared in these cases. The existing `Before.ControlFlags | 0x10` equality is retained: an unexpected flag change still fails preservation verification. These results establish this provider behavior for the listed clean namespace scenarios, not every existing namespace or Windows version.
To repeat on a **disposable Windows lab VM** (this is a mutating integration test):
```powershell
./tests/WmiNamespaceAuditing.DisposableNamespace.Tests.ps1 -AllowDisposableNamespaceWrite -EvidencePath wmi-native.json
```
The script accepts no target namespace. It uses generated `root\WelaSaclTest_<GUID>` names, creates them with CreateOnly, verifies the returned identity, runs the writer only there, and removes only instances it created. The normal read-only test remains separate. It does not enable audit policy, generate controlled Security 4662 evidence or test forwarding. Namespace lifecycle follows Microsoft's [__Namespace contract](https://learn.microsoft.com/en-us/windows/win32/wmisdk/--namespace); SACL-only updates follow the [SetSecurityDescriptor contract](https://learn.microsoft.com/en-us/windows/win32/wmisdk/setsecuritydescriptor-method-in-class---systemsecurity).