agentsmesh 0.19.0 → 0.19.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,64 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.19.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 041b9c5: fix(security): plug input/path/proto-pollution holes in plugin, install, MCP, and config
8
+
9
+ Closes a batch of security audit findings (2 HIGH + 5 MEDIUM):
10
+ - **Plugin source containment** (`src/plugins/load-plugin.ts`) — local plugin
11
+ sources are now resolved with `realpath` and rejected when they escape
12
+ `projectRoot`. A hostile `agentsmesh.yaml` with
13
+ `plugins[].source: "../../tmp/evil.js"` no longer reaches dynamic
14
+ `import()`. Bare npm specifiers continue to resolve through
15
+ `node_modules/<source>`. Both sides are canonicalized so macOS
16
+ `/tmp -> /private/tmp` (and other platform-level symlinks) do not
17
+ produce false positives.
18
+ - **Prototype pollution denylist** (`src/config/core/loader.ts`) —
19
+ `deepMergeObjects` over `agentsmesh.local.yaml` now skips `__proto__`,
20
+ `constructor`, and `prototype` keys. Defense-in-depth: the `yaml` v2
21
+ parser already strips `__proto__`, but this pins the invariant against
22
+ future parser swaps.
23
+ - **Install manifest name validation** (`src/install/core/install-manifest.ts`) —
24
+ `installManifestEntrySchema.name` now refuses path separators, NUL,
25
+ and `.`/`..` segments. A poisoned `installs.yaml` entry can no longer
26
+ drive `rm -rf` outside `.agentsmesh/packs/` at uninstall time.
27
+ - **`git+http://` allowlist** (`src/config/remote/remote-source.ts`) —
28
+ rejected by default; opt-in via `AGENTSMESH_ALLOW_INSECURE_GIT=1` for
29
+ closed-network development. `https://`, `ssh://`, and `file://` are
30
+ unchanged. Closes a MITM window before SHA pinning resolves.
31
+ - **MCP `cwd` / `description` refinement** (`src/mcp/schemas.ts`) — `cwd`
32
+ rejects `..` segments (POSIX + Windows separators), NUL, and newlines;
33
+ `description` rejects NUL and newlines. MCP clients can no longer
34
+ plant a structurally-escaping working directory that downstream agents
35
+ consume via `spawn(command, args, { cwd })`.
36
+ - **Global path redaction in MCP errors** (`src/mcp/errors.ts`) —
37
+ `redactAbsolutePaths` now strips paths anywhere in the string, catching
38
+ embedded paths in stack frames (`at Foo (/Users/...)`) and quoted
39
+ paths in Node errors (`ENOENT, open '/Users/...'`) the prior
40
+ whitespace-anchored regex missed.
41
+ - **`copyDir` symlink hardening** (`src/utils/filesystem/fs-traverse.ts`) —
42
+ `copyDir` now uses `lstat` and skips symlinks. A symlink in the source
43
+ tree pointing outside its root can no longer have its target's bytes
44
+ exfiltrated into the destination (and into any redistributed pack
45
+ built on top of it).
46
+
47
+ Behavioral changes that could affect existing consumers:
48
+ - `git+http://...` extends/installs require `AGENTSMESH_ALLOW_INSECURE_GIT=1`.
49
+ - MCP server entries with `cwd: "../foo"` no longer parse — rewrite as a
50
+ POSIX-relative path without `..` segments.
51
+ - Plugin `source:` entries pointing outside the project tree no longer
52
+ load. The standard `node_modules/<plugin>` and project-local layouts
53
+ are unaffected.
54
+ - A poisoned `installs.yaml` entry whose `name` contains separators or
55
+ `..` is now dropped at parse time (the rest of the manifest survives).
56
+ - A `agentsmesh.local.yaml` payload at `__proto__`, `constructor`, or
57
+ `prototype` keys is silently dropped instead of merged.
58
+
59
+ Branch coverage > 95% on every touched file; full unit/integration suite
60
+ (7596 tests) and plugin e2e suite (57 tests) green.
61
+
3
62
  ## 0.19.0
4
63
 
5
64
  ### Minor Changes
package/dist/canonical.js CHANGED
@@ -19044,7 +19044,9 @@ function parseGitSource(source) {
19044
19044
  } catch {
19045
19045
  return null;
19046
19046
  }
19047
- if (!["https:", "http:", "ssh:", "file:"].includes(parsedUrl.protocol)) {
19047
+ const allowInsecure = process.env.AGENTSMESH_ALLOW_INSECURE_GIT === "1" || process.env.AGENTSMESH_ALLOW_INSECURE_GIT === "true";
19048
+ const allowedProtocols = allowInsecure ? ["https:", "http:", "ssh:", "file:"] : ["https:", "ssh:", "file:"];
19049
+ if (!allowedProtocols.includes(parsedUrl.protocol)) {
19048
19050
  return null;
19049
19051
  }
19050
19052
  return { url, ref };
@@ -20984,10 +20986,12 @@ async function loadConfig(configPath) {
20984
20986
  }
20985
20987
  return result.data;
20986
20988
  }
20989
+ var PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
20987
20990
  function deepMergeObjects(base, overrides2) {
20988
20991
  const result = { ...base };
20989
20992
  for (const [k, v] of Object.entries(overrides2)) {
20990
20993
  if (v === null || v === void 0) continue;
20994
+ if (PROTOTYPE_POLLUTION_KEYS.has(k)) continue;
20991
20995
  const baseVal = result[k];
20992
20996
  if (typeof v === "object" && !Array.isArray(v) && v !== null && typeof baseVal === "object" && baseVal !== null && !Array.isArray(baseVal)) {
20993
20997
  result[k] = deepMergeObjects(