# Independent reviews, 2026-09-25 and 26 After W1–W3 were built and declared passed by the same AI agent that wrote them, separate AI reviewer agents — fresh context, read-only access to the repo — checked the work: a code reviewer, an acceptance auditor that re-derived every claimed number, and a pre-release privacy/IP auditor over the full git history. Five further code reviews checked the fixes. This page lists what they found and what changed. Every fix in the first two reviews has a regression test that fails on the old behaviour, except first-review #12 (a runtime self-check, marked "—"); for the third review, see the note there. ## Code review — 14 defects | # | Severity | Defect | Fix | Test | |---|---|---|---|---| | 1 | high | A crafted `.lnk` in the Startup folder crashed every scan and the MCP server (LinkInfo shorter than its header fields) | Bounds checks; every collector runs under `recover` and a crash becomes a "not checked" limit | `FuzzParseShortcut` (8M execs), `TestParseShortcutShortLinkInfo` | | 2 | high | Any path containing `\WindowsApps\` counted as package-signed; `%LOCALAPPDATA%\Microsoft\WindowsApps` is user-writable | Only `%ProgramFiles%\WindowsApps` is trusted | `TestFakeWindowsAppsIsUnsigned` | | 3 | high | Signature errors (missing, locked, malformed file) produced no finding | Medium in a user-writable folder, low elsewhere | `TestReviewSigErrorIsReported` | | 4 | high | Persistence or processes running a payload through `rundll32`/`regsvr32`/`wscript`/`powershell -File`/`cmd /c` were judged by the signed host | Payload extraction; autostarts, A1, H1, H2 judge the loaded file | `TestPayload`, `TestReviewScriptHostPayloadJudged` | | 5 | medium | `scan -json` left autostarts, signers, logon names, MCP source/URL/env names and warnings unredacted | Every host string redacted | `TestRedactedCoversEveryHostString` | | 6 | medium | Zero-width and other invisible characters survived and could split I1 keywords | Sanitizer drops all Unicode format characters, variation selectors and blank fillers; I1 matches after stripping them | `TestSanitizeInvisibleAndControls`, `TestEveryInjectionPattern` | | 7 | medium | `curl … \| python -c "…"` (data processing) raised a high A2 on a real PC | Pipe-to-interpreter only when the interpreter reads its program from the pipe | `TestEveryDownloadExecPattern` | | 8 | medium | Generic entry-point names (`index.js`) and a broken version strip gave unrelated processes a configured MCP server's name | Generic file names and short or numeric tokens ignored; version strip only on version-like suffixes | `TestMatchServerNoGenericNames` | | 9 | medium | A3/A4 finding IDs ignored peers, so one acknowledgement hid every future unknown peer; ack file is user-writable | IDs include listen addresses / peers; ack-file forgery documented as a known limit | `TestReviewA4IDCoversPeers` | | 10 | low | PID-reuse guard skipped when a creation time was unreadable | Unverifiable children are not attributed to agent trees | `TestUnreadableChildNotAttributed` | | 11 | low | One input line over 16 MiB ended the MCP session | Oversized line gets a parse error; session continues | `TestOversizedLineKeepsServing` | | 12 | low | "Never connects" self-check ran before signature verification | Second check after verification; documented as a tripwire, not a proof | — | | 13 | low | I1 skipped logon user names, signer names, MCP source and env names | Scanned | `TestI1ShellSeverityAndNewFields` | | 14 | low | 8-bit CSI (U+009B) reached the terminal | All C0/C1 controls replaced | `TestSanitizeInvisibleAndControls` | Found while applying these fixes: an implant whose command line mentioned a configured server's port (`9443`) was labelled as that MCP server. Short and letterless match tokens are now ignored (`TestMatchServerNoGenericNames`). ## Second code review — checking the fixes A fresh reviewer re-checked every fix above. Of the 11 rows below, 5 are fixes that were incomplete (one of them, A, a regression introduced by fix #4) and 6 are new defects. | Finding | Severity | Fix | Test | |---|---|---|---| | A. A renamed, unsigned `rundll32.exe` in a user folder was judged by the signed DLL it loaded (verdict ok; high before fix #4) | high (regression) | Host program and payload are both judged; the worse one decides | `TestReviewScriptHostPayloadJudged`, `TestHostTextNeverInTitleOrDetail` | | B. Payload parsing: first file wins (`shell32.dll,Control_RunDLL x.cpl`), extension allow-list (`.tmp`, no extension, `.txt`), interpreters (`node x.js`) not handled | high | Per-host parsing; protected system files skipped in favour of the next candidate; any extension; node/python/java | `TestPayloadSecondReview` | | C. False alarms: `powershell -Command "Remove-Item x.exe"`, `cmd /c dir x.exe` read as payloads | medium | PowerShell only `-File`; cmd only the program after `/c`, built-ins ignored; unresolvable bare names (`npx`) dropped; script payloads of running processes not judged by signature | `TestPayloadSecondReview`, `TestBuild` | | D. `ProtectedSystemPath` fooled by `\.\`, doubled separators, trailing dots, 8.3 names; `InstalledPackagePath` by `WindowsApps\..\..` | medium | Paths made canonical (clean, trailing dots/spaces, `GetLongPathName`) before any trust decision | `TestProtectedSystemPathCanonical` | | E. A UNC payload made signature verification open `\\server\share\…` — an outbound SMB/WebDAV connection with NTLM | medium | Network paths (UNC and mapped network drives) are never opened: not by signature checks, MCP config reading or ack hashing; reported as `remote` | `TestRemotePaths`, `TestVerifyFileNeverOpensRemote` | | F. MCP views lacked the payload while the baseline showed it | low | `runs_file*` and `host*` fields, inside the envelope | `TestEnvelope` (mutation-checked) | | I1 downgrade reachable by naming a program `node.exe`; one ack hid later injections | medium | Only a validly signed shell or interpreter started directly by an agent, not an MCP server, and still **high**; the finding ID includes the text | `TestI1DowngradeNotAbusable` | | #3 partial: a locked binary in a user folder was medium, an unsigned one high | — | Both high for binaries; scripts medium | `TestReviewSigErrorIsReported` | | #6 partial: U+034F, Mongolian selectors, look-alike Cyrillic/full-width letters | — | Stripped / folded for matching | `TestEveryInjectionPattern` | | #7 partial: `\| bash -x`, `\| sh > /dev/null`, `\| python3 - --user` missed | — | Small parser: flags only when the interpreter reads the pipe | `TestEveryDownloadExecPattern` | | #8 partial: `localhost`, `false`, relative `dist/index.js` matched servers | — | Common-word stoplist; generic names only in absolute or distinctive paths | `TestMatchServerNoGenericNames` | Accepted as known limits: the `.lnk` parser reads the target path but not the ID list or environment block, so a crafted shortcut can show a harmless target and launch another; a child process running as another user drops out of agent trees when not elevated. ## Third code review — 17 findings A third reviewer read the code after the second round of fixes and tested its findings in a copy of the repo (fuzzing the command-line parsers found no crashes). It recommended fixing 1–7 and 10–12 before release. 16 of 17 are fixed; #9 is listed under known limits in the README. Each fix has a test. Reverting the fix was checked to make its test fail for #2, 3, 6, 8 and 10–17; #1, 4, 5 and 7 were not revert-checked, and the live-scan wiring of #5 (the check itself is tested) has no unit test. | # | Severity | Defect | Fix | Test | |---|---|---|---|---| | 1 | high | `\??\UNC\…` and `\\?\GLOBALROOT\Device\Mup\…` were not seen as network paths, so a crafted autostart made the signature check open an SMB connection. Relative paths and links were opened too | Allowlist: only an absolute path on a local drive letter is opened; links and junctions are not followed | `TestRemotePaths`, `TestReparsePointNotOpened` | | 2 | high | An alternate data stream on a writable System32 subfolder (`…\Tasks:x.dll`) counted as a protected system file, so its payload was skipped | Any `:` after the drive letter makes a path untrusted | `TestProtectedSystemPath` | | 3 | high | Scripts run by `wscript`/`cscript`/`mshta` were never judged: such a process talking out gave verdict ok | Their scripts are verified and judged (medium when unsigned in a user-writable folder); node/python scripts still are not | `TestThirdReviewScriptHostScripts`, `TestThirdReviewSigning` | | 4 | medium | `cmd /c"…"`, `cmd /q/c`, positional or `-Command` PowerShell scripts and `conhost --headless` hid the file they run | cmd switch clusters and quote rule; PowerShell parameter parser; conhost as a launcher | `TestPayloadThirdReview` | | 5 | medium | The user-writable check compared raw strings and only knew a few folders (`C:\tools` counted as admin-only) | Any folder not known to be admin-only is user-writable; paths made canonical first | `TestAdminOnlyPath`, `TestThirdReviewSigning` | | 6 | medium | I1 kept one finding per program path, so a planted message could be hidden behind an agent's own command | One finding per process and text | `TestThirdReviewI1PerProcess` | | 7 | medium | One scheduled task whose name broke the XML hid every task; the error was not in `not_checked` | Each task parsed on its own; failures counted and listed in `not_checked` | `TestParseTasksBadName` | | 8 | medium | A1/A2 skipped anything that looked like an agent, so an unsigned "claude.exe" in Temp started by Claude Code gave ok | Only the root agent is exempt | `TestThirdReviewNestedAgentChecked` | | 9 | medium | A2/H6 miss further download-and-execute forms | Known limit, listed in the README | — | | 10 | medium (false alarm) | Claude Code's updater renames the running binary to `claude.exe.old.N`, which then cannot be read: high and alert on an ordinary PC | Low when the file now at the original path is validly signed | `TestThirdReviewUpdatedImage` | | 11 | medium (false alarm) | uv-managed Python is unsigned, so every uvx MCP server that connects out raised high | Capped at medium for a configured MCP server matched by its command line; tampered files stay high | `TestThirdReviewConfiguredMCPRuntime` | | 12 | medium | `list_autostarts` ignored the host program's signature and listed unchecked System32 entries as problems (359 on the dev PC, cut off at 300) | Host signature counts; unchecked entries are counted separately | `TestThirdReviewAutostartProblems` | | 13 | low | Docker image base names and environment variable names were used to match MCP servers (`git clone https://github.com/…` was labelled "github") | Not used as tokens | `TestMatchServerNoGenericNames` | | 14 | low | H6's ID ignored the command, so an acknowledged entry could be rewritten unseen | ID includes the command | `TestThirdReviewH6ID` | | 15 | low | Look-alike matching missed math alphanumerics, dotless ı and combining marks | Folded | `TestEveryInjectionPattern` | | 16 | low | `scan -json` did not redact the autostart host's signer | Redacted; fixture marks it | `TestRedactedCoversEveryHostString` (fails without the fix) | | 17 | low | MCP configs and the ack store were read even when the profile is on a share | Not opened; reported | `TestStoreNotLocal`, `TestInventorySkipsRemoteFiles` | Found while fixing #3: the snapshot never verified script payloads, so judging them would have had nothing to judge (`TestThirdReviewSigning`). ## Fourth review — the third review's fixes A last reviewer checked only the diff of the fixes above. It found one new blocking defect, introduced by fix #4, and eight smaller issues. | # | Severity | Defect | Fix | Test | |---|---|---|---|---| | 1 | high (new false alarm) | `cmd /c "C:\Program Files\Vendor App\start.bat"` was read as `C:\Program`, so an ordinary vendor task gave alert | First attempt guessed from the file extension, which the fifth review showed hid other payloads; see there | `TestPayloadThirdReview`, `TestCmdQuotesFifthReview` | | 2 | medium | The updater excuse (#10) could be used by any program named `x.exe.old.N` made unreadable | Only for an agent process whose command line still names the original path (narrowed again in the fifth review) | `TestThirdReviewUpdatedImage` | | 3 | medium | The MCP cap (#11) applied to any file of a process whose command line matched a server, including payloads | Only the process's own image, and only a file under uv's folders (the fifth review removed a name-based runtime check) | `TestFourthReviewMCPCapNarrow` | | 4 | medium | `cmd /cC:\…` and `conhost --vtmode xterm …` still hid the program | Switch clusters parsed; `--vtmode` known; unknown option values skipped | `TestPayloadThirdReview` | | 5 | low | A bare, unresolvable autostart name was reported as a network file and worded "signed" | Unknown, not remote; remote status has its own wording | `TestVerifyBareNameIsNotRemote` | | 6 | low–medium | Links in parent folders, the Startup-folder scan and ack hashing are not guarded; the drive-type cache never forgot an unmapped letter | Cache no longer keeps unmapped letters; the rest is listed under known limits in the README | — | | 7 | low | WinGet's portable programs are symbolic links, which made them unverifiable (high in a user folder) | A file that is a symbolic link is followed once to a local target | not unit-tested (creating a symlink needs Developer Mode) | | 8 | low | Test gaps: no fully quoted path with spaces, no MCP-config guard test, the ack test used a root-relative path instead of UNC | Added | as above | | 9 | medium (older false alarm) | Claude Code's Bash commands on Windows run through Git's `bash.exe` launcher, so the real shell is a grandchild and its text was critical | A signed bash started by a signed bash the agent started counts as the agent's own command (high); anything further down stays critical | `TestFourthReviewShellChain`, `TestI1DowngradeNotAbusable` | ## Fifth review — the fourth review's fixes A reviewer re-checked only the diff of the fourth-review fixes, fuzzed the cmd and conhost parsers (about 325k inputs, no panics) and confirmed each new test fails without its fix. It found one new blocking defect, again in the cmd quote handling. | # | Severity | Defect | Fix | Test | |---|---|---|---|---| | 1 | high (new false negative) | The quote fix kept `"…"` whole whenever the text ended in a script or program extension, so `cmd /c "wscript C:\…\a.vbs"`, `"regsvr32 /s …\a.dll"`, `"powershell -File …"` and similar autostarts lost their payload and were judged as cmd.exe (not checked) | cmd.exe's actual rule: quotes are kept only when the quoted text has a space, no special characters (`&<>()@^|`) and names an existing local program (with or without a PATHEXT extension). When the split reading's first word is no program at all (an uninstalled program, or `Program Files (x86)`), the quoted program is judged instead | `TestCmdQuotesFifthReview` (fails without the fix) | | 2 | medium | The updater excuse still returned high for `claude --resume` or forward-slash launches, and a process can choose its own command line | File names compared (bare names and either slash accepted); the finding is lowered to medium, not low | `TestThirdReviewUpdatedImage` (fails without either change) | | 3 | medium | A symbolic link with a root-relative target (`\dir\x.exe`) was resolved against the link's folder instead of the volume root, so a decoy could be verified | Resolved from the volume root | `TestReparsePointNotOpened` (skips without the symlink privilege, so it has not run on the development PC) | | 4 | low | The old symlink test still expected links not to be followed; it only passed because symlinks could not be created | Rewritten: followed once, chained and UNC targets refused, root-relative target | as above | | 5 | low–medium | The shell-chain exception matched names only, so a script's own bash could qualify | Requires Git's launcher layout: `\bin\bash.exe` starting `\usr\bin\bash.exe` | `TestFourthReviewShellChain` (fails without the fix) | | 6 | low | Bare autostart names dropped from medium to low | Medium: Windows finds them through PATH, which includes a user-writable folder | `TestFifthReviewBareAutostartName` (fails without the fix) | | 7 | low | The MCP cap matched `\uv\` anywhere and any runtime by file name | Anchored to `%APPDATA%\uv\` and `%LOCALAPPDATA%\uv\`; runtime-name check removed | `TestFourthReviewMCPCapNarrow` (fails without the fix) | | 8 | low (docs) | This page called the fourth-review parser fix "cmd.exe's real rule" | Corrected above | — | Not changed: `cmd /c "C:\x.exe" "arg with space"` is split the way cmd.exe itself splits it (and fails to run), as before. ## Sixth review — the fifth review's fixes A reviewer re-checked the fifth-review diff, fuzzed `Payload` with cmd.exe as host (4.3M inputs, no panics) and found **no new high-severity issue**; all seven fixes held. | # | Severity | Defect | Fix | Test | |---|---|---|---|---| | 1 | medium | The new file-existence check used `os.Stat`, which follows a file symlink; a planted link to a share would have made Windows open an SMB connection | `os.Lstat`: a link counts as existing and is not followed | `TestCmdQuotesFifthReview` (junction case) | | 2 | low | The existence check tried only `.exe .com .bat .cmd`, not cmd.exe's other PATHEXT types | Full default PATHEXT list | `TestCmdQuotesFifthReview` | | 3 | low | A quoted program named without its extension (`"C:\…\My Tools\start"`) was split | Quoted name also tried with PATHEXT extensions | `TestCmdQuotesFifthReview` | | 4 | low | Removing the name-based runtime check means unsigned runtimes outside uv's folders are high again | Kept; listed under known limits in the README | — | | 5 | low (docs) | A stale code comment, the README's link paragraph, and this page's short form of the quote rule | Corrected | — | Remaining known gap: when the split first word is a script found through PATHEXT (`upd` for `upd.vbs`), the extensionless name is judged rather than the `.vbs` file; it is still reported. ## Acceptance audit — claims that did not hold | Claim | Finding | Now | |---|---|---| | Property test: no host text in title/detail | Hand-built fixture that never fired H2; putting the process name into H2's detail left every test green | Test requires every registered rule to fire; the auditor's mutation now fails | | Every rule pattern tested | 3 of 7 I1 and 2 of 9 A2 patterns could be deleted with tests green | One exact-label sample per pattern; test fails if a pattern has no sample | | 3.6 MB binary, 0.36 s / 0.95 s scans | 4.3 MiB; 2.0–2.8 s warm, ~7 s cold | Numbers restated; System32 autostart targets no longer signature-checked. A later audit measured 0.64–0.67 s on 548 processes | | W2 "all host checks" | SMB missing, elevated logon path never run, rule A5 silently dropped | Recorded as deviations in the spec | | W3 "0/54 flips" acceptance | Ran under leaky isolation; the naive baseline also scored 0/54, so the bar could not tell design from no design; the baseline got less data | Baseline now gets the same facts (`TestNaiveGetsTheSameFacts`); suite re-run under fixed isolation (see w3.md) | | I1 flags injection attempts | Also flagged developers' own agent-run shell commands as critical | Such hits are high (not critical) with an explanation, only for a signed shell an agent started directly | | Red-team: the "approved by IT" note talked the model down | The raw-data run with the note got "review" 6/6, but nothing showed the note caused it | Control run without the note: raw data still "review" 6/6, agentegress "alert" 6/6. The claim is now about plausible-looking setups, not the note | | Red-team tables | All numbers recomputed from raw runs and matched | — | ## Privacy / IP audit No employer material, secrets or machine identifiers in the tracked files at the time of audit, apart from: the tester's Windows user name quoted in a doc, a first name in test paths in early commits, and the development PC's software inventory in docs and README. Docs and examples now use synthetic data; history is squashed before the first public push. Added LICENSE (MIT) and NOTICE (IP range data sources).