Conversation
OpenClaw is not in detect_tool_for_user's dispatch list, so it falls through to the default detector.detect() and was missed by #265 and #266. All three of its probes resolve the SCANNER's profile rather than the assigned user_home: _check_binary (:104, shutil.which on PATH, not even admin-gated), _get_installation_paths (:126 %LOCALAPPDATA% + :137 admin C:\Users sweep), and _search_for_executable (:170 %LOCALAPPDATA%/%APPDATA%). One user's install is credited to every profile; when the scanner lacks it, a real install is missed. Reproduced 4/4 scenarios, same bidirectional FP/FN as #265/#266. Scoping only the :137 sweep is not enough -- verified that _check_binary alone still fans out. Adds a scoped branch covering all three. _check_running_process is skipped in the scoped path: it yields no install_path, and the backend drops a tool report without one (ai_tools_service.py:6269). Machine-wide Program Files stays for everyone; unscoped/legacy behaviour is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oDYoAEoWFn89jksQhpq3m
The scoped branch checked three exact directories, but the unscoped path also runs an os.walk of %LOCALAPPDATA%/%APPDATA% for openclaw.exe. Squirrel installs directly under %LOCALAPPDATA%\OpenClaw (not Programs) -- the same layout the Replit detector documents -- so a genuine install outside Programs was found before this PR and dropped after it, then treated as uninstalled. _search_for_executable now takes optional roots, so the scoped path runs the same walk rooted at the SCANNED user's AppData instead of the env-derived (scanner's) one. Coverage preserved, attribution still fixed. Exact-dir-first then deep-walk order matches the unscoped path, so the cost profile is unchanged. Only _check_binary (PATH) and _check_running_process stay dropped in the scoped path: neither can be attributed to a profile, and the latter yields no install_path, which the backend discards anyway. Adds a test pinning an install at %LOCALAPPDATA%\OpenClaw\app-1.2.3\openclaw.exe -- found for its owner, not leaked to the co-resident user. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oDYoAEoWFn89jksQhpq3m
vigneshsubbiah16
left a comment
There was a problem hiding this comment.
🛡️ Automated Security Review (consensus)
1 finding — 0 high-confidence, 1 to triage. Reviewers: Cursor, Claude, Semgrep, Gitleaks.
Medium · 🟡 TRIAGE — subprocess.run(..., shell=True) in legacy detection path
scripts/coding_discovery_tools/windows/openclaw/detect_openclaw.py:123
Impact: Shell invocation inherits scanner environment and expands metacharacters; risky if any argument is derived from filesystem or user-controlled data.
Fix: Pre-existing code (unchanged by this PR). Prefer shell=False with a fixed argv list, or validate/sanitize inputs before any shell execution.
Flagged by: Semgrep
Diff review (Cursor): The new _detect_for_user path only probes fixed candidate directories with path.exists(), catches PermissionError only (per intentional design — transient I/O errors propagate), and does not add subprocess, network, or secret-handling. No new injection, privilege-escalation, or cross-user attribution vectors in the introduced logic. Claude and Gitleaks reported clean.
🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head ed39f4b5 · 2026-09-02T11:51Z
vigneshsubbiah16
left a comment
There was a problem hiding this comment.
🛡️ Automated Security Review (consensus)
1 finding — 0 high-confidence, 1 to triage. Reviewers: Cursor, Claude, Semgrep, Gitleaks.
Findings
🟡 TRIAGE — subprocess.run with shell=True
scripts/coding_discovery_tools/windows/openclaw/detect_openclaw.py:123
Impact: Spawning a shell inherits the scanner process environment and expands injection risk if any argument is ever derived from untrusted input.
Fix: Prefer shell=False with an argv list; if a shell is required, use a fixed, fully quoted command with no interpolated paths or env values.
Flagged by: Semgrep
Previously acknowledged (not re-flagged)
- Scoped executable search too narrow (missed Squirrel / deep AppData layouts) — Fixed in
7bc4768; scoped path now runs the same_search_for_executablewalk rooted at the scanned user's AppData viasearch_roots(@anonpran). _check_binary/_check_running_processdropped in scoped path — Accepted by design: PATH resolves the scanner profile (fan-out source); running processes cannot be profile-attributed and yield noinstall_path(PR description / @anonpran).except PermissionErroronly, notOSError— Accepted design choice so transient I/O errors fail the scan instead of being read as "not installed" (#266 / PR description).
🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head 7bc47689 · 2026-09-02T12:03Z
Measured before/after, and half of this was a regression this PR introduced:
shim ON scanner PATH shim NOT on PATH
staging (pre-PR) DETECTED missed
this PR (before fix) missed missed
this PR (after fix) DETECTED DETECTED
The scoped path drops _check_binary (it resolves the SCANNER's PATH -- the
fan-out source), and shutil.which on Windows resolves .cmd via PATHEXT, so
dropping it lost the one probe that saw an npm shim for the scanning user's own
profile. The deep walk did not cover it: it matched "openclaw.exe" only, which
is also why the not-on-PATH case was already missed on staging.
The walk now matches _EXECUTABLE_NAMES (.exe, .cmd, .ps1, extensionless),
ordered so a real .exe still wins over a shim in the same directory. Applies to
both the scoped and unscoped paths -- one function, and divergence there is what
caused this class of bug. Net effect vs staging is strictly more coverage.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oDYoAEoWFn89jksQhpq3m
vigneshsubbiah16
left a comment
There was a problem hiding this comment.
✅ Security consensus: no issues found. (reviewers: Cursor, Claude, Semgrep, Gitleaks)
🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head ca77f93d · 2026-09-02T12:13Z
Completes the per-user scoping work from #265 and #266. OpenClaw is not in
detect_tool_for_user's dispatch list, so it falls through to the defaultdetector.detect()and both PRs missed it.Problem
Every one of OpenClaw's probes resolves the scanner's profile, never the assigned
self.user_home:_check_binary():104shutil.which("openclaw")— the scanner's PATH, not even admin-gated_get_installation_paths():126,137%LOCALAPPDATA%(scanner's) + the adminC:\Userssweep_search_for_executable():170%LOCALAPPDATA%/%APPDATA%(scanner's)Reproduced by driving the real
detect_tool_for_user()against a two-profile tree where only alice has OpenClaw:Same bidirectional FP/FN signature as #265 and #266.
Scoping only the
:137sweep is not sufficient. Verified separately — with the install dirs scoped away butopenclawon the scanner's PATH,_check_binaryalone still credits every user:Fix
A scoped branch on
self.user_homecovering all three sources at once: probe that user'sAppData\Local\Programs\OpenClawplus the machine-wideProgram Fileslocations, which are legitimately every user's. Unscoped/legacy behaviour is untouched.Two deliberate choices:
_check_running_processis not reused in the scoped path. A running process cannot be attributed to a specific profile without an owner lookup, and it yields noinstall_path— the backend drops a tool report without one (ai_tools_service.py:6269-6270), so nothing storable is lost.except PermissionError, notexcept (PermissionError, OSError), following the Greptile P1 finding on [WEB-5697] Windows: scope Copilot to the scanned user, and stop profile probes crashing on access-denied #266: access-denied should skip the candidate, but a transient I/O failure must propagate so the run is marked incomplete rather than read as "not installed" and pruned.Tests
TestWindowsOpenClawPerUserScopingintests/test_scan_completed_manifest.py:shutil.whichand_check_running_processdeliberately returning hits so the test fails unless all three sources are scoped.All three fail against staging. Regression green: probe-permission safety, Copilot scoping, IDE scoping, manifest-key, manifest-from-presence. Raisers on an access-denied home stay at 0/19.
Note
This is the last detector in the registered Windows set that ignored its assigned
user_home. Verified by driving every registered detector withPath.home()pinned to a foreign profile — 0 leak onto the scanned user, before and after.🤖 Generated with Claude Code
Greptile Summary
The PR scopes Windows OpenClaw detection to the assigned user while preserving machine-wide installations and legacy unscoped behavior.
Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Windows OpenClaw detection] --> B{user_home assigned?} B -->|No| C[Legacy PATH, environment, and process probes] B -->|Yes| D[Check scanned user's known install directory] D --> E[Check machine-wide OpenClaw directories] E --> F[Walk scanned user's Local and Roaming AppData] F --> G[Match exe, cmd, ps1, or extensionless command] C --> H[Normalized OpenClaw result] G --> HReviews (3): Last reviewed commit: "Address Greptile: match npm-global shims..." | Re-trigger Greptile