codex-plugin-doctor 1.21.0 → 1.23.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/README.md
CHANGED
|
@@ -298,7 +298,7 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
|
|
|
298
298
|
|
|
299
299
|
`self-test` runs the bundled runtime-complete sample through static validation, runtime MCP probes, and the compatibility scorecard. It is the fastest post-install check after `npm install -g codex-plugin-doctor`.
|
|
300
300
|
|
|
301
|
-
`doctor` checks the local environment, including package version, platform, Node version, npm global prefix, Codex home, and Codex plugin cache visibility. The text output also includes recommended next commands for self-test, installed plugin discovery, runtime checks, compatibility scoring, and CI setup. `doctor contract` publishes the machine-readable output contract, including public JSON schema surfaces, stable-through-1.0 compatibility metadata, and a frozen rule catalog digest. Add `--json` for automation or `--output output-contract.json` to write the contract to disk. `doctor corpus` runs the bundled validation corpus against healthy runtime, risky security, starter skill, and generic MCP packages, then reports whether each case matched its expected outcome. Add `--json` for automation or `--output validation-corpus.json` to write the corpus report to disk. `doctor npm <package>` runs a preinstall scan by packing the npm package with scripts disabled, extracting the publish tarball, and running validation, security, trust, and recommendation checks against the shipped contents. Use a published Codex plugin package as the target; scanning `codex-plugin-doctor` itself intentionally reports a missing plugin manifest because this CLI package is not a plugin package. Add `--json` for automation or `--output npm-preinstall.json` to write the report to disk. `doctor attest <path>` creates a local attestation with stable package/report digests, validation/security/compatibility/trust summary, and verification metadata. Add `--sign-key-env NAME` to attach a local HMAC-SHA256 signature without printing the secret, or `--json --output attestation.json` to write the artifact to disk. `doctor attest verify <attestation.json> --target <path> --sign-key-env NAME` recomputes the package fingerprint, report digest, and HMAC signature offline; verification intentionally treats `generatedAt`, `targetPath`, `verification`, and `signature.keyHint` as unsigned display metadata. `doctor runtime-plan <path>` creates a non-executing runtime plan that lists MCP server commands, safe probe methods, risk reasons, and a stable approval digest before any local server is started. Add `--markdown --output runtime-plan.md` to preserve a review-ready approval artifact with the execution boundary, checklist, servers, probes, and risk reasons. `doctor runtime-policy <path>` evaluates the same runtime plan and security signals, then recommends `allow`, `review`, `sandbox_recommended`, or `deny` before local MCP execution starts. `doctor review-bundle <path> --output <dir> --sign-key-env NAME` writes a signed review directory with runtime plan, runtime policy, attestation, release evidence, manifest, Markdown summary files, and SHA-256 file integrity digests. `doctor review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME` verifies the bundle manifest, expected files, manifest integrity digests, runtime artifacts, signed attestation, and signed release evidence offline before a reviewer trusts the handoff. `doctor review-bundle diff --before <dir> --after <dir>` compares two review bundles and flags risk-increasing changes in status, runtime policy, release readiness, signatures, release evidence, and runtime plan digest. `check --runtime --require-runtime-approval --runtime-approval-digest <digest>` refuses to run runtime probes unless the current plan digest matches the approved digest. `doctor release-evidence <path> --sign-key-env NAME` creates one redacted release bundle with signed attestation, offline verification, corpus, performance, security, trust, package metadata, git release gates, and runtime approval status. Strict release evidence requires a clean tagged worktree; use `--allow-dirty` or `--allow-untagged` only for local rehearsal. `doctor release-evidence verify <evidence.json> --target <path> --sign-key-env NAME` verifies a shared release evidence artifact offline against an explicit package path; the artifact target path is treated as display metadata, not trusted input. `doctor release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME` writes a signed release evidence file and prints the `gh release upload` command; add `--upload` to run the upload through GitHub CLI with `--clobber`. `doctor inspector <path>` builds a safe MCP Inspector launch command from a packaged `.mcp.json` file without starting the Inspector proxy automatically. Use `--server <name>` when the package contains multiple MCP server entries. `doctor diff --before <path> --after <path>` compares two package roots and reports new findings, resolved findings, trust score delta, and whether risk increased. `doctor recommend <path>` turns validation, security, and compatibility signals into a prioritized action plan with blocker, high, medium, and info actions. Add `--json` for automation or `--output recommendations.json` to write the report to disk. `doctor trust <path>` creates a local trust score from package lifecycle scripts, dependency specs, and MCP security findings. Use it before release when you want supply-chain risks summarized as one score. `doctor perf <path>` profiles the shared package analysis pipeline and reports per-stage durations for validation, config, security, compatibility, trust, recommendations, and total runtime. Add `--max-total-ms <ms>` or repeatable `--max-stage-ms stage=ms` to fail CI when a budget is exceeded. `doctor mcp <path>` exposes the generic MCP static health report under the doctor command family without starting local MCP servers. `doctor export --bundle <path>` creates a redacted operator handoff bundle that includes validation JSON, security scorecard data, compatibility matrix, recommendations, and trust score in one file. `doctor snapshot` creates a redacted diagnostics bundle with environment health, client config readiness, installed plugin metadata, and next commands. Add `--json` for machine-readable output or `--output doctor-snapshot.json` to write the bundle to disk. `doctor clients` reports local Codex, Claude Desktop, Cursor, Cline, and Windsurf config readiness. `doctor --update-check` compares the installed CLI version with the latest npm version and prints the upgrade command when a newer release is available.
|
|
301
|
+
`doctor` checks the local environment, including package version, platform, Node version, npm global prefix, Codex home, and Codex plugin cache visibility. The text output also includes recommended next commands for self-test, installed plugin discovery, runtime checks, compatibility scoring, and CI setup. `doctor contract` publishes the machine-readable output contract, including public JSON schema surfaces, stable-through-1.0 compatibility metadata, and a frozen rule catalog digest. Add `--json` for automation or `--output output-contract.json` to write the contract to disk. `doctor corpus` runs the bundled validation corpus against healthy runtime, risky security, starter skill, and generic MCP packages, then reports whether each case matched its expected outcome. Add `--json` for automation or `--output validation-corpus.json` to write the corpus report to disk. `doctor npm <package>` runs a preinstall scan by packing the npm package with scripts disabled, extracting the publish tarball, and running validation, security, trust, and recommendation checks against the shipped contents. The JSON report includes tarball metadata such as filename, path, integrity, shasum, sizes, file count, and extracted package root so automation can prove which archive was scanned. Use a published Codex plugin package as the target; scanning `codex-plugin-doctor` itself intentionally reports a missing plugin manifest because this CLI package is not a plugin package. Add `--json` for automation or `--output npm-preinstall.json` to write the report to disk. `doctor attest <path>` creates a local attestation with stable package/report digests, validation/security/compatibility/trust summary, and verification metadata. Add `--sign-key-env NAME` to attach a local HMAC-SHA256 signature without printing the secret, or `--json --output attestation.json` to write the artifact to disk. `doctor attest verify <attestation.json> --target <path> --sign-key-env NAME` recomputes the package fingerprint, report digest, and HMAC signature offline; verification intentionally treats `generatedAt`, `targetPath`, `verification`, and `signature.keyHint` as unsigned display metadata. `doctor runtime-plan <path>` creates a non-executing runtime plan that lists MCP server commands, safe probe methods, risk reasons, and a stable approval digest before any local server is started. Add `--markdown --output runtime-plan.md` to preserve a review-ready approval artifact with the execution boundary, checklist, servers, probes, and risk reasons. `doctor runtime-policy <path>` evaluates the same runtime plan and security signals, then recommends `allow`, `review`, `sandbox_recommended`, or `deny` before local MCP execution starts. `doctor review-bundle <path> --output <dir> --sign-key-env NAME` writes a signed review directory with runtime plan, runtime policy, attestation, release evidence, manifest, Markdown summary files, and SHA-256 file integrity digests. `doctor review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME` verifies the bundle manifest, expected files, manifest integrity digests, runtime artifacts, signed attestation, and signed release evidence offline before a reviewer trusts the handoff. `doctor review-bundle diff --before <dir> --after <dir>` compares two review bundles and flags risk-increasing changes in status, runtime policy, release readiness, signatures, release evidence, and runtime plan digest. `check --runtime --require-runtime-approval --runtime-approval-digest <digest>` refuses to run runtime probes unless the current plan digest matches the approved digest. `doctor release-evidence <path> --sign-key-env NAME` creates one redacted release bundle with signed attestation, offline verification, corpus, performance, security, trust, package metadata, git release gates, and runtime approval status. Strict release evidence requires a clean tagged worktree; use `--allow-dirty` or `--allow-untagged` only for local rehearsal. `doctor release-evidence verify <evidence.json> --target <path> --sign-key-env NAME` verifies a shared release evidence artifact offline against an explicit package path; the artifact target path is treated as display metadata, not trusted input. `doctor release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME` writes a signed release evidence file and prints the `gh release upload` command; add `--upload` to run the upload through GitHub CLI with `--clobber`. `doctor inspector <path>` builds a safe MCP Inspector launch command from a packaged `.mcp.json` file without starting the Inspector proxy automatically. Use `--server <name>` when the package contains multiple MCP server entries. `doctor diff --before <path> --after <path>` compares two package roots and reports new findings, resolved findings, trust score delta, and whether risk increased. `doctor recommend <path>` turns validation, security, and compatibility signals into a prioritized action plan with blocker, high, medium, and info actions. Add `--json` for automation or `--output recommendations.json` to write the report to disk. `doctor trust <path>` creates a local trust score from package lifecycle scripts, dependency specs, and MCP security findings. Use it before release when you want supply-chain risks summarized as one score. `doctor perf <path>` profiles the shared package analysis pipeline and reports per-stage durations for validation, config, security, compatibility, trust, recommendations, and total runtime. Add `--max-total-ms <ms>` or repeatable `--max-stage-ms stage=ms` to fail CI when a budget is exceeded. `doctor mcp <path>` exposes the generic MCP static health report under the doctor command family without starting local MCP servers. `doctor export --bundle <path>` creates a redacted operator handoff bundle that includes validation JSON, security scorecard data, compatibility matrix, recommendations, and trust score in one file. `doctor snapshot` creates a redacted diagnostics bundle with environment health, client config readiness, installed plugin metadata, and next commands. Add `--json` for machine-readable output or `--output doctor-snapshot.json` to write the bundle to disk. `doctor clients` reports local Codex, Claude Desktop, Cursor, Cline, and Windsurf config readiness. `doctor --update-check` compares the installed CLI version with the latest npm version and prints the upgrade command when a newer release is available.
|
|
302
302
|
|
|
303
303
|
`audit --installed` runs a local ecosystem audit against every discovered Codex plugin in the installed plugin cache. Add `--security` to include security scorecards, `--compat` to include the all-client compatibility matrix, and `--json --output local-audit.json` when you want a shareable machine-readable report. Add `--cache` to reuse unchanged plugin results between runs; add `--changed` to only report plugins whose fingerprint changed since the last cached audit. Use `--cache-file path/to/audit-cache.json` when CI or scripted runs need an explicit cache location.
|
|
304
304
|
|
|
@@ -370,9 +370,9 @@ jobs:
|
|
|
370
370
|
runs-on: ubuntu-latest
|
|
371
371
|
steps:
|
|
372
372
|
- uses: actions/checkout@v5
|
|
373
|
-
- uses: Esquetta/CodexPluginDoctor@v1.
|
|
373
|
+
- uses: Esquetta/CodexPluginDoctor@v1.23.0
|
|
374
374
|
with:
|
|
375
|
-
version: "1.
|
|
375
|
+
version: "1.23.0"
|
|
376
376
|
path: .
|
|
377
377
|
runtime: "true"
|
|
378
378
|
policy: codex-publish
|
|
@@ -13,6 +13,18 @@ export interface DoctorNpmPackageReport {
|
|
|
13
13
|
version: string | null;
|
|
14
14
|
fileCount: number | null;
|
|
15
15
|
};
|
|
16
|
+
tarball: {
|
|
17
|
+
filename: string;
|
|
18
|
+
path: string;
|
|
19
|
+
integrity: string | null;
|
|
20
|
+
shasum: string | null;
|
|
21
|
+
size: number | null;
|
|
22
|
+
unpackedSize: number | null;
|
|
23
|
+
fileCount: number | null;
|
|
24
|
+
packageRoot: string;
|
|
25
|
+
packageName: string | null;
|
|
26
|
+
packageVersion: string | null;
|
|
27
|
+
};
|
|
16
28
|
summary: {
|
|
17
29
|
status: "pass" | "warn" | "fail";
|
|
18
30
|
exitCode: 0 | 1;
|
|
@@ -119,6 +119,9 @@ export async function buildDoctorNpmPackageReport(packageSpec, options = {}) {
|
|
|
119
119
|
const packageRoot = await directoryExists(path.join(extractPath, "package"))
|
|
120
120
|
? path.join(extractPath, "package")
|
|
121
121
|
: extractPath;
|
|
122
|
+
const packageName = typeof metadata.name === "string" ? metadata.name : null;
|
|
123
|
+
const packageVersion = typeof metadata.version === "string" ? metadata.version : null;
|
|
124
|
+
const fileCount = Array.isArray(metadata.files) ? metadata.files.length : null;
|
|
122
125
|
const analysis = await buildPackageAnalysis(packageRoot, {
|
|
123
126
|
environment: options.environment
|
|
124
127
|
});
|
|
@@ -129,9 +132,21 @@ export async function buildDoctorNpmPackageReport(packageSpec, options = {}) {
|
|
|
129
132
|
kind: "doctor.npm",
|
|
130
133
|
packageSpec,
|
|
131
134
|
package: {
|
|
132
|
-
name:
|
|
133
|
-
version:
|
|
134
|
-
fileCount
|
|
135
|
+
name: packageName,
|
|
136
|
+
version: packageVersion,
|
|
137
|
+
fileCount
|
|
138
|
+
},
|
|
139
|
+
tarball: {
|
|
140
|
+
filename: path.basename(tarballPath),
|
|
141
|
+
path: tarballPath,
|
|
142
|
+
integrity: typeof metadata.integrity === "string" ? metadata.integrity : null,
|
|
143
|
+
shasum: typeof metadata.shasum === "string" ? metadata.shasum : null,
|
|
144
|
+
size: typeof metadata.size === "number" ? metadata.size : null,
|
|
145
|
+
unpackedSize: typeof metadata.unpackedSize === "number" ? metadata.unpackedSize : null,
|
|
146
|
+
fileCount,
|
|
147
|
+
packageRoot,
|
|
148
|
+
packageName,
|
|
149
|
+
packageVersion
|
|
135
150
|
},
|
|
136
151
|
summary: {
|
|
137
152
|
status: recommendations.status,
|
|
@@ -160,6 +175,7 @@ export function renderDoctorNpmPackageReport(report, options = {}) {
|
|
|
160
175
|
"==========================",
|
|
161
176
|
`Package: ${packageLabel}`,
|
|
162
177
|
`Spec: ${report.packageSpec}`,
|
|
178
|
+
`Tarball: ${report.tarball.filename}`,
|
|
163
179
|
`Status: ${report.summary.status.toUpperCase()}`,
|
|
164
180
|
`Safe to install: ${report.summary.safeToInstall ? "yes" : "no"}`,
|
|
165
181
|
`Security: ${report.security.status.toUpperCase()} (${report.security.score}/100)`,
|
|
@@ -157,7 +157,7 @@ const publicSchemaDefinitions = [
|
|
|
157
157
|
id: "doctor.npm.json",
|
|
158
158
|
command: "codex-plugin-doctor doctor npm <package> --json",
|
|
159
159
|
outputKind: "doctor.npm",
|
|
160
|
-
required: ["schemaVersion", "kind", "generatedAt", "package", "summary", "validation", "security", "trust", "recommendations"]
|
|
160
|
+
required: ["schemaVersion", "kind", "generatedAt", "package", "tarball", "summary", "validation", "security", "trust", "recommendations"]
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
id: "doctor.risk.diff.json",
|
|
@@ -197,6 +197,24 @@ export const ruleCatalog = [
|
|
|
197
197
|
fix: "Pin dependencies through a package manager or check in a reviewed setup script.",
|
|
198
198
|
example: '{ "command": "npx", "args": ["-y", "@scope/server"] }'
|
|
199
199
|
},
|
|
200
|
+
{
|
|
201
|
+
id: "plugin.security.path_traversal_risk",
|
|
202
|
+
category: "security",
|
|
203
|
+
defaultSeverity: "fail",
|
|
204
|
+
summary: "An MCP server passes a package-external path to a path-like runtime argument.",
|
|
205
|
+
why: "Runtime file arguments outside the plugin root can load code or configuration that was not included in the reviewed package.",
|
|
206
|
+
fix: "Keep runtime file arguments inside the plugin package root, or package the referenced file with the plugin.",
|
|
207
|
+
example: '{ "command": "node", "args": ["server.js", "--config", "config/server.json"] }'
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: "plugin.security.dangerous_env_usage",
|
|
211
|
+
category: "security",
|
|
212
|
+
defaultSeverity: "fail",
|
|
213
|
+
summary: "An MCP server sets an environment variable that can alter code loading.",
|
|
214
|
+
why: "Variables such as NODE_OPTIONS, NODE_PATH, PYTHONPATH, LD_PRELOAD, or DYLD_INSERT_LIBRARIES can inject imports, preload native libraries, or load modules outside the reviewed package.",
|
|
215
|
+
fix: "Remove code-loading environment overrides, or keep referenced modules and preload files inside the reviewed plugin package.",
|
|
216
|
+
example: '{ "env": { "OPENAI_API_KEY": "${OPENAI_API_KEY}" } }'
|
|
217
|
+
},
|
|
200
218
|
{
|
|
201
219
|
id: "plugin.security.cwd_outside_root",
|
|
202
220
|
category: "security",
|
|
@@ -41,6 +41,104 @@ function containsPipeInstaller(args) {
|
|
|
41
41
|
/\b(iwr|irm|invoke-webrequest|invoke-restmethod)\b[^|]*\|\s*(iex|invoke-expression)\b/.test(joinedArgs) ||
|
|
42
42
|
/\binvoke-expression\b/.test(joinedArgs));
|
|
43
43
|
}
|
|
44
|
+
const pathLikeArgFlags = new Set([
|
|
45
|
+
"--config",
|
|
46
|
+
"--config-path",
|
|
47
|
+
"--cwd",
|
|
48
|
+
"--dir",
|
|
49
|
+
"--directory",
|
|
50
|
+
"--file",
|
|
51
|
+
"--import",
|
|
52
|
+
"--loader",
|
|
53
|
+
"--path",
|
|
54
|
+
"--project",
|
|
55
|
+
"--require",
|
|
56
|
+
"--root",
|
|
57
|
+
"--script",
|
|
58
|
+
"--tsconfig",
|
|
59
|
+
"--workspace"
|
|
60
|
+
]);
|
|
61
|
+
const codeLoadingEnvKeys = new Set([
|
|
62
|
+
"LD_PRELOAD",
|
|
63
|
+
"DYLD_INSERT_LIBRARIES"
|
|
64
|
+
]);
|
|
65
|
+
const modulePathEnvKeys = new Set([
|
|
66
|
+
"NODE_PATH",
|
|
67
|
+
"PYTHONPATH",
|
|
68
|
+
"RUBYLIB"
|
|
69
|
+
]);
|
|
70
|
+
function looksLikeEnvReference(value) {
|
|
71
|
+
return /^\$\{?[A-Z0-9_]+\}?$/i.test(value.trim());
|
|
72
|
+
}
|
|
73
|
+
function looksLikePathValue(value) {
|
|
74
|
+
const trimmed = value.trim();
|
|
75
|
+
return (trimmed.includes("..") ||
|
|
76
|
+
trimmed.includes("/") ||
|
|
77
|
+
trimmed.includes("\\") ||
|
|
78
|
+
trimmed.startsWith(".") ||
|
|
79
|
+
trimmed.startsWith("~") ||
|
|
80
|
+
path.isAbsolute(trimmed));
|
|
81
|
+
}
|
|
82
|
+
function isEscapingPathValue(rootPath, value) {
|
|
83
|
+
const trimmed = value.trim();
|
|
84
|
+
if (!trimmed ||
|
|
85
|
+
looksLikeEnvReference(trimmed) ||
|
|
86
|
+
/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ||
|
|
87
|
+
!looksLikePathValue(trimmed)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return !isPathWithinRoot(rootPath, path.resolve(rootPath, trimmed));
|
|
91
|
+
}
|
|
92
|
+
function collectEscapingPathArgs(rootPath, args) {
|
|
93
|
+
if (!Array.isArray(args)) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
const stringArgs = args.filter((arg) => typeof arg === "string");
|
|
97
|
+
const findings = [];
|
|
98
|
+
for (let index = 0; index < stringArgs.length; index += 1) {
|
|
99
|
+
const arg = stringArgs[index];
|
|
100
|
+
const equalsIndex = arg.indexOf("=");
|
|
101
|
+
if (equalsIndex > 0) {
|
|
102
|
+
const flag = arg.slice(0, equalsIndex).toLowerCase();
|
|
103
|
+
const value = arg.slice(equalsIndex + 1);
|
|
104
|
+
if (pathLikeArgFlags.has(flag) && isEscapingPathValue(rootPath, value)) {
|
|
105
|
+
findings.push({ flag, value });
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const flag = arg.toLowerCase();
|
|
110
|
+
const value = stringArgs[index + 1];
|
|
111
|
+
if (pathLikeArgFlags.has(flag) && value && isEscapingPathValue(rootPath, value)) {
|
|
112
|
+
findings.push({ flag, value });
|
|
113
|
+
index += 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return findings;
|
|
117
|
+
}
|
|
118
|
+
function splitModulePathEnvValue(value) {
|
|
119
|
+
const trimmed = value.trim();
|
|
120
|
+
if (/^[a-z]:[\\/]/i.test(trimmed)) {
|
|
121
|
+
return trimmed.includes(";") ? trimmed.split(";") : [trimmed];
|
|
122
|
+
}
|
|
123
|
+
return trimmed.includes(";") ? trimmed.split(";") : trimmed.split(":");
|
|
124
|
+
}
|
|
125
|
+
function hasEscapingModulePath(rootPath, value) {
|
|
126
|
+
return splitModulePathEnvValue(value).some((entry) => isEscapingPathValue(rootPath, entry));
|
|
127
|
+
}
|
|
128
|
+
function isDangerousEnvUsage(rootPath, envKey, envValue) {
|
|
129
|
+
if (typeof envValue !== "string" || looksLikeEnvReference(envValue)) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
const normalizedKey = envKey.toUpperCase();
|
|
133
|
+
if (codeLoadingEnvKeys.has(normalizedKey)) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
if (normalizedKey === "NODE_OPTIONS" &&
|
|
137
|
+
/(?:^|\s)--(?:require|import|loader|experimental-loader)(?:=|\s+)/i.test(envValue)) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
return modulePathEnvKeys.has(normalizedKey) && hasEscapingModulePath(rootPath, envValue);
|
|
141
|
+
}
|
|
44
142
|
const poisonScanExtensions = new Set([
|
|
45
143
|
".json",
|
|
46
144
|
".md",
|
|
@@ -84,12 +182,23 @@ export function auditMcpServerConfig(rootPath, parsedConfig) {
|
|
|
84
182
|
if (containsPipeInstaller(args)) {
|
|
85
183
|
findings.push(buildFinding("fail", "plugin.security.remote_pipe_install", `The MCP server \`${serverName}\` appears to pipe remote content into a shell.`, "Download-and-execute install patterns can run unreviewed remote code during plugin startup.", "Pin dependencies through the package manager or check in a reviewed setup script instead of piping remote content to a shell."));
|
|
86
184
|
}
|
|
185
|
+
for (const riskyArg of collectEscapingPathArgs(rootPath, args)) {
|
|
186
|
+
findings.push(buildFinding("fail", "plugin.security.path_traversal_risk", `The MCP server \`${serverName}\` passes \`${riskyArg.value}\` to path-like arg \`${riskyArg.flag}\`, which escapes the plugin root.`, "Path-like runtime arguments that point outside the package can make startup depend on unreviewed local files or load code outside the reviewed tarball.", "Keep runtime file arguments inside the plugin package root, or package the referenced file with the plugin."));
|
|
187
|
+
}
|
|
87
188
|
if (typeof cwd === "string") {
|
|
88
189
|
const cwdPath = path.resolve(rootPath, cwd);
|
|
89
190
|
if (!isPathWithinRoot(rootPath, cwdPath)) {
|
|
90
191
|
findings.push(buildFinding("fail", "plugin.security.cwd_outside_root", `The MCP server \`${serverName}\` sets cwd outside the plugin root.`, "A working directory outside the package root can make server startup depend on unreviewed local files.", "Keep MCP server `cwd` inside the plugin package root or remove it."));
|
|
91
192
|
}
|
|
92
193
|
}
|
|
194
|
+
if (isPlainObject(serverConfig.env)) {
|
|
195
|
+
for (const [envKey, envValue] of Object.entries(serverConfig.env)) {
|
|
196
|
+
if (!isDangerousEnvUsage(rootPath, envKey, envValue)) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
findings.push(buildFinding("fail", "plugin.security.dangerous_env_usage", `The MCP server \`${serverName}\` sets dangerous code-loading env variable \`${envKey}\`.`, "Environment variables that alter module lookup, preload native libraries, or inject runtime imports can execute code outside the reviewed package.", "Remove code-loading environment overrides, or keep referenced modules and preload files inside the reviewed plugin package."));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
93
202
|
if (typeof url === "string" && /^http:\/\//i.test(url)) {
|
|
94
203
|
findings.push(buildFinding("warn", "plugin.security.insecure_http_url", `The MCP server \`${serverName}\` uses an insecure HTTP URL.`, "Plain HTTP transports can expose MCP traffic on non-local networks and make endpoint identity harder to verify.", "Use HTTPS for remote MCP servers; reserve HTTP for explicit localhost development endpoints."));
|
|
95
204
|
}
|
package/package.json
CHANGED