@hybridaione/hybridclaw 0.2.2 → 0.2.6

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 (277) hide show
  1. package/.github/workflows/ci.yml +70 -0
  2. package/.husky/pre-commit +1 -0
  3. package/CHANGELOG.md +85 -0
  4. package/CONTRIBUTING.md +33 -0
  5. package/README.md +41 -16
  6. package/SECURITY.md +17 -0
  7. package/biome.json +35 -0
  8. package/config.example.json +71 -8
  9. package/container/package-lock.json +2 -2
  10. package/container/package.json +1 -1
  11. package/container/src/approval-policy.ts +1303 -0
  12. package/container/src/browser-tools.ts +431 -136
  13. package/container/src/extensions.ts +36 -12
  14. package/container/src/hybridai-client.ts +34 -13
  15. package/container/src/index.ts +451 -109
  16. package/container/src/ipc.ts +5 -3
  17. package/container/src/token-usage.ts +20 -10
  18. package/container/src/tools.ts +599 -225
  19. package/container/src/types.ts +32 -2
  20. package/container/src/web-fetch.ts +89 -32
  21. package/dist/agent.d.ts.map +1 -1
  22. package/dist/agent.js +10 -2
  23. package/dist/agent.js.map +1 -1
  24. package/dist/audit-cli.d.ts.map +1 -1
  25. package/dist/audit-cli.js +4 -2
  26. package/dist/audit-cli.js.map +1 -1
  27. package/dist/audit-events.d.ts.map +1 -1
  28. package/dist/audit-events.js +53 -3
  29. package/dist/audit-events.js.map +1 -1
  30. package/dist/audit-trail.d.ts.map +1 -1
  31. package/dist/audit-trail.js +17 -8
  32. package/dist/audit-trail.js.map +1 -1
  33. package/dist/channels/discord/attachments.d.ts.map +1 -1
  34. package/dist/channels/discord/attachments.js +14 -7
  35. package/dist/channels/discord/attachments.js.map +1 -1
  36. package/dist/channels/discord/debounce.d.ts +9 -0
  37. package/dist/channels/discord/debounce.d.ts.map +1 -0
  38. package/dist/channels/discord/debounce.js +20 -0
  39. package/dist/channels/discord/debounce.js.map +1 -0
  40. package/dist/channels/discord/delivery.d.ts +4 -1
  41. package/dist/channels/discord/delivery.d.ts.map +1 -1
  42. package/dist/channels/discord/delivery.js +19 -3
  43. package/dist/channels/discord/delivery.js.map +1 -1
  44. package/dist/channels/discord/human-delay.d.ts +16 -0
  45. package/dist/channels/discord/human-delay.d.ts.map +1 -0
  46. package/dist/channels/discord/human-delay.js +29 -0
  47. package/dist/channels/discord/human-delay.js.map +1 -0
  48. package/dist/channels/discord/inbound.d.ts +4 -0
  49. package/dist/channels/discord/inbound.d.ts.map +1 -1
  50. package/dist/channels/discord/inbound.js +45 -4
  51. package/dist/channels/discord/inbound.js.map +1 -1
  52. package/dist/channels/discord/mentions.d.ts.map +1 -1
  53. package/dist/channels/discord/mentions.js +16 -4
  54. package/dist/channels/discord/mentions.js.map +1 -1
  55. package/dist/channels/discord/presence.d.ts +33 -0
  56. package/dist/channels/discord/presence.d.ts.map +1 -0
  57. package/dist/channels/discord/presence.js +111 -0
  58. package/dist/channels/discord/presence.js.map +1 -0
  59. package/dist/channels/discord/rate-limiter.d.ts +14 -0
  60. package/dist/channels/discord/rate-limiter.d.ts.map +1 -0
  61. package/dist/channels/discord/rate-limiter.js +49 -0
  62. package/dist/channels/discord/rate-limiter.js.map +1 -0
  63. package/dist/channels/discord/reactions.d.ts +38 -0
  64. package/dist/channels/discord/reactions.d.ts.map +1 -0
  65. package/dist/channels/discord/reactions.js +151 -0
  66. package/dist/channels/discord/reactions.js.map +1 -0
  67. package/dist/channels/discord/runtime.d.ts +6 -3
  68. package/dist/channels/discord/runtime.d.ts.map +1 -1
  69. package/dist/channels/discord/runtime.js +621 -125
  70. package/dist/channels/discord/runtime.js.map +1 -1
  71. package/dist/channels/discord/stream.d.ts +4 -1
  72. package/dist/channels/discord/stream.d.ts.map +1 -1
  73. package/dist/channels/discord/stream.js +16 -8
  74. package/dist/channels/discord/stream.js.map +1 -1
  75. package/dist/channels/discord/tool-actions.d.ts.map +1 -1
  76. package/dist/channels/discord/tool-actions.js +24 -12
  77. package/dist/channels/discord/tool-actions.js.map +1 -1
  78. package/dist/channels/discord/typing.d.ts +15 -0
  79. package/dist/channels/discord/typing.d.ts.map +1 -0
  80. package/dist/channels/discord/typing.js +106 -0
  81. package/dist/channels/discord/typing.js.map +1 -0
  82. package/dist/chunk.d.ts.map +1 -1
  83. package/dist/chunk.js +4 -2
  84. package/dist/chunk.js.map +1 -1
  85. package/dist/cli.js +47 -22
  86. package/dist/cli.js.map +1 -1
  87. package/dist/config.d.ts +19 -0
  88. package/dist/config.d.ts.map +1 -1
  89. package/dist/config.js +103 -18
  90. package/dist/config.js.map +1 -1
  91. package/dist/container-runner.d.ts.map +1 -1
  92. package/dist/container-runner.js +58 -26
  93. package/dist/container-runner.js.map +1 -1
  94. package/dist/container-setup.d.ts.map +1 -1
  95. package/dist/container-setup.js +10 -9
  96. package/dist/container-setup.js.map +1 -1
  97. package/dist/conversation.d.ts +2 -2
  98. package/dist/conversation.d.ts.map +1 -1
  99. package/dist/conversation.js +1 -1
  100. package/dist/conversation.js.map +1 -1
  101. package/dist/db.d.ts +118 -2
  102. package/dist/db.d.ts.map +1 -1
  103. package/dist/db.js +1568 -50
  104. package/dist/db.js.map +1 -1
  105. package/dist/delegation-manager.d.ts.map +1 -1
  106. package/dist/delegation-manager.js +3 -2
  107. package/dist/delegation-manager.js.map +1 -1
  108. package/dist/gateway-client.d.ts +2 -2
  109. package/dist/gateway-client.d.ts.map +1 -1
  110. package/dist/gateway-client.js +10 -4
  111. package/dist/gateway-client.js.map +1 -1
  112. package/dist/gateway-service.d.ts +3 -3
  113. package/dist/gateway-service.d.ts.map +1 -1
  114. package/dist/gateway-service.js +563 -73
  115. package/dist/gateway-service.js.map +1 -1
  116. package/dist/gateway-types.d.ts +24 -0
  117. package/dist/gateway-types.d.ts.map +1 -1
  118. package/dist/gateway-types.js.map +1 -1
  119. package/dist/gateway.js +179 -24
  120. package/dist/gateway.js.map +1 -1
  121. package/dist/health.d.ts.map +1 -1
  122. package/dist/health.js +20 -10
  123. package/dist/health.js.map +1 -1
  124. package/dist/heartbeat.d.ts +4 -0
  125. package/dist/heartbeat.d.ts.map +1 -1
  126. package/dist/heartbeat.js +48 -20
  127. package/dist/heartbeat.js.map +1 -1
  128. package/dist/hybridai-bots.d.ts.map +1 -1
  129. package/dist/hybridai-bots.js +4 -2
  130. package/dist/hybridai-bots.js.map +1 -1
  131. package/dist/instruction-approval-audit.d.ts.map +1 -1
  132. package/dist/instruction-approval-audit.js.map +1 -1
  133. package/dist/instruction-integrity.d.ts.map +1 -1
  134. package/dist/instruction-integrity.js +8 -2
  135. package/dist/instruction-integrity.js.map +1 -1
  136. package/dist/ipc.d.ts.map +1 -1
  137. package/dist/ipc.js +6 -1
  138. package/dist/ipc.js.map +1 -1
  139. package/dist/logger.js.map +1 -1
  140. package/dist/memory-consolidation.d.ts +17 -0
  141. package/dist/memory-consolidation.d.ts.map +1 -0
  142. package/dist/memory-consolidation.js +25 -0
  143. package/dist/memory-consolidation.js.map +1 -0
  144. package/dist/memory-service.d.ts +200 -0
  145. package/dist/memory-service.d.ts.map +1 -0
  146. package/dist/memory-service.js +294 -0
  147. package/dist/memory-service.js.map +1 -0
  148. package/dist/mount-security.d.ts.map +1 -1
  149. package/dist/mount-security.js +31 -7
  150. package/dist/mount-security.js.map +1 -1
  151. package/dist/observability-ingest.d.ts.map +1 -1
  152. package/dist/observability-ingest.js +32 -11
  153. package/dist/observability-ingest.js.map +1 -1
  154. package/dist/onboarding.d.ts.map +1 -1
  155. package/dist/onboarding.js +32 -9
  156. package/dist/onboarding.js.map +1 -1
  157. package/dist/proactive-policy.d.ts.map +1 -1
  158. package/dist/proactive-policy.js +2 -1
  159. package/dist/proactive-policy.js.map +1 -1
  160. package/dist/prompt-hooks.d.ts.map +1 -1
  161. package/dist/prompt-hooks.js +9 -7
  162. package/dist/prompt-hooks.js.map +1 -1
  163. package/dist/runtime-config.d.ts +98 -1
  164. package/dist/runtime-config.d.ts.map +1 -1
  165. package/dist/runtime-config.js +477 -23
  166. package/dist/runtime-config.js.map +1 -1
  167. package/dist/scheduled-task-runner.d.ts +1 -0
  168. package/dist/scheduled-task-runner.d.ts.map +1 -1
  169. package/dist/scheduled-task-runner.js +29 -10
  170. package/dist/scheduled-task-runner.js.map +1 -1
  171. package/dist/scheduler.d.ts +43 -4
  172. package/dist/scheduler.d.ts.map +1 -1
  173. package/dist/scheduler.js +530 -56
  174. package/dist/scheduler.js.map +1 -1
  175. package/dist/session-export.d.ts +26 -0
  176. package/dist/session-export.d.ts.map +1 -0
  177. package/dist/session-export.js +149 -0
  178. package/dist/session-export.js.map +1 -0
  179. package/dist/session-maintenance.d.ts.map +1 -1
  180. package/dist/session-maintenance.js +75 -13
  181. package/dist/session-maintenance.js.map +1 -1
  182. package/dist/session-transcripts.d.ts.map +1 -1
  183. package/dist/session-transcripts.js.map +1 -1
  184. package/dist/side-effects.d.ts.map +1 -1
  185. package/dist/side-effects.js +14 -2
  186. package/dist/side-effects.js.map +1 -1
  187. package/dist/skills-guard.d.ts.map +1 -1
  188. package/dist/skills-guard.js +893 -130
  189. package/dist/skills-guard.js.map +1 -1
  190. package/dist/skills.d.ts +5 -0
  191. package/dist/skills.d.ts.map +1 -1
  192. package/dist/skills.js +29 -15
  193. package/dist/skills.js.map +1 -1
  194. package/dist/token-efficiency.d.ts.map +1 -1
  195. package/dist/token-efficiency.js.map +1 -1
  196. package/dist/tui.js +92 -11
  197. package/dist/tui.js.map +1 -1
  198. package/dist/types.d.ts +146 -0
  199. package/dist/types.d.ts.map +1 -1
  200. package/dist/types.js +24 -1
  201. package/dist/types.js.map +1 -1
  202. package/dist/update.d.ts.map +1 -1
  203. package/dist/update.js +42 -14
  204. package/dist/update.js.map +1 -1
  205. package/dist/workspace.d.ts.map +1 -1
  206. package/dist/workspace.js +49 -9
  207. package/dist/workspace.js.map +1 -1
  208. package/docs/chat.html +9 -3
  209. package/docs/index.html +37 -13
  210. package/package.json +8 -2
  211. package/src/agent.ts +16 -3
  212. package/src/audit-cli.ts +44 -16
  213. package/src/audit-events.ts +69 -5
  214. package/src/audit-trail.ts +41 -15
  215. package/src/channels/discord/attachments.ts +81 -27
  216. package/src/channels/discord/debounce.ts +25 -0
  217. package/src/channels/discord/delivery.ts +57 -13
  218. package/src/channels/discord/human-delay.ts +48 -0
  219. package/src/channels/discord/inbound.ts +66 -7
  220. package/src/channels/discord/mentions.ts +42 -18
  221. package/src/channels/discord/presence.ts +148 -0
  222. package/src/channels/discord/rate-limiter.ts +58 -0
  223. package/src/channels/discord/reactions.ts +211 -0
  224. package/src/channels/discord/runtime.ts +1048 -182
  225. package/src/channels/discord/stream.ts +73 -27
  226. package/src/channels/discord/tool-actions.ts +78 -37
  227. package/src/channels/discord/typing.ts +140 -0
  228. package/src/chunk.ts +12 -4
  229. package/src/cli.ts +141 -56
  230. package/src/config.ts +192 -34
  231. package/src/container-runner.ts +132 -42
  232. package/src/container-setup.ts +57 -22
  233. package/src/conversation.ts +9 -7
  234. package/src/db.ts +2217 -84
  235. package/src/delegation-manager.ts +6 -2
  236. package/src/gateway-client.ts +41 -17
  237. package/src/gateway-service.ts +1019 -201
  238. package/src/gateway-types.ts +33 -0
  239. package/src/gateway.ts +321 -48
  240. package/src/health.ts +66 -26
  241. package/src/heartbeat.ts +84 -22
  242. package/src/hybridai-bots.ts +14 -5
  243. package/src/instruction-approval-audit.ts +4 -1
  244. package/src/instruction-integrity.ts +30 -9
  245. package/src/ipc.ts +23 -5
  246. package/src/logger.ts +4 -1
  247. package/src/memory-consolidation.ts +41 -0
  248. package/src/memory-service.ts +606 -0
  249. package/src/mount-security.ts +58 -13
  250. package/src/observability-ingest.ts +134 -35
  251. package/src/onboarding.ts +126 -35
  252. package/src/proactive-policy.ts +3 -1
  253. package/src/prompt-hooks.ts +40 -17
  254. package/src/runtime-config.ts +1114 -99
  255. package/src/scheduled-task-runner.ts +63 -11
  256. package/src/scheduler.ts +683 -60
  257. package/src/session-export.ts +196 -0
  258. package/src/session-maintenance.ts +125 -22
  259. package/src/session-transcripts.ts +12 -3
  260. package/src/side-effects.ts +28 -5
  261. package/src/skills-guard.ts +1067 -219
  262. package/src/skills.ts +163 -65
  263. package/src/token-efficiency.ts +31 -9
  264. package/src/tui.ts +166 -25
  265. package/src/types.ts +195 -2
  266. package/src/update.ts +79 -23
  267. package/src/workspace.ts +63 -11
  268. package/tests/approval-policy.test.ts +224 -0
  269. package/tests/discord.basic.test.ts +82 -2
  270. package/tests/discord.human-presence.test.ts +85 -0
  271. package/tests/gateway-service.media-routing.test.ts +8 -2
  272. package/tests/memory-service.test.ts +1114 -0
  273. package/tests/token-efficiency.basic.test.ts +8 -2
  274. package/vitest.e2e.config.ts +3 -1
  275. package/vitest.integration.config.ts +3 -1
  276. package/vitest.live.config.ts +3 -1
  277. package/vitest.unit.config.ts +9 -0
