Skip to content

Commit 0e5fba9

Browse files
committed
Error handling, ServiceWhitelist, test assertions, DefaultConfig fix (v0.4.6)
- Add Write-Verbose/Write-Status/Write-Warning to empty catch blocks in Check-Network.ps1, Helpers.ps1, and AmIHacked.ps1 - Implement ServiceWhitelist in Check-Processes.ps1 service analysis - Expand Invoke-MockScan.ps1 with Assert-FindingCount, double-extension detection, category coverage, severity/remediation/MITRE validation - Fix New-DefaultConfig $PSScriptRoot resolving to lib/ instead of repo root Made-with: Cursor
1 parent f869ae6 commit 0e5fba9

8 files changed

Lines changed: 94 additions & 20 deletions

File tree

AmIHacked.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ if ($script:NonInteractive) {
8080
$script:RedactMap = @{}
8181
$script:SuppressedCount = 0
8282

83-
$script:Version = "0.4.5"
83+
$script:Version = "0.4.6"
8484

8585
# ── Helpers (loaded first) ───────────────────────────────────────────────────
8686

@@ -92,6 +92,7 @@ if (Test-Path $ConfigPath) {
9292
try {
9393
$script:Config = Get-Content $ConfigPath -Raw | ConvertFrom-Json
9494
} catch {
95+
Write-Warning "Failed to parse config at '$ConfigPath': $_. Using built-in defaults."
9596
$script:Config = Get-DefaultConfig
9697
}
9798
} else {

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

7+
## [0.4.6] - 2026-03-15
8+
9+
### Added
10+
- **ServiceWhitelist implementation** -- `ServiceWhitelist` config field is now applied in Check-Processes.ps1 service analysis, skipping whitelisted services from unquoted-path, user-directory, and SYSTEM-outside-standard-dirs checks
11+
- **Test assertions** -- `Assert-FindingCount` helper, double-extension detection assertion, category coverage checks, severity/remediation/MITRE field validation, CIMode JSON `suppressed` key check
12+
13+
### Fixed
14+
- **Error handling** -- replaced empty `catch {}` blocks with `Write-Verbose`/`Write-Status`/`Write-Warning` in Check-Network.ps1 (DNS reverse-lookup, AbuseIPDB), lib/Helpers.ps1 (Run key enumeration), and AmIHacked.ps1 (config parse fallback)
15+
- **`New-DefaultConfig` `$PSScriptRoot` fix** -- default path now resolves via `Split-Path $PSScriptRoot -Parent` at call-time instead of during `param()` evaluation (which pointed to `lib/` instead of project root)
16+
717
## [0.4.5] - 2026-03-15
818

919
### Fixed

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,5 @@ $summary = $jsonLine | ConvertFrom-Json
138138

139139
## Code Conventions
140140

141-
- All `.ps1` files **must** be saved with UTF-8 BOM (`EF BB BF`). PowerShell 5.1 defaults to Windows-1252, which misinterprets multi-byte UTF-8 sequences (e.g. ``, ``) as string delimiters, causing parse errors. **Some edit tools strip BOM on save** -- run `.\fix-bom.ps1` (gitignored dev utility) to re-apply after editing.
141+
- All `.ps1` files **must** be saved with UTF-8 BOM (`EF BB BF`). PowerShell 5.1 defaults to Windows-1252, which misinterprets multi-byte UTF-8 sequences (e.g. ``, ``) as string delimiters, causing parse errors. **Some edit tools strip BOM on save** -- run `.\fix-bom.ps1` to re-apply after editing.
142142
- See `CONTRIBUTING.md` for the full module authoring guide and test harness documentation.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
[![PowerShell 5.1+](https://img.shields.io/badge/PowerShell-5.1%2B-0d1117?style=for-the-badge&logo=powershell&logoColor=5391FE)](https://docs.microsoft.com/powershell/)
1313
[![Windows 10/11](https://img.shields.io/badge/Windows-10%20%2F%2011-0d1117?style=for-the-badge&logo=windows&logoColor=white)](https://www.microsoft.com/windows)
1414
[![License: MIT](https://img.shields.io/badge/License-MIT-0d1117?style=for-the-badge&logoColor=white)](LICENSE)
15-
[![Version](https://img.shields.io/badge/Version-0.4.5-FF6B6B?style=for-the-badge)](#changelog)
15+
[![Version](https://img.shields.io/badge/Version-0.4.6-FF6B6B?style=for-the-badge)](#changelog)
1616

1717
[![Zero Dependencies](https://img.shields.io/badge/Dependencies-Zero-0d1117?style=flat-square&labelColor=0d1117)](#)
1818
[![MITRE ATT&CK](https://img.shields.io/badge/MITRE%20ATT%26CK-40%2B%20Techniques-ff3333?style=flat-square&labelColor=0d1117)](#mitre-attck-coverage)
@@ -140,7 +140,7 @@ Baselines enable **change detection** — the most powerful signal for catching
140140

141141
```
142142
---AMIHACKED-SUMMARY-JSON---
143-
{"verdict":"CAUTION","critical":0,"warning":3,"info":12,"suppressed":0,"total":15,"duration":28.4,"reportPath":"...","version":"0.4.5"}
143+
{"verdict":"CAUTION","critical":0,"warning":3,"info":12,"suppressed":0,"total":15,"duration":28.4,"reportPath":"...","version":"0.4.6"}
144144
```
145145

146146
- Exit code reflects findings: **0** = clean, **1** = warnings only, **2** = critical findings detected

lib/Helpers.ps1

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,11 @@ function Get-DefaultConfig {
297297
}
298298

299299
function New-DefaultConfig {
300-
param([string]$Path = (Join-Path $PSScriptRoot "config\config.json"))
300+
param([string]$Path = "")
301+
if (-not $Path) {
302+
$repoRoot = Split-Path $PSScriptRoot -Parent
303+
$Path = Join-Path $repoRoot "config\config.json"
304+
}
301305

302306
$dir = Split-Path $Path -Parent
303307
if (-not (Test-Path $dir)) {
@@ -487,7 +491,9 @@ function Export-Baseline {
487491
@{ Key = $key; Name = $_.Name; Value = $_.Value }
488492
}
489493
}
490-
} catch {}
494+
} catch {
495+
Write-Verbose "Could not read Run key '${key}': $_"
496+
}
491497
}
492498
)
493499

modules/Check-Network.ps1

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ function Invoke-NetworkChecks {
6363
$severity = "INFO"
6464
}
6565
}
66-
} catch {}
66+
} catch {
67+
Write-Verbose "Reverse-DNS lookup failed for ${ip}: $_"
68+
}
6769

6870
Add-Finding -Severity $severity -Category "Network" `
6971
-Title "High Connection Count to $ip ($($connList.Count) connections)" `
@@ -131,7 +133,9 @@ function Invoke-NetworkChecks {
131133
} `
132134
-MITRE @("T1071.001")
133135
}
134-
} catch {}
136+
} catch {
137+
Write-Status "AbuseIPDB lookup failed for ${ip}: $_" -Color Yellow
138+
}
135139
}
136140
Write-Status "Checked $checkedCount IPs against AbuseIPDB."
137141
} elseif ($script:OfflineMode -and $externalIPs.Count -gt 0) {

modules/Check-Processes.ps1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ function Invoke-ProcessesChecks {
1212
$whitelist = $script:Config.ProcessWhitelist | ForEach-Object { $_.ToLower() }
1313
}
1414

15+
$serviceWhitelist = @()
16+
if ($script:Config.ServiceWhitelist) {
17+
$serviceWhitelist = $script:Config.ServiceWhitelist | ForEach-Object { $_.ToLower() }
18+
}
19+
1520
$processCount = 0
1621
$flaggedCount = 0
1722

@@ -188,6 +193,7 @@ function Invoke-ProcessesChecks {
188193
foreach ($svc in $services) {
189194
$svcPath = $svc.PathName
190195
if (-not $svcPath) { continue }
196+
if ($serviceWhitelist -contains $svc.Name.ToLower()) { continue }
191197

192198
if ($svcPath -match "\\Temp\\" -or $svcPath -match "\\AppData\\" -or $svcPath -match "\\Users\\[^\\]+\\Desktop\\") {
193199
Add-Finding -Severity "CRITICAL" -Category "Process" `

tests/Invoke-MockScan.ps1

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,23 @@ function Assert-FindingExists {
4343
}
4444
}
4545

46+
function Assert-FindingCount {
47+
param(
48+
[object[]]$Findings,
49+
[string]$Category,
50+
[int]$MinCount,
51+
[string]$TestName
52+
)
53+
$count = ($Findings | Where-Object { $_.Category -eq $Category }).Count
54+
if ($count -ge $MinCount) {
55+
Write-Host " [PASS] $TestName ($count findings in '$Category')" -ForegroundColor Green
56+
$script:TestsPassed++
57+
} else {
58+
Write-Host " [FAIL] $TestName — expected >= $MinCount in '$Category', got $count" -ForegroundColor Red
59+
$script:TestsFailed++
60+
}
61+
}
62+
4663
# ── Setup: Create mock artifacts ─────────────────────────────────────────────
4764

4865
Write-Host "`n ╔══════════════════════════════════════════╗" -ForegroundColor Magenta
@@ -112,24 +129,53 @@ if (-not $jsonFiles) {
112129

113130
Assert-FindingExists -Findings $findings -TitlePattern "Stealer Output File.*passwords" -TestName "Detected stealer output file pattern"
114131

115-
# Check that JSON structure is valid
116-
Write-TestHeader "JSON Structure Validation"
117-
if ($results.Version -and $results.SystemInfo -and $results.Findings -and $results.Duration) {
118-
Write-Host " [PASS] JSON structure contains all required fields" -ForegroundColor Green
132+
Assert-FindingExists -Findings $findings -TitlePattern "Double Extension.*\.exe" -TestName "Detected double-extension file (invoice.pdf.exe)"
133+
134+
# Category coverage: each active module should produce at least one finding
135+
Write-TestHeader "Module Coverage"
136+
Assert-FindingCount -Findings $findings -Category "FileSystem" -MinCount 1 -TestName "FileSystem module produced findings"
137+
Assert-FindingCount -Findings $findings -Category "General" -MinCount 1 -TestName "General module produced findings"
138+
139+
# Severity field is always one of the three valid values
140+
Write-TestHeader "Severity Field Validity"
141+
$invalidSeverity = $findings | Where-Object { $_.Severity -notin @("CRITICAL","WARNING","INFO") }
142+
if ($invalidSeverity.Count -eq 0) {
143+
Write-Host " [PASS] All $($findings.Count) findings have valid Severity" -ForegroundColor Green
119144
$script:TestsPassed++
120145
} else {
121-
Write-Host " [FAIL] JSON structure missing required fields" -ForegroundColor Red
146+
Write-Host " [FAIL] $($invalidSeverity.Count) findings have invalid Severity: $(($invalidSeverity | Select-Object -ExpandProperty Severity) -join ', ')" -ForegroundColor Red
147+
$script:TestsFailed++
148+
}
149+
150+
# Every finding has non-empty Remediation
151+
Write-TestHeader "Remediation Field Coverage"
152+
$noRemediation = $findings | Where-Object { -not $_.Remediation }
153+
if ($noRemediation.Count -eq 0) {
154+
Write-Host " [PASS] All findings have Remediation text" -ForegroundColor Green
155+
$script:TestsPassed++
156+
} else {
157+
Write-Host " [FAIL] $($noRemediation.Count) findings missing Remediation: $(($noRemediation | Select-Object -First 3 -ExpandProperty Title) -join '; ')" -ForegroundColor Red
158+
$script:TestsFailed++
159+
}
160+
161+
# Every finding has at least one MITRE tag
162+
Write-TestHeader "MITRE Tag Coverage"
163+
$noMitre = $findings | Where-Object { -not $_.MITRE -or $_.MITRE.Count -eq 0 }
164+
if ($noMitre.Count -eq 0) {
165+
Write-Host " [PASS] All findings have MITRE ATT&CK tags" -ForegroundColor Green
166+
$script:TestsPassed++
167+
} else {
168+
Write-Host " [FAIL] $($noMitre.Count) findings missing MITRE tags: $(($noMitre | Select-Object -First 3 -ExpandProperty Title) -join '; ')" -ForegroundColor Red
122169
$script:TestsFailed++
123170
}
124171

125-
# Check MITRE tags are present on findings
126-
Write-TestHeader "MITRE ATT&CK Tags"
127-
$mitreTagged = $findings | Where-Object { $_.MITRE -and $_.MITRE.Count -gt 0 }
128-
if ($mitreTagged -and $mitreTagged.Count -gt 0) {
129-
Write-Host " [PASS] $($mitreTagged.Count) findings have MITRE ATT&CK tags" -ForegroundColor Green
172+
# Check that JSON structure is valid
173+
Write-TestHeader "JSON Structure Validation"
174+
if ($results.Version -and $results.SystemInfo -and $results.Findings -and $results.Duration) {
175+
Write-Host " [PASS] JSON structure contains all required fields" -ForegroundColor Green
130176
$script:TestsPassed++
131177
} else {
132-
Write-Host " [FAIL] No findings have MITRE ATT&CK tags" -ForegroundColor Red
178+
Write-Host " [FAIL] JSON structure missing required fields" -ForegroundColor Red
133179
$script:TestsFailed++
134180
}
135181

@@ -206,7 +252,8 @@ if (-not $jsonFiles) {
206252
try {
207253
$ciSummary = $jsonLine | ConvertFrom-Json
208254
if ($ciSummary.verdict -and $null -ne $ciSummary.critical -and $null -ne $ciSummary.warning -and
209-
$null -ne $ciSummary.info -and $null -ne $ciSummary.total -and $ciSummary.reportPath) {
255+
$null -ne $ciSummary.info -and $null -ne $ciSummary.suppressed -and
256+
$null -ne $ciSummary.total -and $ciSummary.reportPath) {
210257
Write-Host " [PASS] CIMode JSON summary contains all required fields" -ForegroundColor Green
211258
$script:TestsPassed++
212259
} else {

0 commit comments

Comments
 (0)