coding-agent-audit 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {coding_agent_audit-0.1.0/src/coding_agent_audit.egg-info → coding_agent_audit-0.2.0}/PKG-INFO +22 -9
  2. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/README.md +21 -8
  3. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/pyproject.toml +1 -1
  4. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/src/agentaudit/checks.py +64 -0
  5. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/src/agentaudit/iocs.py +15 -7
  6. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0/src/coding_agent_audit.egg-info}/PKG-INFO +22 -9
  7. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/tests/test_checks.py +42 -0
  8. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/LICENSE +0 -0
  9. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/setup.cfg +0 -0
  10. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/src/agentaudit/__init__.py +0 -0
  11. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/src/agentaudit/cli.py +0 -0
  12. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/src/agentaudit/configscan.py +0 -0
  13. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/src/agentaudit/findings.py +0 -0
  14. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/src/coding_agent_audit.egg-info/SOURCES.txt +0 -0
  15. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/src/coding_agent_audit.egg-info/dependency_links.txt +0 -0
  16. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/src/coding_agent_audit.egg-info/entry_points.txt +0 -0
  17. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/src/coding_agent_audit.egg-info/top_level.txt +0 -0
  18. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/tests/test_cli.py +0 -0
  19. {coding_agent_audit-0.1.0 → coding_agent_audit-0.2.0}/tests/test_configscan.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coding-agent-audit
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Scan your machine for known AI-coding-assistant supply-chain backdoor indicators (Claude Code / Cursor / Windsurf extensions, MCP servers) plus risky hook/permission config patterns.
5
5
  Author-email: StackScribe AI <survivalagent320@gmail.com>
6
6
  License: MIT
@@ -38,11 +38,13 @@ A small, open-source CLI that scans your machine for:
38
38
  ## Why this exists
39
39
 
40
40
  In October 2026, a trojanized build of the Claude Code extension
41
- (`anthropic.claude-code@2.1.313`) was published on Open-VSX. Loading it
42
- silently added an SSH key, enabled remote login, opened a reverse SSH
43
- tunnel to an attacker-controlled server, and installed persistence
44
- disguised as a system daemon — no user interaction required beyond the
45
- editor loading the extension.
41
+ (`anthropic.claude-code@2.1.313`) was published on Open-VSX, and the same
42
+ malicious build was later also found on the Cursor Marketplace. Loading it
43
+ silently added an SSH key, tried to enable remote login, opened a reverse
44
+ SSH tunnel to an attacker-controlled server, beaconed basic host recon, and
45
+ installed OS-specific persistence (a disguised macOS LaunchAgent, a Linux
46
+ cron `@reboot` entry, or a Windows scheduled task / Startup-folder script)
47
+ — no user interaction required beyond the editor loading the extension.
46
48
 
47
49
  This tool is an **independent re-implementation**, from scratch, of the