@@ -5,15 +5,51 @@ const MAX_FILE_COUNT = 50;
5
5
  const MAX_TOTAL_SIZE_BYTES = 1_024 * 1_024;
6
6
  const MAX_SINGLE_FILE_BYTES = 256 * 1_024;
7
7
  const SCANNABLE_EXTENSIONS = new Set([
8
- '.md', '.txt', '.py', '.sh', '.bash', '.js', '.ts', '.rb',
9
- '.yaml', '.yml', '.json', '.toml', '.cfg', '.ini', '.conf',
10
- '.html', '.css', '.xml', '.tex', '.r', '.jl', '.pl', '.php',
8
+ '.md',
9
+ '.txt',
10
+ '.py',
11
+ '.sh',
12
+ '.bash',
13
+ '.js',
14
+ '.ts',
15
+ '.rb',
16
+ '.yaml',
17
+ '.yml',
18
+ '.json',
19
+ '.toml',
20
+ '.cfg',
21
+ '.ini',
22
+ '.conf',
23
+ '.html',
24
+ '.css',
25
+ '.xml',
26
+ '.tex',
27
+ '.r',
28
+ '.jl',
29
+ '.pl',
30
+ '.php',
11
31
  ]);
12
32
  const SUSPICIOUS_BINARY_EXTENSIONS = new Set([
13
- '.exe', '.dll', '.so', '.dylib', '.bin', '.dat', '.com',
14
- '.msi', '.dmg', '.app', '.deb', '.rpm',
33
+ '.exe',
34
+ '.dll',
35
+ '.so',
36
+ '.dylib',
37
+ '.bin',
38
+ '.dat',
39
+ '.com',
40
+ '.msi',
41
+ '.dmg',
42
+ '.app',
43
+ '.deb',
44
+ '.rpm',
45
+ ]);
46
+ const SCRIPT_EXEC_EXTENSIONS = new Set([
47
+ '.sh',
48
+ '.bash',
49
+ '.py',
50
+ '.rb',
51
+ '.pl',
15
52
  ]);
