Collect native 4688 validation components with a fixed benign probe (#413)

* Add opt-in native 4688 validation component collector

* Link native validation changelog to PR 413

* Reject contradictory native probe context observations

* Resolve probe artifact paths against the PowerShell location
This commit is contained in:
田中ザック Isaac Mathis authored and GitHub committed 2026-09-20 18:08:34 +09:00
1 parent 7719063f6f
commit 4431533535
10 files changed
+429

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
name: Native 4688 validation collection
on:
push:
branches: ['**']
paths:
- 'WELA.ps1'
- 'scripts/NativeValidation.ps1'
- 'scripts/ControlApplicability.ps1'
- 'scripts/Configuration.ps1'
- 'modules/AuditProfiles.psm1'
- 'tests/NativeValidation*'
- '.github/workflows/native-validation.yml'
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
native-probe:
strategy:
fail-fast: false
matrix:
os: [windows-2022, windows-2025]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Synthetic rejection regressions in Windows PowerShell 5.1
shell: powershell
run: ./tests/NativeValidation.Tests.ps1
- name: Actual native 4688 probe with exact disposable policy restoration
shell: powershell
run: ./tests/NativeValidation.Windows.Tests.ps1 -AllowDisposablePolicyWrite
- name: Synthetic rejection regressions in PowerShell 7
shell: pwsh
run: ./tests/NativeValidation.Tests.ps1
- name: Actual native 4688 probe from PowerShell 7 with restoration
shell: pwsh
run: ./tests/NativeValidation.Windows.Tests.ps1 -AllowDisposablePolicyWrite
+1
View File
@@ -4,6 +4,7 @@
**改善:**
- ネイティブ Security 4688 の無害な固定プローブを収集する明示的な `native-validation` を追加。型付き前提条件、イベントの厳密な照合、前後の状態、ハッシュ付き証拠を新規の非公開ディレクトリに記録します。不完全・上限到達・曖昧・ドリフトの結果は未検証のままです。監査ポリシー変更や Sigma 利用可能ルール数の加算は行いません。使い捨て Server 2022/2025 テストで実イベントとポリシー復元を確認し、Windows 11/DC/ADCS とバックエンドの検証は別途必要です。 (#413) (@Shirofune-Security)
- ローカル監査権限と`CrashOnAuditFail`を監査・計画・出典別に設定する任意実行の`audit-integrity`を追加しました。実際のクライアント・メンバーサーバー・DCを区別し、Microsoft SCTで省略された設定は保持します。対象SIDの一覧、権限削除の明示指定、権限単位のLSA更新、完全な復旧記録と変更直前・変更後の検証により、無関係な権限を保持します。復旧状態では変更せず、ネイティブCIは読み取りだけを行います。トークン・GPO・サービス・イベントの検証は別途ラボで必要となり、Sigma検知範囲には加算しません。 (#412) (@Shirofune-Security)
- 読み取り専用の`retention-health`を追加し、送信元/収集サーバーの標準ログバッファ、確認した先頭レコードの経過日数、申告されたアーカイブ方針、件数を制限したローカルEVTX/ACL一覧、各言語のWEF・時刻情報とログ消失・消去・満杯の兆候をJSONと単独で表示できるHTMLに分けて記録します。保持されたイベントの時刻に基づく件数率とUTF-8 XMLバイト数の試算には上限・前提を明示し、容量・完全な保持・実効読み取り権限・時刻同期・転送成功の証明とは扱いません。複数ホストでのロールオーバー・復旧・到着検証は別途必要です。 (#410) (@Shirofune-Security)
- 読み取り専用の`control-applicability`を追加し、旧CISのApplication Guard監査要件を保持しつつ、Windows 11 24H2以降では削除済み・対象外と表示します。`default-evidence`で正確なビルド・パッチ・ドメイン参加・役割を含む現状を記録し、出典とレビュー情報が一致する参照環境と比較できます。旧ベースラインの既定値は履歴情報として保持し、未検証の既定値はUnknownと表示します。合成テストと読み取り専用CIからクリーンインストールやイベント生成の証明は行いません。 (#409) (@Shirofune-Security)
+1
View File
@@ -4,6 +4,7 @@
**Improvements:**
- Added opt-in `native-validation` to collect a fixed benign Security 4688 probe with typed prerequisites, exact native event matching, before/after state and hashed components in a new private directory. Partial, capped, ambiguous and drifted results remain unverified; the collector changes no audit policy and grants no Sigma readiness credit. Disposable Server 2022/2025 tests exercise real events with verified policy restoration; Windows 11/DC/ADCS and backend acceptance remain separate. (#413) (@Shirofune-Security)
- Added opt-in `audit-integrity` audit, plan and source-profile configuration for local audit privileges and `CrashOnAuditFail`, with separate actual client/member/DC scope and preserved Microsoft SCT omissions. Exact affected SIDs, explicit privilege-removal consent, per-right LSA updates, complete recovery journals and fresh/readback checks preserve unrelated privileges. Recovery states are blocked; native CI reads policy only, while token, GPO, service and event validation remains a lab requirement without Sigma credit. (#412) (@Shirofune-Security)
- Added read-only `retention-health` source/collector JSON and self-contained HTML reports separating native buffers, observed record-boundary ages, declared archive policy, bounded local EVTX/ACL inventory, localized WEF/time evidence and loss/clear/full indicators. Retained-event timestamp rates and UTF-8 XML-byte scenarios expose caps and assumptions; none establish archive capacity, complete retention, effective reader access, synchronized time or successful forwarding. Multi-host rollover/recovery/arrival validation remains pending. (#410) (@Shirofune-Security)
- Added read-only `control-applicability` for the historical CIS Application Guard audit requirement, reporting removal on Windows 11 24H2+ without remediation. Added exact build/patch/join/role native snapshots and provenance-bound reference comparison through `default-evidence`. Legacy baseline default strings are retained as historical hints while public defaults remain Unknown without reviewed scenario evidence. Synthetic fixtures and native read-only CI do not claim clean-install or event-generation proof. (#409) (@Shirofune-Security)
+19
View File
@@ -62,6 +62,9 @@
[ValidateSet('OneSettings','SecurityWarning')][string[]]$NotificationControl,
[ValidateRange(1,90)][int]$WarningPercent = 90,
[switch]$EnablePrivacyChannel,
[ValidateSet('Plan','Run')][string]$ProbeAction = 'Plan',
[string]$ProbeOutputPath,
[ValidateRange(1,30)][int]$ProbeTimeoutSeconds = 15,
[switch]$Help
)
@@ -81,6 +84,7 @@ $SaclTargetsPath = Join-Path $ScriptRoot "config/audit_sacl_targets.json"
. (Join-Path $ScriptRoot "scripts/SmbAuditing.ps1")
. (Join-Path $ScriptRoot "scripts/LdapDiagnostics.ps1")
. (Join-Path $ScriptRoot "scripts/ControlApplicability.ps1")
. (Join-Path $ScriptRoot "scripts/NativeValidation.ps1")
. (Join-Path $ScriptRoot "scripts/AuditNotifications.ps1")
. (Join-Path $ScriptRoot "scripts/AdObjectSacl.ps1")
. (Join-Path $ScriptRoot "scripts/AppLockerReadiness.ps1")
@@ -1814,6 +1818,7 @@ Usage:
./WELA.ps1 control-applicability # Read-only historical native feature/build assessment
./WELA.ps1 default-evidence -Help # Exact-context observed snapshots and reviewed reference comparison
./WELA.ps1 audit-notifications -Help # OneSettings audit and Security warning policy
./WELA.ps1 native-validation -Help # Collect a fixed native 4688 probe without changing policy
./WELA.ps1 version # Show the WELA version
./WELA.ps1 help # Show this help
"@
@@ -1825,6 +1830,13 @@ Write-Host ""
Write-Host "WELA v$WELAVersion - $WELAReleaseName"
Write-Host ""
if ($Cmd -ne 'native-validation' -and @($PSBoundParameters.Keys | Where-Object { $_ -in @('ProbeAction','ProbeOutputPath','ProbeTimeoutSeconds') }).Count) {
throw 'Probe options require native-validation. No command was run.'
}
if ($Cmd -eq 'native-validation' -and @($PSBoundParameters.Keys | Where-Object { $_ -notin @('Cmd','ProbeAction','ProbeOutputPath','ProbeTimeoutSeconds','Help') }).Count) {
throw 'native-validation accepts only its dedicated probe options. No command was run.'
}
if ($Cmd -ne 'audit-integrity' -and @($PSBoundParameters.Keys | Where-Object { $_ -in @('IntegrityAction','IntegrityProfile','AllowPrivilegeRemoval') }).Count) {
throw 'Integrity options require the dedicated audit-integrity command. No command was run.'
}
@@ -1917,6 +1929,13 @@ if ($Profile -and $Cmd.ToLower() -in @('plan', 'audit', 'audit-settings', 'confi
}
switch ($Cmd.ToLower()) {
'native-validation' {
if ($Help) { Write-Host 'Usage: ./WELA.ps1 native-validation [-ProbeAction Plan|Run] [-ProbeOutputPath new-directory] [-ProbeTimeoutSeconds 1..30]. Plan reads prerequisites; Run launches a fixed benign cmd.exe probe and collects exact native Security 4688 XML. No policy changes or Sigma readiness credit. See docs/native-validation.md.'; return }
$report=Invoke-WelaNativeValidation -Action $ProbeAction -OutputPath $ProbeOutputPath -TimeoutSeconds $ProbeTimeoutSeconds
$report
if ($report.ExitCode -ne 0) { exit 1 }
}
'audit-integrity' {
if ($Help) { Write-Host 'Usage: ./WELA.ps1 audit-integrity [-IntegrityAction Audit|Plan|Configure] [-IntegrityProfile source-id] [-AllowPrivilegeRemoval] [-Auto] [-DryRun] [-BackupPath new-directory] [-ResultsPath report.json]. Audit is read-only; Plan/Configure require an exact source profile. See docs/audit-integrity.md.'; return }
if ($Profile -or $Baseline -or $Role -or $Build -or $HtmlPath) { throw 'audit-integrity observes the actual local Windows host; use -IntegrityProfile and -ResultsPath, without Security profiles, role/build overrides or HTML.' }
+36
View File
@@ -0,0 +1,36 @@
# Native event validation components
`native-validation` collects a fixed, benign native Security 4688 process-creation event. Sysmon is out of scope. It is the first executable collector for issue #387; it does not complete every role, event family or Sigma/backend acceptance case.
```powershell
# Read-only prerequisite assessment. No child process or output directory.
./WELA.ps1 native-validation
# Explicitly launch one fixed System32 cmd.exe echo command and collect its event.
./WELA.ps1 native-validation -ProbeAction Run -ProbeOutputPath C:\Lab\evidence\probe-001
```
Use 64-bit elevated Windows PowerShell 5.1 or PowerShell 7 on a reviewed Windows 11 build (22000, 22621, 22631, 26100 or 26200), Server 2022 (20348) or Server 2025 (26100). The collector detects the actual client/member/DC/ADCS context; combined DC+CA hosts and unknown builds remain unsupported. Exact patch, edition, architecture, join state and installed roles must be readable. Policy administration remains a separate operator action: this command does not enable auditing, refresh GPO, change SACLs, clear logs, restart services or contact a backend.
Prerequisites are effective Process Creation Success auditing, DWORD `SCENoApplyLegacyAuditPolicy=1`, DWORD `ProcessCreationIncludeCmdLine_Enabled=1` and an enabled/readable Security channel. Plan reports observed prerequisites only; Run repeats the observations before launch and after collection. A changed audit mask, prerequisite or host context prevents a successful result. Observations do not establish persistence through a later policy refresh.
The two native host readers must agree on build. The reported role, patch, join
state and installed-role summary must also agree with the detailed host
observations; a client/member observation cannot be labeled as a DC, and an AD CS
label additionally requires the installed CA role. Contradictory evidence is
reported as Unverified even if the process event itself matches.
Run launches the Windows system `cmd.exe` with `/d /c echo WELA_PROBE_<random-guid>`; callers cannot supply executable paths or commands. It retains only the single matching event XML, using native provider identity, EventID 4688/version 2, successful-audit keyword, local computer name (or joined FQDN), time window, child PID, creator PID, executable path and complete fixed command line. Duplicate matches, unknown schema, access denial, drift, timeout or a 512-event query cap produce `Unverified` and exit 1. Event polling defaults to 15 seconds and supports `-ProbeTimeoutSeconds 1..30`; each synchronous Windows query can take additional time. The child process has a separate 10-second limit. The collector never exports the entire Security log.
The destination must be a new directory under an existing parent. On Windows, its ACL is restricted to the current user, SYSTEM and local Administrators. Files use CreateNew and cannot overwrite prior evidence. A completed collection contains `before-state.json`, `process.json`, `event.xml`, `after-state.json` and `manifest.json`. The manifest fingerprints each component using SHA-256 and preserves the collector status. Partial artifacts and diagnostics remain available after failure. These hashes detect changes relative to the manifest; they are not signatures or proof against a malicious evidence author. Review and protect the directory before sharing its host/policy metadata.
## Completing rule and backend evidence
`NativeEventObserved` means the fixed probe produced the expected local telemetry in the recorded context and time. `ReadyRuleCredit` always remains zero. The random benign command will ordinarily not satisfy an existing detection rule.
The component bundle is deliberately a different kind from `WelaNativeRuleEvidence` and cannot be supplied directly as a completed readiness bundle. To validate a specific rule, use the [native rule eligibility contract](native-rule-eligibility.md), retain its full original rule, obtain an independent complete normalization review, generate a suitable controlled scenario, and preserve backend ingestion, the translated query and its actual successful result. Do not relabel this probe as a rule match or invent the missing artifacts. A supported native event and state snapshot can inform that lab work; the original component manifest remains the provenance record. No backend name, rule match or query result is manufactured by this command.
## Verification and remaining acceptance
Synthetic tests exercise identity/schema/time/command mismatches, disabled or mistyped prerequisites, drift, caps, denied access, output collisions and public command isolation. They provide no native event evidence. A separate explicitly opted-in GitHub-hosted runner test temporarily enables the three prerequisites, captures a real matching 4688 event on Server 2022/2025 under PowerShell 5.1/7 and restores the exact previous audit mask and registry value kinds/absence in `finally`, verifying restoration. The production collector contains no policy writer. CI does not provision a DC or CA, test Windows 11, forward an event or execute a Sigma query. Those cases remain isolated-lab acceptance work before closing #387.
Microsoft documents the event schema and command-line prerequisite in [4688: A new process has been created](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4688). The collector requires the reviewed modern version 2 schema rather than extrapolating from older versions.
+183
View File
@@ -0,0 +1,183 @@
# Fixed, opt-in native telemetry probe. This collector never changes Windows policy.
function Get-WelaProbeState {
if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT -or -not [Environment]::Is64BitProcess) { throw 'Native validation requires 64-bit Windows PowerShell.' }
$context = Get-WelaDefaultContext
if (-not (Test-WelaDefaultContextComplete $context)) { throw "Complete native host context is required: $($context.Diagnostic)" }
$hostContext = Get-WelaHostContext
if ($hostContext.Build -ne $context.Build) { throw 'Native host readers disagree about the Windows build.' }
if (($hostContext.Role -eq 'Client' -and $hostContext.Build -notin @(22000,22621,22631,26100,26200)) -or
($hostContext.Role -ne 'Client' -and $hostContext.Build -notin @(20348,26100))) { throw 'Host build is outside the reviewed Windows 11 / Server 2022 and 2025 probe scope.' }
$policies = Get-WelaEffectiveAuditPolicy
$precedence = Get-WelaRegistryState -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' -Name 'SCENoApplyLegacyAuditPolicy'
$commandLine = Get-WelaRegistryState -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit' -Name 'ProcessCreationIncludeCmdLine_Enabled'
$channel = Get-WinEvent -ListLog Security -ErrorAction Stop
if (@($channel).Count -ne 1 -or $channel.LogName -ne 'Security') { throw 'Exact Security log configuration is unavailable.' }
[pscustomobject][ordered]@{
capturedAtUtc = [DateTime]::UtcNow.ToString('o')
context = [pscustomobject][ordered]@{computer=[Environment]::MachineName;role=$hostContext.Role;build=$context.Build;patch="$($context.Build).$($context.UBR)";domainJoined=$context.DomainJoined;installedRoles=@($context.InstalledRoles)}
hostObservation = $context
auditPolicies = $policies
auditPrecedence = $precedence
commandLineCapture = $commandLine
securityChannelEnabled = [bool]$channel.IsEnabled
}
}
function Assert-WelaProbePrerequisites {
param($State)
$guid = '0cce922b-69ae-11d9-bed3-505054503030'
if (-not $State -or -not (Test-WelaDefaultContextComplete $State.hostObservation)) { throw 'Complete observed host context is required.' }
if ($State.context.role -notin @('Client','MemberServer','DomainController','ADCS') -or $State.context.build -ne $State.hostObservation.Build) { throw 'Host context is inconsistent.' }
$observed=$State.hostObservation
$roleMatches=switch ($State.context.role) {
'Client' { $observed.ProductType -eq 1 -and $observed.DomainRole -in @(0,1) }
'DomainController' { $observed.ProductType -eq 2 -and $observed.DomainRole -in @(4,5) }
'MemberServer' { $observed.ProductType -eq 3 -and $observed.DomainRole -in @(2,3) }
'ADCS' { $observed.ProductType -eq 3 -and $observed.DomainRole -in @(2,3) -and $observed.InstalledRoles -contains 'ADCS-Cert-Authority' }
}
if (-not $roleMatches -or $State.context.domainJoined -isnot [bool] -or
$State.context.domainJoined -ne $observed.DomainJoined -or
$State.context.patch -cne "$($observed.Build).$($observed.UBR)" -or
$State.context.installedRoles -isnot [array] -or
(@($State.context.installedRoles | Sort-Object -Unique) -join "`n") -cne (@($observed.InstalledRoles | Sort-Object -Unique) -join "`n")) {
throw 'Probe role, patch, join or installed-role summary contradicts the detailed host observation.'
}
$mask = $State.auditPolicies[$guid]
if (($mask -isnot [int] -and $mask -isnot [long]) -or $mask -notin @(1,3)) { throw 'Effective Process Creation success auditing is required; no policy was changed.' }
foreach ($entry in @($State.auditPrecedence,$State.commandLineCapture)) {
if (-not $entry.ValueExists -or $entry.Type -ne 'DWord' -or ($entry.Value -isnot [int] -and $entry.Value -isnot [long]) -or $entry.Value -ne 1) { throw 'Typed DWORD=1 audit precedence and process command-line capture are required; no policy was changed.' }
}
if ($State.securityChannelEnabled -isnot [bool] -or -not $State.securityChannelEnabled) { throw 'Security channel must be observed enabled.' }
}
function Get-WelaProbeStateKey {
param($State)
# Timestamps differ; every observed policy and exact host context must remain stable.
[ordered]@{context=$State.context;host=(Get-WelaDefaultContextKey $State.hostObservation);policies=@($State.auditPolicies.GetEnumerator() | Sort-Object Key | ForEach-Object { "$($_.Key)=$($_.Value)" });precedence=$State.auditPrecedence;commandLine=$State.commandLineCapture;security=$State.securityChannelEnabled} | ConvertTo-Json -Depth 10 -Compress
}
function Start-WelaProbeProcess {
$executable = Join-Path ([Environment]::GetFolderPath('System')) 'cmd.exe'
if (-not (Test-Path -LiteralPath $executable -PathType Leaf)) { throw 'Native System32 cmd.exe is unavailable.' }
$marker = 'WELA_PROBE_' + [guid]::NewGuid().ToString('N')
$start = New-Object Diagnostics.ProcessStartInfo
$start.FileName=$executable; $start.Arguments='/d /c echo ' + $marker
$start.UseShellExecute=$false; $start.CreateNoWindow=$true
$start.RedirectStandardOutput=$true; $start.RedirectStandardError=$true
$began=[DateTime]::UtcNow
$process=[Diagnostics.Process]::Start($start)
try {
$processId=$process.Id
if (-not $process.WaitForExit(10000)) { $process.Kill(); throw 'Fixed benign cmd.exe probe timed out.' }
$output=$process.StandardOutput.ReadToEnd(); $diagnostic=$process.StandardError.ReadToEnd()
if ($process.ExitCode -ne 0 -or $output.Trim() -cne $marker) { throw "Fixed benign probe failed: $diagnostic" }
[pscustomobject]@{ProcessId=$processId;ParentProcessId=$PID;Executable=$executable;Arguments=$start.Arguments;Marker=$marker;StartedUtc=$began.ToString('o');CompletedUtc=[DateTime]::UtcNow.ToString('o');ExitCode=$process.ExitCode}
} finally { $process.Dispose() }
}
function Read-WelaProbeEvents {
param([DateTime]$StartUtc,[DateTime]$EndUtc,[int]$MaximumEvents=512)
try {
$records=@(Get-WinEvent -FilterHashtable @{LogName='Security';ProviderName='Microsoft-Windows-Security-Auditing';Id=4688;StartTime=$StartUtc;EndTime=$EndUtc} -MaxEvents $MaximumEvents -ErrorAction Stop)
$xml=@(foreach ($record in $records) { try { [string]$record.ToXml() } finally { $record.Dispose() } })
[pscustomobject]@{Xml=$xml;Capped=($records.Count -ge $MaximumEvents)}
} catch {
if ($_.FullyQualifiedErrorId -like 'NoMatchingEventsFound*') { return [pscustomobject]@{Xml=@();Capped=$false} }
throw
}
}
function Test-WelaProbeEvent {
param([string]$Xml,$Process,$State,[DateTime]$EndUtc)
$reader=$null
try {
$settings=New-Object Xml.XmlReaderSettings
$settings.DtdProcessing=[Xml.DtdProcessing]::Prohibit; $settings.XmlResolver=$null; $settings.MaxCharactersInDocument=4194304
$reader=[Xml.XmlReader]::Create([IO.StringReader]::new($Xml),$settings)
$doc=New-Object Xml.XmlDocument; $doc.XmlResolver=$null; $doc.Load($reader)
$ns=New-Object Xml.XmlNamespaceManager($doc.NameTable); $ns.AddNamespace('e','http://schemas.microsoft.com/win/2004/08/events/event')
if ($doc.DocumentElement.LocalName -cne 'Event' -or $doc.DocumentElement.NamespaceURI -cne $ns.LookupNamespace('e')) { return $false }
if ($doc.SelectNodes('/e:Event/e:System',$ns).Count -ne 1 -or $doc.SelectNodes('/e:Event/e:EventData',$ns).Count -ne 1 -or $doc.SelectNodes('/e:Event/e:UserData',$ns).Count) { return $false }
$system=@{}
foreach ($name in @('Provider','EventID','Version','EventRecordID','Channel','Computer','Keywords','TimeCreated')) {
$nodes=$doc.SelectNodes("/e:Event/e:System/e:$name",$ns)
if ($nodes.Count -ne 1) { return $false }; $system[$name]=$nodes[0]
}
if ($system.Provider.GetAttribute('Name') -cne 'Microsoft-Windows-Security-Auditing' -or
$system.Provider.GetAttribute('Guid').Trim('{}') -ine '54849625-5478-4994-a5ba-3e3b0328c30d' -or
$system.EventID.InnerText -cne '4688' -or $system.Version.InnerText -cne '2' -or $system.Channel.InnerText -cne 'Security' -or
$system.Keywords.InnerText -ine '0x8020000000000000' -or $system.EventRecordID.InnerText -notmatch '^[1-9][0-9]*$') { return $false }
$computers=@($State.context.computer)
if ($State.context.domainJoined) { $computers+= "$($State.context.computer).$($State.hostObservation.Domain)" }
if ($system.Computer.InnerText -notin $computers) { return $false }
$eventTime=[DateTimeOffset]::Parse($system.TimeCreated.GetAttribute('SystemTime'),[Globalization.CultureInfo]::InvariantCulture)
if ($eventTime.UtcDateTime -lt ([DateTimeOffset]::Parse($Process.StartedUtc)).UtcDateTime -or $eventTime.UtcDateTime -gt $EndUtc) { return $false }
$data=@{}
foreach ($node in $doc.SelectNodes('/e:Event/e:EventData/e:Data',$ns)) {
$name=$node.GetAttribute('Name'); if (-not $name -or $data.ContainsKey($name)) { return $false }; $data[$name]=$node.InnerText
}
if ($data.NewProcessId -notmatch '^0x[0-9a-f]+$' -or $data.ProcessId -notmatch '^0x[0-9a-f]+$') { return $false }
if ([Convert]::ToInt64($data.NewProcessId.Substring(2),16) -ne $Process.ProcessId -or [Convert]::ToInt64($data.ProcessId.Substring(2),16) -ne $Process.ParentProcessId -or $data.NewProcessName -ine $Process.Executable) { return $false }
# Exactly the fixed invocation, permitting the native runtime's image quoting.
return $data.CommandLine -ieq ($Process.Executable + ' ' + $Process.Arguments) -or $data.CommandLine -ieq ('"' + $Process.Executable + '" ' + $Process.Arguments)
} catch { return $false } finally { if ($reader) { $reader.Dispose() } }
}
function Write-WelaProbeArtifact {
param([string]$Root,[string]$Name,[string]$Text)
$path=Join-Path $Root $Name
$bytes=[Text.UTF8Encoding]::new($false).GetBytes($Text)
$stream=[IO.File]::Open($path,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None)
try { $stream.Write($bytes,0,$bytes.Length) } finally { $stream.Dispose() }
[pscustomobject]@{path=$Name;sha256=(Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash.ToLowerInvariant()}
}
function Invoke-WelaNativeValidation {
param([ValidateSet('Plan','Run')][string]$Action='Plan',[string]$OutputPath,[ValidateRange(1,30)][int]$TimeoutSeconds=15)
if (($Action -eq 'Run') -ne (-not [string]::IsNullOrWhiteSpace($OutputPath))) { throw 'Run requires a new -ProbeOutputPath directory; Plan does not write artifacts.' }
$report=[pscustomobject][ordered]@{SchemaVersion=1;Kind='WelaNativeProbeComponents';Probe='security-4688-command-line-v1';Action=$Action;Status='Unverified';ExitCode=0;GeneratedUtc=[DateTime]::UtcNow.ToString('o');PolicyChanges=0;ReadyRuleCredit=0;Scope='Built-in Windows only. Sysmon excluded. Native event collection is not complete-rule or backend validation.';RequiredEvidence=@('Reviewed complete rule and normalization','Backend ingestion','Translated query and successful query result');BeforeState=$null;AfterState=$null;Process=$null;Artifacts=@();Diagnostic='';OutputPath=$null}
# Reserve a new private directory before any process is launched. New-Item fails on collisions.
if ($Action -eq 'Run') {
# PowerShell location can differ from the process working directory.
$provider=$null; $drive=$null
$full=$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath,[ref]$provider,[ref]$drive)
if ($provider.Name -ne 'FileSystem') { throw 'Probe output must use the filesystem.' }
$parent=Split-Path $full -Parent
if (-not (Test-Path -LiteralPath $parent -PathType Container)) { throw 'Output parent directory must already exist.' }
$null=New-Item -ItemType Directory -Path $full -ErrorAction Stop
$report.OutputPath=$full
if ([Environment]::OSVersion.Platform -eq [PlatformID]::Win32NT) {
$acl=New-Object Security.AccessControl.DirectorySecurity
$acl.SetAccessRuleProtection($true,$false)
foreach ($sid in @([Security.Principal.WindowsIdentity]::GetCurrent().User.Value,'S-1-5-18','S-1-5-32-544') | Select-Object -Unique) {
$acl.AddAccessRule([Security.AccessControl.FileSystemAccessRule]::new([Security.Principal.SecurityIdentifier]::new($sid),'FullControl','ContainerInherit,ObjectInherit','None','Allow'))
}
Set-Acl -LiteralPath $full -AclObject $acl -ErrorAction Stop
}
}
try {
$before=Get-WelaProbeState; $report.BeforeState=$before
$beforeKey=Get-WelaProbeStateKey $before
if ($Action -eq 'Run') { $report.Artifacts+=Write-WelaProbeArtifact $full 'before-state.json' ($before | ConvertTo-Json -Depth 16) }
Assert-WelaProbePrerequisites $before
if ($Action -eq 'Plan') { $report.Status='PrerequisitesObserved'; return $report }
$fresh=Get-WelaProbeState; Assert-WelaProbePrerequisites $fresh
if ((Get-WelaProbeStateKey $fresh) -cne $beforeKey) { throw 'Native prerequisite or context drift before probe launch.' }
$process=Start-WelaProbeProcess; $report.Process=$process
$report.Artifacts+=Write-WelaProbeArtifact $full 'process.json' ($process | ConvertTo-Json -Depth 6)
$timer=[Diagnostics.Stopwatch]::StartNew(); $matches=@()
do {
$end=[DateTime]::UtcNow
$batch=Read-WelaProbeEvents -StartUtc ([DateTimeOffset]::Parse($process.StartedUtc).UtcDateTime) -EndUtc $end
if ($batch.Capped) { throw '4688 query reached its 512-event cap; collection is incomplete. Retry in a quieter isolated environment.' }
$matches=@($batch.Xml | Where-Object { Test-WelaProbeEvent -Xml $_ -Process $process -State $before -EndUtc $end })
if ($matches.Count -gt 1) { throw 'Ambiguous native probe events; no event was selected.' }
if ($matches.Count -eq 1) { break }
if ($timer.Elapsed.TotalSeconds -lt $TimeoutSeconds) { Start-Sleep -Milliseconds 250 }
} while ($timer.Elapsed.TotalSeconds -lt $TimeoutSeconds)
if ($matches.Count -ne 1) { throw 'No exact native 4688 event with the probe PID, creator PID, path and full command line arrived within the timeout.' }
$report.Artifacts+=Write-WelaProbeArtifact $full 'event.xml' ([string]$matches[0])
$after=Get-WelaProbeState; $report.AfterState=$after
$report.Artifacts+=Write-WelaProbeArtifact $full 'after-state.json' ($after | ConvertTo-Json -Depth 16)
Assert-WelaProbePrerequisites $after
if ((Get-WelaProbeStateKey $after) -cne $beforeKey) { throw 'Observed host or policy drift during native probe collection.' }
$report.Status='NativeEventObserved'
} catch { $report.Status='Unverified'; $report.ExitCode=1; $report.Diagnostic=$_.Exception.Message }
if ($Action -eq 'Run') {
$null=Write-WelaProbeArtifact $full 'manifest.json' ($report | ConvertTo-Json -Depth 20)
}
return $report
}
+88
View File
@@ -0,0 +1,88 @@
$ErrorActionPreference='Stop'
$repo=Split-Path $PSScriptRoot -Parent
. (Join-Path $repo 'scripts/ControlApplicability.ps1')
. (Join-Path $repo 'scripts/NativeValidation.ps1')
$script:checks=0
function Assert($Condition,[string]$Message) { if (-not $Condition) { throw "FAIL: $Message" }; $script:checks++ }
function Throws([scriptblock]$Code,[string]$Message) { $caught=$false; try { & $Code | Out-Null } catch { $caught=$true }; Assert $caught $Message }
$context=[pscustomobject]@{Status='Observed';RolesStatus='Observed';ProductType=3;DomainRole=2;DomainJoined=$false;Build=20348;UBR=4000;Edition='ServerDatacenter';Domain='WORKGROUP';Architecture='64-bit';ProcessorArchitecture=9;InstalledRoles=@('Web-Server')}
$script:state=[pscustomobject]@{capturedAtUtc=[DateTime]::UtcNow.ToString('o');context=[pscustomobject]@{computer='test-host';role='MemberServer';build=20348;patch='20348.4000';domainJoined=$false;installedRoles=@('Web-Server')};hostObservation=$context;auditPolicies=@{'0cce922b-69ae-11d9-bed3-505054503030'=1};auditPrecedence=[pscustomobject]@{KeyExists=$true;ValueExists=$true;Type='DWord';Value=1};commandLineCapture=[pscustomobject]@{KeyExists=$true;ValueExists=$true;Type='DWord';Value=1};securityChannelEnabled=$true}
$script:process=[pscustomobject]@{ProcessId=123;ParentProcessId=456;Executable='C:\Windows\System32\cmd.exe';Arguments='/d /c echo WELA_PROBE_0123456789abcdef0123456789abcdef';Marker='WELA_PROBE_0123456789abcdef0123456789abcdef';StartedUtc=[DateTime]::UtcNow.AddSeconds(-1).ToString('o');ExitCode=0}
$time=[DateTime]::UtcNow.ToString('o')
$script:xml=@"
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"><System><Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}"/><EventID>4688</EventID><Version>2</Version><EventRecordID>100</EventRecordID><Channel>Security</Channel><Computer>test-host</Computer><Keywords>0x8020000000000000</Keywords><TimeCreated SystemTime="$time"/></System><EventData><Data Name="NewProcessId">0x7b</Data><Data Name="ProcessId">0x1c8</Data><Data Name="NewProcessName">C:\Windows\System32\cmd.exe</Data><Data Name="CommandLine">"C:\Windows\System32\cmd.exe" /d /c echo WELA_PROBE_0123456789abcdef0123456789abcdef</Data></EventData></Event>
"@
Assert (Test-WelaProbeEvent $xml $process $state ([DateTime]::UtcNow)) 'complete native-shaped event matches'
foreach ($replacement in @(@('0x7b','0x7c'),@('0x1c8','0x1c9'),@('WELA_PROBE_0123456789abcdef0123456789abcdef','WELA_PROBE_wrong'),@('<EventID>4688</EventID>','<EventID>4689</EventID>'),@('<Version>2</Version>','<Version>1</Version>'),@('test-host','other-host'),@('0x8020000000000000','0x8010000000000000'),@('<Channel>Security</Channel>','<Channel>System</Channel>'),@('<EventRecordID>100</EventRecordID>','<EventRecordID>0</EventRecordID>'),@('Name="Microsoft-Windows-Security-Auditing"','Name="Other"'),@('<Data Name="ProcessId">0x1c8</Data>','<Data Name="ProcessId">0x1c8</Data><Data Name="ProcessId">0x1c8</Data>'))) {
Assert (-not (Test-WelaProbeEvent ($xml.Replace($replacement[0],$replacement[1])) $process $state ([DateTime]::UtcNow))) "mismatch rejected: $($replacement[0])"
}
Assert (-not (Test-WelaProbeEvent ('<!DOCTYPE Event [<!ENTITY xx SYSTEM "file:///etc/passwd">]>'+$xml) $process $state ([DateTime]::UtcNow))) 'external entity rejected'
Assert (-not (Test-WelaProbeEvent $xml $process $state ([DateTime]::UtcNow.AddDays(-1)))) 'future relative event rejected'
$prior=$process.StartedUtc; $process.StartedUtc=[DateTime]::UtcNow.AddDays(1).ToString('o')
Assert (-not (Test-WelaProbeEvent $xml $process $state ([DateTime]::UtcNow))) 'event predating probe rejected'; $process.StartedUtc=$prior
Assert-WelaProbePrerequisites $state
foreach ($field in @('role','patch','domainJoined','installedRoles')) {
$prior=$state.context.$field
switch ($field) {
'role' { $state.context.role='DomainController' }
'patch' { $state.context.patch='20348.9999' }
'domainJoined' { $state.context.domainJoined=$true }
'installedRoles' { $state.context.installedRoles=@('ADCS-Cert-Authority') }
}
Throws { Assert-WelaProbePrerequisites $state } "contradictory native context $field rejected"
$state.context.$field=$prior
}
$state.context.role='ADCS'
Throws { Assert-WelaProbePrerequisites $state } 'CA label without its installed role cannot establish a CA probe context'
$state.context.role='MemberServer'
foreach ($mask in @(0,2,$null,'1')) { $state.auditPolicies['0cce922b-69ae-11d9-bed3-505054503030']=$mask; Throws { Assert-WelaProbePrerequisites $state } 'missing success bit or unknown mask rejected' }
$state.auditPolicies['0cce922b-69ae-11d9-bed3-505054503030']=1
$state.auditPrecedence.Type='String'; Throws { Assert-WelaProbePrerequisites $state } 'string masquerading as DWORD rejected'; $state.auditPrecedence.Type='DWord'
$state.commandLineCapture.Value=0; Throws { Assert-WelaProbePrerequisites $state } 'missing command line capture rejected'; $state.commandLineCapture.Value=1
$state.securityChannelEnabled=$false; Throws { Assert-WelaProbePrerequisites $state } 'disabled Security rejected'; $state.securityChannelEnabled=$true
$state.hostObservation.RolesStatus='Unknown'; Throws { Assert-WelaProbePrerequisites $state } 'unknown roles rejected'; $state.hostObservation.RolesStatus='Observed'
$script:launched=0; $script:reads=0; $script:scenario='success'
function Get-WelaProbeState { $script:reads++; if ($scenario -eq 'denied') { throw 'denied' }; if ($scenario -eq 'drift' -and $reads -gt 2) { $script:state.auditPolicies['0cce922b-69ae-11d9-bed3-505054503030']=3 }; return $script:state }
function Start-WelaProbeProcess { $script:launched++; return $script:process }
function Read-WelaProbeEvents {
if ($scenario -eq 'query-denied') { throw 'Security access denied' }
if ($scenario -eq 'ambiguous') { return [pscustomobject]@{Xml=@($script:xml,$script:xml);Capped=$false} }
if ($scenario -eq 'timeout') { return [pscustomobject]@{Xml=@();Capped=$false} }
[pscustomobject]@{Xml=@($script:xml);Capped=($scenario -eq 'cap')}
}
$root=Join-Path ([IO.Path]::GetTempPath()) ('wela-native-probe-tests-'+[guid]::NewGuid().ToString('N')); $null=New-Item -ItemType Directory -Path $root
try {
# PowerShell's current location is independent of the process working directory.
$relativeName='relative-probe-'+[guid]::NewGuid().ToString('N')
Push-Location $root
try {
$relative=Invoke-WelaNativeValidation -Action Run -OutputPath $relativeName -TimeoutSeconds 1
Assert ($relative.ExitCode -eq 0 -and $relative.OutputPath -eq (Join-Path $root $relativeName)) 'relative probe destination resolves against the PowerShell location'
Assert (Test-Path -LiteralPath (Join-Path $root "$relativeName/manifest.json")) 'relative probe manifest is written in the requested location'
} finally { Pop-Location }
$script:launched=0
$plan=Invoke-WelaNativeValidation
Assert ($plan.Status -eq 'PrerequisitesObserved' -and $launched -eq 0 -and $plan.Artifacts.Count -eq 0) 'default Plan observes without launching or exporting'
Throws { Invoke-WelaNativeValidation -Action Run } 'Run needs new destination'
Throws { Invoke-WelaNativeValidation -OutputPath (Join-Path $root 'unused') } 'Plan rejects output destination'
foreach ($case in @('success','cap','ambiguous','timeout','denied','query-denied','drift')) {
$script:scenario=$case; $script:reads=0; $script:state.auditPolicies['0cce922b-69ae-11d9-bed3-505054503030']=1
$destination=Join-Path $root $case
$result=Invoke-WelaNativeValidation -Action Run -OutputPath $destination -TimeoutSeconds 1
Assert (Test-Path (Join-Path $destination 'manifest.json')) 'partial and completed manifests are retained'
Assert ($result.ReadyRuleCredit -eq 0 -and $result.PolicyChanges -eq 0) 'no policy changes or readiness credit'
if ($case -eq 'success') {
Assert ($result.Status -eq 'NativeEventObserved' -and $result.ExitCode -eq 0 -and $result.Artifacts.Count -eq 4) 'exact native probe artifact set collected'
foreach ($artifact in $result.Artifacts) { Assert ((Get-FileHash -LiteralPath (Join-Path $destination $artifact.path)).Hash.ToLowerInvariant() -ceq $artifact.sha256) 'artifact hashes verify' }
} else { Assert ($result.Status -eq 'Unverified' -and $result.ExitCode -eq 1 -and $result.Diagnostic) "failure cannot claim telemetry: $case" }
Throws { Invoke-WelaNativeValidation -Action Run -OutputPath $destination } 'existing output directory refused'
}
} finally { Remove-Item -LiteralPath $root -Recurse -Force }
# The public dispatcher must reject probe options before it reaches unrelated mutation paths.
foreach ($args in @(@('configure','-Profile','wela','-ProbeAction','Plan'),@('native-validation','-Auto'),@('native-validation','-Role','Client','-Build','26100'))) {
$saved=$ErrorActionPreference; $ErrorActionPreference='Continue'
try { $output=& (Get-Process -Id $PID).Path -NoProfile -File (Join-Path $repo 'WELA.ps1') @args 2>&1; $code=$LASTEXITCODE } finally { $ErrorActionPreference=$saved }
Assert ($code -ne 0 -and ($output -join ' ') -match 'No command') 'public scope guard fails before execution'
}
$global:LASTEXITCODE=0
Write-Host "Passed $script:checks native-validation assertions. Fixtures are synthetic; no native telemetry is claimed."
+62
View File
@@ -0,0 +1,62 @@
param([switch]$AllowDisposablePolicyWrite)
$ErrorActionPreference='Stop'
if ($env:OS -ne 'Windows_NT') { Write-Host 'Skipped: native Windows is required.'; exit 0 }
if (-not $AllowDisposablePolicyWrite -or $env:GITHUB_ACTIONS -ne 'true' -or $env:RUNNER_ENVIRONMENT -ne 'github-hosted') { throw 'This native event test requires explicit policy-write opt-in on a disposable GitHub-hosted runner.' }
$repo=Split-Path $PSScriptRoot -Parent
. (Join-Path $repo 'scripts/Configuration.ps1')
. (Join-Path $repo 'scripts/ControlApplicability.ps1')
. (Join-Path $repo 'scripts/NativeValidation.ps1')
Import-Module (Join-Path $repo 'modules/AuditProfiles.psm1') -Force
$guid='0cce922b-69ae-11d9-bed3-505054503030'
$controls=@(
[pscustomobject]@{Path='HKLM:\SYSTEM\CurrentControlSet\Control\Lsa';Name='SCENoApplyLegacyAuditPolicy'},
[pscustomobject]@{Path='HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit';Name='ProcessCreationIncludeCmdLine_Enabled'}
)
$beforeMask=(Get-WelaEffectiveAuditPolicy)[$guid]
foreach ($control in $controls) { $control | Add-Member NoteProperty Before (Get-WelaRegistryState -Path $control.Path -Name $control.Name) }
$root=Join-Path ([IO.Path]::GetTempPath()) ('wela-native-4688-'+[guid]::NewGuid().ToString('N'))
$null=New-Item -ItemType Directory -Path $root
$receipt=Join-Path $root 'policy-before.json'
[pscustomobject]@{AuditMask=$beforeMask;Controls=$controls} | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $receipt -Encoding UTF8
$touched=$false; $restored=$false
try {
$touched=$true
foreach ($control in $controls) {
if (-not (Test-Path -LiteralPath $control.Path)) { $null=New-WelaRegistryKey -Path $control.Path }
$null=New-ItemProperty -LiteralPath $control.Path -Name $control.Name -Value 1 -PropertyType DWord -Force
}
Set-WelaEffectiveAuditPolicy -Guid $guid -Mask 1 -Mode minimum
$destination=Join-Path $root 'collection'
$result=Invoke-WelaNativeValidation -Action Run -OutputPath $destination -TimeoutSeconds 30
if ($result.ExitCode -ne 0 -or $result.Status -ne 'NativeEventObserved') { throw ($result | ConvertTo-Json -Depth 20) }
if ($result.ReadyRuleCredit -ne 0 -or $result.Artifacts.Count -ne 4) { throw 'Collector mislabeled incomplete evidence.' }
foreach ($artifact in $result.Artifacts) {
if ((Get-FileHash -LiteralPath (Join-Path $destination $artifact.path)).Hash.ToLowerInvariant() -cne $artifact.sha256) { throw 'Native artifact hash mismatch.' }
}
$event=[IO.File]::ReadAllText((Join-Path $destination 'event.xml'))
if (-not (Test-WelaProbeEvent $event $result.Process $result.BeforeState ([DateTime]::UtcNow))) { throw 'Native event cannot be independently matched.' }
Write-Host "Native 4688 event observed on $($result.BeforeState.context.role) $($result.BeforeState.context.patch) under PowerShell $($PSVersionTable.PSVersion). Complete-rule, backend and other-role validation remain pending."
} finally {
if ($touched) {
$errors=@()
try { Set-WelaEffectiveAuditPolicy -Guid $guid -Mask $beforeMask -Mode exact } catch { $errors+=$_.Exception.Message }
foreach ($control in $controls) {
try {
if ($control.Before.ValueExists) { $null=New-ItemProperty -LiteralPath $control.Path -Name $control.Name -Value $control.Before.Value -PropertyType $control.Before.Type -Force }
else { Remove-ItemProperty -LiteralPath $control.Path -Name $control.Name -ErrorAction SilentlyContinue }
if (-not $control.Before.KeyExists -and (Test-Path -LiteralPath $control.Path)) {
$key=Get-Item -LiteralPath $control.Path
if ($key.ValueCount -eq 0 -and $key.SubKeyCount -eq 0) { Remove-Item -LiteralPath $control.Path -ErrorAction Stop }
}
$after=Get-WelaRegistryState -Path $control.Path -Name $control.Name
if (($after | ConvertTo-Json -Compress) -cne ($control.Before | ConvertTo-Json -Compress)) { throw "Registry restoration differs: $($control.Name)" }
} catch { $errors+=$_.Exception.Message }
}
try { if ((Get-WelaEffectiveAuditPolicy)[$guid] -ne $beforeMask) { throw 'Audit mask restoration differs.' } } catch { $errors+=$_.Exception.Message }
$restored=$errors.Count -eq 0
if (-not $restored) { throw "Policy restoration failed; receipt retained at $receipt : $($errors -join '; ')" }
}
if ($restored) { Remove-Item -LiteralPath $root -Recurse -Force }
}
$global:LASTEXITCODE=0
Write-Host 'Native 4688 collection and exact policy restoration passed.'
+1
View File
@@ -7,6 +7,7 @@
**改善:**
- ネイティブ Security 4688 の無害な固定プローブを収集する明示的な `native-validation` を追加。型付き前提条件、イベントの厳密な照合、前後の状態、ハッシュ付き証拠を新規の非公開ディレクトリに記録します。不完全・上限到達・曖昧・ドリフトの結果は未検証のままです。監査ポリシー変更や Sigma 利用可能ルール数の加算は行いません。使い捨て Server 2022/2025 テストで実イベントとポリシー復元を確認し、Windows 11/DC/ADCS とバックエンドの検証は別途必要です。 (#413) (@Shirofune-Security)
- ローカル監査権限と`CrashOnAuditFail`を監査・計画・出典別に設定する任意実行の`audit-integrity`を追加しました。実際のクライアント・メンバーサーバー・DCを区別し、Microsoft SCTで省略された設定は保持します。対象SIDの一覧、権限削除の明示指定、権限単位のLSA更新、完全な復旧記録と変更直前・変更後の検証により、無関係な権限を保持します。復旧状態では変更せず、ネイティブCIは読み取りだけを行います。トークン・GPO・サービス・イベントの検証は別途ラボで必要となり、Sigma検知範囲には加算しません。 (#412) (@Shirofune-Security)
- 読み取り専用の`retention-health`を追加し、送信元/収集サーバーの標準ログバッファ、確認した先頭レコードの経過日数、申告されたアーカイブ方針、件数を制限したローカルEVTX/ACL一覧、各言語のWEF・時刻情報とログ消失・消去・満杯の兆候をJSONと単独で表示できるHTMLに分けて記録します。保持されたイベントの時刻に基づく件数率とUTF-8 XMLバイト数の試算には上限・前提を明示し、容量・完全な保持・実効読み取り権限・時刻同期・転送成功の証明とは扱いません。複数ホストでのロールオーバー・復旧・到着検証は別途必要です。 (#410) (@Shirofune-Security)
- 読み取り専用の`control-applicability`を追加し、旧CISのApplication Guard監査要件を保持しつつ、Windows 11 24H2以降では削除済み・対象外と表示します。`default-evidence`で正確なビルド・パッチ・ドメイン参加・役割を含む現状を記録し、出典とレビュー情報が一致する参照環境と比較できます。旧ベースラインの既定値は履歴情報として保持し、未検証の既定値はUnknownと表示します。合成テストと読み取り専用CIからクリーンインストールやイベント生成の証明は行いません。 (#409) (@Shirofune-Security)
+1
View File
@@ -7,6 +7,7 @@
**Improvements:**
- Added opt-in `native-validation` to collect a fixed benign Security 4688 probe with typed prerequisites, exact native event matching, before/after state and hashed components in a new private directory. Partial, capped, ambiguous and drifted results remain unverified; the collector changes no audit policy and grants no Sigma readiness credit. Disposable Server 2022/2025 tests exercise real events with verified policy restoration; Windows 11/DC/ADCS and backend acceptance remain separate. (#413) (@Shirofune-Security)
- Added opt-in `audit-integrity` audit, plan and source-profile configuration for local audit privileges and `CrashOnAuditFail`, with separate actual client/member/DC scope and preserved Microsoft SCT omissions. Exact affected SIDs, explicit privilege-removal consent, per-right LSA updates, complete recovery journals and fresh/readback checks preserve unrelated privileges. Recovery states are blocked; native CI reads policy only, while token, GPO, service and event validation remains a lab requirement without Sigma credit. (#412) (@Shirofune-Security)
- Added read-only `retention-health` source/collector JSON and self-contained HTML reports separating native buffers, observed record-boundary ages, declared archive policy, bounded local EVTX/ACL inventory, localized WEF/time evidence and loss/clear/full indicators. Retained-event timestamp rates and UTF-8 XML-byte scenarios expose caps and assumptions; none establish archive capacity, complete retention, effective reader access, synchronized time or successful forwarding. Multi-host rollover/recovery/arrival validation remains pending. (#410) (@Shirofune-Security)
- Added read-only `control-applicability` for the historical CIS Application Guard audit requirement, reporting removal on Windows 11 24H2+ without remediation. Added exact build/patch/join/role native snapshots and provenance-bound reference comparison through `default-evidence`. Legacy baseline default strings are retained as historical hints while public defaults remain Unknown without reviewed scenario evidence. Synthetic fixtures and native read-only CI do not claim clean-install or event-generation proof. (#409) (@Shirofune-Security)