weavatrix 0.3.6 → 0.3.7

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.
@@ -0,0 +1,37 @@
1
+ # Weavatrix 0.3.7
2
+
3
+ 0.3.7 fixes three defects surfaced by cross-repository validation: a
4
+ reverse-shell detection gap, an unbounded ripgrep-path probe that could hang the
5
+ server, and a `search_code` path that excluded `node_modules` only through the
6
+ gitignore stack.
7
+
8
+ ## Reverse-shell pre-filter now covers the confirming regex
9
+
10
+ The malware content sweep uses each rule's `pattern` as the ripgrep pre-filter
11
+ and only then applies the stricter `re` to confirm. The reverse-shell `pattern`
12
+ had drifted narrower than its `re`: it required `SOCK_STREAM` followed by
13
+ `/bin/sh` or `exec`, so socket-backed shells using `connect`, `subprocess`,
14
+ `pty.spawn` or `os.dup2` — and default `socket.socket()` calls with no
15
+ `SOCK_STREAM` literal — were dropped before they could be confirmed. The
16
+ pre-filter is now a superset of the confirming regex, and a new invariant test
17
+ asserts that every payload the confirming regex flags is also surfaced by the
18
+ pre-filter.
19
+
20
+ A separate, deeper case is deliberately left for a follow-up: multi-line reverse
21
+ shells, where ripgrep's per-line matching cannot span a `socket` call and the
22
+ shell invocation on different lines.
23
+
24
+ ## Bounded ripgrep-path probe
25
+
26
+ The `where`/`which rg` fallback in the MCP ripgrep resolver ran `spawnSync`
27
+ without a timeout on the main thread, so a slow or unreachable PATH entry (for
28
+ example a disconnected network drive) could hang the server. The probe now runs
29
+ with a three-second timeout and `windowsHide`.
30
+
31
+ ## search_code excludes node_modules explicitly
32
+
33
+ The `search_code` ripgrep invocation passed `--hidden` with only a `.git`
34
+ exclusion, leaving `node_modules` out of results solely through ripgrep's
35
+ implicit gitignore stack — which fails for repositories that commit their
36
+ dependency tree or lack a gitignore. An explicit `!node_modules` glob is now
37
+ applied; a caller-supplied glob is still appended afterward and wins.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weavatrix",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "type": "module",
5
5
  "description": "Local repository intelligence MCP for AI coding agents: reusable architecture graph for fast application understanding, Health, dead code, clones, history, blast radius and architecture safeguards.",
6
6
  "author": "Sergii Ziborov <sergii.ziborov@gmail.com>",
@@ -40,6 +40,7 @@
40
40
  "docs/releases/v0.3.4.md",
41
41
  "docs/releases/v0.3.5.md",
42
42
  "docs/releases/v0.3.6.md",
43
+ "docs/releases/v0.3.7.md",
43
44
  "docs/releases/v0.2.19.md",
44
45
  "README.md",
45
46
  "SECURITY.md",
package/src/mcp-rg.mjs CHANGED
@@ -43,7 +43,7 @@ export function createRgResolver(selfDir) {
43
43
  } catch { /* optional packaged ripgrep path */ }
44
44
  for (const c of editorRgCandidates()) if (existsSync(c)) return (rgPath = c)
45
45
  try {
46
- const probe = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['rg'], {encoding: 'utf8', env: childProcessEnv()})
46
+ const probe = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['rg'], {encoding: 'utf8', env: childProcessEnv(), timeout: 3000, windowsHide: true})
47
47
  const p = probe.status === 0 ? probe.stdout.split(/\r?\n/)[0].trim() : ''
48
48
  if (p && existsSync(p)) return (rgPath = p)
49
49
  } catch { /* optional PATH probe */ }
@@ -14,7 +14,10 @@ const MAX_SOURCE_CONTEXT_LINES = 1000
14
14
  function rgSearch(repoRoot, resolveRg, query, { isRegex, glob, maxResults }, spawnRg = spawnSync) {
15
15
  const rg = resolveRg()
16
16
  if (!rg) return null
17
- const args = ['--line-number', '--no-heading', '--color', 'never', '--hidden', '-g', '!.git/**', '-m', '100', '-i']
17
+ // Exclude node_modules explicitly: --hidden + the default gitignore stack is the only thing that
18
+ // keeps a dependency tree out, which fails for repos that commit node_modules or lack a gitignore.
19
+ // A caller-supplied glob is appended after this and still wins (rg: last matching glob decides).
20
+ const args = ['--line-number', '--no-heading', '--color', 'never', '--hidden', '-g', '!.git/**', '-g', '!node_modules', '-m', '100', '-i']
18
21
  if (!isRegex) args.push('--fixed-strings')
19
22
  if (glob) args.push('-g', glob)
20
23
  // Run from the repository root and search `.` so path globs such as `src/**` are evaluated
@@ -35,7 +35,10 @@ export const CONTENT_RULES = [
35
35
  nearZeroFp: true,
36
36
  // canonical reverse/bind shells — a shell wired to a socket. Essentially never legitimate inside a
37
37
  // published package: /dev/tcp redirection, netcat -e, interactive-shell redirect, mkfifo pipe shell.
38
- pattern: "/dev/tcp/[0-9]|\\bnc(at)?\\s+-e\\b|\\b(ba)?sh\\s+-i\\b\\s*(2)?>&|mkfifo\\b.{0,60}(ba)?sh\\b|0<&196|socket\\.socket\\(.{0,40}(SOCK_STREAM).{0,80}(/bin/sh|exec)",
38
+ // The rg pre-filter must stay a superset of `re` below: rg surfaces lines matching this string and
39
+ // only then is `re` applied to confirm, so a narrower pre-filter silently drops real reverse shells
40
+ // (e.g. socket + pty.spawn / os.dup2 / subprocess, or a default socket.socket() without SOCK_STREAM).
41
+ pattern: "/dev/tcp/[0-9]|\\bnc(at)?\\s+-e\\b|\\b(ba)?sh\\s+-i\\b\\s*(2)?>&|mkfifo\\b.{0,60}(ba)?sh\\b|0<&196|socket\\.socket\\(.{0,120}(connect|SOCK_STREAM).{0,180}(/bin/sh|subprocess|pty\\.spawn|os\\.dup2)",
39
42
  re: /\/dev\/tcp\/[0-9]|\bnc(at)?\s+-e\b|\b(ba)?sh\s+-i\b\s*(2)?>&|mkfifo\b[\s\S]{0,60}?\b(ba)?sh\b|0<&196|socket\.socket\([\s\S]{0,120}?(connect|SOCK_STREAM)[\s\S]{0,180}?(\/bin\/sh|subprocess|pty\.spawn|os\.dup2)/i,
40
43
  what: "reverse/bind-shell pattern (interactive shell wired to a socket)",
41
44
  },