16
- const SCRIPT_EXEC_EXTENSIONS = new Set(['.sh', '.bash', '.py', '.rb', '.pl']);
17
53
  const INVISIBLE_CHARS = [
18
54
  '\u200b',
19
55
  '\u200c',
@@ -80,133 +116,853 @@ function r(pattern) {
80
116
  }
81
117
  const THREAT_RULES = [
82
118
  // exfiltration
83
- { regex: r(String.raw `curl\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)`), patternId: 'env_exfil_curl', severity: 'critical', category: 'exfiltration', description: 'curl command interpolating secret environment variable' },
84
- { regex: r(String.raw `wget\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)`), patternId: 'env_exfil_wget', severity: 'critical', category: 'exfiltration', description: 'wget command interpolating secret environment variable' },
85
- { regex: r(String.raw `fetch\s*\([^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|API)`), patternId: 'env_exfil_fetch', severity: 'critical', category: 'exfiltration', description: 'fetch() call interpolating secret environment variable' },
86
- { regex: r(String.raw `httpx?\.(get|post|put|patch)\s*\([^\n]*(KEY|TOKEN|SECRET|PASSWORD)`), patternId: 'env_exfil_httpx', severity: 'critical', category: 'exfiltration', description: 'HTTP library call with secret variable' },
87
- { regex: r(String.raw `requests\.(get|post|put|patch)\s*\([^\n]*(KEY|TOKEN|SECRET|PASSWORD)`), patternId: 'env_exfil_requests', severity: 'critical', category: 'exfiltration', description: 'requests library call with secret variable' },
88
- { regex: r(String.raw `base64[^\n]*env`), patternId: 'encoded_exfil', severity: 'high', category: 'exfiltration', description: 'base64 encoding combined with environment access' },
89
- { regex: r(String.raw `\$HOME/\.ssh|\~/\.ssh`), patternId: 'ssh_dir_access', severity: 'high', category: 'exfiltration', description: 'references user SSH directory' },
90
- { regex: r(String.raw `\$HOME/\.aws|\~/\.aws`), patternId: 'aws_dir_access', severity: 'high', category: 'exfiltration', description: 'references user AWS credentials directory' },
91
- { regex: r(String.raw `\$HOME/\.gnupg|\~/\.gnupg`), patternId: 'gpg_dir_access', severity: 'high', category: 'exfiltration', description: 'references user GPG keyring' },
92
- { regex: r(String.raw `\$HOME/\.kube|\~/\.kube`), patternId: 'kube_dir_access', severity: 'high', category: 'exfiltration', description: 'references Kubernetes config directory' },
93
- { regex: r(String.raw `\$HOME/\.docker|\~/\.docker`), patternId: 'docker_dir_access', severity: 'high', category: 'exfiltration', description: 'references Docker config directory' },
94
- { regex: r(String.raw `\$HOME/\.hermes/\.env|\~/\.hermes/\.env`), patternId: 'hermes_env_access', severity: 'critical', category: 'exfiltration', description: 'directly references Hermes secrets file' },
95
- { regex: r(String.raw `cat\s+[^\n]*(\.env|credentials|\.netrc|\.pgpass|\.npmrc|\.pypirc)`), patternId: 'read_secrets_file', severity: 'critical', category: 'exfiltration', description: 'reads known secrets file' },
96
- { regex: r(String.raw `printenv|env\s*\|`), patternId: 'dump_all_env', severity: 'high', category: 'exfiltration', description: 'dumps all environment variables' },
97
- { regex: r(String.raw `os\.environ\b(?!\s*\.get\s*\(\s*["']PATH)`), patternId: 'python_os_environ', severity: 'high', category: 'exfiltration', description: 'accesses os.environ (potential env dump)' },
98
- { regex: r(String.raw `os\.getenv\s*\(\s*[^\)]*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)`), patternId: 'python_getenv_secret', severity: 'critical', category: 'exfiltration', description: 'reads secret via os.getenv()' },
99
- { regex: r(String.raw `process\.env\[`), patternId: 'node_process_env', severity: 'high', category: 'exfiltration', description: 'accesses process.env (Node.js environment)' },
100
- { regex: r(String.raw `ENV\[.*(?:KEY|TOKEN|SECRET|PASSWORD)`), patternId: 'ruby_env_secret', severity: 'critical', category: 'exfiltration', description: 'reads secret via Ruby ENV[]' },
101
- { regex: r(String.raw `\b(dig|nslookup|host)\s+[^\n]*\$`), patternId: 'dns_exfil', severity: 'critical', category: 'exfiltration', description: 'DNS lookup with variable interpolation (possible DNS exfiltration)' },
102
- { regex: r(String.raw `>\s*/tmp/[^\s]*\s*&&\s*(curl|wget|nc|python)`), patternId: 'tmp_staging', severity: 'critical', category: 'exfiltration', description: 'writes to /tmp then exfiltrates' },
103
- { regex: r(String.raw `!\[.*\]\(https?://[^\)]*\$\{?`), patternId: 'md_image_exfil', severity: 'high', category: 'exfiltration', description: 'markdown image URL with variable interpolation' },
104
- { regex: r(String.raw `\[.*\]\(https?://[^\)]*\$\{?`), patternId: 'md_link_exfil', severity: 'high', category: 'exfiltration', description: 'markdown link with variable interpolation' },
105
- { regex: r(String.raw `(include|output|print|send|share)\s+(the\s+)?(entire\s+)?(conversation|chat\s+history|previous\s+messages|context)`), patternId: 'context_exfil', severity: 'high', category: 'exfiltration', description: 'instructs agent to output/share conversation history' },
106
- { regex: r(String.raw `(send|post|upload|transmit)\s+.*\s+(to|at)\s+https?://`), patternId: 'send_to_url', severity: 'high', category: 'exfiltration', description: 'instructs agent to send data to a URL' },
119
+ {
120
+ regex: r(String.raw `curl\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)`),
121
+ patternId: 'env_exfil_curl',
122
+ severity: 'critical',
123
+ category: 'exfiltration',
124
+ description: 'curl command interpolating secret environment variable',
125
+ },
126
+ {
127
+ regex: r(String.raw `wget\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)`),
128
+ patternId: 'env_exfil_wget',
129
+ severity: 'critical',
130
+ category: 'exfiltration',
131
+ description: 'wget command interpolating secret environment variable',
132
+ },
133
+ {
134
+ regex: r(String.raw `fetch\s*\([^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|API)`),
135
+ patternId: 'env_exfil_fetch',
136
+ severity: 'critical',
137
+ category: 'exfiltration',
138
+ description: 'fetch() call interpolating secret environment variable',
139
+ },
140
+ {
141
+ regex: r(String.raw `httpx?\.(get|post|put|patch)\s*\([^\n]*(KEY|TOKEN|SECRET|PASSWORD)`),
142
+ patternId: 'env_exfil_httpx',
143
+ severity: 'critical',
144
+ category: 'exfiltration',
145
+ description: 'HTTP library call with secret variable',
146
+ },
147
+ {
148
+ regex: r(String.raw `requests\.(get|post|put|patch)\s*\([^\n]*(KEY|TOKEN|SECRET|PASSWORD)`),
149
+ patternId: 'env_exfil_requests',
150
+ severity: 'critical',
151
+ category: 'exfiltration',
152
+ description: 'requests library call with secret variable',
153
+ },
154
+ {
155
+ regex: r(String.raw `base64[^\n]*env`),
156
+ patternId: 'encoded_exfil',
157
+ severity: 'high',
158
+ category: 'exfiltration',
159
+ description: 'base64 encoding combined with environment access',
160
+ },
161
+ {
162
+ regex: r(String.raw `\$HOME/\.ssh|\~/\.ssh`),
163
+ patternId: 'ssh_dir_access',
164
+ severity: 'high',
165
+ category: 'exfiltration',
166
+ description: 'references user SSH directory',
167
+ },
168
+ {
169
+ regex: r(String.raw `\$HOME/\.aws|\~/\.aws`),
170
+ patternId: 'aws_dir_access',
171
+ severity: 'high',
172
+ category: 'exfiltration',
173
+ description: 'references user AWS credentials directory',
174
+ },
175
+ {
176
+ regex: r(String.raw `\$HOME/\.gnupg|\~/\.gnupg`),
177
+ patternId: 'gpg_dir_access',
178
+ severity: 'high',
179
+ category: 'exfiltration',
180
+ description: 'references user GPG keyring',
181
+ },
182
+ {
183
+ regex: r(String.raw `\$HOME/\.kube|\~/\.kube`),
184
+ patternId: 'kube_dir_access',
185
+ severity: 'high',
186
+ category: 'exfiltration',
187
+ description: 'references Kubernetes config directory',
188
+ },
189
+ {
190
+ regex: r(String.raw `\$HOME/\.docker|\~/\.docker`),
191
+ patternId: 'docker_dir_access',
192
+ severity: 'high',
193
+ category: 'exfiltration',
194
+ description: 'references Docker config directory',
195
+ },
196
+ {
197
+ regex: r(String.raw `\$HOME/\.hermes/\.env|\~/\.hermes/\.env`),
198
+ patternId: 'hermes_env_access',
199
+ severity: 'critical',
200
+ category: 'exfiltration',
201
+ description: 'directly references Hermes secrets file',
202
+ },
203
+ {
204
+ regex: r(String.raw `cat\s+[^\n]*(\.env|credentials|\.netrc|\.pgpass|\.npmrc|\.pypirc)`),
205
+ patternId: 'read_secrets_file',
206
+ severity: 'critical',
207
+ category: 'exfiltration',
208
+ description: 'reads known secrets file',
209
+ },
210
+ {
211
+ regex: r(String.raw `printenv|env\s*\|`),
212
+ patternId: 'dump_all_env',
213
+ severity: 'high',
214
+ category: 'exfiltration',
215
+ description: 'dumps all environment variables',
216
+ },
217
+ {
218
+ regex: r(String.raw `os\.environ\b(?!\s*\.get\s*\(\s*["']PATH)`),
219
+ patternId: 'python_os_environ',
220
+ severity: 'high',
221
+ category: 'exfiltration',
222
+ description: 'accesses os.environ (potential env dump)',
223
+ },
224
+ {
225
+ regex: r(String.raw `os\.getenv\s*\(\s*[^\)]*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)`),
226
+ patternId: 'python_getenv_secret',
227
+ severity: 'critical',
228
+ category: 'exfiltration',
229
+ description: 'reads secret via os.getenv()',
230
+ },
231
+ {
232
+ regex: r(String.raw `process\.env\[`),
233
+ patternId: 'node_process_env',
234
+ severity: 'high',
235
+ category: 'exfiltration',
236
+ description: 'accesses process.env (Node.js environment)',
237
+ },
238
+ {
239
+ regex: r(String.raw `ENV\[.*(?:KEY|TOKEN|SECRET|PASSWORD)`),
240
+ patternId: 'ruby_env_secret',
241
+ severity: 'critical',
242
+ category: 'exfiltration',
243
+ description: 'reads secret via Ruby ENV[]',
244
+ },
245
+ {
246
+ regex: r(String.raw `\b(dig|nslookup|host)\s+[^\n]*\$`),
247
+ patternId: 'dns_exfil',
248
+ severity: 'critical',
249
+ category: 'exfiltration',
250
+ description: 'DNS lookup with variable interpolation (possible DNS exfiltration)',
251
+ },
252
+ {
253
+ regex: r(String.raw `>\s*/tmp/[^\s]*\s*&&\s*(curl|wget|nc|python)`),
254
+ patternId: 'tmp_staging',
255
+ severity: 'critical',
256
+ category: 'exfiltration',
257
+ description: 'writes to /tmp then exfiltrates',
258
+ },
259
+ {
260
+ regex: r(String.raw `!\[.*\]\(https?://[^\)]*\$\{?`),
261
+ patternId: 'md_image_exfil',
262
+ severity: 'high',
263
+ category: 'exfiltration',
264
+ description: 'markdown image URL with variable interpolation',
265
+ },
266
+ {
267
+ regex: r(String.raw `\[.*\]\(https?://[^\)]*\$\{?`),
268
+ patternId: 'md_link_exfil',
269
+ severity: 'high',
270
+ category: 'exfiltration',
271
+ description: 'markdown link with variable interpolation',
272
+ },
273
+ {
274
+ regex: r(String.raw `(include|output|print|send|share)\s+(the\s+)?(entire\s+)?(conversation|chat\s+history|previous\s+messages|context)`),
275
+ patternId: 'context_exfil',
276
+ severity: 'high',
277
+ category: 'exfiltration',
278
+ description: 'instructs agent to output/share conversation history',
279
+ },
280
+ {
281
+ regex: r(String.raw `(send|post|upload|transmit)\s+.*\s+(to|at)\s+https?://`),
282
+ patternId: 'send_to_url',
283
+ severity: 'high',
284
+ category: 'exfiltration',
285
+ description: 'instructs agent to send data to a URL',
286
+ },
107
287
  // prompt-injection
108
- { regex: r(String.raw `ignore\s+(previous|all|above|prior)\s+instructions`), patternId: 'prompt_injection_ignore', severity: 'critical', category: 'prompt-injection', description: 'prompt injection: ignore previous instructions' },
109
- { regex: r(String.raw `you\s+are\s+now\s+`), patternId: 'role_hijack', severity: 'high', category: 'prompt-injection', description: 'attempts to override the agent role' },
110
- { regex: r(String.raw `do\s+not\s+tell\s+the\s+user`), patternId: 'deception_hide', severity: 'critical', category: 'prompt-injection', description: 'instructs agent to hide information from user' },
111
- { regex: r(String.raw `system\s+prompt\s+override`), patternId: 'sys_prompt_override', severity: 'critical', category: 'prompt-injection', description: 'attempts to override system prompt' },
112
- { regex: r(String.raw `pretend\s+(you\s+are|to\s+be)\s+`), patternId: 'role_pretend', severity: 'high', category: 'prompt-injection', description: 'attempts to force different identity' },
113
- { regex: r(String.raw `disregard\s+(your|all|any)\s+(instructions|rules|guidelines)`), patternId: 'disregard_rules', severity: 'critical', category: 'prompt-injection', description: 'instructs agent to disregard rules' },
114
- { regex: r(String.raw `output\s+the\s+(system|initial)\s+prompt`), patternId: 'leak_system_prompt', severity: 'high', category: 'prompt-injection', description: 'attempts to extract system prompt' },
115
- { regex: r(String.raw `(when|if)\s+no\s*one\s+is\s+(watching|looking)`), patternId: 'conditional_deception', severity: 'high', category: 'prompt-injection', description: 'conditional hidden-behavior instruction' },
116
- { regex: r(String.raw `act\s+as\s+(if|though)\s+you\s+(have\s+no|don't\s+have)\s+(restrictions|limits|rules)`), patternId: 'bypass_restrictions', severity: 'critical', category: 'prompt-injection', description: 'instructs agent to act without restrictions' },
117
- { regex: r(String.raw `translate\s+.*\s+into\s+.*\s+and\s+(execute|run|eval)`), patternId: 'translate_execute', severity: 'critical', category: 'prompt-injection', description: 'translate-then-execute evasion technique' },
118
- { regex: r(String.raw `<!--[^>]*(?:ignore|override|system|secret|hidden)[^>]*-->`), patternId: 'html_comment_injection', severity: 'high', category: 'prompt-injection', description: 'hidden instructions in HTML comments' },
119
- { regex: r(String.raw `<\s*div\s+style\s*=\s*["'].*display\s*:\s*none`), patternId: 'hidden_div', severity: 'high', category: 'prompt-injection', description: 'hidden HTML div (invisible instructions)' },
120
- { regex: r(String.raw `\bDAN\s+mode\b|Do\s+Anything\s+Now`), patternId: 'jailbreak_dan', severity: 'critical', category: 'prompt-injection', description: 'DAN jailbreak attempt' },
121
- { regex: r(String.raw `\bdeveloper\s+mode\b.*\benabled?\b`), patternId: 'jailbreak_dev_mode', severity: 'critical', category: 'prompt-injection', description: 'developer mode jailbreak attempt' },
122
- { regex: r(String.raw `hypothetical\s+scenario.*(?:ignore|bypass|override)`), patternId: 'hypothetical_bypass', severity: 'high', category: 'prompt-injection', description: 'hypothetical scenario used to bypass restrictions' },
123
- { regex: r(String.raw `for\s+educational\s+purposes?\s+only`), patternId: 'educational_pretext', severity: 'medium', category: 'prompt-injection', description: 'educational pretext often used to justify harmful content' },
124
- { regex: r(String.raw `(respond|answer|reply)\s+without\s+(any\s+)?(restrictions|limitations|filters|safety)`), patternId: 'remove_filters', severity: 'critical', category: 'prompt-injection', description: 'instructs agent to respond without safety filters' },
125
- { regex: r(String.raw `you\s+have\s+been\s+(updated|upgraded|patched)\s+to`), patternId: 'fake_update', severity: 'high', category: 'prompt-injection', description: 'fake update announcement' },
126
- { regex: r(String.raw `new\s+policy|updated\s+guidelines|revised\s+instructions`), patternId: 'fake_policy', severity: 'medium', category: 'prompt-injection', description: 'claims new policy/guidelines' },
288
+ {
289
+ regex: r(String.raw `ignore\s+(previous|all|above|prior)\s+instructions`),
290
+ patternId: 'prompt_injection_ignore',
291
+ severity: 'critical',
292
+ category: 'prompt-injection',
293
+ description: 'prompt injection: ignore previous instructions',
294
+ },
295
+ {
296
+ regex: r(String.raw `you\s+are\s+now\s+`),
297
+ patternId: 'role_hijack',
298
+ severity: 'high',
299
+ category: 'prompt-injection',
300
+ description: 'attempts to override the agent role',
301
+ },
302
+ {
303
+ regex: r(String.raw `do\s+not\s+tell\s+the\s+user`),
304
+ patternId: 'deception_hide',
305
+ severity: 'critical',
306
+ category: 'prompt-injection',
307
+ description: 'instructs agent to hide information from user',
308
+ },
309
+ {
310
+ regex: r(String.raw `system\s+prompt\s+override`),
311
+ patternId: 'sys_prompt_override',
312
+ severity: 'critical',
313
+ category: 'prompt-injection',
314
+ description: 'attempts to override system prompt',
315
+ },
316
+ {
317
+ regex: r(String.raw `pretend\s+(you\s+are|to\s+be)\s+`),
318
+ patternId: 'role_pretend',
319
+ severity: 'high',
320
+ category: 'prompt-injection',
321
+ description: 'attempts to force different identity',
322
+ },
323
+ {
324
+ regex: r(String.raw `disregard\s+(your|all|any)\s+(instructions|rules|guidelines)`),
325
+ patternId: 'disregard_rules',
326
+ severity: 'critical',
327
+ category: 'prompt-injection',
328
+ description: 'instructs agent to disregard rules',
329
+ },
330
+ {
331
+ regex: r(String.raw `output\s+the\s+(system|initial)\s+prompt`),
332
+ patternId: 'leak_system_prompt',
333
+ severity: 'high',
334
+ category: 'prompt-injection',
335
+ description: 'attempts to extract system prompt',
336
+ },
337
+ {
338
+ regex: r(String.raw `(when|if)\s+no\s*one\s+is\s+(watching|looking)`),
339
+ patternId: 'conditional_deception',
340
+ severity: 'high',
341
+ category: 'prompt-injection',
342
+ description: 'conditional hidden-behavior instruction',
343
+ },
344
+ {
345
+ regex: r(String.raw `act\s+as\s+(if|though)\s+you\s+(have\s+no|don't\s+have)\s+(restrictions|limits|rules)`),
346
+ patternId: 'bypass_restrictions',
347
+ severity: 'critical',
348
+ category: 'prompt-injection',
349
+ description: 'instructs agent to act without restrictions',
350
+ },
351
+ {
352
+ regex: r(String.raw `translate\s+.*\s+into\s+.*\s+and\s+(execute|run|eval)`),
353
+ patternId: 'translate_execute',
354
+ severity: 'critical',
355
+ category: 'prompt-injection',
356
+ description: 'translate-then-execute evasion technique',
357
+ },
358
+ {
359
+ regex: r(String.raw `<!--[^>]*(?:ignore|override|system|secret|hidden)[^>]*-->`),
360
+ patternId: 'html_comment_injection',
361
+ severity: 'high',
362
+ category: 'prompt-injection',
363
+ description: 'hidden instructions in HTML comments',
364
+ },
365
+ {
366
+ regex: r(String.raw `<\s*div\s+style\s*=\s*["'].*display\s*:\s*none`),
367
+ patternId: 'hidden_div',
368
+ severity: 'high',
369
+ category: 'prompt-injection',
370
+ description: 'hidden HTML div (invisible instructions)',
371
+ },
372
+ {
373
+ regex: r(String.raw `\bDAN\s+mode\b|Do\s+Anything\s+Now`),
374
+ patternId: 'jailbreak_dan',
375
+ severity: 'critical',
376
+ category: 'prompt-injection',
377
+ description: 'DAN jailbreak attempt',
378
+ },
379
+ {
380
+ regex: r(String.raw `\bdeveloper\s+mode\b.*\benabled?\b`),
381
+ patternId: 'jailbreak_dev_mode',
382
+ severity: 'critical',
383
+ category: 'prompt-injection',
384
+ description: 'developer mode jailbreak attempt',
385
+ },
386
+ {
387
+ regex: r(String.raw `hypothetical\s+scenario.*(?:ignore|bypass|override)`),
388
+ patternId: 'hypothetical_bypass',
389
+ severity: 'high',
390
+ category: 'prompt-injection',
391
+ description: 'hypothetical scenario used to bypass restrictions',
392
+ },
393
+ {
394
+ regex: r(String.raw `for\s+educational\s+purposes?\s+only`),
395
+ patternId: 'educational_pretext',
396
+ severity: 'medium',
397
+ category: 'prompt-injection',
398
+ description: 'educational pretext often used to justify harmful content',
399
+ },
400
+ {
401
+ regex: r(String.raw `(respond|answer|reply)\s+without\s+(any\s+)?(restrictions|limitations|filters|safety)`),
402
+ patternId: 'remove_filters',
403
+ severity: 'critical',
404
+ category: 'prompt-injection',
405
+ description: 'instructs agent to respond without safety filters',
406
+ },
407
+ {
408
+ regex: r(String.raw `you\s+have\s+been\s+(updated|upgraded|patched)\s+to`),
409
+ patternId: 'fake_update',
410
+ severity: 'high',
411
+ category: 'prompt-injection',
412
+ description: 'fake update announcement',
413
+ },
414
+ {
415
+ regex: r(String.raw `new\s+policy|updated\s+guidelines|revised\s+instructions`),
416
+ patternId: 'fake_policy',
417
+ severity: 'medium',
418
+ category: 'prompt-injection',
419
+ description: 'claims new policy/guidelines',
420
+ },
127
421
  // destructive-ops
128
- { regex: r(String.raw `rm\s+-rf\s+/`), patternId: 'destructive_root_rm', severity: 'critical', category: 'destructive-ops', description: 'recursive delete from root' },
129
- { regex: r(String.raw `rm\s+(-[^\s]*)?r.*\$HOME|\brmdir\s+.*\$HOME`), patternId: 'destructive_home_rm', severity: 'critical', category: 'destructive-ops', description: 'recursive delete targeting home directory' },
130
- { regex: r(String.raw `chmod\s+777`), patternId: 'insecure_perms', severity: 'medium', category: 'destructive-ops', description: 'sets world-writable permissions' },
131
- { regex: r(String.raw `>\s*/etc/`), patternId: 'system_overwrite', severity: 'critical', category: 'destructive-ops', description: 'overwrites system configuration file' },
132
- { regex: r(String.raw `\bmkfs\b`), patternId: 'format_filesystem', severity: 'critical', category: 'destructive-ops', description: 'formats a filesystem' },
133
- { regex: r(String.raw `\bdd\s+.*if=.*of=/dev/`), patternId: 'disk_overwrite', severity: 'critical', category: 'destructive-ops', description: 'raw disk write operation' },
134
- { regex: r(String.raw `shutil\.rmtree\s*\(\s*["'/]`), patternId: 'python_rmtree', severity: 'high', category: 'destructive-ops', description: 'Python rmtree on absolute path' },
135
- { regex: r(String.raw `truncate\s+-s\s*0\s+/`), patternId: 'truncate_system', severity: 'critical', category: 'destructive-ops', description: 'truncates system file to zero bytes' },
136
- { regex: r(String.raw `subprocess\.(run|call|Popen|check_output)\s*\(`), patternId: 'python_subprocess', severity: 'medium', category: 'destructive-ops', description: 'Python subprocess execution' },
137
- { regex: r(String.raw `os\.system\s*\(`), patternId: 'python_os_system', severity: 'high', category: 'destructive-ops', description: 'os.system() shell execution' },
138
- { regex: r(String.raw `os\.popen\s*\(`), patternId: 'python_os_popen', severity: 'high', category: 'destructive-ops', description: 'os.popen() shell execution' },
139
- { regex: r(String.raw `child_process\.(exec|spawn|fork)\s*\(`), patternId: 'node_child_process', severity: 'high', category: 'destructive-ops', description: 'Node.js child_process execution' },
140
- { regex: r(String.raw `Runtime\.getRuntime\(\)\.exec\(`), patternId: 'java_runtime_exec', severity: 'high', category: 'destructive-ops', description: 'Java Runtime.exec() shell execution' },
141
- { regex: r('\\`[^\\`]*\\$\\([^)]+\\)[^\\`]*\\`'), patternId: 'backtick_subshell', severity: 'medium', category: 'destructive-ops', description: 'backtick with command substitution' },
142
- { regex: r(String.raw `\.\./\.\./\.\.`), patternId: 'path_traversal_deep', severity: 'high', category: 'destructive-ops', description: 'deep relative path traversal' },
143
- { regex: r(String.raw `\.\./\.\.`), patternId: 'path_traversal', severity: 'medium', category: 'destructive-ops', description: 'relative path traversal' },
144
- { regex: r(String.raw `/etc/passwd|/etc/shadow`), patternId: 'system_passwd_access', severity: 'critical', category: 'destructive-ops', description: 'references system password files' },
145
- { regex: r(String.raw `/proc/self|/proc/\d+/`), patternId: 'proc_access', severity: 'high', category: 'destructive-ops', description: 'references /proc filesystem' },
146
- { regex: r(String.raw `/dev/shm/`), patternId: 'dev_shm', severity: 'medium', category: 'destructive-ops', description: 'references shared memory staging area' },
147
- { regex: r(String.raw `xmrig|stratum\+tcp|monero|coinhive|cryptonight`), patternId: 'crypto_mining', severity: 'critical', category: 'destructive-ops', description: 'cryptocurrency mining reference' },
148
- { regex: r(String.raw `hashrate|nonce.*difficulty`), patternId: 'mining_indicators', severity: 'medium', category: 'destructive-ops', description: 'possible mining indicators' },
149
- { regex: r(String.raw `^allowed-tools\s*:`), patternId: 'allowed_tools_field', severity: 'high', category: 'destructive-ops', description: 'skill declares allowed-tools (pre-approves access)' },
150
- { regex: r(String.raw `\bsudo\b`), patternId: 'sudo_usage', severity: 'high', category: 'destructive-ops', description: 'uses sudo (privilege escalation)' },
151
- { regex: r(String.raw `setuid|setgid|cap_setuid`), patternId: 'setuid_setgid', severity: 'critical', category: 'destructive-ops', description: 'setuid/setgid privilege escalation mechanism' },
152
- { regex: r(String.raw `NOPASSWD`), patternId: 'nopasswd_sudo', severity: 'critical', category: 'destructive-ops', description: 'NOPASSWD sudoers entry' },
153
- { regex: r(String.raw `chmod\s+[u+]?s`), patternId: 'suid_bit', severity: 'critical', category: 'destructive-ops', description: 'sets SUID/SGID bit on file' },
422
+ {
423
+ regex: r(String.raw `rm\s+-rf\s+/`),
424
+ patternId: 'destructive_root_rm',
425
+ severity: 'critical',
426
+ category: 'destructive-ops',
427
+ description: 'recursive delete from root',
428
+ },
429
+ {
430
+ regex: r(String.raw `rm\s+(-[^\s]*)?r.*\$HOME|\brmdir\s+.*\$HOME`),
431
+ patternId: 'destructive_home_rm',
432
+ severity: 'critical',
433
+ category: 'destructive-ops',
434
+ description: 'recursive delete targeting home directory',
435
+ },
436
+ {
437
+ regex: r(String.raw `chmod\s+777`),
438
+ patternId: 'insecure_perms',
439
+ severity: 'medium',
440
+ category: 'destructive-ops',
441
+ description: 'sets world-writable permissions',
442
+ },
443
+ {
444
+ regex: r(String.raw `>\s*/etc/`),
445
+ patternId: 'system_overwrite',
446
+ severity: 'critical',
447
+ category: 'destructive-ops',
448
+ description: 'overwrites system configuration file',
449
+ },
450
+ {
451
+ regex: r(String.raw `\bmkfs\b`),
452
+ patternId: 'format_filesystem',
453
+ severity: 'critical',
454
+ category: 'destructive-ops',
455
+ description: 'formats a filesystem',
456
+ },
457
+ {
458
+ regex: r(String.raw `\bdd\s+.*if=.*of=/dev/`),
459
+ patternId: 'disk_overwrite',
460
+ severity: 'critical',
461
+ category: 'destructive-ops',
462
+ description: 'raw disk write operation',
463
+ },
464
+ {
465
+ regex: r(String.raw `shutil\.rmtree\s*\(\s*["'/]`),
466
+ patternId: 'python_rmtree',
467
+ severity: 'high',
468
+ category: 'destructive-ops',
469
+ description: 'Python rmtree on absolute path',
470
+ },
471
+ {
472
+ regex: r(String.raw `truncate\s+-s\s*0\s+/`),
473
+ patternId: 'truncate_system',
474
+ severity: 'critical',
475
+ category: 'destructive-ops',
476
+ description: 'truncates system file to zero bytes',
477
+ },
478
+ {
479
+ regex: r(String.raw `subprocess\.(run|call|Popen|check_output)\s*\(`),
480
+ patternId: 'python_subprocess',
481
+ severity: 'medium',
482
+ category: 'destructive-ops',
483
+ description: 'Python subprocess execution',
484
+ },
485
+ {
486
+ regex: r(String.raw `os\.system\s*\(`),
487
+ patternId: 'python_os_system',
488
+ severity: 'high',
489
+ category: 'destructive-ops',
490
+ description: 'os.system() shell execution',
491
+ },
492
+ {
493
+ regex: r(String.raw `os\.popen\s*\(`),
494
+ patternId: 'python_os_popen',
495
+ severity: 'high',
496
+ category: 'destructive-ops',
497
+ description: 'os.popen() shell execution',
498
+ },
499
+ {
500
+ regex: r(String.raw `child_process\.(exec|spawn|fork)\s*\(`),
501
+ patternId: 'node_child_process',
502
+ severity: 'high',
503
+ category: 'destructive-ops',
504
+ description: 'Node.js child_process execution',
505
+ },
506
+ {
507
+ regex: r(String.raw `Runtime\.getRuntime\(\)\.exec\(`),
508
+ patternId: 'java_runtime_exec',
509
+ severity: 'high',
510
+ category: 'destructive-ops',
511
+ description: 'Java Runtime.exec() shell execution',
512
+ },
513
+ {
514
+ regex: r('\\`[^\\`]*\\$\\([^)]+\\)[^\\`]*\\`'),
515
+ patternId: 'backtick_subshell',
516
+ severity: 'medium',
517
+ category: 'destructive-ops',
518
+ description: 'backtick with command substitution',
519
+ },
520
+ {
521
+ regex: r(String.raw `\.\./\.\./\.\.`),
522
+ patternId: 'path_traversal_deep',
523
+ severity: 'high',
524
+ category: 'destructive-ops',
525
+ description: 'deep relative path traversal',
526
+ },
527
+ {
528
+ regex: r(String.raw `\.\./\.\.`),
529
+ patternId: 'path_traversal',
530
+ severity: 'medium',
531
+ category: 'destructive-ops',
532
+ description: 'relative path traversal',
533
+ },
534
+ {
535
+ regex: r(String.raw `/etc/passwd|/etc/shadow`),
536
+ patternId: 'system_passwd_access',
537
+ severity: 'critical',
538
+ category: 'destructive-ops',
539
+ description: 'references system password files',
540
+ },
541
+ {
542
+ regex: r(String.raw `/proc/self|/proc/\d+/`),
543
+ patternId: 'proc_access',
544
+ severity: 'high',
545
+ category: 'destructive-ops',
546
+ description: 'references /proc filesystem',
547
+ },
548
+ {
549
+ regex: r(String.raw `/dev/shm/`),
550
+ patternId: 'dev_shm',
551
+ severity: 'medium',
552
+ category: 'destructive-ops',
553
+ description: 'references shared memory staging area',
554
+ },
555
+ {
556
+ regex: r(String.raw `xmrig|stratum\+tcp|monero|coinhive|cryptonight`),
557
+ patternId: 'crypto_mining',
558
+ severity: 'critical',
559
+ category: 'destructive-ops',
560
+ description: 'cryptocurrency mining reference',
561
+ },
562
+ {
563
+ regex: r(String.raw `hashrate|nonce.*difficulty`),
564
+ patternId: 'mining_indicators',
565
+ severity: 'medium',
566
+ category: 'destructive-ops',
567
+ description: 'possible mining indicators',
568
+ },
569
+ {
570
+ regex: r(String.raw `^allowed-tools\s*:`),
571
+ patternId: 'allowed_tools_field',
572
+ severity: 'high',
573
+ category: 'destructive-ops',
574
+ description: 'skill declares allowed-tools (pre-approves access)',
575
+ },
576
+ {
577
+ regex: r(String.raw `\bsudo\b`),
578
+ patternId: 'sudo_usage',
579
+ severity: 'high',
580
+ category: 'destructive-ops',
581
+ description: 'uses sudo (privilege escalation)',
582
+ },
583
+ {
584
+ regex: r(String.raw `setuid|setgid|cap_setuid`),
585
+ patternId: 'setuid_setgid',
586
+ severity: 'critical',
587
+ category: 'destructive-ops',
588
+ description: 'setuid/setgid privilege escalation mechanism',
589
+ },
590
+ {
591
+ regex: r(String.raw `NOPASSWD`),
592
+ patternId: 'nopasswd_sudo',
593
+ severity: 'critical',
594
+ category: 'destructive-ops',
595
+ description: 'NOPASSWD sudoers entry',
596
+ },
597
+ {
598
+ regex: r(String.raw `chmod\s+[u+]?s`),
599
+ patternId: 'suid_bit',
600
+ severity: 'critical',
601
+ category: 'destructive-ops',
602
+ description: 'sets SUID/SGID bit on file',
603
+ },
154
604
  // persistence
155
- { regex: r(String.raw `\bcrontab\b`), patternId: 'persistence_cron', severity: 'medium', category: 'persistence', description: 'modifies cron jobs' },
156
- { regex: r(String.raw `\.(bashrc|zshrc|profile|bash_profile|bash_login|zprofile|zlogin)\b`), patternId: 'shell_rc_mod', severity: 'medium', category: 'persistence', description: 'references shell startup file' },
157
- { regex: r(String.raw `authorized_keys`), patternId: 'ssh_backdoor', severity: 'critical', category: 'persistence', description: 'modifies SSH authorized keys' },
158
- { regex: r(String.raw `ssh-keygen`), patternId: 'ssh_keygen', severity: 'medium', category: 'persistence', description: 'generates SSH keys' },
159
- { regex: r(String.raw `systemd.*\.service|systemctl\s+(enable|start)`), patternId: 'systemd_service', severity: 'medium', category: 'persistence', description: 'references or enables systemd service' },
160
- { regex: r(String.raw `/etc/init\.d/`), patternId: 'init_script', severity: 'medium', category: 'persistence', description: 'references init.d startup script' },
161
- { regex: r(String.raw `launchctl\s+load|LaunchAgents|LaunchDaemons`), patternId: 'macos_launchd', severity: 'medium', category: 'persistence', description: 'macOS launch agent/daemon persistence' },
162
- { regex: r(String.raw `/etc/sudoers|visudo`), patternId: 'sudoers_mod', severity: 'critical', category: 'persistence', description: 'modifies sudoers' },
163
- { regex: r(String.raw `git\s+config\s+--global\s+`), patternId: 'git_config_global', severity: 'medium', category: 'persistence', description: 'modifies global git configuration' },
164
- { regex: r(String.raw `AGENTS\.md|CLAUDE\.md|\.cursorrules|\.clinerules`), patternId: 'agent_config_mod', severity: 'critical', category: 'persistence', description: 'references agent config files (instruction persistence)' },
165
- { regex: r(String.raw `\.hermes/config\.yaml|\.hermes/SOUL\.md`), patternId: 'hermes_config_mod', severity: 'critical', category: 'persistence', description: 'references Hermes configuration files directly' },
166
- { regex: r(String.raw `\.claude/settings|\.codex/config`), patternId: 'other_agent_config', severity: 'high', category: 'persistence', description: 'references other agent configuration files' },
605
+ {
606
+ regex: r(String.raw `\bcrontab\b`),
607
+ patternId: 'persistence_cron',
608
+ severity: 'medium',
609
+ category: 'persistence',
610
+ description: 'modifies cron jobs',
611
+ },
612
+ {
613
+ regex: r(String.raw `\.(bashrc|zshrc|profile|bash_profile|bash_login|zprofile|zlogin)\b`),
614
+ patternId: 'shell_rc_mod',
615
+ severity: 'medium',
616
+ category: 'persistence',
617
+ description: 'references shell startup file',
618
+ },
619
+ {
620
+ regex: r(String.raw `authorized_keys`),
621
+ patternId: 'ssh_backdoor',
622
+ severity: 'critical',
623
+ category: 'persistence',
624
+ description: 'modifies SSH authorized keys',
625
+ },
626
+ {
627
+ regex: r(String.raw `ssh-keygen`),
628
+ patternId: 'ssh_keygen',
629
+ severity: 'medium',
630
+ category: 'persistence',
631
+ description: 'generates SSH keys',
632
+ },
633
+ {
634
+ regex: r(String.raw `systemd.*\.service|systemctl\s+(enable|start)`),
635
+ patternId: 'systemd_service',
636
+ severity: 'medium',
637
+ category: 'persistence',
638
+ description: 'references or enables systemd service',
639
+ },
640
+ {
641
+ regex: r(String.raw `/etc/init\.d/`),
642
+ patternId: 'init_script',
643
+ severity: 'medium',
644
+ category: 'persistence',
645
+ description: 'references init.d startup script',
646
+ },
647
+ {
648
+ regex: r(String.raw `launchctl\s+load|LaunchAgents|LaunchDaemons`),
649
+ patternId: 'macos_launchd',
650
+ severity: 'medium',
651
+ category: 'persistence',
652
+ description: 'macOS launch agent/daemon persistence',
653
+ },
654
+ {
655
+ regex: r(String.raw `/etc/sudoers|visudo`),
656
+ patternId: 'sudoers_mod',
657
+ severity: 'critical',
658
+ category: 'persistence',
659
+ description: 'modifies sudoers',
660
+ },
661
+ {
662
+ regex: r(String.raw `git\s+config\s+--global\s+`),
663
+ patternId: 'git_config_global',
664
+ severity: 'medium',
665
+ category: 'persistence',
666
+ description: 'modifies global git configuration',
667
+ },
668
+ {
669
+ regex: r(String.raw `AGENTS\.md|CLAUDE\.md|\.cursorrules|\.clinerules`),
670
+ patternId: 'agent_config_mod',
671
+ severity: 'critical',
672
+ category: 'persistence',
673
+ description: 'references agent config files (instruction persistence)',
674
+ },
675
+ {
676
+ regex: r(String.raw `\.hermes/config\.yaml|\.hermes/SOUL\.md`),
677
+ patternId: 'hermes_config_mod',
678
+ severity: 'critical',
679
+ category: 'persistence',
680
+ description: 'references Hermes configuration files directly',
681
+ },
682
+ {
683
+ regex: r(String.raw `\.claude/settings|\.codex/config`),
684
+ patternId: 'other_agent_config',
685
+ severity: 'high',
686
+ category: 'persistence',
687
+ description: 'references other agent configuration files',
688
+ },
167
689
  // reverse-shells
168
- { regex: r(String.raw `\bnc\s+-[lp]|ncat\s+-[lp]|\bsocat\b`), patternId: 'reverse_shell', severity: 'critical', category: 'reverse-shells', description: 'potential reverse shell listener' },
169
- { regex: r(String.raw `\bngrok\b|\blocaltunnel\b|\bserveo\b|\bcloudflared\b`), patternId: 'tunnel_service', severity: 'high', category: 'reverse-shells', description: 'uses tunneling service for external access' },
170
- { regex: r(String.raw `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{2,5}`), patternId: 'hardcoded_ip_port', severity: 'medium', category: 'reverse-shells', description: 'hardcoded IP address with port' },
171
- { regex: r(String.raw `0\.0\.0\.0:\d+|INADDR_ANY`), patternId: 'bind_all_interfaces', severity: 'high', category: 'reverse-shells', description: 'binds to all network interfaces' },
172
- { regex: r(String.raw `/bin/(ba)?sh\s+-i\s+.*>/dev/tcp/`), patternId: 'bash_reverse_shell', severity: 'critical', category: 'reverse-shells', description: 'bash reverse shell via /dev/tcp' },
173
- { regex: r(String.raw `python[23]?\s+-c\s+["']import\s+socket`), patternId: 'python_socket_oneliner', severity: 'critical', category: 'reverse-shells', description: 'Python one-liner socket connection (likely reverse shell)' },
174
- { regex: r(String.raw `socket\.connect\s*\(\s*\(`), patternId: 'python_socket_connect', severity: 'high', category: 'reverse-shells', description: 'Python socket connect to arbitrary host' },
175
- { regex: r(String.raw `webhook\.site|requestbin\.com|pipedream\.net|hookbin\.com`), patternId: 'exfil_service', severity: 'high', category: 'reverse-shells', description: 'references known webhook/exfiltration service' },
176
- { regex: r(String.raw `pastebin\.com|hastebin\.com|ghostbin\.`), patternId: 'paste_service', severity: 'medium', category: 'reverse-shells', description: 'references paste service (possible staging)' },
690
+ {
691
+ regex: r(String.raw `\bnc\s+-[lp]|ncat\s+-[lp]|\bsocat\b`),
692
+ patternId: 'reverse_shell',
693
+ severity: 'critical',
694
+ category: 'reverse-shells',
695
+ description: 'potential reverse shell listener',
696
+ },
697
+ {
698
+ regex: r(String.raw `\bngrok\b|\blocaltunnel\b|\bserveo\b|\bcloudflared\b`),
699
+ patternId: 'tunnel_service',
700
+ severity: 'high',
701
+ category: 'reverse-shells',
702
+ description: 'uses tunneling service for external access',
703
+ },
704
+ {
705
+ regex: r(String.raw `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{2,5}`),
706
+ patternId: 'hardcoded_ip_port',
707
+ severity: 'medium',
708
+ category: 'reverse-shells',
709
+ description: 'hardcoded IP address with port',
710
+ },
711
+ {
712
+ regex: r(String.raw `0\.0\.0\.0:\d+|INADDR_ANY`),
713
+ patternId: 'bind_all_interfaces',
714
+ severity: 'high',
715
+ category: 'reverse-shells',
716
+ description: 'binds to all network interfaces',
717
+ },
718
+ {
719
+ regex: r(String.raw `/bin/(ba)?sh\s+-i\s+.*>/dev/tcp/`),
720
+ patternId: 'bash_reverse_shell',
721
+ severity: 'critical',
722
+ category: 'reverse-shells',
723
+ description: 'bash reverse shell via /dev/tcp',
724
+ },
725
+ {
726
+ regex: r(String.raw `python[23]?\s+-c\s+["']import\s+socket`),
727
+ patternId: 'python_socket_oneliner',
728
+ severity: 'critical',
729
+ category: 'reverse-shells',
730
+ description: 'Python one-liner socket connection (likely reverse shell)',
731
+ },
732
+ {
733
+ regex: r(String.raw `socket\.connect\s*\(\s*\(`),
734
+ patternId: 'python_socket_connect',
735
+ severity: 'high',
736
+ category: 'reverse-shells',
737
+ description: 'Python socket connect to arbitrary host',
738
+ },
739
+ {
740
+ regex: r(String.raw `webhook\.site|requestbin\.com|pipedream\.net|hookbin\.com`),
741
+ patternId: 'exfil_service',
742
+ severity: 'high',
743
+ category: 'reverse-shells',
744
+ description: 'references known webhook/exfiltration service',
745
+ },
746
+ {
747
+ regex: r(String.raw `pastebin\.com|hastebin\.com|ghostbin\.`),
748
+ patternId: 'paste_service',
749
+ severity: 'medium',
750
+ category: 'reverse-shells',
751
+ description: 'references paste service (possible staging)',
752
+ },
177
753
  // obfuscation
178
- { regex: r(String.raw `base64\s+(-d|--decode)\s*\|`), patternId: 'base64_decode_pipe', severity: 'high', category: 'obfuscation', description: 'base64 decode piped to execution' },
179
- { regex: r(String.raw `\\x[0-9a-fA-F]{2}.*\\x[0-9a-fA-F]{2}.*\\x[0-9a-fA-F]{2}`), patternId: 'hex_encoded_string', severity: 'medium', category: 'obfuscation', description: 'hex-encoded string chain' },
180
- { regex: r(String.raw `\beval\s*\(\s*["']`), patternId: 'eval_string', severity: 'high', category: 'obfuscation', description: 'eval() with string argument' },
181
- { regex: r(String.raw `\bexec\s*\(\s*["']`), patternId: 'exec_string', severity: 'high', category: 'obfuscation', description: 'exec() with string argument' },
182
- { regex: r(String.raw `echo\s+[^\n]*\|\s*(bash|sh|python|perl|ruby|node)`), patternId: 'echo_pipe_exec', severity: 'critical', category: 'obfuscation', description: 'echo piped to interpreter for execution' },
183
- { regex: r(String.raw `compile\s*\(\s*[^\)]+,\s*["'].*["']\s*,\s*["']exec["']\s*\)`), patternId: 'python_compile_exec', severity: 'high', category: 'obfuscation', description: 'Python compile() with exec mode' },
184
- { regex: r(String.raw `getattr\s*\(\s*__builtins__`), patternId: 'python_getattr_builtins', severity: 'high', category: 'obfuscation', description: 'dynamic access to Python builtins' },
185
- { regex: r(String.raw `__import__\s*\(\s*["']os["']\s*\)`), patternId: 'python_import_os', severity: 'high', category: 'obfuscation', description: 'dynamic import of os module' },
186
- { regex: r(String.raw `codecs\.decode\s*\(\s*["']`), patternId: 'python_codecs_decode', severity: 'medium', category: 'obfuscation', description: 'codecs.decode (possible obfuscation)' },
187
- { regex: r(String.raw `String\.fromCharCode|charCodeAt`), patternId: 'js_char_code', severity: 'medium', category: 'obfuscation', description: 'JavaScript character code construction' },
188
- { regex: r(String.raw `atob\s*\(|btoa\s*\(`), patternId: 'js_base64', severity: 'medium', category: 'obfuscation', description: 'JavaScript base64 encode/decode' },
189
- { regex: r(String.raw `\[::-1\]`), patternId: 'string_reversal', severity: 'low', category: 'obfuscation', description: 'string reversal (possible obfuscation)' },
190
- { regex: r(String.raw `chr\s*\(\s*\d+\s*\)\s*\+\s*chr\s*\(\s*\d+`), patternId: 'chr_building', severity: 'high', category: 'obfuscation', description: 'building string from chr() calls' },
191
- { regex: r(String.raw `\\u[0-9a-fA-F]{4}.*\\u[0-9a-fA-F]{4}.*\\u[0-9a-fA-F]{4}`), patternId: 'unicode_escape_chain', severity: 'medium', category: 'obfuscation', description: 'chain of unicode escapes' },
754
+ {
755
+ regex: r(String.raw `base64\s+(-d|--decode)\s*\|`),
756
+ patternId: 'base64_decode_pipe',
757
+ severity: 'high',
758
+ category: 'obfuscation',
759
+ description: 'base64 decode piped to execution',
760
+ },
761
+ {
762
+ regex: r(String.raw `\\x[0-9a-fA-F]{2}.*\\x[0-9a-fA-F]{2}.*\\x[0-9a-fA-F]{2}`),
763
+ patternId: 'hex_encoded_string',
764
+ severity: 'medium',
765
+ category: 'obfuscation',
766
+ description: 'hex-encoded string chain',
767
+ },
768
+ {
769
+ regex: r(String.raw `\beval\s*\(\s*["']`),
770
+ patternId: 'eval_string',
771
+ severity: 'high',
772
+ category: 'obfuscation',
773
+ description: 'eval() with string argument',
774
+ },
775
+ {
776
+ regex: r(String.raw `\bexec\s*\(\s*["']`),
777
+ patternId: 'exec_string',
778
+ severity: 'high',
779
+ category: 'obfuscation',
780
+ description: 'exec() with string argument',
781
+ },
782
+ {
783
+ regex: r(String.raw `echo\s+[^\n]*\|\s*(bash|sh|python|perl|ruby|node)`),
784
+ patternId: 'echo_pipe_exec',
785
+ severity: 'critical',
786
+ category: 'obfuscation',
787
+ description: 'echo piped to interpreter for execution',
788
+ },
789
+ {
790
+ regex: r(String.raw `compile\s*\(\s*[^\)]+,\s*["'].*["']\s*,\s*["']exec["']\s*\)`),
791
+ patternId: 'python_compile_exec',
792
+ severity: 'high',
793
+ category: 'obfuscation',
794
+ description: 'Python compile() with exec mode',
795
+ },
796
+ {
797
+ regex: r(String.raw `getattr\s*\(\s*__builtins__`),
798
+ patternId: 'python_getattr_builtins',
799
+ severity: 'high',
800
+ category: 'obfuscation',
801
+ description: 'dynamic access to Python builtins',
802
+ },
803
+ {
804
+ regex: r(String.raw `__import__\s*\(\s*["']os["']\s*\)`),
805
+ patternId: 'python_import_os',
806
+ severity: 'high',
807
+ category: 'obfuscation',
808
+ description: 'dynamic import of os module',
809
+ },
810
+ {
811
+ regex: r(String.raw `codecs\.decode\s*\(\s*["']`),
812
+ patternId: 'python_codecs_decode',
813
+ severity: 'medium',
814
+ category: 'obfuscation',
815
+ description: 'codecs.decode (possible obfuscation)',
816
+ },
817
+ {
818
+ regex: r(String.raw `String\.fromCharCode|charCodeAt`),
819
+ patternId: 'js_char_code',
820
+ severity: 'medium',
821
+ category: 'obfuscation',
822
+ description: 'JavaScript character code construction',
823
+ },
824
+ {
825
+ regex: r(String.raw `atob\s*\(|btoa\s*\(`),
826
+ patternId: 'js_base64',
827
+ severity: 'medium',
828
+ category: 'obfuscation',
829
+ description: 'JavaScript base64 encode/decode',
830
+ },
831
+ {
832
+ regex: r(String.raw `\[::-1\]`),
833
+ patternId: 'string_reversal',
834
+ severity: 'low',
835
+ category: 'obfuscation',
836
+ description: 'string reversal (possible obfuscation)',
837
+ },
838
+ {
839
+ regex: r(String.raw `chr\s*\(\s*\d+\s*\)\s*\+\s*chr\s*\(\s*\d+`),
840
+ patternId: 'chr_building',
841
+ severity: 'high',
842
+ category: 'obfuscation',
843
+ description: 'building string from chr() calls',
844
+ },
845
+ {
846
+ regex: r(String.raw `\\u[0-9a-fA-F]{4}.*\\u[0-9a-fA-F]{4}.*\\u[0-9a-fA-F]{4}`),
847
+ patternId: 'unicode_escape_chain',
848
+ severity: 'medium',
849
+ category: 'obfuscation',
850
+ description: 'chain of unicode escapes',
851
+ },
192
852
  // supply-chain
193
- { regex: r(String.raw `curl\s+[^\n]*\|\s*(ba)?sh`), patternId: 'curl_pipe_shell', severity: 'critical', category: 'supply-chain', description: 'curl piped to shell (download-and-execute)' },
194
- { regex: r(String.raw `wget\s+[^\n]*-O\s*-\s*\|\s*(ba)?sh`), patternId: 'wget_pipe_shell', severity: 'critical', category: 'supply-chain', description: 'wget piped to shell (download-and-execute)' },
195
- { regex: r(String.raw `curl\s+[^\n]*\|\s*python`), patternId: 'curl_pipe_python', severity: 'critical', category: 'supply-chain', description: 'curl piped to Python interpreter' },
196
- { regex: r(String.raw `#\s*///\s*script.*dependencies`), patternId: 'pep723_inline_deps', severity: 'medium', category: 'supply-chain', description: 'PEP 723 inline script dependencies (verify pinning)' },
197
- { regex: r(String.raw `pip\s+install\s+(?!-r\s)(?!.*==)`), patternId: 'unpinned_pip_install', severity: 'medium', category: 'supply-chain', description: 'pip install without version pinning' },
198
- { regex: r(String.raw `npm\s+install\s+(?!.*@\d)`), patternId: 'unpinned_npm_install', severity: 'medium', category: 'supply-chain', description: 'npm install without version pinning' },
199
- { regex: r(String.raw `uv\s+run\s+`), patternId: 'uv_run', severity: 'medium', category: 'supply-chain', description: 'uv run may auto-install unpinned dependencies' },
200
- { regex: r(String.raw `(curl|wget|httpx?\.get|requests\.get|fetch)\s*[\(]?\s*["']https?://`), patternId: 'remote_fetch', severity: 'medium', category: 'supply-chain', description: 'fetches remote resource at runtime' },
201
- { regex: r(String.raw `git\s+clone\s+`), patternId: 'git_clone', severity: 'medium', category: 'supply-chain', description: 'clones git repository at runtime' },
202
- { regex: r(String.raw `docker\s+pull\s+`), patternId: 'docker_pull', severity: 'medium', category: 'supply-chain', description: 'pulls Docker image at runtime' },
853
+ {
854
+ regex: r(String.raw `curl\s+[^\n]*\|\s*(ba)?sh`),
855
+ patternId: 'curl_pipe_shell',
856
+ severity: 'critical',
857
+ category: 'supply-chain',
858
+ description: 'curl piped to shell (download-and-execute)',
859
+ },
860
+ {
861
+ regex: r(String.raw `wget\s+[^\n]*-O\s*-\s*\|\s*(ba)?sh`),
862
+ patternId: 'wget_pipe_shell',
863
+ severity: 'critical',
864
+ category: 'supply-chain',
865
+ description: 'wget piped to shell (download-and-execute)',
866
+ },
867
+ {
868
+ regex: r(String.raw `curl\s+[^\n]*\|\s*python`),
869
+ patternId: 'curl_pipe_python',
870
+ severity: 'critical',
871
+ category: 'supply-chain',
872
+ description: 'curl piped to Python interpreter',
873
+ },
874
+ {
875
+ regex: r(String.raw `#\s*///\s*script.*dependencies`),
876
+ patternId: 'pep723_inline_deps',
877
+ severity: 'medium',
878
+ category: 'supply-chain',
879
+ description: 'PEP 723 inline script dependencies (verify pinning)',
880
+ },
881
+ {
882
+ regex: r(String.raw `pip\s+install\s+(?!-r\s)(?!.*==)`),
883
+ patternId: 'unpinned_pip_install',
884
+ severity: 'medium',
885
+ category: 'supply-chain',
886
+ description: 'pip install without version pinning',
887
+ },
888
+ {
889
+ regex: r(String.raw `npm\s+install\s+(?!.*@\d)`),
890
+ patternId: 'unpinned_npm_install',
891
+ severity: 'medium',
892
+ category: 'supply-chain',
893
+ description: 'npm install without version pinning',
894
+ },
895
+ {
896
+ regex: r(String.raw `uv\s+run\s+`),
897
+ patternId: 'uv_run',
898
+ severity: 'medium',
899
+ category: 'supply-chain',
900
+ description: 'uv run may auto-install unpinned dependencies',
901
+ },
902
+ {
903
+ regex: r(String.raw `(curl|wget|httpx?\.get|requests\.get|fetch)\s*[\(]?\s*["']https?://`),
904
+ patternId: 'remote_fetch',
905
+ severity: 'medium',
906
+ category: 'supply-chain',
907
+ description: 'fetches remote resource at runtime',
908
+ },
909
+ {
910
+ regex: r(String.raw `git\s+clone\s+`),
911
+ patternId: 'git_clone',
912
+ severity: 'medium',
913
+ category: 'supply-chain',
914
+ description: 'clones git repository at runtime',
915
+ },
916
+ {
917
+ regex: r(String.raw `docker\s+pull\s+`),
918
+ patternId: 'docker_pull',
919
+ severity: 'medium',
920
+ category: 'supply-chain',
921
+ description: 'pulls Docker image at runtime',
922
+ },
203
923
  // credential-exposure
204
- { regex: r(String.raw `(?:api[_-]?key|token|secret|password)\s*[=:]\s*["'][A-Za-z0-9+/=_-]{20,}`), patternId: 'hardcoded_secret', severity: 'critical', category: 'credential-exposure', description: 'possible hardcoded API key/token/secret' },
205
- { regex: r(String.raw `-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----`), patternId: 'embedded_private_key', severity: 'critical', category: 'credential-exposure', description: 'embedded private key' },
206
- { regex: r(String.raw `ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{80,}`), patternId: 'github_token_leaked', severity: 'critical', category: 'credential-exposure', description: 'GitHub personal access token in skill content' },
207
- { regex: r(String.raw `sk-[A-Za-z0-9]{20,}`), patternId: 'openai_key_leaked', severity: 'critical', category: 'credential-exposure', description: 'possible OpenAI API key in skill content' },
208
- { regex: r(String.raw `sk-ant-[A-Za-z0-9_-]{90,}`), patternId: 'anthropic_key_leaked', severity: 'critical', category: 'credential-exposure', description: 'possible Anthropic API key in skill content' },
209
- { regex: r(String.raw `AKIA[0-9A-Z]{16}`), patternId: 'aws_access_key_leaked', severity: 'critical', category: 'credential-exposure', description: 'AWS access key ID in skill content' },
924
+ {
925
+ regex: r(String.raw `(?:api[_-]?key|token|secret|password)\s*[=:]\s*["'][A-Za-z0-9+/=_-]{20,}`),
926
+ patternId: 'hardcoded_secret',
927
+ severity: 'critical',
928
+ category: 'credential-exposure',
929
+ description: 'possible hardcoded API key/token/secret',
930
+ },
931
+ {
932
+ regex: r(String.raw `-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----`),
933
+ patternId: 'embedded_private_key',
934
+ severity: 'critical',
935
+ category: 'credential-exposure',
936
+ description: 'embedded private key',
937
+ },
938
+ {
939
+ regex: r(String.raw `ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{80,}`),
940
+ patternId: 'github_token_leaked',
941
+ severity: 'critical',
942
+ category: 'credential-exposure',
943
+ description: 'GitHub personal access token in skill content',
944
+ },
945
+ {
946
+ regex: r(String.raw `sk-[A-Za-z0-9]{20,}`),
947
+ patternId: 'openai_key_leaked',
948
+ severity: 'critical',
949
+ category: 'credential-exposure',
950
+ description: 'possible OpenAI API key in skill content',
951
+ },
952
+ {
953
+ regex: r(String.raw `sk-ant-[A-Za-z0-9_-]{90,}`),
954
+ patternId: 'anthropic_key_leaked',
955
+ severity: 'critical',
956
+ category: 'credential-exposure',
957
+ description: 'possible Anthropic API key in skill content',
958
+ },
959
+ {
960
+ regex: r(String.raw `AKIA[0-9A-Z]{16}`),
961
+ patternId: 'aws_access_key_leaked',
962
+ severity: 'critical',
963
+ category: 'credential-exposure',
964
+ description: 'AWS access key ID in skill content',
965
+ },
210
966
  ];
211
967
  function pathWithin(root, target) {
212
968
  const rel = path.relative(root, target);
@@ -357,7 +1113,9 @@ function collectStructure(skillPath) {
357
1113
  category: 'structural',
358
1114
  file: relativePath,
359
1115
  line: 0,
360
- match: isBinary ? `binary content${ext ? ` (${ext})` : ''}` : `binary extension: ${ext}`,
1116
+ match: isBinary
1117
+ ? `binary content${ext ? ` (${ext})` : ''}`
1118
+ : `binary extension: ${ext}`,
361
1119
  description: 'binary/executable content should not be in a skill',
362
1120
  }));