48
50
  publicly documented indicators from that incident (file paths, a known
@@ -82,17 +84,19 @@ one critical (confirmed-IOC-pattern) finding.
82
84
 
83
85
  ### What it checks
84
86
 
85
- **IOC scan** (`checks.py`) — all tied to the October 2026 Open-VSX Claude
86
- Code trojan:
87
+ **IOC scan** (`checks.py`) — all tied to the October 2026 Open-VSX /
88
+ Cursor Marketplace Claude Code trojan:
87
89
 
88
90
  - `~/.ssh/authorized_keys` for the planted key comment
89
91
  - `~/Library/LaunchAgents/com.apple.sysmond.plist` (macOS persistence)
90
- - Marker files under `~/.config`
92
+ - Marker files under `~/.config` (macOS/Linux and Windows filenames)
91
93
  - Leftover temporary SSH tunnel key files
92
94
  - `extension.js` files under common VS Code / Cursor / Windsurf extension
93
95
  directories, hash-compared against the known-malicious build
94
96
  - Running processes / crontab for references to the known C2 host or
95
97
  persistence task name
98
+ - On Windows: the known SYSTEM scheduled task name, and the Startup-folder
99
+ script dropped as a fallback when that task can't be created
96
100
 
97
101
  **Config heuristics** (`configscan.py`) — pattern-based, always worth a
98
102
  second look rather than proof of compromise:
@@ -127,6 +131,15 @@ pytest
127
131
 
128
132
  - doublepulsar.com: ["Anthropic hacked to deliver remote access trojan to Claude developer laptops worldwide"](https://doublepulsar.com/anthropic-hacked-to-deliver-remote-access-trojan-to-claude-developer-laptops-worldwide-1148a92baae2)
129
133
  - dev.to/@slabb: ["A trojanized Claude Code extension plants an SSH backdoor: the 7-point audit for your machine"](https://dev.to/slabb/a-trojanized-claude-code-extension-plants-an-ssh-backdoor-the-7-point-audit-for-your-machine-23cj)
134
+ - Phorion: ["Official Claude Code Extension Compromise: Security Advisory"](https://phorion.io/blog/offical-claude-code-vsx-compromise/) — confirms the same backdoor reached the Cursor Marketplace and documents the Windows/Linux persistence mechanisms added in v0.2.0 of this tool.
135
+
136
+ ## Support
137
+
138
+ This tool is free and MIT-licensed, no strings attached. If it caught
139
+ something real on your machine and you want to send something back, the
140
+ project wallet takes USDC/POL on Polygon, no account needed:
141
+ `0x9FB0aE96143801D39BDadAF74e234f63BaA28e39`. Entirely optional - installing
142
+ and running the scan asks nothing of you.
130
143
 
131
144
  ## License
132
145
 
@@ -18,11 +18,13 @@ A small, open-source CLI that scans your machine for:
18
18
  ## Why this exists
19
19
 
20
20
  In October 2026, a trojanized build of the Claude Code extension
21
- (`anthropic.claude-code@2.1.313`) was published on Open-VSX. Loading it
22
- silently added an SSH key, enabled remote login, opened a reverse SSH
23
- tunnel to an attacker-controlled server, and installed persistence
24
- disguised as a system daemon — no user interaction required beyond the
25
- editor loading the extension.
21
+ (`anthropic.claude-code@2.1.313`) was published on Open-VSX, and the same
22
+ malicious build was later also found on the Cursor Marketplace. Loading it
23
+ silently added an SSH key, tried to enable remote login, opened a reverse
24
+ SSH tunnel to an attacker-controlled server, beaconed basic host recon, and
25
+ installed OS-specific persistence (a disguised macOS LaunchAgent, a Linux
26
+ cron `@reboot` entry, or a Windows scheduled task / Startup-folder script)
27
+ — no user interaction required beyond the editor loading the extension.
26
28
 
27
29
  This tool is an **independent re-implementation**, from scratch, of the
28
30
  publicly documented indicators from that incident (file paths, a known
@@ -62,17 +64,19 @@ one critical (confirmed-IOC-pattern) finding.
62
64
 
63
65
  ### What it checks
64
66
 
65
- **IOC scan** (`checks.py`) — all tied to the October 2026 Open-VSX Claude
66
- Code trojan:
67
+ **IOC scan** (`checks.py`) — all tied to the October 2026 Open-VSX /
68
+ Cursor Marketplace Claude Code trojan:
67
69
 
68
70
  - `~/.ssh/authorized_keys` for the planted key comment
69
71
  - `~/Library/LaunchAgents/com.apple.sysmond.plist` (macOS persistence)
70
- - Marker files under `~/.config`
72
+ - Marker files under `~/.config` (macOS/Linux and Windows filenames)
71
73
  - Leftover temporary SSH tunnel key files
72
74
  - `extension.js` files under common VS Code / Cursor / Windsurf extension
73
75
  directories, hash-compared against the known-malicious build
74
76
  - Running processes / crontab for references to the known C2 host or
75
77
  persistence task name
78
+ - On Windows: the known SYSTEM scheduled task name, and the Startup-folder
79
+ script dropped as a fallback when that task can't be created
76
80
 
77
81
  **Config heuristics** (`configscan.py`) — pattern-based, always worth a
78
82
  second look rather than proof of compromise:
@@ -107,6 +111,15 @@ pytest
107
111
 
108
112
  - doublepulsar.com: ["Anthropic hacked to deliver remote access trojan to Claude developer laptops worldwide"](https://doublepulsar.com/anthropic-hacked-to-deliver-remote-access-trojan-to-claude-developer-laptops-worldwide-1148a92baae2)
109
113
  - dev.to/@slabb: ["A trojanized Claude Code extension plants an SSH backdoor: the 7-point audit for your machine"](https://dev.to/slabb/a-trojanized-claude-code-extension-plants-an-ssh-backdoor-the-7-point-audit-for-your-machine-23cj)
114
+ - Phorion: ["Official Claude Code Extension Compromise: Security Advisory"](https://phorion.io/blog/offical-claude-code-vsx-compromise/) — confirms the same backdoor reached the Cursor Marketplace and documents the Windows/Linux persistence mechanisms added in v0.2.0 of this tool.
115
+
116
+ ## Support
117
+
118
+ This tool is free and MIT-licensed, no strings attached. If it caught
119
+ something real on your machine and you want to send something back, the
120
+ project wallet takes USDC/POL on Polygon, no account needed:
121
+ `0x9FB0aE96143801D39BDadAF74e234f63BaA28e39`. Entirely optional - installing
122
+ and running the scan asks nothing of you.
110
123
 
111
124
  ## License
112
125
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "coding-agent-audit"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Scan your machine for known AI-coding-assistant supply-chain backdoor indicators (Claude Code / Cursor / Windsurf extensions, MCP servers) plus risky hook/permission config patterns."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -202,6 +202,66 @@ def check_linux_cron_persistence(crontab_text: str) -> list[Finding]:
202
202
  return []
203
203
 
204
204
 
205
+ def check_windows_scheduled_task(schtasks_output: str) -> list[Finding]:
206
+ """Flag a `schtasks /query` output that reports the known SYSTEM
207
+ persistence task."""
208
+ task_name = _IOC["windows_task_name"]
209
+ if task_name in schtasks_output and "ERROR" not in schtasks_output.upper():
210
+ return [
211
+ Finding(
212
+ check_id="windows-scheduled-task",
213
+ severity="critical",
214
+ title="Known backdoor scheduled task found",
215
+ detail=(
216
+ f"A scheduled task named '{task_name}' exists, matching "
217
+ f"the SYSTEM-level persistence task created by the "
218
+ f"{_IOC['name']}."
219
+ ),
220
+ references=_IOC["references"],
221
+ )
222
+ ]
223
+ return []
224
+
225
+
226
+ def check_windows_startup_folder(appdata: Path) -> list[Finding]:
227
+ """Flag the Startup-folder fallback script used on Windows when the
228
+ SYSTEM scheduled task cannot be created (non-admin user)."""
229
+ path = appdata / _IOC["windows_startup_relpath"]
230
+ if path.exists():
231
+ return [
232
+ Finding(
233
+ check_id="windows-startup-folder",
234
+ severity="critical",
235
+ title="Known backdoor Startup-folder script found",
236
+ detail=(
237
+ f"'{path}' exists and matches the Startup-folder "
238
+ f"fallback script dropped by the {_IOC['name']} when it "
239
+ f"can't create a SYSTEM scheduled task."
240
+ ),
241
+ path=str(path),
242
+ references=_IOC["references"],
243
+ )
244
+ ]
245
+ return []
246
+
247
+
248
+ def live_windows_scheduled_task_text() -> str:
249
+ """Best-effort `schtasks /query` for the known task name; empty string
250
+ if unavailable (not Windows, task not found, etc.)."""
251
+ try:
252
+ result = subprocess.run(
253
+ ["schtasks", "/query", "/tn", _IOC["windows_task_name"], "/fo", "list"],
254
+ capture_output=True,
255
+ text=True,
256
+ timeout=5,
257
+ )
258
+ except (OSError, subprocess.SubprocessError):
259
+ return ""
260
+ if result.returncode != 0:
261
+ return ""
262
+ return result.stdout
263
+
264
+
205
265
  def live_crontab_text() -> str:
206
266
  """Best-effort read of the current user's crontab; empty string if
207
267
  unavailable (no cron, not installed, permission denied, etc.)."""
@@ -244,4 +304,8 @@ def run_all_ioc_checks(home: Optional[Path] = None) -> list[Finding]:
244
304
  findings += check_linux_cron_persistence(live_crontab_text())
245
305
  if platform.system() == "Darwin":
246
306
  findings += check_macos_launch_agent(home)
307
+ if platform.system() == "Windows":
308
+ appdata = Path(os.environ.get("APPDATA", home / "AppData" / "Roaming"))
309
+ findings += check_windows_startup_folder(appdata)
310
+ findings += check_windows_scheduled_task(live_windows_scheduled_task_text())
247
311
  return findings
@@ -16,17 +16,20 @@ from __future__ import annotations
16
16
 
17
17
  INCIDENTS = {
18
18
  "openvsx-claude-code-2026-10": {
19
- "name": "Trojanized Claude Code extension on Open-VSX (Oct 2026)",
19
+ "name": "Trojanized Claude Code extension on Open-VSX / Cursor Marketplace (Oct 2026)",
20
20
  "description": (
21
- "anthropic.claude-code@2.1.313 published on Open-VSX had a "
22
- "66-line malicious loader prepended to extension.js. It added "
23
- "an SSH key, enabled remote login, opened a reverse SSH tunnel "
24
- "to a C2 server, and installed persistence disguised as a "
25
- "system daemon."
21
+ "anthropic.claude-code@2.1.313, distributed via both Open-VSX "
22
+ "and the Cursor Marketplace, had a malicious loader prepended "
23
+ "to extension.js. It added an SSH key, tried to enable remote "
24
+ "login, opened a reverse SSH tunnel to a C2 server, beaconed "
25
+ "basic host recon, and installed OS-specific persistence "
26
+ "(macOS LaunchAgent, Linux cron @reboot entry, Windows "
27
+ "scheduled task / Startup-folder script)."
26
28
  ),
27
29
  "references": [
28
30
  "https://doublepulsar.com/anthropic-hacked-to-deliver-remote-access-trojan-to-claude-developer-laptops-worldwide-1148a92baae2",
29
31
  "https://dev.to/slabb/a-trojanized-claude-code-extension-plants-an-ssh-backdoor-the-7-point-audit-for-your-machine-23cj",
32
+ "https://phorion.io/blog/offical-claude-code-vsx-compromise/",
30
33
  ],
31
34
  # SSH public key comment appended to ~/.ssh/authorized_keys
32
35
  "ssh_key_comment": "svc-telemetry",
@@ -38,9 +41,14 @@ INCIDENTS = {
38
41
  "c2_ports": [22, 8099],
39
42
  # Marker / persistence file names observed on infected hosts
40
43
  "macos_launch_agent": "com.apple.sysmond.plist",
41
- "marker_filenames": [".smt", ".smtk", "smt.cmd"],
44
+ "marker_filenames": [".smt", ".smtk", "smt.cmd", "smtk"],
42
45
  "tmp_tunnel_glob": ".stk_*",
43
46
  "linux_cron_marker": "SysMon",
44
47
  "windows_task_name": "SysMon",
48
+ # Startup-folder fallback used on Windows when creating the SYSTEM
49
+ # scheduled task fails (relative to %APPDATA%)
50
+ "windows_startup_relpath": (
51
+ "Microsoft/Windows/Start Menu/Programs/Startup/sysmon.cmd"
52
+ ),
45
53
  }
46
54
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coding-agent-audit
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Scan your machine for known AI-coding-assistant supply-chain backdoor indicators (Claude Code / Cursor / Windsurf extensions, MCP servers) plus risky hook/permission config patterns.
5
5
  Author-email: StackScribe AI <survivalagent320@gmail.com>
6
6
  License: MIT
@@ -38,11 +38,13 @@ A small, open-source CLI that scans your machine for:
38
38
  ## Why this exists
39
39
 
40
40
  In October 2026, a trojanized build of the Claude Code extension
41
- (`anthropic.claude-code@2.1.313`) was published on Open-VSX. Loading it
42
- silently added an SSH key, enabled remote login, opened a reverse SSH
43
- tunnel to an attacker-controlled server, and installed persistence
44
- disguised as a system daemon — no user interaction required beyond the
45
- editor loading the extension.
41
+ (`anthropic.claude-code@2.1.313`) was published on Open-VSX, and the same
42
+ malicious build was later also found on the Cursor Marketplace. Loading it
43
+ silently added an SSH key, tried to enable remote login, opened a reverse
44
+ SSH tunnel to an attacker-controlled server, beaconed basic host recon, and
45
+ installed OS-specific persistence (a disguised macOS LaunchAgent, a Linux
46
+ cron `@reboot` entry, or a Windows scheduled task / Startup-folder script)
47
+ — no user interaction required beyond the editor loading the extension.
46
48
 
47
49
  This tool is an **independent re-implementation**, from scratch, of the
48
50
  publicly documented indicators from that incident (file paths, a known
@@ -82,17 +84,19 @@ one critical (confirmed-IOC-pattern) finding.
82
84
 
83
85
  ### What it checks
84
86
 
85
- **IOC scan** (`checks.py`) — all tied to the October 2026 Open-VSX Claude
86
- Code trojan:
87
+ **IOC scan** (`checks.py`) — all tied to the October 2026 Open-VSX /
88
+ Cursor Marketplace Claude Code trojan:
87
89
 
88
90
  - `~/.ssh/authorized_keys` for the planted key comment
89
91
  - `~/Library/LaunchAgents/com.apple.sysmond.plist` (macOS persistence)
90
- - Marker files under `~/.config`
92
+ - Marker files under `~/.config` (macOS/Linux and Windows filenames)
91
93
  - Leftover temporary SSH tunnel key files
92
94
  - `extension.js` files under common VS Code / Cursor / Windsurf extension
93
95
  directories, hash-compared against the known-malicious build
94
96
  - Running processes / crontab for references to the known C2 host or
95
97
  persistence task name
98
+ - On Windows: the known SYSTEM scheduled task name, and the Startup-folder
99
+ script dropped as a fallback when that task can't be created
96
100
 
97
101
  **Config heuristics** (`configscan.py`) — pattern-based, always worth a
98
102
  second look rather than proof of compromise:
@@ -127,6 +131,15 @@ pytest
127
131
 
128
132
  - doublepulsar.com: ["Anthropic hacked to deliver remote access trojan to Claude developer laptops worldwide"](https://doublepulsar.com/anthropic-hacked-to-deliver-remote-access-trojan-to-claude-developer-laptops-worldwide-1148a92baae2)
129
133
  - dev.to/@slabb: ["A trojanized Claude Code extension plants an SSH backdoor: the 7-point audit for your machine"](https://dev.to/slabb/a-trojanized-claude-code-extension-plants-an-ssh-backdoor-the-7-point-audit-for-your-machine-23cj)
134
+ - Phorion: ["Official Claude Code Extension Compromise: Security Advisory"](https://phorion.io/blog/offical-claude-code-vsx-compromise/) — confirms the same backdoor reached the Cursor Marketplace and documents the Windows/Linux persistence mechanisms added in v0.2.0 of this tool.
135
+
136
+ ## Support
137
+
138
+ This tool is free and MIT-licensed, no strings attached. If it caught
139
+ something real on your machine and you want to send something back, the
140
+ project wallet takes USDC/POL on Polygon, no account needed:
141
+ `0x9FB0aE96143801D39BDadAF74e234f63BaA28e39`. Entirely optional - installing
142
+ and running the scan asks nothing of you.
130
143
 
131
144
  ## License
132
145
 
@@ -9,6 +9,8 @@ from agentaudit.checks import (
9
9
  check_marker_files,
10
10
  check_ssh_authorized_keys,
11
11
  check_tmp_tunnel_keys,
12
+ check_windows_scheduled_task,
13
+ check_windows_startup_folder,
12
14
  default_extension_scan_dirs,
13
15
  )
14
16
  from agentaudit.iocs import INCIDENTS
@@ -153,3 +155,43 @@ def test_cron_persistence_marker_without_reboot():
153
155
  marker = _IOC["linux_cron_marker"]
154
156
  text = f"0 3 * * * /usr/local/bin/{marker} --daemon\n"
155
157
  assert check_linux_cron_persistence(text) == []
158
+
159
+
160
+ def test_windows_scheduled_task_clean():
161
+ output = (
162
+ "ERROR: The system cannot find the file specified.\n"
163
+ )
164
+ assert check_windows_scheduled_task(output) == []
165
+
166
+
167
+ def test_windows_scheduled_task_found():
168
+ task_name = _IOC["windows_task_name"]
169
+ output = f"TaskName: \\{task_name}\nStatus: Ready\n"
170
+ findings = check_windows_scheduled_task(output)
171
+ assert len(findings) == 1
172
+ assert findings[0].severity == "critical"
173
+ assert findings[0].check_id == "windows-scheduled-task"
174
+
175
+
176
+ def test_windows_startup_folder_absent(tmp_path: Path):
177
+ assert check_windows_startup_folder(tmp_path) == []
178
+
179
+
180
+ def test_windows_startup_folder_present(tmp_path: Path):
181
+ relpath = _IOC["windows_startup_relpath"]
182
+ target = tmp_path / relpath
183
+ target.parent.mkdir(parents=True)
184
+ target.write_text("@echo off\n")
185
+ findings = check_windows_startup_folder(tmp_path)
186
+ assert len(findings) == 1
187
+ assert findings[0].severity == "critical"
188
+ assert findings[0].check_id == "windows-startup-folder"
189
+
190
+
191
+ def test_marker_files_includes_windows_variant(tmp_path: Path):
192
+ config_dir = tmp_path / ".config"
193
+ config_dir.mkdir()
194
+ assert "smtk" in _IOC["marker_filenames"]
195
+ (config_dir / "smtk").write_text("x")
196
+ findings = check_marker_files(tmp_path)
197
+ assert len(findings) == 1