agentsmesh 0.20.0 → 0.21.0
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 +13 -0
- package/README.md +1 -0
- package/dist/canonical.js +59 -11
- package/dist/canonical.js.map +1 -1
- package/dist/cli.js +159 -157
- package/dist/engine.js +59 -11
- package/dist/engine.js.map +1 -1
- package/dist/index.js +59 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.21.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b1efca1: Harden install pipeline against third-party supply-chain attacks.
|
|
8
|
+
- **Strip elevated artifacts from non-local sources by default.** `hooks.yaml`, `permissions.yaml`, and `mcp.json` are now removed from any pack installed from a `github:`, `gitlab:`, or `git+...` source unless you opt in. These three files control your agent's tool settings (shell hooks, granted permissions, MCP launch specs) and a malicious pack shipping any of them could otherwise execute arbitrary local commands the next time the matching event fires. Opt in per-artifact with `--accept-hooks`, `--accept-permissions`, `--accept-mcp`, or all three with `--accept-elevated`. Local sources remain trusted as before.
|
|
9
|
+
- **Skill supporting-file traversal no longer follows symlinks.** A pack containing `skills/foo/keys -> /Users/victim/.ssh` previously pulled external bytes (private keys, etc.) into the canonical skill content. Skill traversal now uses the existing `readDirRecursiveNoSymlinks` helper, mirroring the hardening already applied to install-manifest hashing.
|
|
10
|
+
- **Redact credentials from remote-fetch error output.** `oauth2:<token>@`, `x-access-token:<token>@`, and any other userinfo-bearing URLs are now masked (`https://***@host/...`) in console warnings and thrown error messages so GitHub PATs and GitLab tokens never leak into CI logs, terminal scrollback, or log shippers.
|
|
11
|
+
- **Gate `git+file://` sources behind `AGENTSMESH_ALLOW_LOCAL_GIT=1`.** On shared/multi-tenant hosts a `git+file:///tmp/world-writable-repo` `extends:` clause could silently consume a repo planted by another user; combined with downstream elevated-artifact emission this was a local priv-esc vector. Set `AGENTSMESH_ALLOW_LOCAL_GIT=1` to enable for closed-network development.
|
|
12
|
+
- **Allowlist tar entry types on GitHub tarball extraction.** Previously only `Link` and `SymbolicLink` were rejected (denylist). Now only `File` and `Directory` entries extract; FIFOs, devices, hardlinks, and any future/exotic tar variant are rejected by default.
|
|
13
|
+
|
|
14
|
+
These changes apply to `agentsmesh install`, `agentsmesh refresh`, and any `extends:` resolution against a non-local source. They are behavior changes for users who were silently inheriting hooks/permissions/mcp from a remote pack — re-run with the matching `--accept-*` flag (or `--accept-elevated`) to preserve previous behavior intentionally.
|
|
15
|
+
|
|
3
16
|
## 0.20.0
|
|
4
17
|
|
|
5
18
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -233,6 +233,7 @@ agentsmesh check [--global]
|
|
|
233
233
|
agentsmesh merge [--global]
|
|
234
234
|
agentsmesh matrix [--global] [--targets <csv>] [--verbose]
|
|
235
235
|
agentsmesh install <source> [--sync] [--path <dir>] [--target <id>] [--as <kind>] [--name <id>] [--extends] [--all] [--dry-run] [--global] [--force]
|
|
236
|
+
[--accept-hooks|--accept-permissions|--accept-mcp|--accept-elevated]
|
|
236
237
|
agentsmesh uninstall <name>[,<name>...] [--all] [--keep-pack] [--keep-generated] [--dry-run] [--global] [--force]
|
|
237
238
|
agentsmesh installs list [--global]
|
|
238
239
|
agentsmesh refresh [<name>[,<name>...]] [--dry-run] [--force] [--json] [--global]
|
package/dist/canonical.js
CHANGED
|
@@ -185,6 +185,33 @@ async function readDirRecursive(dir, visited, branchSegments) {
|
|
|
185
185
|
);
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
|
+
async function readDirRecursiveNoSymlinks(dir, branchSegments) {
|
|
189
|
+
const currentBranchSegments = branchSegments ?? [basename(dir)];
|
|
190
|
+
try {
|
|
191
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
192
|
+
const files = [];
|
|
193
|
+
for (const ent of entries) {
|
|
194
|
+
if (ent.isSymbolicLink()) continue;
|
|
195
|
+
const full = join(dir, ent.name);
|
|
196
|
+
if (ent.isDirectory()) {
|
|
197
|
+
const nextSegments = [...currentBranchSegments, ent.name];
|
|
198
|
+
if (shouldSkipRecursiveBranch(nextSegments)) continue;
|
|
199
|
+
files.push(...await readDirRecursiveNoSymlinks(full, nextSegments));
|
|
200
|
+
} else if (ent.isFile()) {
|
|
201
|
+
files.push(full);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return files;
|
|
205
|
+
} catch (err) {
|
|
206
|
+
const e = err;
|
|
207
|
+
if (e.code === "ENOENT" || e.code === "ENOTDIR" || e.code === "EACCES") return [];
|
|
208
|
+
throw new FileSystemError(
|
|
209
|
+
dir,
|
|
210
|
+
`Failed to read directory ${dir}: ${e.message}. Check permissions.`,
|
|
211
|
+
{ cause: err, errnoCode: e.code }
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
188
215
|
var MAX_RECURSIVE_DEPTH, MAX_SEGMENT_REPETITIONS;
|
|
189
216
|
var init_fs_traverse = __esm({
|
|
190
217
|
"src/utils/filesystem/fs-traverse.ts"() {
|
|
@@ -18731,6 +18758,19 @@ init_fs();
|
|
|
18731
18758
|
|
|
18732
18759
|
// src/config/remote/git-remote.ts
|
|
18733
18760
|
init_fs();
|
|
18761
|
+
|
|
18762
|
+
// src/utils/output/redact-url-secrets.ts
|
|
18763
|
+
var URL_WITH_CREDENTIALS = /([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^/@\s"'<>]+)@([^\s"'<>]+)/g;
|
|
18764
|
+
function redactUrlSecrets(message) {
|
|
18765
|
+
return message.replace(
|
|
18766
|
+
URL_WITH_CREDENTIALS,
|
|
18767
|
+
(_full, scheme, _userinfo, rest) => {
|
|
18768
|
+
return `${scheme}***@${rest}`;
|
|
18769
|
+
}
|
|
18770
|
+
);
|
|
18771
|
+
}
|
|
18772
|
+
|
|
18773
|
+
// src/config/remote/git-remote.ts
|
|
18734
18774
|
var execFileAsync = promisify(execFile);
|
|
18735
18775
|
var REPO_DIRNAME = "repo";
|
|
18736
18776
|
function ensureNotFlag(value, kind) {
|
|
@@ -18764,12 +18804,13 @@ async function fetchGitRemoteExtend(parsed, extendName, options, cacheDir, build
|
|
|
18764
18804
|
await rm(stagedRoot, { recursive: true, force: true });
|
|
18765
18805
|
const allowFallback = options.allowOfflineFallback !== false;
|
|
18766
18806
|
if (allowFallback && await hasCachedRepo(cacheRepoDir)) {
|
|
18807
|
+
const rawMsg = err instanceof Error ? err.message : String(err);
|
|
18767
18808
|
console.warn(
|
|
18768
|
-
`[agentsmesh] Remote fetch failed for ${extendName}; using cached version. Error: ${
|
|
18809
|
+
`[agentsmesh] Remote fetch failed for ${extendName}; using cached version. Error: ${redactUrlSecrets(rawMsg)}`
|
|
18769
18810
|
);
|
|
18770
18811
|
return readCachedRepo(cacheRepoDir);
|
|
18771
18812
|
}
|
|
18772
|
-
throw err;
|
|
18813
|
+
throw err instanceof Error ? Object.assign(new Error(redactUrlSecrets(err.message)), { cause: err.cause }) : err;
|
|
18773
18814
|
}
|
|
18774
18815
|
}
|
|
18775
18816
|
async function readCachedRepo(repoDir) {
|
|
@@ -18930,13 +18971,14 @@ async function fetchGithubRemoteExtend(parsed, extendName, options, cacheDir, bu
|
|
|
18930
18971
|
if (allowFallback && await exists(extractDir)) {
|
|
18931
18972
|
const topDir2 = await findExtractTopDir(extractDir);
|
|
18932
18973
|
if (topDir2) {
|
|
18974
|
+
const rawMsg = err instanceof Error ? err.message : String(err);
|
|
18933
18975
|
console.warn(
|
|
18934
|
-
`[agentsmesh] Network failed for ${extendName}; using cached version. Error: ${
|
|
18976
|
+
`[agentsmesh] Network failed for ${extendName}; using cached version. Error: ${redactUrlSecrets(rawMsg)}`
|
|
18935
18977
|
);
|
|
18936
18978
|
return { resolvedPath: join(extractDir, topDir2), version: tag };
|
|
18937
18979
|
}
|
|
18938
18980
|
}
|
|
18939
|
-
throw err;
|
|
18981
|
+
throw err instanceof Error ? Object.assign(new Error(redactUrlSecrets(err.message)), { cause: err.cause }) : err;
|
|
18940
18982
|
}
|
|
18941
18983
|
await rm(extractDir, { recursive: true, force: true });
|
|
18942
18984
|
await mkdir(extractDir, { recursive: true });
|
|
@@ -18947,12 +18989,14 @@ async function fetchGithubRemoteExtend(parsed, extendName, options, cacheDir, bu
|
|
|
18947
18989
|
file: tarPath,
|
|
18948
18990
|
cwd: extractDir,
|
|
18949
18991
|
strict: true,
|
|
18992
|
+
// Allowlist entry types instead of denylist: only `File` and `Directory`
|
|
18993
|
+
// can be extracted. Hardlinks (`Link`), symlinks (`SymbolicLink`), FIFOs,
|
|
18994
|
+
// character/block devices, and any future/exotic tar entry type are
|
|
18995
|
+
// rejected. A denylist would silently let an unknown variant through.
|
|
18950
18996
|
filter: (entryPath, entry) => {
|
|
18951
18997
|
if (isZipSlipPath(entryPath)) return false;
|
|
18952
|
-
|
|
18953
|
-
|
|
18954
|
-
}
|
|
18955
|
-
return true;
|
|
18998
|
+
const type = entry && "type" in entry ? entry.type : void 0;
|
|
18999
|
+
return type === "File" || type === "Directory";
|
|
18956
19000
|
}
|
|
18957
19001
|
});
|
|
18958
19002
|
} finally {
|
|
@@ -19050,8 +19094,11 @@ function parseGitSource(source) {
|
|
|
19050
19094
|
return null;
|
|
19051
19095
|
}
|
|
19052
19096
|
const allowInsecure = process.env.AGENTSMESH_ALLOW_INSECURE_GIT === "1" || process.env.AGENTSMESH_ALLOW_INSECURE_GIT === "true";
|
|
19053
|
-
const
|
|
19054
|
-
|
|
19097
|
+
const allowLocalGit = process.env.AGENTSMESH_ALLOW_LOCAL_GIT === "1" || process.env.AGENTSMESH_ALLOW_LOCAL_GIT === "true";
|
|
19098
|
+
const allowed = ["https:", "ssh:"];
|
|
19099
|
+
if (allowInsecure) allowed.push("http:");
|
|
19100
|
+
if (allowLocalGit) allowed.push("file:");
|
|
19101
|
+
if (!allowed.includes(parsedUrl.protocol)) {
|
|
19055
19102
|
return null;
|
|
19056
19103
|
}
|
|
19057
19104
|
return { url, ref };
|
|
@@ -19576,6 +19623,7 @@ async function parseAgents(agentsDir, opts = {}) {
|
|
|
19576
19623
|
|
|
19577
19624
|
// src/canonical/features/skills.ts
|
|
19578
19625
|
init_fs();
|
|
19626
|
+
init_fs_traverse();
|
|
19579
19627
|
init_markdown();
|
|
19580
19628
|
init_boilerplate_filter();
|
|
19581
19629
|
async function readContent(path) {
|
|
@@ -19594,7 +19642,7 @@ function sanitizeSkillName(raw) {
|
|
|
19594
19642
|
return raw.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
19595
19643
|
}
|
|
19596
19644
|
async function listSupportingFiles(skillDir) {
|
|
19597
|
-
const files = await
|
|
19645
|
+
const files = await readDirRecursiveNoSymlinks(skillDir);
|
|
19598
19646
|
const result = [];
|
|
19599
19647
|
for (const absPath of files) {
|
|
19600
19648
|
const raw = absPath.slice(skillDir.length + 1);
|