codex-plugin-doctor 1.51.0 → 1.53.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 +17 -2
- package/dist/core/bounded-http-client.d.ts +23 -0
- package/dist/core/bounded-http-client.js +241 -0
- package/dist/core/doctor-export-bundle.js +3 -1
- package/dist/core/output-contract.js +50 -1
- package/dist/core/release-check.d.ts +4 -0
- package/dist/core/release-check.js +4 -0
- package/dist/core/release-evidence.d.ts +4 -0
- package/dist/core/release-evidence.js +113 -7
- package/dist/core/remote-mcp-probe.d.ts +17 -0
- package/dist/core/remote-mcp-probe.js +284 -0
- package/dist/core/remote-network-policy.d.ts +24 -0
- package/dist/core/remote-network-policy.js +125 -0
- package/dist/core/remote-oauth-readiness.d.ts +12 -0
- package/dist/core/remote-oauth-readiness.js +295 -0
- package/dist/core/remote-transport-reliability.d.ts +18 -0
- package/dist/core/remote-transport-reliability.js +243 -0
- package/dist/core/remote-url-policy.d.ts +7 -0
- package/dist/core/remote-url-policy.js +49 -0
- package/dist/core/runtime-plan.d.ts +3 -0
- package/dist/core/runtime-plan.js +34 -2
- package/dist/core/runtime-policy.js +5 -1
- package/dist/core/runtime-probe.d.ts +10 -1
- package/dist/core/runtime-probe.js +89 -29
- package/dist/core/sse-observation.d.ts +7 -0
- package/dist/core/sse-observation.js +67 -0
- package/dist/core/validate-plugin.js +35 -3
- package/dist/domain/types.d.ts +26 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/mcp/generic-mcp-doctor.d.ts +4 -0
- package/dist/mcp/generic-mcp-doctor.js +15 -5
- package/dist/reporting/render-markdown-report.js +38 -12
- package/dist/reporting/render-text-report.js +32 -10
- package/dist/rules/rule-catalog.js +289 -1
- package/dist/run-cli.js +172 -5
- package/dist/security/security-audit.js +34 -7
- package/package.json +1 -1
package/dist/run-cli.js
CHANGED
|
@@ -99,11 +99,78 @@ function parseRuntimeSandbox(flags, options = { requireRuntime: true }) {
|
|
|
99
99
|
}
|
|
100
100
|
return value;
|
|
101
101
|
}
|
|
102
|
+
function parseRemoteNetworkFlags(flags, runtime) {
|
|
103
|
+
let allowNetwork = false;
|
|
104
|
+
let allowLocalNetwork = false;
|
|
105
|
+
let allowSessionLifecycle = false;
|
|
106
|
+
let requireRemoteReliability = false;
|
|
107
|
+
for (let index = 0; index < flags.length; index += 1) {
|
|
108
|
+
const flag = flags[index];
|
|
109
|
+
if (flag === "--allow-network") {
|
|
110
|
+
if (allowNetwork)
|
|
111
|
+
return new CliUsageError("Duplicate runtime network flag: --allow-network.");
|
|
112
|
+
if (flags[index + 1] && !flags[index + 1].startsWith("--")) {
|
|
113
|
+
return new CliUsageError("--allow-network does not accept a value.");
|
|
114
|
+
}
|
|
115
|
+
allowNetwork = true;
|
|
116
|
+
}
|
|
117
|
+
else if (flag === "--allow-local-network") {
|
|
118
|
+
if (allowLocalNetwork)
|
|
119
|
+
return new CliUsageError("Duplicate runtime network flag: --allow-local-network.");
|
|
120
|
+
if (flags[index + 1] && !flags[index + 1].startsWith("--")) {
|
|
121
|
+
return new CliUsageError("--allow-local-network does not accept a value.");
|
|
122
|
+
}
|
|
123
|
+
allowLocalNetwork = true;
|
|
124
|
+
}
|
|
125
|
+
else if (flag === "--allow-session-lifecycle") {
|
|
126
|
+
if (allowSessionLifecycle)
|
|
127
|
+
return new CliUsageError("Duplicate runtime remote flag: --allow-session-lifecycle.");
|
|
128
|
+
if (flags[index + 1] && !flags[index + 1].startsWith("--")) {
|
|
129
|
+
return new CliUsageError("--allow-session-lifecycle does not accept a value.");
|
|
130
|
+
}
|
|
131
|
+
allowSessionLifecycle = true;
|
|
132
|
+
}
|
|
133
|
+
else if (flag === "--require-remote-reliability") {
|
|
134
|
+
if (requireRemoteReliability)
|
|
135
|
+
return new CliUsageError("Duplicate runtime remote flag: --require-remote-reliability.");
|
|
136
|
+
if (flags[index + 1] && !flags[index + 1].startsWith("--")) {
|
|
137
|
+
return new CliUsageError("--require-remote-reliability does not accept a value.");
|
|
138
|
+
}
|
|
139
|
+
requireRemoteReliability = true;
|
|
140
|
+
}
|
|
141
|
+
else if (flag?.startsWith("--allow-network=") ||
|
|
142
|
+
flag?.startsWith("--allow-local-network=") ||
|
|
143
|
+
flag?.startsWith("--allow-session-lifecycle=") ||
|
|
144
|
+
flag?.startsWith("--require-remote-reliability=")) {
|
|
145
|
+
return new CliUsageError(`${flag.split("=", 1)[0]} does not accept a value.`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (allowSessionLifecycle && !runtime) {
|
|
149
|
+
return new CliUsageError("--allow-session-lifecycle requires --runtime.");
|
|
150
|
+
}
|
|
151
|
+
if (requireRemoteReliability && !runtime) {
|
|
152
|
+
return new CliUsageError("--require-remote-reliability requires --runtime and --allow-network.");
|
|
153
|
+
}
|
|
154
|
+
if ((allowNetwork || allowLocalNetwork) && !runtime) {
|
|
155
|
+
return new CliUsageError("--allow-network requires --runtime.");
|
|
156
|
+
}
|
|
157
|
+
if (allowLocalNetwork && !allowNetwork) {
|
|
158
|
+
return new CliUsageError("--allow-local-network requires --allow-network.");
|
|
159
|
+
}
|
|
160
|
+
if (allowSessionLifecycle && !allowNetwork) {
|
|
161
|
+
return new CliUsageError("--allow-session-lifecycle requires --allow-network.");
|
|
162
|
+
}
|
|
163
|
+
if (requireRemoteReliability && !allowNetwork) {
|
|
164
|
+
return new CliUsageError("--require-remote-reliability requires --runtime and --allow-network.");
|
|
165
|
+
}
|
|
166
|
+
return { allowNetwork, allowLocalNetwork, allowSessionLifecycle, requireRemoteReliability };
|
|
167
|
+
}
|
|
102
168
|
function printUsage(io) {
|
|
103
|
-
io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--policy codex-publish|mcp-strict|security] [--compat] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime] [--sandbox docker] [--require-runtime-approval --runtime-approval-digest <digest>] [--verbose-runtime] [--explain] [--no-animations] [--ascii] [--changed-since <ref>]\n codex-plugin-doctor audit --installed [filter] [--policy codex-publish|mcp-strict|security] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\n codex-plugin-doctor audit deps <path> [--policy codex-publish|mcp-strict|security] [--recommend] [--json|--sarif] [--output <path>]\n codex-plugin-doctor mcp <path> [--runtime] [--json] [--output <path>]\n codex-plugin-doctor security <path> [--policy security] [--json|--scorecard]\n codex-plugin-doctor compat <path> [--all|--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor suppress add <path> [--fingerprint <sha256> --reason <text> --expires-at YYYY-MM-DD] [--config <path>] [--json]\n codex-plugin-doctor suppress list <path> [--config <path>] [--json]\n codex-plugin-doctor suppress remove <path> [--fingerprint <sha256>|--index <n>] [--config <path>] [--json]\n codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor watch <path> [--runtime] [--json] [--output <path>] [--debounce-ms <ms>] [--max-iterations <n>] [--fail-fast] [--accumulate-json <path>]\n codex-plugin-doctor doctor [npm <package>|contract|corpus [--manifest <corpus.json>] [--json] [--output <path>]|corpus metrics --manifest <corpus.json> [--json|--markdown] [--output <path>] [--min-precision <0..1>] [--min-recall <0..1>] [--max-false-positive-rate <0..1>]|runtime-plan <path> [--
|
|
169
|
+
io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--policy codex-publish|mcp-strict|security] [--compat] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime [--allow-network [--allow-local-network]]] [--sandbox docker] [--require-runtime-approval --runtime-approval-digest <digest>] [--verbose-runtime] [--explain] [--no-animations] [--ascii] [--changed-since <ref>]\n codex-plugin-doctor audit --installed [filter] [--policy codex-publish|mcp-strict|security] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\n codex-plugin-doctor audit deps <path> [--policy codex-publish|mcp-strict|security] [--recommend] [--json|--sarif] [--output <path>]\n codex-plugin-doctor mcp <path> [--runtime [--allow-network [--allow-local-network]]] [--json] [--output <path>]\n codex-plugin-doctor security <path> [--policy security] [--json|--scorecard]\n codex-plugin-doctor compat <path> [--all|--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor suppress add <path> [--fingerprint <sha256> --reason <text> --expires-at YYYY-MM-DD] [--config <path>] [--json]\n codex-plugin-doctor suppress list <path> [--config <path>] [--json]\n codex-plugin-doctor suppress remove <path> [--fingerprint <sha256>|--index <n>] [--config <path>] [--json]\n codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor watch <path> [--runtime] [--json] [--output <path>] [--debounce-ms <ms>] [--max-iterations <n>] [--fail-fast] [--accumulate-json <path>]\n codex-plugin-doctor doctor [npm <package>|contract|corpus [--manifest <corpus.json>] [--json] [--output <path>]|corpus metrics --manifest <corpus.json> [--json|--markdown] [--output <path>] [--min-precision <0..1>] [--min-recall <0..1>] [--max-false-positive-rate <0..1>]|runtime-plan <path> [--sandbox docker] [--json|--markdown] [--output <path>]|runtime-policy <path> [--sandbox docker] [--json] [--output <path>]|review-bundle <path> --output <dir> --sign-key-env NAME [--json] [--allow-dirty] [--allow-untagged]|review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME [--json] [--output <path>] [--failures-only]|review-bundle diff --before <dir> --after <dir> [--json]|attest <path> [--sign-key-env NAME]|attest verify <attestation.json> --target <path> --sign-key-env NAME|release-evidence <path> --sign-key-env NAME [--runtime [--allow-network [--allow-local-network]]] [--sandbox docker] [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>]|release-evidence verify <evidence.json> --target <path> --sign-key-env NAME|release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME [--runtime [--allow-network [--allow-local-network]]] [--sandbox docker] [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>] [--upload]|mcp <path> [--runtime [--allow-network [--allow-local-network]]]|inspector <path>|diff --before <path> --after <path>|recommend <path>|trust <path>|perf <path> [--max-total-ms <ms>] [--max-stage-ms stage=ms]|export --bundle <path>|snapshot|clients|--json|--update-check]\n codex-plugin-doctor init [path] [--template skill-only|mcp-stdio|mcp-http|full-runtime]\n codex-plugin-doctor init-ci [path]\n codex-plugin-doctor init-git-hooks [path] [--force] [--json]\n codex-plugin-doctor init-git-hooks [path] --remove [--json]\n codex-plugin-doctor completion bash|zsh|fish\n codex-plugin-doctor config validate <path> [--json]\n codex-plugin-doctor release check <path> [--json] [--runtime [--allow-network [--allow-local-network]]] [--sandbox docker]\n codex-plugin-doctor self-test\n codex-plugin-doctor list --installed\n codex-plugin-doctor explain <finding-id>\n codex-plugin-doctor --version\n\nFirst run:\n codex-plugin-doctor doctor\n codex-plugin-doctor self-test\n codex-plugin-doctor init my-plugin\n codex-plugin-doctor check . --runtime --explain");
|
|
104
170
|
io.writeStderr("Corpus quality regression: codex-plugin-doctor doctor corpus metrics diff --before <metrics.json> --after <metrics.json> [--fail-on-regression] [--json|--markdown] [--output <path>]");
|
|
105
171
|
io.writeStderr("Suppression governance: codex-plugin-doctor suppress list <path> [--fail-on-expired] [--fail-on-invalid] [--warn-expiring-within-days <days>]\n codex-plugin-doctor suppress prune <path> [--apply] [--json]");
|
|
106
172
|
io.writeStderr("Baseline gating: codex-plugin-doctor baseline create <path> --output <path> [--runtime]\n codex-plugin-doctor check <path> --baseline <path>");
|
|
173
|
+
io.writeStderr("Remote MCP runtime flags (check, mcp, release check, and doctor release-evidence): --runtime --allow-network [--allow-local-network] [--allow-session-lifecycle] [--require-remote-reliability]\n --allow-session-lifecycle requires --runtime --allow-network and can terminate a remote session.\n --require-remote-reliability requires --runtime --allow-network, blocks non-pass reliability, and does not grant network consent.");
|
|
107
174
|
}
|
|
108
175
|
const suppressUsageText = [
|
|
109
176
|
"Usage:",
|
|
@@ -652,6 +719,10 @@ function buildGenericMcpDoctorCommandArgs(commandTarget, flags) {
|
|
|
652
719
|
let jsonOutput = false;
|
|
653
720
|
let outputPath = null;
|
|
654
721
|
let runtime = false;
|
|
722
|
+
let allowNetwork = false;
|
|
723
|
+
let allowLocalNetwork = false;
|
|
724
|
+
let allowSessionLifecycle = false;
|
|
725
|
+
let requireRemoteReliability = false;
|
|
655
726
|
for (let index = 0; index < flags.length; index += 1) {
|
|
656
727
|
const flag = flags[index];
|
|
657
728
|
if (flag === "--runtime") {
|
|
@@ -668,6 +739,38 @@ function buildGenericMcpDoctorCommandArgs(commandTarget, flags) {
|
|
|
668
739
|
jsonOutput = true;
|
|
669
740
|
continue;
|
|
670
741
|
}
|
|
742
|
+
if (flag === "--allow-network") {
|
|
743
|
+
if (allowNetwork)
|
|
744
|
+
return "Duplicate MCP flag: --allow-network.";
|
|
745
|
+
allowNetwork = true;
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (flag === "--allow-local-network") {
|
|
749
|
+
if (allowLocalNetwork)
|
|
750
|
+
return "Duplicate MCP flag: --allow-local-network.";
|
|
751
|
+
allowLocalNetwork = true;
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
if (flag === "--allow-session-lifecycle") {
|
|
755
|
+
if (flags[index + 1] && !flags[index + 1].startsWith("--")) {
|
|
756
|
+
return "--allow-session-lifecycle does not accept a value.";
|
|
757
|
+
}
|
|
758
|
+
allowSessionLifecycle = true;
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
if (flag === "--require-remote-reliability") {
|
|
762
|
+
if (flags[index + 1] && !flags[index + 1].startsWith("--")) {
|
|
763
|
+
return "--require-remote-reliability does not accept a value.";
|
|
764
|
+
}
|
|
765
|
+
requireRemoteReliability = true;
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
if (flag.startsWith("--allow-network=") ||
|
|
769
|
+
flag.startsWith("--allow-local-network=") ||
|
|
770
|
+
flag.startsWith("--allow-session-lifecycle=") ||
|
|
771
|
+
flag.startsWith("--require-remote-reliability=")) {
|
|
772
|
+
return `${flag.split("=", 1)[0]} does not accept a value.`;
|
|
773
|
+
}
|
|
671
774
|
if (flag === "--output") {
|
|
672
775
|
if (outputPath !== null) {
|
|
673
776
|
return "Duplicate MCP flag: --output.";
|
|
@@ -684,11 +787,19 @@ function buildGenericMcpDoctorCommandArgs(commandTarget, flags) {
|
|
|
684
787
|
? `Unknown MCP flag: ${flag}.`
|
|
685
788
|
: `Unexpected MCP argument: ${flag}.`;
|
|
686
789
|
}
|
|
790
|
+
const remoteNetwork = parseRemoteNetworkFlags(flags, runtime);
|
|
791
|
+
if (remoteNetwork instanceof CliUsageError) {
|
|
792
|
+
return remoteNetwork.message;
|
|
793
|
+
}
|
|
687
794
|
return {
|
|
688
795
|
targetPath: commandTarget,
|
|
689
796
|
jsonOutput,
|
|
690
797
|
outputPath,
|
|
691
|
-
runtime
|
|
798
|
+
runtime,
|
|
799
|
+
allowNetwork: remoteNetwork.allowNetwork,
|
|
800
|
+
allowLocalNetwork: remoteNetwork.allowLocalNetwork,
|
|
801
|
+
allowSessionLifecycle,
|
|
802
|
+
requireRemoteReliability
|
|
692
803
|
};
|
|
693
804
|
}
|
|
694
805
|
function parsePerformanceThresholds(flags) {
|
|
@@ -976,7 +1087,11 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
976
1087
|
env: terminalContext.env,
|
|
977
1088
|
platform: terminalContext.platform
|
|
978
1089
|
}, {
|
|
979
|
-
runtime: parsedMcpArgs.runtime
|
|
1090
|
+
runtime: parsedMcpArgs.runtime,
|
|
1091
|
+
allowNetwork: parsedMcpArgs.allowNetwork,
|
|
1092
|
+
allowLocalNetwork: parsedMcpArgs.allowLocalNetwork,
|
|
1093
|
+
allowSessionLifecycle: parsedMcpArgs.allowSessionLifecycle,
|
|
1094
|
+
requireRemoteReliability: parsedMcpArgs.requireRemoteReliability
|
|
980
1095
|
});
|
|
981
1096
|
const renderedReport = parsedMcpArgs.jsonOutput
|
|
982
1097
|
? renderGenericMcpDoctorJson(report)
|
|
@@ -1218,7 +1333,12 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1218
1333
|
? null
|
|
1219
1334
|
: assetFlags[runtimeApprovalDigestIndex + 1];
|
|
1220
1335
|
const runtime = assetFlags.includes("--runtime");
|
|
1336
|
+
const remoteNetwork = parseRemoteNetworkFlags(assetFlags, runtime);
|
|
1221
1337
|
let runtimeSandbox;
|
|
1338
|
+
if (remoteNetwork instanceof CliUsageError) {
|
|
1339
|
+
io.writeStderr(remoteNetwork.message);
|
|
1340
|
+
return 2;
|
|
1341
|
+
}
|
|
1222
1342
|
try {
|
|
1223
1343
|
runtimeSandbox = parseRuntimeSandbox(assetFlags);
|
|
1224
1344
|
}
|
|
@@ -1282,6 +1402,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1282
1402
|
requireRuntimeApproval,
|
|
1283
1403
|
runtimeApprovalDigest,
|
|
1284
1404
|
runtime,
|
|
1405
|
+
...(remoteNetwork.allowNetwork ? { allowNetwork: true } : {}),
|
|
1406
|
+
...(remoteNetwork.allowLocalNetwork ? { allowLocalNetwork: true } : {}),
|
|
1407
|
+
...(remoteNetwork.allowSessionLifecycle ? { allowSessionLifecycle: true } : {}),
|
|
1408
|
+
...(remoteNetwork.requireRemoteReliability ? { requireRemoteReliability: true } : {}),
|
|
1285
1409
|
...(runtimeSandbox ? { sandbox: runtimeSandbox } : {}),
|
|
1286
1410
|
environment: {
|
|
1287
1411
|
env: terminalContext.env,
|
|
@@ -1290,6 +1414,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1290
1414
|
runCheck: options.runCheckImpl ?? runCheck,
|
|
1291
1415
|
performanceThresholds: parsedThresholds.thresholds
|
|
1292
1416
|
});
|
|
1417
|
+
if (!evidence.releaseReady) {
|
|
1418
|
+
io.writeStdout(jsonOutput ? renderDoctorReleaseEvidenceJson(evidence) : renderDoctorReleaseEvidence(evidence));
|
|
1419
|
+
return evidence.exitCode;
|
|
1420
|
+
}
|
|
1293
1421
|
await writeFile(resolvedOutputPath, renderDoctorReleaseEvidenceJson(evidence), "utf8");
|
|
1294
1422
|
let uploaded = false;
|
|
1295
1423
|
const uploadArgs = ["release", "upload", tag, resolvedOutputPath, "--clobber"];
|
|
@@ -1387,7 +1515,12 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1387
1515
|
? null
|
|
1388
1516
|
: evidenceFlags[runtimeApprovalDigestIndex + 1];
|
|
1389
1517
|
const runtime = evidenceFlags.includes("--runtime");
|
|
1518
|
+
const remoteNetwork = parseRemoteNetworkFlags(evidenceFlags, runtime);
|
|
1390
1519
|
let runtimeSandbox;
|
|
1520
|
+
if (remoteNetwork instanceof CliUsageError) {
|
|
1521
|
+
io.writeStderr(remoteNetwork.message);
|
|
1522
|
+
return 2;
|
|
1523
|
+
}
|
|
1391
1524
|
try {
|
|
1392
1525
|
runtimeSandbox = parseRuntimeSandbox(evidenceFlags);
|
|
1393
1526
|
}
|
|
@@ -1434,6 +1567,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1434
1567
|
requireRuntimeApproval,
|
|
1435
1568
|
runtimeApprovalDigest,
|
|
1436
1569
|
runtime,
|
|
1570
|
+
...(remoteNetwork.allowNetwork ? { allowNetwork: true } : {}),
|
|
1571
|
+
...(remoteNetwork.allowLocalNetwork ? { allowLocalNetwork: true } : {}),
|
|
1572
|
+
...(remoteNetwork.allowSessionLifecycle ? { allowSessionLifecycle: true } : {}),
|
|
1573
|
+
...(remoteNetwork.requireRemoteReliability ? { requireRemoteReliability: true } : {}),
|
|
1437
1574
|
...(runtimeSandbox ? { sandbox: runtimeSandbox } : {}),
|
|
1438
1575
|
environment: {
|
|
1439
1576
|
env: terminalContext.env,
|
|
@@ -2257,7 +2394,11 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
2257
2394
|
env: terminalContext.env,
|
|
2258
2395
|
platform: terminalContext.platform
|
|
2259
2396
|
}, {
|
|
2260
|
-
runtime: parsedMcpArgs.runtime
|
|
2397
|
+
runtime: parsedMcpArgs.runtime,
|
|
2398
|
+
allowNetwork: parsedMcpArgs.allowNetwork,
|
|
2399
|
+
allowLocalNetwork: parsedMcpArgs.allowLocalNetwork,
|
|
2400
|
+
allowSessionLifecycle: parsedMcpArgs.allowSessionLifecycle,
|
|
2401
|
+
requireRemoteReliability: parsedMcpArgs.requireRemoteReliability
|
|
2261
2402
|
});
|
|
2262
2403
|
const renderedReport = parsedMcpArgs.jsonOutput
|
|
2263
2404
|
? renderGenericMcpDoctorJson(report)
|
|
@@ -2534,7 +2675,12 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
2534
2675
|
: remainingArgs.slice(1);
|
|
2535
2676
|
const jsonOutput = releaseFlags.includes("--json");
|
|
2536
2677
|
const runtimeProbeEnabled = releaseFlags.includes("--runtime");
|
|
2678
|
+
const remoteNetwork = parseRemoteNetworkFlags(releaseFlags, runtimeProbeEnabled);
|
|
2537
2679
|
let runtimeSandbox;
|
|
2680
|
+
if (remoteNetwork instanceof CliUsageError) {
|
|
2681
|
+
io.writeStderr(remoteNetwork.message);
|
|
2682
|
+
return 2;
|
|
2683
|
+
}
|
|
2538
2684
|
try {
|
|
2539
2685
|
runtimeSandbox = parseRuntimeSandbox(releaseFlags);
|
|
2540
2686
|
}
|
|
@@ -2571,6 +2717,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
2571
2717
|
env: terminalContext.env,
|
|
2572
2718
|
platform: terminalContext.platform,
|
|
2573
2719
|
runtime: runtimeProbeEnabled,
|
|
2720
|
+
...(remoteNetwork.allowNetwork ? { allowNetwork: true } : {}),
|
|
2721
|
+
...(remoteNetwork.allowLocalNetwork ? { allowLocalNetwork: true } : {}),
|
|
2722
|
+
...(remoteNetwork.allowSessionLifecycle ? { allowSessionLifecycle: true } : {}),
|
|
2723
|
+
...(remoteNetwork.requireRemoteReliability ? { requireRemoteReliability: true } : {}),
|
|
2574
2724
|
...(runtimeSandbox ? { runtimeSandbox } : {}),
|
|
2575
2725
|
runCheck: options.runCheckImpl
|
|
2576
2726
|
});
|
|
@@ -2717,6 +2867,11 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
2717
2867
|
const effectiveRuntimeProbeEnabled = runtimeProbeEnabled ||
|
|
2718
2868
|
checkProfile === "publish" ||
|
|
2719
2869
|
policyEnablesRuntime(policy);
|
|
2870
|
+
const remoteNetwork = parseRemoteNetworkFlags(normalizedFlags, effectiveRuntimeProbeEnabled);
|
|
2871
|
+
if (remoteNetwork instanceof CliUsageError) {
|
|
2872
|
+
io.writeStderr(remoteNetwork.message);
|
|
2873
|
+
return 2;
|
|
2874
|
+
}
|
|
2720
2875
|
if (requireRuntimeApproval && !effectiveRuntimeProbeEnabled) {
|
|
2721
2876
|
io.writeStderr("Runtime approval requires runtime probing. Add --runtime, --profile publish, or a runtime-enabled policy.");
|
|
2722
2877
|
return 2;
|
|
@@ -2778,6 +2933,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
2778
2933
|
plugin,
|
|
2779
2934
|
result: applyDoctorConfig(await runCheckImpl(plugin.rootPath, {
|
|
2780
2935
|
runtime: effectiveRuntimeProbeEnabled,
|
|
2936
|
+
...(remoteNetwork.allowNetwork ? { allowNetwork: true } : {}),
|
|
2937
|
+
...(remoteNetwork.allowLocalNetwork ? { allowLocalNetwork: true } : {}),
|
|
2938
|
+
...(remoteNetwork.allowSessionLifecycle ? { allowSessionLifecycle: true } : {}),
|
|
2939
|
+
...(remoteNetwork.requireRemoteReliability ? { requireRemoteReliability: true } : {}),
|
|
2781
2940
|
runtimeTranscript: effectiveRuntimeProbeEnabled && verboseRuntime
|
|
2782
2941
|
? (line) => io.writeStderr(line)
|
|
2783
2942
|
: undefined
|
|
@@ -2847,7 +3006,11 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
2847
3006
|
continue;
|
|
2848
3007
|
}
|
|
2849
3008
|
const pluginResult = applyDoctorConfig(await runCheckImpl(pluginRoot, {
|
|
2850
|
-
runtime: effectiveRuntimeProbeEnabled
|
|
3009
|
+
runtime: effectiveRuntimeProbeEnabled,
|
|
3010
|
+
...(remoteNetwork.allowNetwork ? { allowNetwork: true } : {}),
|
|
3011
|
+
...(remoteNetwork.allowLocalNetwork ? { allowLocalNetwork: true } : {}),
|
|
3012
|
+
...(remoteNetwork.allowSessionLifecycle ? { allowSessionLifecycle: true } : {}),
|
|
3013
|
+
...(remoteNetwork.requireRemoteReliability ? { requireRemoteReliability: true } : {})
|
|
2851
3014
|
}), applyPolicyToDoctorConfig(applyCheckProfile(await loadDoctorConfig(pluginRoot, configPath), checkProfile), policy));
|
|
2852
3015
|
io.writeStdout(`\n${pluginResult.targetPath}: ${pluginResult.status.toUpperCase()}`);
|
|
2853
3016
|
validatedCount += 1;
|
|
@@ -2869,6 +3032,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
2869
3032
|
const doctorConfig = applyPolicyToDoctorConfig(applyCheckProfile(await loadDoctorConfig(targetPath, configPath), checkProfile), policy);
|
|
2870
3033
|
const configuredResult = applyDoctorConfig(await runCheckImpl(targetPath, {
|
|
2871
3034
|
runtime: effectiveRuntimeProbeEnabled,
|
|
3035
|
+
...(remoteNetwork.allowNetwork ? { allowNetwork: true } : {}),
|
|
3036
|
+
...(remoteNetwork.allowLocalNetwork ? { allowLocalNetwork: true } : {}),
|
|
3037
|
+
...(remoteNetwork.allowSessionLifecycle ? { allowSessionLifecycle: true } : {}),
|
|
3038
|
+
...(remoteNetwork.requireRemoteReliability ? { requireRemoteReliability: true } : {}),
|
|
2872
3039
|
...(runtimeSandbox ? { runtimeSandbox } : {}),
|
|
2873
3040
|
...(effectiveRuntimeProbeEnabled && verboseRuntime
|
|
2874
3041
|
? { runtimeTranscript: (line) => io.writeStderr(line) }
|
|
@@ -2,6 +2,7 @@ import { readFile, readdir, stat } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { discoverPackage } from "../core/discover-package.js";
|
|
4
4
|
import { readJsonFile } from "../core/read-json-file.js";
|
|
5
|
+
import { inspectRemoteMcpUrl } from "../core/remote-url-policy.js";
|
|
5
6
|
import { validatePlugin } from "../core/validate-plugin.js";
|
|
6
7
|
import { formatFindingFingerprintLine, withFindingFingerprints } from "../reporting/finding-fingerprint.js";
|
|
7
8
|
import { formatFindingEvidenceLine } from "../reporting/format-finding-evidence.js";
|
|
@@ -18,6 +19,16 @@ function buildFinding(severity, id, message, impact, suggestedFix, evidence) {
|
|
|
18
19
|
function isPlainObject(value) {
|
|
19
20
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20
21
|
}
|
|
22
|
+
function remoteUrlIssueFindingId(issue) {
|
|
23
|
+
return issue === "insecure_non_loopback"
|
|
24
|
+
? "plugin.security.insecure_http_url"
|
|
25
|
+
: `plugin.security.remote_mcp_url.${issue}`;
|
|
26
|
+
}
|
|
27
|
+
function remoteUrlIssueMessage(issue) {
|
|
28
|
+
return issue === "insecure_non_loopback"
|
|
29
|
+
? "uses an insecure public HTTP URL"
|
|
30
|
+
: `uses a remote MCP URL with ${issue.replaceAll("_", " ")}`;
|
|
31
|
+
}
|
|
21
32
|
function isPathWithinRoot(rootPath, candidatePath) {
|
|
22
33
|
const relativePath = path.relative(rootPath, candidatePath);
|
|
23
34
|
return (relativePath === "" ||
|
|
@@ -245,19 +256,33 @@ export function auditMcpServerConfig(rootPath, parsedConfig, options = {}) {
|
|
|
245
256
|
}));
|
|
246
257
|
}
|
|
247
258
|
}
|
|
248
|
-
if (typeof url === "string"
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
259
|
+
if (typeof url === "string") {
|
|
260
|
+
const inspection = inspectRemoteMcpUrl(url);
|
|
261
|
+
for (const issue of inspection.issues) {
|
|
262
|
+
findings.push(buildFinding("fail", remoteUrlIssueFindingId(issue), `The MCP server \`${serverName}\` ${remoteUrlIssueMessage(issue)}.`, "Unsafe or ambiguous remote transport configuration can expose credentials or prevent reliable MCP connectivity.", "Use an absolute HTTPS URL without credentials, query parameters, fragments, or numeric IP literals; HTTP is only supported for localhost development.", {
|
|
263
|
+
serverName,
|
|
264
|
+
configPath,
|
|
265
|
+
url: inspection.sanitizedUrl
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
if (inspection.parsedUrl?.hostname === "0.0.0.0") {
|
|
269
|
+
findings.push(buildFinding("warn", "plugin.security.mcp_binds_all_interfaces", `The MCP server \`${serverName}\` URL binds to \`0.0.0.0\`.`, "Servers that listen on all interfaces can accept connections from external hosts, which is rarely intended for local MCP development.", "Use `127.0.0.1` or `localhost` instead of `0.0.0.0` unless external access is explicitly required.", {
|
|
270
|
+
serverName,
|
|
271
|
+
configPath,
|
|
272
|
+
url: inspection.sanitizedUrl
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
253
275
|
}
|
|
254
276
|
}
|
|
255
277
|
return findings;
|
|
256
278
|
}
|
|
257
279
|
const externalUrlPattern = /https?:\/\/[^\s`"'<>)]+/gi;
|
|
258
|
-
async function auditSkillExternalReferences(rootPath) {
|
|
280
|
+
async function auditSkillExternalReferences(rootPath, mcpConfigPath) {
|
|
259
281
|
const findings = [];
|
|
260
282
|
for (const filePath of await collectPromptPoisoningScanFiles(rootPath)) {
|
|
283
|
+
if (mcpConfigPath !== null && path.resolve(filePath) === mcpConfigPath) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
261
286
|
const content = await readFile(filePath, "utf8");
|
|
262
287
|
const matches = content.match(externalUrlPattern);
|
|
263
288
|
if (!matches) {
|
|
@@ -422,7 +447,9 @@ export async function buildSecurityAudit(targetPath) {
|
|
|
422
447
|
...(await auditMcpCommandSurface(discoveredPackage)),
|
|
423
448
|
...(await auditChildProcessSourceSurface(discoveredPackage.rootPath)),
|
|
424
449
|
...(await auditPromptPoisoningSurface(discoveredPackage.rootPath)),
|
|
425
|
-
...(await auditSkillExternalReferences(discoveredPackage.rootPath
|
|
450
|
+
...(await auditSkillExternalReferences(discoveredPackage.rootPath, discoveredPackage.manifest.mcpServers
|
|
451
|
+
? path.resolve(discoveredPackage.rootPath, discoveredPackage.manifest.mcpServers)
|
|
452
|
+
: null))
|
|
426
453
|
];
|
|
427
454
|
return buildSecurityAuditFromFindings(discoveredPackage.rootPath, findings);
|
|
428
455
|
}
|
package/package.json
CHANGED