mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-23 22:53:00 +02:00
Gate historical controls and require evidence for Windows defaults (#409)
* Gate historical controls and require provenance for Windows defaults * Bind default evidence to UTC provenance and native architecture * Reference PR 409 in applicability changelogs
This commit is contained in:
1 parent
d35b1374d0
commit
14ac8667d4
13 files changed
+666
-6
No files matched your search
@@ -2,5 +2,14 @@
|
||||
/config/security_rules.json text eol=lf
|
||||
/config/eid_subcategory_mapping.csv text eol=lf
|
||||
/config/rule_eligibility_manifest.json text eol=lf
|
||||
|
||||
# Exact-context default evidence pins these source/collector bytes.
|
||||
/config/baselines.json text eol=lf
|
||||
/config/audit_profiles.json text eol=lf
|
||||
/config/control_applicability.json text eol=lf
|
||||
/scripts/ControlApplicability.ps1 text eol=lf
|
||||
/scripts/Configuration.ps1 text eol=lf
|
||||
/modules/NativeProviders.psm1 text eol=lf
|
||||
/modules/AuditProfiles.psm1 text eol=lf
|
||||
# Full upstream rule artifacts retain their exact pinned bytes on every platform.
|
||||
/config/provider_rule_sources/*.yml -text whitespace=-blank-at-eol
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Control applicability and default evidence
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
control-applicability:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-2022, windows-2025]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Fixtures on Windows PowerShell 5.1
|
||||
shell: powershell
|
||||
run: ./tests/ControlApplicability.Tests.ps1
|
||||
- name: Native read-only capture on Windows PowerShell 5.1
|
||||
shell: powershell
|
||||
run: ./tests/ControlApplicability.Windows.Tests.ps1
|
||||
- name: Fixtures on PowerShell 7
|
||||
shell: pwsh
|
||||
run: ./tests/ControlApplicability.Tests.ps1
|
||||
- name: Native read-only capture on PowerShell 7
|
||||
shell: pwsh
|
||||
run: ./tests/ControlApplicability.Windows.Tests.ps1
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
**改善:**
|
||||
|
||||
- 読み取り専用の`control-applicability`を追加し、旧CISのApplication Guard監査要件を保持しつつ、Windows 11 24H2以降では削除済み・対象外と表示します。`default-evidence`で正確なビルド・パッチ・ドメイン参加・役割を含む現状を記録し、出典とレビュー情報が一致する参照環境と比較できます。旧ベースラインの既定値は履歴情報として保持し、未検証の既定値はUnknownと表示します。合成テストと読み取り専用CIからクリーンインストールやイベント生成の証明は行いません。 (#409) (@Shirofune-Security)
|
||||
- Windows標準のDNS Client/Server、CAPI2、WinRM、RDP Clientについて、明示的に選択するprovider-packsの監査・計画とチャネル設定を追加しました。固定した完全なルール定義と実際のプロバイダー・チャネル・イベントスキーマを確認し、DNSのチャネル名不一致や不明な前提条件を保持します。復旧記録付きの変更では大きいバッファ・保持方式・ACLを維持し、Analyticalと従来のDNSログは手動確認のみとします。イベント生成・バックエンド検証や検知範囲の向上は未確認です。 (#411) (@Shirofune-Security)
|
||||
- 任意実行の`audit-notifications`を追加し、OneSettings監査とSecurityログ警告しきい値の監査・計画・設定に対応しました。対象とチャネル変更の明示指定、OS・ADMX確認、型付き復旧記録と変更検出を行い、既存の低いしきい値とチャネルACL・保存方式を保持します。ポリシー一致と実イベント生成を分け、Windowsラボの証拠とSigma利用可能性は未検証と表示します。 (#408) (@Shirofune-Security)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
**Improvements:**
|
||||
|
||||
- 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)
|
||||
- Added explicit native DNS Client/Server, CAPI2, WinRM and RDP Client provider-pack inventory and selective channel configuration. Pinned full rule definitions and live provider/channel/event schemas retain DNS channel mismatches and unknown prerequisites; journaled opt-in changes preserve larger buffers, retention and ACLs. Analytical/classic DNS remain manual-only, and no event/backend readiness uplift is claimed. (#411) (@Shirofune-Security)
|
||||
- Added opt-in `audit-notifications` audit/plan/configure for OneSettings auditing and Security log warning thresholds, with explicit control/channel selections, reviewed host and ADMX gates, typed recovery journals and drift checks. Earlier warning thresholds and channel ACL/retention are preserved. Reports separate policy matches from warning/event generation; Windows lab evidence and Sigma eligibility remain unverified. (#408) (@Shirofune-Security)
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@
|
||||
[ValidateRange(1,2147483647)][int]$LdapSearchTimeMs,
|
||||
[ValidateRange(1,2147483647)][int]$LdapExpensiveThreshold,
|
||||
[ValidateRange(1,2147483647)][int]$LdapInefficientThreshold,
|
||||
[ValidateSet('Capture','Compare')][string]$DefaultEvidenceAction = 'Capture',
|
||||
[string]$DefaultEvidencePath,
|
||||
[ValidateSet('List','Audit','Plan','Configure')][string]$ProviderAction = 'List',
|
||||
[string[]]$ProviderPack,
|
||||
[ValidateSet('Audit','Plan','Configure')][string]$NotificationAction = 'Audit',
|
||||
@@ -72,6 +74,7 @@ $SaclTargetsPath = Join-Path $ScriptRoot "config/audit_sacl_targets.json"
|
||||
. (Join-Path $ScriptRoot "scripts/FirewallLogging.ps1")
|
||||
. (Join-Path $ScriptRoot "scripts/SmbAuditing.ps1")
|
||||
. (Join-Path $ScriptRoot "scripts/LdapDiagnostics.ps1")
|
||||
. (Join-Path $ScriptRoot "scripts/ControlApplicability.ps1")
|
||||
. (Join-Path $ScriptRoot "scripts/AuditNotifications.ps1")
|
||||
. (Join-Path $ScriptRoot "scripts/AdObjectSacl.ps1")
|
||||
. (Join-Path $ScriptRoot "scripts/AppLockerReadiness.ps1")
|
||||
@@ -107,7 +110,9 @@ class WELA {
|
||||
[array] $NativeSources = @()
|
||||
[array] $Rules
|
||||
[hashtable] $RulesCount
|
||||
[string] $DefaultSetting = ""
|
||||
[string] $DefaultSetting = "Unknown"
|
||||
[string] $LegacyDefaultHint = ""
|
||||
[string] $DefaultEvidence = "No exact-context reviewed default evidence; historical hints are not host defaults."
|
||||
[string] $RecommendedSetting = ""
|
||||
[string] $Volume = ""
|
||||
[string] $Note = ""
|
||||
@@ -126,7 +131,8 @@ class WELA {
|
||||
$this.SubCategory = $SubCategory
|
||||
$this.CurrentSetting = $CurrentSetting
|
||||
$this.Rules = $Rules
|
||||
$this.DefaultSetting = $DefaultSetting
|
||||
$this.LegacyDefaultHint = $DefaultSetting
|
||||
$this.DefaultSetting = "Unknown"
|
||||
$this.RecommendedSetting = $RecommendedSetting
|
||||
$this.Volume = $Volume
|
||||
$this.Note = $Note
|
||||
@@ -175,6 +181,7 @@ class WELA {
|
||||
}
|
||||
if ($this.DefaultSetting) {
|
||||
Write-Host " - Default Setting: $($this.DefaultSetting)"
|
||||
Write-Host " - Default Evidence: $($this.DefaultEvidence)"
|
||||
}
|
||||
if ($this.CurrentSetting) {
|
||||
Write-Host " - Current Setting: $($this.CurrentSetting)"
|
||||
@@ -749,7 +756,7 @@ function AuditLogSetting {
|
||||
Write-Host ""
|
||||
}
|
||||
} elseif ($outType -eq "table") {
|
||||
$auditResult | Select-Object -Property Category, SubCategory, RuleCount, DefaultSetting, CurrentSetting, ChannelState, GenerationReadiness, RecommendedSetting, Volume | Format-Table
|
||||
$auditResult | Select-Object -Property Category, SubCategory, RuleCount, DefaultSetting, DefaultEvidence, CurrentSetting, ChannelState, GenerationReadiness, RecommendedSetting, Volume | Format-Table
|
||||
}
|
||||
|
||||
# 1つのルールが複数カテゴリに属するため、集計とCSVはルールID単位で重複排除する
|
||||
@@ -764,7 +771,7 @@ function AuditLogSetting {
|
||||
$currentJson = Join-Path $script:ScriptRoot "mitre-ttp-navigator-current.json"
|
||||
$idealJson = Join-Path $script:ScriptRoot "mitre-ttp-navigator-ideal.json"
|
||||
|
||||
$auditResult | Select-Object -Property Category, SubCategory, RuleCount, RuleCountByLevel, DefaultSetting, CurrentSetting, ChannelState, GenerationReadiness, RecommendedSetting, Volume, Note,
|
||||
$auditResult | Select-Object -Property Category, SubCategory, RuleCount, RuleCountByLevel, DefaultSetting, DefaultEvidence, LegacyDefaultHint, CurrentSetting, ChannelState, GenerationReadiness, RecommendedSetting, Volume, Note,
|
||||
@{ Name = 'NativeSourceEvidence'; Expression = { if ($_.NativeSources.Count) { ConvertTo-Json -InputObject $_.NativeSources -Depth 12 -Compress } else { '' } } } |
|
||||
Export-Csv -Path $auditCsv -NoTypeInformation
|
||||
$usableRules | Select-Object title, level, service, category, description, id, EligibilityState, EligibilityReasons | Export-Csv -Path $usableCsv -NoTypeInformation
|
||||
@@ -778,7 +785,7 @@ function AuditLogSetting {
|
||||
if ($outType -eq "gui") {
|
||||
$usableRules | Select-Object title, level, service, category, description, id | Out-GridView -Title "Usable Detection Rules"
|
||||
$unUsableRules | Select-Object title, level, service, category, description, id | Out-GridView -Title "Unusable Detection Rules"
|
||||
$auditResult | Select-Object -Property Category, SubCategory, RuleCount, RuleCountByLevel, DefaultSetting, CurrentSetting, ChannelState, GenerationReadiness, RecommendedSetting, Volume, Note | Out-GridView -Title "WELA Audit Result"
|
||||
$auditResult | Select-Object -Property Category, SubCategory, RuleCount, RuleCountByLevel, DefaultSetting, DefaultEvidence, LegacyDefaultHint, CurrentSetting, ChannelState, GenerationReadiness, RecommendedSetting, Volume, Note | Out-GridView -Title "WELA Audit Result"
|
||||
}
|
||||
|
||||
Write-Output "Audit check result saved to: $auditCsv"
|
||||
@@ -1791,6 +1798,8 @@ Usage:
|
||||
./WELA.ps1 configure-sacl # Add targeted File System/Registry audit SACLs (ASEP keys + sensitive files) needed by the rules, without global auditing
|
||||
./WELA.ps1 configure-sacl -Auto # ...automatically without prompts
|
||||
./WELA.ps1 update-rules # Update rule config files from https://github.com/Yamato-Security/WELA
|
||||
./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 version # Show the WELA version
|
||||
./WELA.ps1 help # Show this help
|
||||
@@ -1803,6 +1812,10 @@ Write-Host ""
|
||||
Write-Host "WELA v$WELAVersion - $WELAReleaseName"
|
||||
Write-Host ""
|
||||
|
||||
if ($Cmd -ne 'default-evidence' -and @($PSBoundParameters.Keys | Where-Object { $_ -in @('DefaultEvidenceAction','DefaultEvidencePath') }).Count) {
|
||||
throw 'Default evidence options require default-evidence. No command was run.'
|
||||
}
|
||||
|
||||
if ($Cmd -eq 'audit-notifications' -and @($PSBoundParameters.Keys | Where-Object { $_ -notin @('Cmd','NotificationAction','NotificationControl','WarningPercent','EnablePrivacyChannel','Auto','DryRun','BackupPath','ResultsPath','Help') }).Count) {
|
||||
throw 'audit-notifications accepts only notification, consent/dry-run, recovery and JSON output options. No command was run.'
|
||||
}
|
||||
@@ -1881,6 +1894,23 @@ if ($Profile -and $Cmd.ToLower() -in @('plan', 'audit', 'audit-settings', 'confi
|
||||
}
|
||||
|
||||
switch ($Cmd.ToLower()) {
|
||||
'control-applicability' {
|
||||
if ($Help) { Write-Host 'Usage: ./WELA.ps1 control-applicability [-ResultsPath report.json]. Read-only historical feature/build assessment; see docs/control-applicability.md.'; return }
|
||||
if ($Profile -or $Baseline -or $Role -or $Build -or $HtmlPath -or $Auto -or $BackupPath -or $PlanPath) { throw 'control-applicability reads actual local context and only accepts -ResultsPath; configuration and context overrides are unsupported.' }
|
||||
$context=Get-WelaDefaultContext
|
||||
$controls=@(Get-WelaHistoricalControls -Context $context)
|
||||
$report=[pscustomobject]@{Scope='Native historical controls; Sysmon excluded';Context=$context;Controls=$controls;Catalog=(Get-WelaControlCatalog)}
|
||||
if ($ResultsPath) { $report | ConvertTo-Json -Depth 16 | Set-Content -LiteralPath $ResultsPath -Encoding UTF8 -ErrorAction Stop }
|
||||
$report
|
||||
if (@($controls | Where-Object {$_.Applicability.Status -eq 'Unknown' -or $_.PolicyState -eq 'Unknown'}).Count) { exit 1 }
|
||||
}
|
||||
'default-evidence' {
|
||||
if ($Help) { Write-Host 'Usage: ./WELA.ps1 default-evidence [-DefaultEvidenceAction Capture|Compare] [-DefaultEvidencePath reviewed-snapshot.json] [-ResultsPath report.json]. Capture observes current settings and never labels them as defaults. See docs/control-applicability.md.'; return }
|
||||
if ($Profile -or $Baseline -or $Role -or $Build -or $HtmlPath -or $Auto -or $BackupPath -or $PlanPath) { throw 'default-evidence reads actual local context and accepts only evidence/output options.' }
|
||||
$report=Invoke-WelaDefaultEvidenceCommand -Action $DefaultEvidenceAction -ReferencePath $DefaultEvidencePath -ResultsPath $ResultsPath
|
||||
$report
|
||||
if ($report.ExitCode) { exit $report.ExitCode }
|
||||
}
|
||||
'audit-notifications' {
|
||||
if ($Help) { Write-Host 'Usage: ./WELA.ps1 audit-notifications [-NotificationAction Audit|Plan|Configure] [-NotificationControl OneSettings,SecurityWarning] [-WarningPercent 1..90] [-EnablePrivacyChannel] [-Auto] [-DryRun] [-BackupPath new-directory] [-ResultsPath report.json]. See docs/audit-notifications.md.'; return }
|
||||
if ($Profile -or $Baseline -or $Role -or $Build -or $HtmlPath) { throw 'audit-notifications uses actual host context and -ResultsPath; profile/role/build overrides and HTML are unsupported.' }
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"reviewedOn": "2026-09-19",
|
||||
"scope": "Historical native controls; not a replacement for command-specific applicability checks",
|
||||
"controls": [
|
||||
{
|
||||
"id": "application-guard-auditing",
|
||||
"title": "Application Guard audit events",
|
||||
"productTypes": [1],
|
||||
"minBuild": 22000,
|
||||
"maxBuild": 26099,
|
||||
"reviewedBuilds": [22000, 22621, 22631],
|
||||
"editions": ["Professional", "ProfessionalN", "ProfessionalEducation", "ProfessionalEducationN", "Enterprise", "EnterpriseN", "Education", "EducationN"],
|
||||
"feature": "Windows-Defender-ApplicationGuard",
|
||||
"registryPath": "HKLM:\\SOFTWARE\\Policies\\Microsoft\\AppHVSI",
|
||||
"valueName": "AuditApplicationGuard",
|
||||
"requiredType": "DWord",
|
||||
"requiredValue": 1,
|
||||
"source": {
|
||||
"title": "CIS Microsoft Windows 11 Enterprise Benchmark",
|
||||
"version": "4.0.0 (historical)",
|
||||
"control": "18.10.44.1",
|
||||
"requirement": "Enabled",
|
||||
"url": "https://www.cisecurity.org/benchmark/microsoft_windows_desktop"
|
||||
},
|
||||
"removal": {
|
||||
"firstBuild": 26100,
|
||||
"release": "Windows 11 24H2",
|
||||
"url": "https://learn.microsoft.com/en-us/windows/security/application-security/application-isolation/microsoft-defender-application-guard/md-app-guard-overview"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
# Historical controls and Windows default evidence
|
||||
|
||||
These read-only commands address issue #385. Sysmon is out of scope. They do not
|
||||
install optional features, modify registry/audit policy or grant Sigma eligibility.
|
||||
|
||||
```powershell
|
||||
./WELA.ps1 control-applicability -ResultsPath applicability.json
|
||||
./WELA.ps1 default-evidence -ResultsPath observed.json
|
||||
./WELA.ps1 default-evidence -DefaultEvidenceAction Compare -DefaultEvidencePath reviewed-clean-install.json -ResultsPath comparison.json
|
||||
```
|
||||
|
||||
Run in 64-bit PowerShell. Complete native role/feature and effective audit-policy
|
||||
reads can require elevation. Context is always observed locally; `-Role`/`-Build`
|
||||
overrides and configuration options are rejected. A partial capture preserves
|
||||
successful observations and reports failed ones as Unknown, returning a nonzero
|
||||
exit code. Failed reference qualification also returns nonzero.
|
||||
|
||||
## Application Guard
|
||||
|
||||
The versioned `config/control_applicability.json` retains CIS Windows 11 Enterprise
|
||||
v4.0.0 control 18.10.44.1 and its historical `AuditApplicationGuard=1` requirement.
|
||||
It records a Windows 11 minimum build (22000), maximum pre-removal build (26099),
|
||||
reviewed releases (22000/22621/22631), editions, optional feature and source/removal
|
||||
metadata. It is an initial historical-control catalog; existing advanced audit
|
||||
profiles and SMB/provider commands keep their own applicability checks.
|
||||
|
||||
Microsoft removed Application Guard starting Windows 11 24H2 (build 26100).
|
||||
24H2/25H2 and later client builds therefore report NotApplicable without querying
|
||||
or attempting to reinstall it, even if stale registry or feature metadata remains.
|
||||
Servers, including DC/AD CS, are outside this historical client source scope.
|
||||
On reviewed older builds, an enabled `Windows-Defender-ApplicationGuard` feature
|
||||
allows read-only auditing of the typed registry value. Disabled/absent features
|
||||
are NotApplicable; unreviewed builds/editions, inaccessible or pending feature
|
||||
states remain Unknown. The original source requirement is always retained.
|
||||
There is no automatic historical remediation, and an enabled feature is not proof
|
||||
of application readiness or event generation.
|
||||
|
||||
## Default column correction
|
||||
|
||||
The existing baseline JSON contains historical default strings without sufficient
|
||||
build/patch/role provenance. Public audit assessments now show `DefaultSetting`
|
||||
as Unknown and retain those strings separately as `LegacyDefaultHint` in CSV,
|
||||
JSON, HTML and GUI output. `DefaultEvidence` explains the distinction. Standard
|
||||
output likewise explains why the default is Unknown. Recommendations/current
|
||||
observations are unchanged. The versioned `windows-defaults-*` advanced-policy
|
||||
profiles remain explicitly documentary reference profiles, not clean-machine
|
||||
snapshots. This PR does not replace those reference profiles or fabricate lab data.
|
||||
|
||||
To use a reviewed scenario snapshot, run the dedicated `default-evidence Compare`
|
||||
command. It does not silently rewrite normal audit columns or baseline metadata.
|
||||
Use a separate `ResultsPath`: a comparison refuses an output path that resolves
|
||||
to its `DefaultEvidencePath`, preserving the reviewed input artifact.
|
||||
The comparison reports a per-control default/reference and match/difference only
|
||||
when reference provenance and exact context qualify; other defaults stay Unknown.
|
||||
Channel observations retain individual channel names and states; registry records
|
||||
retain each path, type, value and absence. They are not collapsed to assumed defaults.
|
||||
|
||||
## Capturing and reviewing reference scenarios
|
||||
|
||||
Capture records the exact Windows build and UBR patch, edition, localized OS
|
||||
architecture and independently observed `Win32_Processor.Architecture` platform
|
||||
code (x64 = 9, ARM64 = 12),
|
||||
product type, domain role, join state/domain, and a complete installed feature/role
|
||||
inventory. A CA role or DC promotion creates a distinct scenario rather than a
|
||||
universal "Server default". It includes current typed registry state, native channel
|
||||
metadata and all 59 canonical advanced audit subcategories. Individual failures
|
||||
remain Unknown. It also records source versions and SHA-256 fingerprints for the
|
||||
baseline/profile/applicability catalogs, this collector script and its native
|
||||
reader helpers (`Configuration.ps1`, `NativeProviders.psm1`, `AuditProfiles.psm1`);
|
||||
these inputs use LF checkouts so byte fingerprints are portable. Missing,
|
||||
unsupported or inconsistent processor codes leave context Unknown; the localized
|
||||
OS architecture string alone cannot qualify a reference.
|
||||
|
||||
Every capture is `EvidenceKind: ObservedState`. WELA cannot prove that a machine is
|
||||
a clean installation. A domain-joined machine's effective state may include domain
|
||||
policy; a CI runner is customized. Neither is automatically a Windows default.
|
||||
|
||||
An independent reviewer should preserve the original capture and create a reviewed
|
||||
copy only after checking the clean image, provisioning and effective GPO/MDM policy
|
||||
evidence. Change `EvidenceKind` to `ReviewedCleanInstall`, and populate `Review`:
|
||||
|
||||
| Field | Required evidence |
|
||||
|---|---|
|
||||
| `Reviewer`, `ReviewedUtc` | Identified reviewer and explicit ISO-8601 UTC review time at/after capture, ending in `Z` |
|
||||
| `ImageSha256` | SHA-256 of the clean installation image |
|
||||
| `SnapshotId` | Reproducible VM/image snapshot identifier |
|
||||
| `PolicyEvidenceSha256` | SHA-256 of retained effective GPO/MDM/provisioning evidence |
|
||||
| `ProvisioningNotes` | Patch installation, join/promotion/CA steps, policy scope and deviations |
|
||||
|
||||
Do not remove observed Unknown entries or rewrite observations to match desired
|
||||
recommendations. Keep the hashed image/policy artifacts with the review record.
|
||||
`CapturedUtc` and `ReviewedUtc` must use `YYYY-MM-DDTHH:mm:ss[.fffffff]Z` (optional
|
||||
one to seven fractional digits). Localized dates, missing timezone suffixes,
|
||||
offset spellings and future timestamps are rejected; there is no clock grace
|
||||
period. Preserve their original strings when editing the JSON artifact.
|
||||
PowerShell 7.5 and later preserve timestamp strings with `-DateKind String`;
|
||||
Windows PowerShell 5.1 preserves strings without that option. Earlier PowerShell 7
|
||||
releases deserialize timestamps automatically, so an already deserialized
|
||||
`DateTime` is accepted only with `Kind=Utc`, then subjected to the same ordering
|
||||
and assessment-time checks. Local and unspecified `DateTime` values are rejected;
|
||||
original lexical spelling cannot be revalidated after an older engine normalizes it.
|
||||
The importer validates structure, fingerprints and exact scenario context;
|
||||
it does not authenticate the reviewer or inspect external artifacts. Its report
|
||||
explicitly labels that provenance as operator-declared. Duplicate IDs, missing
|
||||
review fields, source/collector drift or any context mismatch prevent defaults
|
||||
from being used. Unknown/missing individual observations remain Unknown even if
|
||||
the reference otherwise qualifies. Hashes must be refreshed through a new review
|
||||
when source or collector files change; do not relabel old observations blindly.
|
||||
|
||||
Before closing #385, capture independently reviewed clean Windows 11 24H2/25H2,
|
||||
Server 2022/2025 member, promoted DC and AD CS scenarios with exact patches and
|
||||
policy evidence. Include historical Application Guard-enabled/disabled clients
|
||||
where still available. Synthetic fixtures test the comparison boundaries; native
|
||||
Windows CI demonstrates read-only collection only. This PR includes no measured
|
||||
clean-install snapshots or end-to-end event/ingestion evidence.
|
||||
|
||||
Sources: [Microsoft Application Guard removal](https://learn.microsoft.com/en-us/windows/security/application-security/application-isolation/microsoft-defender-application-guard/md-app-guard-overview),
|
||||
[Win32 processor platform codes](https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-processor),
|
||||
[localized OS architecture property](https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-operatingsystem),
|
||||
[CIS Windows benchmarks](https://www.cisecurity.org/benchmark/microsoft_windows_desktop)
|
||||
(reviewed Windows 11 Enterprise v4.0.0, 18.10.44.1), and the versioned source records
|
||||
in `config/audit_profiles.json`.
|
||||
@@ -187,7 +187,7 @@ function Export-WelaAuditAssessment {
|
||||
Note = 'Only evidence-qualified Ready rules are usable. Policy/channel matches are configuration estimates; missing full rule logic, fields, outcomes, SACL, ingestion or query evidence remains Conditional.'
|
||||
}
|
||||
Eligibility = $Eligibility
|
||||
Results = @($Rows | Select-Object Category, SubCategory, CurrentSetting, DefaultSetting, RecommendedSetting, RuleCount, ChannelState, GenerationReadiness, NativeSources, Note)
|
||||
Results = @($Rows | Select-Object Category, SubCategory, CurrentSetting, DefaultSetting, DefaultEvidence, LegacyDefaultHint, RecommendedSetting, RuleCount, ChannelState, GenerationReadiness, NativeSources, Note)
|
||||
}
|
||||
if ($ResultsPath) { $report | ConvertTo-Json -Depth 16 | Set-Content -LiteralPath $ResultsPath -Encoding UTF8 -ErrorAction Stop }
|
||||
if ($HtmlPath) {
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
# Read-only historical control applicability and provenance-bound default evidence.
|
||||
function Get-WelaDefaultProcessorArchitecture {
|
||||
# Win32_OperatingSystem.OSArchitecture is localized; retain an independent,
|
||||
# documented platform code rather than inferring x64/ARM64 from that string.
|
||||
$processors=@(Get-CimInstance Win32_Processor -ErrorAction Stop)
|
||||
if (-not $processors.Count) { throw 'Processor architecture evidence is missing.' }
|
||||
$codes=@(foreach ($processor in $processors) {
|
||||
if ($processor.Architecture -isnot [ValueType] -or $processor.Architecture -notin @(9,12)) {
|
||||
throw 'Processor architecture is missing or outside the reviewed x64/ARM64 scope.'
|
||||
}
|
||||
[int]$processor.Architecture
|
||||
})
|
||||
$unique=@($codes | Sort-Object -Unique)
|
||||
if ($unique.Count -ne 1) { throw 'Conflicting processor architecture evidence.' }
|
||||
return $unique[0]
|
||||
}
|
||||
|
||||
function Get-WelaDefaultContext {
|
||||
$result=[ordered]@{Status='Unknown';Build=$null;UBR=$null;Edition=$null;ProductType=$null;DomainRole=$null;DomainJoined=$null;Domain=$null;Architecture=$null;ProcessorArchitecture=$null;InstalledRoles=@();RolesStatus='Unknown';Diagnostic=''}
|
||||
try {
|
||||
if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) { throw 'Windows is required for a native default snapshot.' }
|
||||
if (-not [Environment]::Is64BitProcess) { throw 'Use 64-bit PowerShell for exact native registry and role observations.' }
|
||||
$os=Get-CimInstance Win32_OperatingSystem -ErrorAction Stop
|
||||
$computer=Get-CimInstance Win32_ComputerSystem -ErrorAction Stop
|
||||
$version=Get-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -ErrorAction Stop
|
||||
if ([string]$os.BuildNumber -notmatch '^\d+$' -or $null -eq $version.UBR -or -not $version.EditionID -or
|
||||
$os.ProductType -notin @(1,2,3) -or $computer.PartOfDomain -isnot [bool]) { throw 'Incomplete host build, patch, edition or join evidence.' }
|
||||
if (($os.ProductType -eq 1 -and $computer.DomainRole -notin @(0,1)) -or
|
||||
($os.ProductType -eq 2 -and $computer.DomainRole -notin @(4,5)) -or
|
||||
($os.ProductType -eq 3 -and $computer.DomainRole -notin @(2,3))) { throw 'Conflicting host role observations.' }
|
||||
$result.Build=[int]$os.BuildNumber; $result.UBR=[int]$version.UBR; $result.Edition=[string]$version.EditionID
|
||||
$result.ProductType=[int]$os.ProductType; $result.DomainRole=[int]$computer.DomainRole
|
||||
$result.DomainJoined=[bool]$computer.PartOfDomain; $result.Domain=[string]$computer.Domain; $result.Architecture=[string]$os.OSArchitecture
|
||||
$result.ProcessorArchitecture=Get-WelaDefaultProcessorArchitecture
|
||||
if ($os.ProductType -eq 1) {
|
||||
$result.InstalledRoles=@(Get-WindowsOptionalFeature -Online -ErrorAction Stop | Where-Object State -eq 'Enabled' | ForEach-Object { [string]$_.FeatureName } | Sort-Object -Unique)
|
||||
} else {
|
||||
$result.InstalledRoles=@(Get-WindowsFeature -ErrorAction Stop | Where-Object Installed -eq $true | ForEach-Object { [string]$_.Name } | Sort-Object -Unique)
|
||||
}
|
||||
$result.RolesStatus='Observed'; $result.Status='Observed'
|
||||
} catch { $result.Diagnostic=$_.Exception.Message }
|
||||
[pscustomobject]$result
|
||||
}
|
||||
|
||||
function Get-WelaControlCatalog {
|
||||
$catalog=Get-Content -LiteralPath (Join-Path $PSScriptRoot '../config/control_applicability.json') -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
|
||||
if ($catalog.schemaVersion -ne 1) { throw 'Unsupported control applicability catalog.' }
|
||||
return $catalog
|
||||
}
|
||||
|
||||
function Resolve-WelaControlApplicability {
|
||||
param($Definition,$Context,[string]$FeatureState='Unknown')
|
||||
$status='Unknown'; $reason='Host build/product type could not be established.'
|
||||
if ($Context.ProductType -in @(1,2,3) -and $Context.Build -is [ValueType] -and $Context.Build -gt 0) {
|
||||
if ($Context.ProductType -notin $Definition.productTypes) { $status='NotApplicable'; $reason='Historical source requirement applies to Windows 11 clients only.' }
|
||||
elseif ($Context.Build -ge $Definition.removal.firstBuild) { $status='NotApplicable'; $reason="Removed starting $($Definition.removal.release); no remediation is proposed." }
|
||||
elseif ($Context.Build -lt $Definition.minBuild) { $status='NotApplicable'; $reason='Outside the Windows 11 source scope.' }
|
||||
elseif ($Context.Build -gt $Definition.maxBuild -or $Context.Build -notin $Definition.reviewedBuilds) { $reason='Build not reviewed; no inferred support.' }
|
||||
elseif ($Context.Edition -notin $Definition.editions) { $reason='Edition is not in the reviewed historical support set.' }
|
||||
elseif ($FeatureState -in @('Disabled','DisabledWithPayloadRemoved','NotPresent')) { $status='NotApplicable'; $reason='Optional feature is unavailable or disabled; retained source requirement is conditional on its presence.' }
|
||||
elseif ($FeatureState -eq 'Enabled') { $status='Applicable'; $reason='Historical build and enabled feature observed; application/runtime event generation remains unverified.' }
|
||||
else { $reason='Optional feature state is unknown or pending; no remediation is proposed.' }
|
||||
}
|
||||
[pscustomobject]@{Status=$status;Reason=$reason;FeatureState=$FeatureState;Remediation=$null;SourceRequirement=$Definition.source;MinBuild=$Definition.minBuild;MaxBuild=$Definition.maxBuild;Removal=$Definition.removal}
|
||||
}
|
||||
|
||||
function Get-WelaHistoricalControls {
|
||||
param($Context=(Get-WelaDefaultContext))
|
||||
foreach ($definition in (Get-WelaControlCatalog).controls) {
|
||||
$feature='Unknown'; $featureError=$null; $policy=$null; $policyState='Not assessed'
|
||||
$gate=Resolve-WelaControlApplicability $definition $Context
|
||||
# Never query DISM or propose reinstalling a feature on removed/server builds.
|
||||
if ($gate.Status -eq 'Unknown' -and $Context.Build -in $definition.reviewedBuilds -and $Context.ProductType -in $definition.productTypes -and $Context.Edition -in $definition.editions) {
|
||||
try {
|
||||
$observed=@(Get-WindowsOptionalFeature -Online -FeatureName $definition.feature -ErrorAction Stop)
|
||||
if ($observed.Count -ne 1 -or $observed[0].FeatureName -ne $definition.feature) { throw 'Exact optional feature result missing or ambiguous.' }
|
||||
$feature=[string]$observed[0].State
|
||||
} catch { $featureError=$_.Exception.Message }
|
||||
}
|
||||
$gate=Resolve-WelaControlApplicability $definition $Context $feature
|
||||
if ($gate.Status -eq 'Applicable') {
|
||||
try {
|
||||
$policy=Get-WelaRegistryState -Path $definition.registryPath -Name $definition.valueName
|
||||
$policyState=if (-not $policy.ValueExists) {'Not configured'} elseif ($policy.Type -ne $definition.requiredType) {'Unknown'} elseif ($policy.Value -eq $definition.requiredValue) {'Policy matches'} else {'Policy differs'}
|
||||
} catch { $policyState='Unknown'; $featureError=$_.Exception.Message }
|
||||
}
|
||||
[pscustomobject]@{Id=$definition.id;Title=$definition.title;Applicability=$gate;FeatureDiagnostic=$featureError;Policy=$policy;PolicyState=$policyState;AutomaticConfiguration='Unavailable: read-only historical assessment';EventGeneration='Unverified';DefaultSetting='Unknown'}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-WelaDefaultSourceFingerprints {
|
||||
foreach ($path in @('config/baselines.json','config/audit_profiles.json','config/control_applicability.json','scripts/ControlApplicability.ps1',
|
||||
'scripts/Configuration.ps1','modules/NativeProviders.psm1','modules/AuditProfiles.psm1')) {
|
||||
[pscustomobject]@{Path=$path;Sha256=(Get-FileHash -LiteralPath (Join-Path $PSScriptRoot "../$path") -Algorithm SHA256 -ErrorAction Stop).Hash.ToLowerInvariant()}
|
||||
}
|
||||
}
|
||||
|
||||
function ConvertFrom-WelaDefaultEvidenceJson {
|
||||
param([string]$Text)
|
||||
$arguments=@{InputObject=$Text;ErrorAction='Stop'}
|
||||
# Preserve the timestamp's exact source spelling for strict UTC validation.
|
||||
if ((Get-Command ConvertFrom-Json).Parameters.ContainsKey('DateKind')) { $arguments.DateKind='String' }
|
||||
ConvertFrom-Json @arguments
|
||||
}
|
||||
|
||||
function Get-WelaDefaultControlDefinitions {
|
||||
$config=Get-Content -LiteralPath (Join-Path $PSScriptRoot '../config/baselines.json') -Raw -ErrorAction Stop | ConvertFrom-Json
|
||||
$seen=@{}
|
||||
foreach ($item in $config.catalog) {
|
||||
if ($item.currentSetting.type -eq 'auditpol') { continue }
|
||||
$seen[$item.id]=$true
|
||||
[pscustomobject]@{Id=$item.id;Kind=$item.currentSetting.type;Definition=$item.currentSetting}
|
||||
}
|
||||
$profiles=Import-WelaAuditProfiles
|
||||
foreach ($item in $profiles.catalog) {
|
||||
$id='audit:'+([string]$item.guid).ToLowerInvariant()
|
||||
if ($seen.ContainsKey($id)) { throw 'Duplicate default evidence audit GUID.' }; $seen[$id]=$true
|
||||
[pscustomobject]@{Id=$id;Kind='auditpol';Definition=$item}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-WelaDefaultObservation {
|
||||
param($Control)
|
||||
$state='Unknown'; $value=$null; $raw=@(); $diagnostic=''
|
||||
try {
|
||||
switch ($Control.Kind) {
|
||||
'auditpol' {
|
||||
$mask=Get-WelaAuditPolicyMask -Guid $Control.Definition.guid
|
||||
$value=Format-WelaAuditMask $mask; $raw=@([pscustomobject]@{Guid=$Control.Definition.guid;Mask=$mask}); $state='Observed'
|
||||
}
|
||||
'native-channel' {
|
||||
$raw=@(foreach ($channel in $Control.Definition.channels) { Get-WelaNativeChannel -Name $channel })
|
||||
if (-not $raw.Count -or @($raw | Where-Object State -notin @('Enabled','Disabled','Not installed')).Count) { throw 'One or more channel observations are unknown.' }
|
||||
$value=(@($raw | ForEach-Object { "$($_.Name)=$($_.State)" }) -join '; '); $state='Observed'
|
||||
}
|
||||
'registry' {
|
||||
$raw=@(foreach ($path in $Control.Definition.paths) { [pscustomobject]@{Path=$path;Name=$Control.Definition.name;State=(Get-WelaRegistryState -Path $path -Name $Control.Definition.name)} })
|
||||
if (-not $raw.Count) { throw 'Registry definition has no paths.' }
|
||||
$value=(@($raw | ForEach-Object { if (-not $_.State.ValueExists) {"$($_.Path)=Not configured"} else {"$($_.Path)=$($_.State.Type):$($_.State.Value)"} }) -join '; '); $state='Observed'
|
||||
}
|
||||
default { throw 'Default observation kind is not supported.' }
|
||||
}
|
||||
} catch { $state='Unknown'; $value=$null; $diagnostic=$_.Exception.Message }
|
||||
[pscustomobject]@{Id=$Control.Id;Kind=$Control.Kind;Status=$state;Value=$value;Raw=$raw;Diagnostic=$diagnostic}
|
||||
}
|
||||
|
||||
function New-WelaDefaultSnapshot {
|
||||
[pscustomobject][ordered]@{
|
||||
SchemaVersion=1;EvidenceKind='ObservedState';CapturedUtc=[DateTime]::UtcNow.ToString('o');ComputerName=$env:COMPUTERNAME
|
||||
Context=(Get-WelaDefaultContext);Sources=@(Get-WelaDefaultSourceFingerprints)
|
||||
SourceVersions=(Import-WelaAuditProfiles).sources
|
||||
Review=[pscustomobject]@{Reviewer=$null;ReviewedUtc=$null;ImageSha256=$null;SnapshotId=$null;PolicyEvidenceSha256=$null;ProvisioningNotes=$null}
|
||||
Observations=@(Get-WelaDefaultControlDefinitions | ForEach-Object { Get-WelaDefaultObservation $_ })
|
||||
Warning='Observed settings are not Windows defaults. Only independently reviewed clean-install provenance can qualify a reference; domain join, promotion and CA installation define distinct scenarios.'
|
||||
}
|
||||
}
|
||||
|
||||
function Test-WelaDefaultContextComplete {
|
||||
param($Context)
|
||||
$consistent=($Context.ProductType -eq 1 -and $Context.DomainRole -in @(0,1)) -or
|
||||
($Context.ProductType -eq 2 -and $Context.DomainRole -in @(4,5)) -or ($Context.ProductType -eq 3 -and $Context.DomainRole -in @(2,3))
|
||||
$joined=$Context.DomainRole -in @(1,3,4,5)
|
||||
return $consistent -and $Context.DomainJoined -eq $joined -and $Context.Status -eq 'Observed' -and $Context.RolesStatus -eq 'Observed' -and
|
||||
($Context.Build -is [int] -or $Context.Build -is [long]) -and $Context.Build -gt 0 -and
|
||||
($Context.UBR -is [int] -or $Context.UBR -is [long]) -and $Context.UBR -ge 0 -and
|
||||
-not [string]::IsNullOrWhiteSpace($Context.Edition) -and $Context.ProductType -in @(1,2,3) -and
|
||||
$Context.DomainRole -in @(0,1,2,3,4,5) -and $Context.DomainJoined -is [bool] -and
|
||||
-not [string]::IsNullOrWhiteSpace($Context.Domain) -and -not [string]::IsNullOrWhiteSpace($Context.Architecture) -and
|
||||
($Context.ProcessorArchitecture -is [int] -or $Context.ProcessorArchitecture -is [long]) -and $Context.ProcessorArchitecture -in @(9,12) -and
|
||||
$null -ne $Context.InstalledRoles
|
||||
}
|
||||
|
||||
function Get-WelaDefaultContextKey {
|
||||
param($Context)
|
||||
# Fixed property order; host name intentionally excluded so matching lab peers can compare.
|
||||
[ordered]@{Build=$Context.Build;UBR=$Context.UBR;Edition=$Context.Edition;ProductType=$Context.ProductType;DomainRole=$Context.DomainRole;DomainJoined=$Context.DomainJoined;Domain=$Context.Domain;Architecture=$Context.Architecture;ProcessorArchitecture=$Context.ProcessorArchitecture;InstalledRoles=@($Context.InstalledRoles | Sort-Object -Unique)} | ConvertTo-Json -Depth 5 -Compress
|
||||
}
|
||||
|
||||
function Test-WelaReviewedDefaultEvidence {
|
||||
param($Evidence,$Context,$Sources,[DateTimeOffset]$AssessmentUtc=[DateTimeOffset]::UtcNow)
|
||||
$reason=''
|
||||
if (-not $Evidence -or $Evidence.SchemaVersion -ne 1 -or $Evidence.EvidenceKind -ne 'ReviewedCleanInstall') { $reason='Reference is not a reviewed clean-install scenario.' }
|
||||
elseif (-not (Test-WelaDefaultContextComplete $Context) -or -not (Test-WelaDefaultContextComplete $Evidence.Context)) { $reason='Exact host/role/patch evidence is incomplete.' }
|
||||
elseif ((Get-WelaDefaultContextKey $Context) -cne (Get-WelaDefaultContextKey $Evidence.Context)) { $reason='Build, patch, edition, architecture, domain/join or installed-role context differs.' }
|
||||
else {
|
||||
$review=$Evidence.Review
|
||||
$captured=[DateTimeOffset]::MinValue; $reviewed=[DateTimeOffset]::MinValue
|
||||
$capturedText=$Evidence.CapturedUtc; $reviewedText=$review.ReviewedUtc
|
||||
# PowerShell 6 through 7.4 deserialize Z timestamps as UTC DateTime and
|
||||
# have no -DateKind String option. Local/Unspecified values remain invalid.
|
||||
if ($capturedText -is [DateTime] -and $capturedText.Kind -eq [DateTimeKind]::Utc) { $capturedText=$capturedText.ToString('o') }
|
||||
if ($reviewedText -is [DateTime] -and $reviewedText.Kind -eq [DateTimeKind]::Utc) { $reviewedText=$reviewedText.ToString('o') }
|
||||
$utcPattern='\A[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,7})?Z\z'
|
||||
if ($capturedText -isnot [string] -or $reviewedText -isnot [string] -or
|
||||
$capturedText -cnotmatch $utcPattern -or $reviewedText -cnotmatch $utcPattern -or
|
||||
-not [DateTimeOffset]::TryParse($capturedText,[Globalization.CultureInfo]::InvariantCulture,[Globalization.DateTimeStyles]::AssumeUniversal,[ref]$captured) -or
|
||||
-not [DateTimeOffset]::TryParse($reviewedText,[Globalization.CultureInfo]::InvariantCulture,[Globalization.DateTimeStyles]::AssumeUniversal,[ref]$reviewed) -or
|
||||
$reviewed -lt $captured -or $captured -gt $AssessmentUtc -or $reviewed -gt $AssessmentUtc -or
|
||||
[string]::IsNullOrWhiteSpace($review.Reviewer) -or [string]::IsNullOrWhiteSpace($review.SnapshotId) -or
|
||||
[string]::IsNullOrWhiteSpace($review.ProvisioningNotes) -or $review.ImageSha256 -notmatch '^[a-fA-F0-9]{64}$' -or
|
||||
$review.PolicyEvidenceSha256 -notmatch '^[a-fA-F0-9]{64}$') { $reason='Clean-install review/image/snapshot/policy provenance is incomplete.' }
|
||||
elseif (@($Sources).Count -ne @($Evidence.Sources).Count) { $reason='Source fingerprint set differs.' }
|
||||
else {
|
||||
foreach ($source in $Sources) {
|
||||
$matches=@($Evidence.Sources | Where-Object Path -eq $source.Path)
|
||||
if ($matches.Count -ne 1 -or $matches[0].Sha256 -cne $source.Sha256) { $reason='Source or collector version fingerprint differs.'; break }
|
||||
}
|
||||
$ids=@{}
|
||||
foreach ($observation in $Evidence.Observations) {
|
||||
if (-not $observation.Id -or $ids.ContainsKey($observation.Id)) { $reason='Missing or duplicated observation ID.'; break }
|
||||
$ids[$observation.Id]=$true
|
||||
}
|
||||
}
|
||||
}
|
||||
[pscustomobject]@{Accepted=($reason -eq '');Reason=$(if ($reason) {$reason} else {'Operator-reviewed scenario reference; provenance is declared, not independently authenticated by WELA.'})}
|
||||
}
|
||||
|
||||
function Get-WelaDefaultComparison {
|
||||
param($Snapshot,$Reference)
|
||||
$review=Test-WelaReviewedDefaultEvidence -Evidence $Reference -Context $Snapshot.Context -Sources $Snapshot.Sources
|
||||
$rows=foreach ($current in $Snapshot.Observations) {
|
||||
$match=@($Reference.Observations | Where-Object Id -eq $current.Id)
|
||||
$default='Unknown'; $reason=$review.Reason; $comparison='Unknown'
|
||||
if ($review.Accepted -and $match.Count -eq 1 -and $match[0].Status -eq 'Observed' -and
|
||||
$match[0].Value -is [string] -and $match[0].Value.Length -gt 0 -and $match[0].Kind -eq $current.Kind) {
|
||||
$default=$match[0].Value
|
||||
if ($current.Status -eq 'Observed') { $comparison=if ($current.Value -ceq $default) {'Matches reference'} else {'Differs from reference'} }
|
||||
} elseif ($review.Accepted) { $reason='Reference control is missing, unknown or malformed.' }
|
||||
[pscustomobject]@{Id=$current.Id;Current=$current;DefaultSetting=$default;DefaultEvidence=$reason;Comparison=$comparison}
|
||||
}
|
||||
[pscustomobject]@{ReferenceReview=$review;Results=@($rows);Scope='Scenario-specific native settings only; no event generation or Sigma eligibility claim.'}
|
||||
}
|
||||
|
||||
function Invoke-WelaDefaultEvidenceCommand {
|
||||
param([ValidateSet('Capture','Compare')][string]$Action='Capture',[string]$ReferencePath,[string]$ResultsPath)
|
||||
if ($Action -eq 'Compare' -and -not $ReferencePath) { throw 'Compare requires DefaultEvidencePath.' }
|
||||
if ($Action -eq 'Capture' -and $ReferencePath) { throw 'DefaultEvidencePath requires Compare.' }
|
||||
if ($Action -eq 'Compare' -and $ResultsPath) {
|
||||
$referenceFull=[IO.Path]::GetFullPath($ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ReferencePath))
|
||||
$resultsFull=[IO.Path]::GetFullPath($ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ResultsPath))
|
||||
if ([string]::Equals($referenceFull,$resultsFull,[StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw 'ResultsPath must differ from DefaultEvidencePath; comparison output cannot overwrite its reference artifact.'
|
||||
}
|
||||
}
|
||||
$snapshot=New-WelaDefaultSnapshot
|
||||
$report=if ($Action -eq 'Compare') {
|
||||
$reference=ConvertFrom-WelaDefaultEvidenceJson (Get-Content -LiteralPath $ReferencePath -Raw -ErrorAction Stop)
|
||||
$comparison=Get-WelaDefaultComparison -Snapshot $snapshot -Reference $reference
|
||||
[pscustomobject]@{Action=$Action;Snapshot=$snapshot;Comparison=$comparison;ExitCode=$(if ($comparison.ReferenceReview.Accepted -and @($snapshot.Observations | Where-Object Status -eq 'Unknown').Count -eq 0) {0} else {1})}
|
||||
} else { [pscustomobject]@{Action=$Action;Snapshot=$snapshot;ExitCode=$(if ((Test-WelaDefaultContextComplete $snapshot.Context) -and @($snapshot.Observations | Where-Object Status -eq 'Unknown').Count -eq 0) {0} else {1})} }
|
||||
# Capture exports the snapshot directly so its exact artifact can be reviewed.
|
||||
if ($ResultsPath) { $(if ($Action -eq 'Capture') {$snapshot} else {$report}) | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $ResultsPath -Encoding UTF8 -ErrorAction Stop }
|
||||
return $report
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
$ErrorActionPreference='Stop'
|
||||
$root=Split-Path $PSScriptRoot -Parent
|
||||
. (Join-Path $root 'scripts/ControlApplicability.ps1')
|
||||
Import-Module (Join-Path $root 'modules/AuditProfiles.psm1')
|
||||
$script:count=0
|
||||
function Assert($Condition,$Message) { if (-not $Condition) { throw $Message }; $script:count++ }
|
||||
function Copy-Object($Value) { ConvertFrom-WelaDefaultEvidenceJson ($Value | ConvertTo-Json -Depth 20) }
|
||||
$context=[pscustomobject]@{Status='Observed';Build=22631;UBR=1234;Edition='Enterprise';ProductType=1;DomainRole=1;DomainJoined=$true;Domain='lab.test';Architecture='64-bit';ProcessorArchitecture=9;InstalledRoles=@('FeatureB','FeatureA');RolesStatus='Observed';Diagnostic='fixture'}
|
||||
$script:processorRows=@()
|
||||
function Get-CimInstance { param($ClassName,$ErrorAction) if ($ClassName -ne 'Win32_Processor') { throw 'Unexpected fixture CIM class.' }; $script:processorRows }
|
||||
foreach ($code in @(9,12)) {
|
||||
$script:processorRows=@([pscustomobject]@{Architecture=[uint16]$code},[pscustomobject]@{Architecture=[uint16]$code})
|
||||
Assert ((Get-WelaDefaultProcessorArchitecture) -eq $code) 'Native processor platform code distinguishes x64 and ARM64 independently of OS string/process emulation.'
|
||||
}
|
||||
foreach ($codes in @(@(),@(9,12),@($null),@(99),@('9'))) {
|
||||
$script:processorRows=@($codes | ForEach-Object { [pscustomobject]@{Architecture=$_} })
|
||||
$rejected=$false; try { Get-WelaDefaultProcessorArchitecture | Out-Null } catch { $rejected=$true }
|
||||
Assert $rejected 'Missing, conflicting, unsupported or malformed processor architecture cannot qualify exact context.'
|
||||
}
|
||||
$definition=(Get-WelaControlCatalog).controls[0]
|
||||
foreach ($build in @(26100,26200,30000)) {
|
||||
$ctx=Copy-Object $context; $ctx.Build=$build
|
||||
foreach ($feature in @('Enabled','Disabled','Unknown')) {
|
||||
$row=Resolve-WelaControlApplicability $definition $ctx $feature
|
||||
Assert ($row.Status -eq 'NotApplicable' -and $null -eq $row.Remediation -and $row.Reason -match 'Removed') 'Removed Application Guard has no misleading remediation, even if stale feature metadata says Enabled.'
|
||||
}
|
||||
}
|
||||
foreach ($product in @(2,3)) {
|
||||
$ctx=Copy-Object $context; $ctx.ProductType=$product
|
||||
Assert ((Resolve-WelaControlApplicability $definition $ctx Enabled).Status -eq 'NotApplicable') 'Client historical source does not apply to a server/DC/CA.'
|
||||
}
|
||||
foreach ($state in @('Disabled','DisabledWithPayloadRemoved','NotPresent')) {
|
||||
Assert ((Resolve-WelaControlApplicability $definition $context $state).Status -eq 'NotApplicable') 'Absent/disabled feature is distinguished from an auditing mismatch.'
|
||||
}
|
||||
foreach ($state in @('Unknown','EnablePending','DisablePending')) {
|
||||
Assert ((Resolve-WelaControlApplicability $definition $context $state).Status -eq 'Unknown') 'Unknown/pending feature cannot claim applicability.'
|
||||
}
|
||||
Assert ((Resolve-WelaControlApplicability $definition $context Enabled).Status -eq 'Applicable') 'Reviewed historical enabled feature is applicable.'
|
||||
$ctx=Copy-Object $context; $ctx.Build=25000
|
||||
Assert ((Resolve-WelaControlApplicability $definition $ctx Enabled).Status -eq 'Unknown') 'Unreviewed insider build remains unknown.'
|
||||
$ctx=Copy-Object $context; $ctx.Edition='Core'
|
||||
Assert ((Resolve-WelaControlApplicability $definition $ctx Enabled).Status -eq 'Unknown') 'Unreviewed edition is never given remediation.'
|
||||
$script:featureReads=0; $script:registryReads=0
|
||||
function Get-WindowsOptionalFeature { param([switch]$Online,$FeatureName,$ErrorAction) $script:featureReads++; [pscustomobject]@{FeatureName=$FeatureName;State='Enabled'} }
|
||||
function Get-WelaRegistryState { param($Path,$Name) $script:registryReads++; [pscustomobject]@{KeyExists=$true;ValueExists=$true;Type='DWord';Value=1} }
|
||||
$ctx=Copy-Object $context; $ctx.Build=26100
|
||||
$row=@(Get-WelaHistoricalControls $ctx)[0]
|
||||
Assert ($row.Applicability.Status -eq 'NotApplicable' -and $script:featureReads -eq 0 -and $script:registryReads -eq 0) 'Removed feature is not queried or remediated.'
|
||||
$row=@(Get-WelaHistoricalControls $context)[0]
|
||||
Assert ($row.PolicyState -eq 'Policy matches' -and $row.EventGeneration -eq 'Unverified') 'Actual policy observation stays separate from event proof.'
|
||||
function Get-WelaDefaultContext { $context }
|
||||
function Get-WelaNativeChannel { param($Name) [pscustomobject]@{Name=$Name;State='Enabled';IsEnabled=$true;LogMode='Circular'} }
|
||||
function Get-WelaAuditPolicyMask { param($Guid) 1 }
|
||||
$snapshot=New-WelaDefaultSnapshot
|
||||
Assert ($snapshot.EvidenceKind -eq 'ObservedState' -and $snapshot.Observations.Count -gt 59) 'Capture always labels current observations, including canonical audit categories.'
|
||||
$reference=Copy-Object $snapshot
|
||||
$compare=Get-WelaDefaultComparison $snapshot $reference
|
||||
Assert (-not $compare.ReferenceReview.Accepted -and @($compare.Results | Where-Object DefaultSetting -ne 'Unknown').Count -eq 0) 'Observed current settings are never promoted to defaults.'
|
||||
$reference.EvidenceKind='ReviewedCleanInstall'
|
||||
$reference.Review=[pscustomobject]@{Reviewer='Fixture reviewer';ReviewedUtc=[DateTime]::UtcNow.ToString('o');ImageSha256=('a'*64);SnapshotId='test-only';PolicyEvidenceSha256=('b'*64);ProvisioningNotes='Synthetic validation fixture, not Windows lab evidence'}
|
||||
$reference.Context.InstalledRoles=@('FeatureA','FeatureB')
|
||||
$compare=Get-WelaDefaultComparison $snapshot $reference
|
||||
Assert ($compare.ReferenceReview.Accepted -and @($compare.Results | Where-Object Comparison -ne 'Matches reference').Count -eq 0) 'Exact reviewed context can provide a scenario reference, with order-independent role inventory.'
|
||||
if ((Get-Command ConvertFrom-Json).Parameters.ContainsKey('DateKind') -or $PSVersionTable.PSVersion.Major -le 5) {
|
||||
Assert ($reference.CapturedUtc -is [string] -and $reference.Review.ReviewedUtc -is [string]) 'JSON import retains exact timestamp strings instead of silently localizing them.'
|
||||
} else {
|
||||
Assert ($reference.CapturedUtc -is [DateTime] -and $reference.CapturedUtc.Kind -eq [DateTimeKind]::Utc) 'Earlier PowerShell 7 imports explicit Z timestamps as UTC, not Local or Unspecified.'
|
||||
}
|
||||
$candidate=Copy-Object $reference
|
||||
$candidate.CapturedUtc=([DateTimeOffset]$candidate.CapturedUtc).UtcDateTime
|
||||
$candidate.Review.ReviewedUtc=([DateTimeOffset]$candidate.Review.ReviewedUtc).UtcDateTime
|
||||
Assert ((Test-WelaReviewedDefaultEvidence $candidate $snapshot.Context $snapshot.Sources).Accepted) 'Earlier PowerShell 7 deserialized UTC DateTime values retain their explicit UTC semantics.'
|
||||
foreach ($kind in @([DateTimeKind]::Local,[DateTimeKind]::Unspecified)) {
|
||||
foreach ($field in @('Capture','Review')) {
|
||||
$candidate=Copy-Object $reference
|
||||
if ($field -eq 'Capture') { $candidate.CapturedUtc=[DateTime]::SpecifyKind(([DateTimeOffset]$candidate.CapturedUtc).UtcDateTime,$kind) }
|
||||
else { $candidate.Review.ReviewedUtc=[DateTime]::SpecifyKind(([DateTimeOffset]$candidate.Review.ReviewedUtc).UtcDateTime,$kind) }
|
||||
Assert (-not (Test-WelaReviewedDefaultEvidence $candidate $snapshot.Context $snapshot.Sources).Accepted) 'Local or Unspecified DateTime cannot qualify UTC provenance even on a UTC-configured host.'
|
||||
}
|
||||
}
|
||||
$candidate=Copy-Object $reference; $candidate.Review.ReviewedUtc=[DateTime]::UtcNow.AddDays(1)
|
||||
Assert (-not (Test-WelaReviewedDefaultEvidence $candidate $snapshot.Context $snapshot.Sources).Accepted) 'Deserialized UTC DateTime retains the nonfuture validation boundary.'
|
||||
foreach ($time in @('2040-01-01T00:00:00Z','09/19/2026 01:00:00','2026-09-19T01:00:00','2026-09-19T01:00:00+00:00','2026-99-99T01:00:00Z',"2020-01-01T00:00:00Z`n")) {
|
||||
foreach ($field in @('Capture','Review')) {
|
||||
$candidate=Copy-Object $reference
|
||||
if ($field -eq 'Capture') {$candidate.CapturedUtc=$time} else {$candidate.Review.ReviewedUtc=$time}
|
||||
Assert (-not (Test-WelaReviewedDefaultEvidence $candidate $snapshot.Context $snapshot.Sources).Accepted) 'Future, malformed, localized or non-Z timestamps cannot qualify a clean-install reference.'
|
||||
}
|
||||
}
|
||||
$candidate=Copy-Object $reference; $candidate.CapturedUtc='2040-01-01T00:00:00Z'; $candidate.Review.ReviewedUtc='2040-01-01T00:00:01Z'
|
||||
Assert (-not (Test-WelaReviewedDefaultEvidence $candidate $snapshot.Context $snapshot.Sources ([DateTimeOffset]'2039-12-31T23:59:59Z')).Accepted) 'Chronologically ordered future capture and review still fail the assessment-time boundary.'
|
||||
foreach ($helper in @('scripts/Configuration.ps1','modules/NativeProviders.psm1','modules/AuditProfiles.psm1')) {
|
||||
$candidate=Copy-Object $reference
|
||||
$source=@($candidate.Sources | Where-Object Path -eq $helper)
|
||||
Assert ($source.Count -eq 1) "Actual collector helper is fingerprinted: $helper"
|
||||
$source[0].Sha256=('d'*64)
|
||||
Assert (-not (Test-WelaReviewedDefaultEvidence $candidate $snapshot.Context $snapshot.Sources).Accepted) "Changed native reader semantics invalidate reference provenance: $helper"
|
||||
}
|
||||
foreach ($field in @('Build','UBR','Edition','ProductType','DomainRole','DomainJoined','Domain','Architecture','ProcessorArchitecture','InstalledRoles','Status','RolesStatus')) {
|
||||
$candidate=Copy-Object $reference
|
||||
switch ($field) {
|
||||
'Build' {$candidate.Context.Build=26100}
|
||||
'UBR' {$candidate.Context.UBR=1235}
|
||||
'ProductType' {$candidate.Context.ProductType=3}
|
||||
'DomainRole' {$candidate.Context.DomainRole=3}
|
||||
'DomainJoined' {$candidate.Context.DomainJoined=$false}
|
||||
'ProcessorArchitecture' {$candidate.Context.ProcessorArchitecture=12}
|
||||
'InstalledRoles' {$candidate.Context.InstalledRoles=@('ADCS-Cert-Authority')}
|
||||
default {$candidate.Context.$field='Different'}
|
||||
}
|
||||
$compare=Get-WelaDefaultComparison $snapshot $candidate
|
||||
Assert (-not $compare.ReferenceReview.Accepted -and @($compare.Results | Where-Object DefaultSetting -ne 'Unknown').Count -eq 0) "Context mismatch $field cannot leak a default."
|
||||
}
|
||||
$candidate=Copy-Object $reference; $candidate.Context.PSObject.Properties.Remove('ProcessorArchitecture')
|
||||
Assert (-not (Test-WelaReviewedDefaultEvidence $candidate $snapshot.Context $snapshot.Sources).Accepted) 'Legacy evidence without native processor architecture remains Unknown, even when localized OS architecture matches.'
|
||||
foreach ($field in @('Reviewer','ReviewedUtc','ImageSha256','SnapshotId','PolicyEvidenceSha256','ProvisioningNotes')) {
|
||||
$candidate=Copy-Object $reference; $candidate.Review.$field=$null
|
||||
Assert (-not (Test-WelaReviewedDefaultEvidence $candidate $snapshot.Context $snapshot.Sources).Accepted) "Missing $field fails review."
|
||||
}
|
||||
$candidate=Copy-Object $reference; $candidate.Sources[0].Sha256=('c'*64)
|
||||
Assert (-not (Test-WelaReviewedDefaultEvidence $candidate $snapshot.Context $snapshot.Sources).Accepted) 'Changed source/catalog/collector bytes invalidate the reference.'
|
||||
$candidate=Copy-Object $reference; $candidate.Observations+=@($candidate.Observations[0])
|
||||
Assert (-not (Test-WelaReviewedDefaultEvidence $candidate $snapshot.Context $snapshot.Sources).Accepted) 'Duplicate IDs invalidate reference.'
|
||||
$candidate=Copy-Object $reference; $candidate.Observations[0].Status='Unknown'
|
||||
$compare=Get-WelaDefaultComparison $snapshot $candidate
|
||||
Assert ($compare.ReferenceReview.Accepted -and $compare.Results[0].DefaultSetting -eq 'Unknown') 'Unknown individual control remains unknown in an otherwise reviewed artifact.'
|
||||
$temp=Join-Path ([IO.Path]::GetTempPath()) ('wela-default-reference-'+[guid]::NewGuid().ToString('N'))
|
||||
$null=New-Item -ItemType Directory -Path $temp
|
||||
try {
|
||||
$referencePath=Join-Path $temp 'reference.json'
|
||||
$reference | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $referencePath -Encoding UTF8
|
||||
$beforeHash=(Get-FileHash -LiteralPath $referencePath -Algorithm SHA256).Hash
|
||||
foreach ($output in @($referencePath,(Join-Path $temp './reference.json'))) {
|
||||
$rejected=$false
|
||||
try { Invoke-WelaDefaultEvidenceCommand -Action Compare -ReferencePath $referencePath -ResultsPath $output | Out-Null }
|
||||
catch { $rejected=$_.Exception.Message -match 'cannot overwrite its reference artifact' }
|
||||
Assert $rejected 'Equivalent input/output paths are rejected before exporting over reviewed evidence.'
|
||||
Assert ((Get-FileHash -LiteralPath $referencePath -Algorithm SHA256).Hash -ceq $beforeHash) 'Rejected reference/output collision preserves exact evidence bytes.'
|
||||
}
|
||||
$output=Join-Path $temp 'comparison.json'
|
||||
$report=Invoke-WelaDefaultEvidenceCommand -Action Compare -ReferencePath $referencePath -ResultsPath $output
|
||||
Assert ($report.Comparison.ReferenceReview.Accepted -and (Test-Path -LiteralPath $output) -and (Get-FileHash -LiteralPath $referencePath -Algorithm SHA256).Hash -ceq $beforeHash) 'Distinct comparison output imports strict timestamps and preserves its reviewed input.'
|
||||
} finally { Remove-Item -LiteralPath $temp -Recurse -Force }
|
||||
function Get-WelaNativeChannel { param($Name) throw 'channel read denied' }
|
||||
$partial=New-WelaDefaultSnapshot
|
||||
Assert (@($partial.Observations | Where-Object {$_.Kind -eq 'native-channel' -and $_.Status -eq 'Unknown'}).Count -gt 0 -and @($partial.Observations | Where-Object {$_.Kind -eq 'auditpol' -and $_.Status -eq 'Observed'}).Count -eq 59) 'One failed source does not discard independent successful observations.'
|
||||
# The real renderer keeps source-less legacy strings explicitly separate in all assessment rows.
|
||||
$tokens=$null; $errors=$null
|
||||
$ast=[System.Management.Automation.Language.Parser]::ParseFile((Join-Path $root 'WELA.ps1'),[ref]$tokens,[ref]$errors)
|
||||
Assert ($errors.Count -eq 0) 'Public script parses.'
|
||||
$class=$ast.Find({param($node) $node -is [System.Management.Automation.Language.TypeDefinitionAst] -and $node.Name -eq 'WELA'},$true)
|
||||
. ([scriptblock]::Create($class.Extent.Text))
|
||||
$row=[WELA]::New('Test','Test','Enabled',@(),'Enabled','Enabled','','')
|
||||
Assert ($row.DefaultSetting -eq 'Unknown' -and $row.LegacyDefaultHint -eq 'Enabled' -and $row.DefaultEvidence -match 'No exact-context') 'Legacy default hints cannot be shown as current host defaults.'
|
||||
$exe=(Get-Process -Id $PID).Path
|
||||
$ErrorActionPreference='Continue'
|
||||
try { $out=& $exe -NoProfile -File (Join-Path $root 'WELA.ps1') configure -Profile wela-2.2.0 -DefaultEvidenceAction Capture 2>&1; $code=$LASTEXITCODE } finally { $ErrorActionPreference='Stop' }
|
||||
Assert ($code -ne 0 -and ($out -join "`n") -match 'Default evidence options require') 'Wrong-command evidence options fail before profile configuration.'
|
||||
Write-Host "PASS: $script:count applicability/default evidence assertions (synthetic fixtures, no default claims)."
|
||||
$global:LASTEXITCODE=0
|
||||
@@ -0,0 +1,19 @@
|
||||
$ErrorActionPreference='Stop'
|
||||
$root=Split-Path $PSScriptRoot -Parent
|
||||
. (Join-Path $root 'scripts/Configuration.ps1')
|
||||
. (Join-Path $root 'scripts/ControlApplicability.ps1')
|
||||
Import-Module (Join-Path $root 'modules/AuditProfiles.psm1')
|
||||
Import-Module (Join-Path $root 'modules/NativeProviders.psm1')
|
||||
$report=Invoke-WelaDefaultEvidenceCommand -Action Capture
|
||||
$snapshot=$report.Snapshot
|
||||
if (-not (Test-WelaDefaultContextComplete $snapshot.Context)) { throw "Native snapshot context incomplete: $($snapshot.Context.Diagnostic)" }
|
||||
if ($snapshot.EvidenceKind -ne 'ObservedState' -or $snapshot.Review.Reviewer) { throw 'Native observed state was incorrectly promoted to a reviewed default.' }
|
||||
if (@($snapshot.Observations | Where-Object Kind -eq 'auditpol').Count -ne 59) { throw 'Canonical audit inventory incomplete.' }
|
||||
$comparison=Get-WelaDefaultComparison $snapshot $snapshot
|
||||
if ($comparison.ReferenceReview.Accepted -or @($comparison.Results | Where-Object DefaultSetting -ne 'Unknown').Count) { throw 'Observed state cannot establish defaults.' }
|
||||
$controls=@(Get-WelaHistoricalControls -Context $snapshot.Context)
|
||||
if ($controls[0].Applicability.Status -ne 'NotApplicable' -or $controls[0].Applicability.Remediation) { throw 'Server must not receive historical client Application Guard remediation.' }
|
||||
$snapshot.Context | ConvertTo-Json -Depth 5 | Write-Host
|
||||
$snapshot.Observations | Select-Object Id,Status,Value,Diagnostic | Format-Table -AutoSize | Out-Host
|
||||
Write-Host 'PASS: native build/patch/join/role and policy/channel capture is read-only. This runner is not a clean-install reference.'
|
||||
$global:LASTEXITCODE=0
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
**改善:**
|
||||
|
||||
- 読み取り専用の`control-applicability`を追加し、旧CISのApplication Guard監査要件を保持しつつ、Windows 11 24H2以降では削除済み・対象外と表示します。`default-evidence`で正確なビルド・パッチ・ドメイン参加・役割を含む現状を記録し、出典とレビュー情報が一致する参照環境と比較できます。旧ベースラインの既定値は履歴情報として保持し、未検証の既定値はUnknownと表示します。合成テストと読み取り専用CIからクリーンインストールやイベント生成の証明は行いません。 (#409) (@Shirofune-Security)
|
||||
- Windows標準のDNS Client/Server、CAPI2、WinRM、RDP Clientについて、明示的に選択するprovider-packsの監査・計画とチャネル設定を追加しました。固定した完全なルール定義と実際のプロバイダー・チャネル・イベントスキーマを確認し、DNSのチャネル名不一致や不明な前提条件を保持します。復旧記録付きの変更では大きいバッファ・保持方式・ACLを維持し、Analyticalと従来のDNSログは手動確認のみとします。イベント生成・バックエンド検証や検知範囲の向上は未確認です。 (#411) (@Shirofune-Security)
|
||||
- 任意実行の`audit-notifications`を追加し、OneSettings監査とSecurityログ警告しきい値の監査・計画・設定に対応しました。対象とチャネル変更の明示指定、OS・ADMX確認、型付き復旧記録と変更検出を行い、既存の低いしきい値とチャネルACL・保存方式を保持します。ポリシー一致と実イベント生成を分け、Windowsラボの証拠とSigma利用可能性は未検証と表示します。 (#408) (@Shirofune-Security)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
**Improvements:**
|
||||
|
||||
- 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)
|
||||
- Added explicit native DNS Client/Server, CAPI2, WinRM and RDP Client provider-pack inventory and selective channel configuration. Pinned full rule definitions and live provider/channel/event schemas retain DNS channel mismatches and unknown prerequisites; journaled opt-in changes preserve larger buffers, retention and ACLs. Analytical/classic DNS remain manual-only, and no event/backend readiness uplift is claimed. (#411) (@Shirofune-Security)
|
||||
- Added opt-in `audit-notifications` audit/plan/configure for OneSettings auditing and Security log warning thresholds, with explicit control/channel selections, reviewed host and ADMX gates, typed recovery journals and drift checks. Earlier warning thresholds and channel ACL/retention are preserved. Reports separate policy matches from warning/event generation; Windows lab evidence and Sigma eligibility remain unverified. (#408) (@Shirofune-Security)
|
||||
|
||||
|
||||
Reference in new issue
Block a user