v2026.09.25.004: the Intel Engine loads threat intel; every intel match is report-only - #5
Open
cdburgess75 wants to merge 2 commits into
Open
cdburgess75 wants to merge 2 commits into
cdburgess75 wants to merge 2 commits into
Conversation
…ch is report-only
Since v1.002 the Intel Engine read $Script:Config.IntelEngine_PrimarySource,
which Config did not have. Under StrictMode 2 that threw before any download,
cache write or IntelSource, so every device on every run reported
'Hardcoded fallback' and 0 hash, filename and C2 IOCs. The property is now in
Config.
The parser kept whole lines ('hash;comment', 'regex;score'), which no hash or
file name could equal. ConvertFrom-IntelFeed keeps the SHA256, the domain or
IPv4, and the filename regex with its score and false-positive regex;
Find-IntelFilenameMatch applies filename IOCs as LOKI does (case-sensitive
regex over a full path), scored 60 or more.
Intel has never loaded in the field, and its consumers kill processes and
remove Run values, shortcuts and files. Every intel match now goes through
Add-IntelHit: logged, counted (intel_hits), a Low finding, never an IOC, and
never acted on. Hard-coded lists act as before. Hosts-file C2 matching is by
whole name. Downloads use -UseBasicParsing; list sizes are sanity-checked and
the cache is only replaced when every list has entries.
New tests/Test-IntelEngine.ps1 runs Phase 1 and every intel consumer verbatim
under StrictMode 2, and checks every $Script:Config.<Name> via the AST.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…che trust From three independent reviews (PS 5.1/StrictMode, fleet safety, diff/tests): - Payload: new 'intel' object replaces intel_hits/intel_paths_skipped. The first 50 matches carry source, target, indicator, score, what a hard-coded match there would do, and a matched file's SHA256 and signer; plus paths_checked, match_seconds, regex_timeouts and list_date. The device log is not uploaded, so this is the data the next release decides on. - Guards against a bad upstream line: entries matching known-good values (core Windows binaries, the empty-file SHA256, top domains) are left out; a regex that times out is switched off; matching is capped at 30 s as well as 3,000 paths; users' folders are scanned first. - Cache: trusted only if SYSTEM or Administrators own it (a local user can create it in ProgramData) and every list still parses to 100+ entries; a future timestamp is stale; replaced only after all three lists download, so a partial refresh no longer resets its age; a failed write is logged. - C2: whole labels plus subdomains (as LOKI), IPv6 sinkholes are blocks, and the DNS check also tests the resolved data. - Process matches record 'is only reported (vendor path)' where a hard-coded match would not be killed either. - Fix: a local $score in Add-IntelHit overwrote the $Score parameter (PowerShell names are case-insensitive); caught by the new test. - Changelog claims corrected: 49 filename IOCs do score 100; the old parser could not match filenames or hashes, so the Config fix alone would have raised C2 IOCs, not kills. - Test: 17 Phase 1 scenarios, both matchers, Add-IntelHit evidence and caps, and each consumer's exact matches and actions; catches 22 of 23 mutations (the other is equivalent). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This was referenced Sep 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR is stacked on #4, which is stacked on #3, #2 and #1.
fix/engine-child-scope.The bug (confirmed, reproduced verbatim)
The Intel Engine's
Invoke-SafeBlockread$Script:Config.IntelEngine_PrimarySourcein the$consolidatedliteral, and$Script:Confighas no such property.Under StrictMode 2 that throws before any download, cache write or
IntelSource. With no cache written, every run takes the same path.I ran the Phase 1 block verbatim under StrictMode 2 with the real Config literal. It logs exactly this, at INFO level, so it reaches the file log only:
Intel Engine skipped - The property 'IntelEngine_PrimarySource' cannot be found on this object. Verify that the property exists.The fix adds
IntelEngine_PrimarySource(and the newIntelEngine_MinFilenameScore) to$Script:Config.The new test also checks every
$Script:Config.<Name>in the script against the Config literal via the AST. It is clean today (52 defined), and with the fix reverted it names the line.1. What the parser kept
I read the real Neo23x0 files (September 2026). Each file's own header documents its format. LOKI's
loki.pyconfirms the semantics: filename IOCs are compiled case-sensitive andsearched in the full path, with the third field as a false-positive veto.filename-iocs.txtREGEX;SCORE[;FP REGEX]. 3,846 data lines, of which 139 are Unix paths.\\usbclass\.sys;80Contains($proc.Name)againstregex;score, or[regex]::Escape(line), which looks for the literal text\\usbclass\.sys;80.hash-iocs.txtHASH;COMMENTorHASH;SCORE;COMMENT: 1,039 MD5, 946 SHA1, 1,260 SHA256sha;commentContains($sha256)againstsha;comment.c2-iocs.txt;scoreearn.fmis listed and would have matchedlearn.fm, and5.149.249.226would have matched15.149.249.226.Also found:
Trim(). Had the file ever been served with CRLF, every blank line ("\r") would have become'', and''escaped matches every Run value and hosts line.Last-Modifiedheader (only an ETag), so the HEAD check always falls to its catch.2. Every consumer, and exactly what a hit does
Before this PR, "IOC" meant:
Log-IOC, which adds a High finding titledIOC: .... Battlefield (bf/alerts.py) raises a Critical alert for any title starting "IOC".IOCsFound++, which costs 15 points (up to 50 in all), sets exit code 2, shows the "Action Required" banner, and starts a Defender quick scan.FilenameIOCs.Contains($proc.Name)Stop-Process -Forceplus an IOC, unless it runs under Windows or Program Fileswould_have: "kills the process", or "is only reported (vendor path)".$val -match [regex]::Escape($ioc)Remove-ItemPropertyplus an IOCwould_have: "removes the Run value".Remove-ItemPropertyplus an IOCContains($lnk.BaseName)Remove-Itemon the .lnk plus an IOCwould_have: "deletes the shortcut".Users)Contains($f.Name)Remove-Itemon the user's file plus an IOCwould_have: "deletes the file".Contains($f.Name)HashIOCs.Contains($sha256)3. Report-only
Every intel match now goes through
Add-IntelHit, and nowhere else.intel.matches. Each has the kind, source, target, indicator, score andwould_have. For a matched file it also has the SHA256 and Authenticode signer. The device log is not uploaded, so this is the data the next release decides on.Intel match (report-only): <kind> in <source> - <target>.IOCsFound, the score, the exit code, the banner or the Defender scan. Battlefield raises no alert for it, because it is Low and its title does not start with "IOC".Against the real lists, 3 of a hand-picked 52 common Windows paths match at score 60 or more:
\\tmp\.exe;60on...\Temp\tmp.exe;\\new\.exe;60onDownloads\new.exe;\\k7sysmon\.exe;60on...\K7 Computing\...\k7sysmon.exe, the name of a K7 antivirus component. The pattern is case-sensitive, so it matters whether the real binary is lower-case.LOKI's own warning level is 60 (alert 100, notice 40). Below 60 a match is only a "notice", so filename IOCs below 60 are not loaded (
$SK_IntelEngine_MinFilenameScore = 60). That leaves out 1,523.4. Size caps and other guards against a bad upstream list
The feed is a third-party GitHub repository, and one bad line would reach every device.
.,\\and(?i)c:, which the fleet review showed would match every path. So are the empty-file SHA256 and top domains such asmicrosoft.com. None of today's entries is dropped.-UseBasicParsing. Without it, 5.1 hands text responses to the IE engine, which fails under SYSTEM wherever IE's first run was never completed. The progress bar is off.What it does in Battlefield
ioc_alertsis unchanged by intel.bf/findings.pyclassifies unknown titles as VULN.bf/report.pygroups findings by catalogue id, so all of a tenant's unclassified findings collapse into one card, titled after whichever one is seen first.Intel match (report-only): ... C:\Users\jsmith\...would appear in the Assessment Report as a weakness, and could fold unclassified High findings into a Low card.^Intel match \(report-only\)as a DETECTION (triage required, false positives declared), so these appear once under "Detections requiring triage", outside the risk rating and without the raw titles. It also groups unclassified findings by title.intelobject: Battlefield stores the whole report (ADR 0002), so it needs no ingest change; nothing displays it yet. This query is read-only and has not been executed:LATEST_SK_VERSIONinbattlefield.envwhen this ships.Tests
New
tests/Test-IntelEngine.ps1, in the style ofTest-EngineScope.ps1. It runs everything verbatim fromShellKnight.ps1under StrictMode 2, with mocked cmdlets.Phase 1, 17 scenarios, with
Invoke-WebRequestserving lists in the real formats:{}, corrupt, one-list-empty and legacy whole-line cache;It asserts
IntelSource, the counts (non-zero where expected), what was left out and why, whether the cache was written, and-UseBasicParsing.Both matchers: full paths, case,
(?i), the FP regex, command lines, timeouts, both caps, and C2 labels and subdomains.Add-IntelHit: it is never an IOC, the evidence is right, and the caps hold.All nine consumers, run verbatim: each intel match reported (kind, source, target,
would_have), each action taken, the IOC count, hosts-file blocks, and no hashing without hash intel.AST check: every
$Script:Config.<Name>against the Config literal.Results:
Trim()after the comment test, and the later empty-entry guards make that change harmless.Test-BiosDate,Test-DeviceIdentity,Test-EngineScope(including its child-scope AST check) andTest-OsEolstill pass.Review: three independent reviewers, covering PS 5.1/StrictMode, fleet safety, and the diff and tests. Their findings are addressed in e271743, except the Battlefield change.
.Content, the multi-lineifassignment, and scoping.$scoreoverwrote the$Scoreparameter, since PowerShell names are case-insensitive.Before merging: one real run
Run this in an elevated Windows PowerShell 5.1:
When it finishes, check the newest
C:\ProgramData\ShellKnight\Logs\ShellKnight_*.log:Intel Engine skipped,Assessment Engine skippedorDevice identity skippedline.Intel Engine - downloaded ...: N usable entrieslines.... hash IOCs | ... filename IOCs | ... C2 IOCs loadedline shows about 1,175 / 2,184 / 1,792.left out:line shows0 matching a known-good path; 0 known-good hashes or C2 entries.Intel source Live (Neo23x0);Intel matches N;Intel paths checked N in S s. S decides whether the 30 s cap suits 5.1. The PS 7 figure is about 1.3 ms a path; the 5.1 reviewer's estimate is 15 to 40 s at the 3,000-path cap.Intel ... matchWARN line, noKilled processorRemoved ...line names the same thing.Check the files as well:
C:\ProgramData\ShellKnight\Intel\neo23x0_consolidated.jsonis about 250 KB, and(Get-Acl <file>).Owneris SYSTEM or BUILTIN\Administrators.C:\ProgramData\ShellKnight\JSON\*.json,intel_sourceisLive (Neo23x0), the three IOC counts are non-zero, andintel.match_secondsandintel.matchesare present.ioc_alertsdoes not count intel matches.Run it a second time: it should report
Cache (current)and make no list downloads.In Battlefield, the device should show a v2026.09.25.004 run with that
intel_source. Then remove the test task withschtasks /delete /tn SK-Test /f.Follow-ups (not in this PR)
intel.matches(the catalogue entry is cdburgess75/BattleField#14).intel.matches. For example, act on hash matches on unsigned files first, and filename matches only at high scores on unsigned files outside vendor paths.Last-Modified, so the lists refresh every 7 days regardless. Use the ETag, or drop the HEAD request.Win32_Processoften reportsC:\WINDOWS\..., and case-sensitive patterns miss those. This only causes misses.$name -match 'raccoon'), the same bug class as the NVIDIA incident, and a match removes the value. They are not intel, so they are unchanged here.run.ps1andconfig.jsoninC:\ProgramData\ShellKnight, and the SYSTEM task executesrun.ps1. It is worth a separate look.$Script:FolderIOCsis declared and never used.🤖 Generated with Claude Code