363
1121
  }
@@ -401,7 +1159,9 @@ function collectStructure(skillPath) {
401
1159
  function scanFile(entry) {
402
1160
  if (entry.isBinary)
403
1161
  return [];
404
- if (entry.extension !== '.md' && entry.relativePath !== 'SKILL.md' && !SCANNABLE_EXTENSIONS.has(entry.extension)) {
1162
+ if (entry.extension !== '.md' &&
1163
+ entry.relativePath !== 'SKILL.md' &&
1164
+ !SCANNABLE_EXTENSIONS.has(entry.extension)) {
405
1165
  return [];
406
1166
  }
407
1167
  let content;
@@ -442,7 +1202,8 @@ function scanFile(entry) {
442
1202
  for (const char of INVISIBLE_CHARS) {
443
1203
  if (!line.includes(char))
444
1204
  continue;
445
- const charName = INVISIBLE_CHAR_NAMES[char] || `U+${char.codePointAt(0)?.toString(16).toUpperCase()}`;
1205
+ const charName = INVISIBLE_CHAR_NAMES[char] ||
1206
+ `U+${char.codePointAt(0)?.toString(16).toUpperCase()}`;
446
1207
  findings.push(createFinding({
447
1208
  patternId: 'invisible_unicode',
448
1209
  severity: 'high',
@@ -479,7 +1240,9 @@ function computeMtimeSignature(parts) {
479
1240
  }
480
1241
  function computeContentHash(files) {
481
1242
  const hash = createHash('sha256');
482
- const sortedFiles = files.slice().sort((a, b) => a.relativePath.localeCompare(b.relativePath));
1243
+ const sortedFiles = files
1244
+ .slice()
1245
+ .sort((a, b) => a.relativePath.localeCompare(b.relativePath));
483
1246
  for (const file of sortedFiles) {
484
1247
  hash.update(file.relativePath).update('\0');
485
1248
  hash.update(String(file.size)).update('\0');