codex-plugin-doctor 1.32.0 → 1.34.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 +9 -7
- package/dist/core/dep-audit.d.ts +15 -2
- package/dist/core/dep-audit.js +112 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/policy/policy-packs.d.ts +2 -0
- package/dist/policy/policy-packs.js +9 -0
- package/dist/rules/rule-catalog.js +9 -0
- package/dist/run-cli.js +30 -8
- package/dist/security/security-audit.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -234,10 +234,12 @@ codex-plugin-doctor audit --installed
|
|
|
234
234
|
codex-plugin-doctor audit --installed --security --compat
|
|
235
235
|
codex-plugin-doctor audit --installed --security --compat --json --output local-audit.json
|
|
236
236
|
codex-plugin-doctor audit --installed --security --compat --cache
|
|
237
|
-
codex-plugin-doctor audit --installed --changed --cache
|
|
238
|
-
codex-plugin-doctor audit deps .
|
|
239
|
-
codex-plugin-doctor audit deps . --json --output dependency-audit.json
|
|
240
|
-
codex-plugin-doctor
|
|
237
|
+
codex-plugin-doctor audit --installed --changed --cache
|
|
238
|
+
codex-plugin-doctor audit deps .
|
|
239
|
+
codex-plugin-doctor audit deps . --json --output dependency-audit.json
|
|
240
|
+
codex-plugin-doctor audit deps . --recommend
|
|
241
|
+
codex-plugin-doctor audit deps . --recommend --json --output dependency-remediation.json
|
|
242
|
+
codex-plugin-doctor watch .
|
|
241
243
|
codex-plugin-doctor watch . --json --output watch-latest.json --debounce-ms 500
|
|
242
244
|
codex-plugin-doctor init-git-hooks .
|
|
243
245
|
codex-plugin-doctor init-git-hooks . --json
|
|
@@ -302,7 +304,7 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
|
|
|
302
304
|
|
|
303
305
|
`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
306
|
|
|
305
|
-
`audit deps <path>` runs `npm audit --json` for the target package and normalizes dependency vulnerability results into a stable `schemaVersion: "1.0.0"` report. Use `--json --output dependency-audit.json` when CI needs machine-readable status, vulnerability counts, and the raw npm audit payload.
|
|
307
|
+
`audit deps <path>` runs `npm audit --json` for the target package and normalizes dependency vulnerability results into a stable `schemaVersion: "1.0.0"` report. Use `--json --output dependency-audit.json` when CI needs machine-readable status, vulnerability counts, and the raw npm audit payload. Add `--recommend` to include prioritized remediation actions for direct upgrades, direct no-fix packages, transitive parent updates, and manual transitive review.
|
|
306
308
|
|
|
307
309
|
`watch <path>` continuously validates a plugin package after file changes. Use `--debounce-ms <ms>` to tune noisy repositories, `--runtime` when runtime probes are required, and `--json --output watch-latest.json` when another process should consume the latest validation snapshot.
|
|
308
310
|
|
|
@@ -400,9 +402,9 @@ jobs:
|
|
|
400
402
|
runs-on: ubuntu-latest
|
|
401
403
|
steps:
|
|
402
404
|
- uses: actions/checkout@v5
|
|
403
|
-
- uses: Esquetta/CodexPluginDoctor@v1.
|
|
405
|
+
- uses: Esquetta/CodexPluginDoctor@v1.34.0
|
|
404
406
|
with:
|
|
405
|
-
version: "1.
|
|
407
|
+
version: "1.34.0"
|
|
406
408
|
path: .
|
|
407
409
|
runtime: "true"
|
|
408
410
|
policy: codex-publish
|
package/dist/core/dep-audit.d.ts
CHANGED
|
@@ -12,6 +12,19 @@ export interface DepAuditReport {
|
|
|
12
12
|
totalVulnerabilities: number;
|
|
13
13
|
auditJson: unknown;
|
|
14
14
|
}
|
|
15
|
+
export interface DepAuditRecommendation {
|
|
16
|
+
packageName: string;
|
|
17
|
+
priority: "critical" | "high" | "moderate" | "low";
|
|
18
|
+
action: "upgrade_direct" | "replace_direct" | "update_parent" | "review_transitive";
|
|
19
|
+
summary: string;
|
|
20
|
+
breakingChangeRisk: "low" | "medium" | "high" | "unknown";
|
|
21
|
+
}
|
|
22
|
+
export declare function buildDepAuditRecommendations(vulnerabilities: DepAuditVulnerability[]): DepAuditRecommendation[];
|
|
15
23
|
export declare function buildDepAudit(targetPath: string): Promise<DepAuditReport>;
|
|
16
|
-
export declare function renderDepAudit(report: DepAuditReport
|
|
17
|
-
|
|
24
|
+
export declare function renderDepAudit(report: DepAuditReport, options?: {
|
|
25
|
+
recommendations?: boolean;
|
|
26
|
+
}): string;
|
|
27
|
+
export declare function renderDepAuditJson(report: DepAuditReport, options?: {
|
|
28
|
+
recommendations?: boolean;
|
|
29
|
+
}): string;
|
|
30
|
+
export declare function renderDepAuditSarif(report: DepAuditReport): string;
|
package/dist/core/dep-audit.js
CHANGED
|
@@ -82,6 +82,66 @@ function computeStatus(vulnerabilities) {
|
|
|
82
82
|
}
|
|
83
83
|
return "warn";
|
|
84
84
|
}
|
|
85
|
+
function severityRank(severity) {
|
|
86
|
+
return severity === "critical" ? 0 :
|
|
87
|
+
severity === "high" ? 1 :
|
|
88
|
+
severity === "moderate" ? 2 : 3;
|
|
89
|
+
}
|
|
90
|
+
function remediationRank(vulnerability) {
|
|
91
|
+
if (vulnerability.isDirect && vulnerability.fixAvailable) {
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
if (vulnerability.isDirect) {
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
return vulnerability.fixAvailable ? 2 : 3;
|
|
98
|
+
}
|
|
99
|
+
export function buildDepAuditRecommendations(vulnerabilities) {
|
|
100
|
+
return [...vulnerabilities]
|
|
101
|
+
.sort((left, right) => {
|
|
102
|
+
const remediationDelta = remediationRank(left) - remediationRank(right);
|
|
103
|
+
if (remediationDelta !== 0) {
|
|
104
|
+
return remediationDelta;
|
|
105
|
+
}
|
|
106
|
+
return severityRank(left.severity) - severityRank(right.severity);
|
|
107
|
+
})
|
|
108
|
+
.map((vuln) => {
|
|
109
|
+
if (vuln.isDirect && vuln.fixAvailable) {
|
|
110
|
+
return {
|
|
111
|
+
packageName: vuln.name,
|
|
112
|
+
priority: vuln.severity,
|
|
113
|
+
action: "upgrade_direct",
|
|
114
|
+
summary: `Upgrade direct dependency \`${vuln.name}\` with \`npm update ${vuln.name}\` or an explicit package upgrade.`,
|
|
115
|
+
breakingChangeRisk: vuln.severity === "critical" ? "high" : "medium"
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (vuln.isDirect) {
|
|
119
|
+
return {
|
|
120
|
+
packageName: vuln.name,
|
|
121
|
+
priority: vuln.severity,
|
|
122
|
+
action: "replace_direct",
|
|
123
|
+
summary: `Replace or pin direct dependency \`${vuln.name}\`; npm did not report an automatic fix.`,
|
|
124
|
+
breakingChangeRisk: "high"
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (vuln.fixAvailable) {
|
|
128
|
+
return {
|
|
129
|
+
packageName: vuln.name,
|
|
130
|
+
priority: vuln.severity,
|
|
131
|
+
action: "update_parent",
|
|
132
|
+
summary: `Update the parent dependency for transitive package \`${vuln.name}\`.`,
|
|
133
|
+
breakingChangeRisk: "medium"
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
packageName: vuln.name,
|
|
138
|
+
priority: vuln.severity,
|
|
139
|
+
action: "review_transitive",
|
|
140
|
+
summary: `Review transitive package \`${vuln.name}\`; npm did not report an automatic fix.`,
|
|
141
|
+
breakingChangeRisk: "unknown"
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
}
|
|
85
145
|
export async function buildDepAudit(targetPath) {
|
|
86
146
|
const resolvedPath = path.resolve(targetPath);
|
|
87
147
|
const packageJsonPath = resolvePackageJson(resolvedPath);
|
|
@@ -140,7 +200,7 @@ export async function buildDepAudit(targetPath) {
|
|
|
140
200
|
auditJson
|
|
141
201
|
};
|
|
142
202
|
}
|
|
143
|
-
export function renderDepAudit(report) {
|
|
203
|
+
export function renderDepAudit(report, options = {}) {
|
|
144
204
|
const lines = [
|
|
145
205
|
"Dependency Vulnerability Audit",
|
|
146
206
|
"=============================",
|
|
@@ -159,15 +219,65 @@ export function renderDepAudit(report) {
|
|
|
159
219
|
vuln.severity === "moderate" ? "MODERATE" : "LOW";
|
|
160
220
|
lines.push(`${tag.padEnd(10)} ${vuln.name}`, ` Direct: ${vuln.isDirect ? "yes" : "no"}`, ` Fix available: ${vuln.fixAvailable ? "yes" : "no"}`, vuln.via.length > 0 ? ` Via: ${vuln.via.join(", ")}` : "", "");
|
|
161
221
|
}
|
|
222
|
+
if (options.recommendations) {
|
|
223
|
+
const recommendations = buildDepAuditRecommendations(report.vulnerabilities);
|
|
224
|
+
lines.push("Next actions", "------------");
|
|
225
|
+
if (recommendations.length === 0) {
|
|
226
|
+
lines.push("No remediation actions needed.");
|
|
227
|
+
}
|
|
228
|
+
for (const [index, recommendation] of recommendations.entries()) {
|
|
229
|
+
lines.push(`${index + 1}. ${recommendation.summary}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
162
232
|
return lines.join("\n");
|
|
163
233
|
}
|
|
164
|
-
export function renderDepAuditJson(report) {
|
|
234
|
+
export function renderDepAuditJson(report, options = {}) {
|
|
165
235
|
return JSON.stringify({
|
|
166
236
|
schemaVersion: "1.0.0",
|
|
167
237
|
targetPath: report.targetPath,
|
|
168
238
|
status: report.status,
|
|
169
239
|
totalVulnerabilities: report.totalVulnerabilities,
|
|
170
240
|
vulnerabilities: report.vulnerabilities,
|
|
241
|
+
...(options.recommendations
|
|
242
|
+
? { recommendations: buildDepAuditRecommendations(report.vulnerabilities) }
|
|
243
|
+
: {}),
|
|
171
244
|
audit: report.auditJson
|
|
172
245
|
}, null, 2);
|
|
173
246
|
}
|
|
247
|
+
export function renderDepAuditSarif(report) {
|
|
248
|
+
const rules = report.vulnerabilities.map((vuln) => ({
|
|
249
|
+
id: `dep-audit.${vuln.name}`,
|
|
250
|
+
name: vuln.name,
|
|
251
|
+
shortDescription: {
|
|
252
|
+
text: `${vuln.severity} severity vulnerability in ${vuln.name}${vuln.fixAvailable ? " (fix available)" : ""}`
|
|
253
|
+
},
|
|
254
|
+
help: {
|
|
255
|
+
text: vuln.via.length > 0 ? `Via: ${vuln.via.join(", ")}` : "No additional information."
|
|
256
|
+
}
|
|
257
|
+
}));
|
|
258
|
+
const results = report.vulnerabilities.map((vuln, index) => ({
|
|
259
|
+
ruleId: `dep-audit.${vuln.name}`,
|
|
260
|
+
ruleIndex: index,
|
|
261
|
+
level: vuln.severity === "critical" || vuln.severity === "high" ? "error" : "warning",
|
|
262
|
+
message: {
|
|
263
|
+
text: `${vuln.severity}: ${vuln.name} ${vuln.fixAvailable ? "(fix available)" : "(no fix available)"}`
|
|
264
|
+
}
|
|
265
|
+
}));
|
|
266
|
+
const sarif = {
|
|
267
|
+
version: "2.1.0",
|
|
268
|
+
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
|
269
|
+
runs: [
|
|
270
|
+
{
|
|
271
|
+
tool: {
|
|
272
|
+
driver: {
|
|
273
|
+
name: "codex-plugin-doctor (dep-audit)",
|
|
274
|
+
informationUri: "https://github.com/Esquetta/CodexPluginDoctor",
|
|
275
|
+
rules
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
results
|
|
279
|
+
}
|
|
280
|
+
]
|
|
281
|
+
};
|
|
282
|
+
return JSON.stringify(sarif, null, 2);
|
|
283
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,9 +22,9 @@ export { buildDoctorNpmPackageReport, renderDoctorNpmPackageReport, renderDoctor
|
|
|
22
22
|
export { buildDoctorRiskDiffReport, renderDoctorRiskDiffReport, renderDoctorRiskDiffReportJson, type BuildDoctorRiskDiffReportOptions, type DoctorRiskDiffReport, type RiskDiffFinding, type RiskFindingCategory } from "./core/risk-diff.js";
|
|
23
23
|
export { buildDoctorInspectorReport, renderDoctorInspectorReport, renderDoctorInspectorReportJson, type BuildDoctorInspectorReportOptions, type DoctorInspectorReport } from "./core/inspector-bridge.js";
|
|
24
24
|
export { buildEcosystemAudit, renderEcosystemAudit, renderEcosystemAuditJson, type EcosystemAuditReport } from "./audit/ecosystem-audit.js";
|
|
25
|
-
export { applyPolicyToDoctorConfig, applyPolicyToSecurityAudit, parsePolicyPack, policyEnablesRuntime, policyFailsOnWarnings, policyPackNames, type PolicyPackName } from "./policy/policy-packs.js";
|
|
25
|
+
export { applyPolicyToDepAudit, applyPolicyToDoctorConfig, applyPolicyToSecurityAudit, parsePolicyPack, policyEnablesRuntime, policyFailsOnWarnings, policyPackNames, type PolicyPackName } from "./policy/policy-packs.js";
|
|
26
26
|
export { buildGenericMcpDoctor, renderGenericMcpDoctor, renderGenericMcpDoctorJson, type GenericMcpDoctorReport } from "./mcp/generic-mcp-doctor.js";
|
|
27
27
|
export { watchPlugin, type WatchPluginOptions, type WatchPluginResult } from "./core/watch-plugin.js";
|
|
28
|
-
export { buildDepAudit, renderDepAudit, renderDepAuditJson, type DepAuditReport, type DepAuditVulnerability } from "./core/dep-audit.js";
|
|
28
|
+
export { buildDepAudit, buildDepAuditRecommendations, renderDepAudit, renderDepAuditJson, renderDepAuditSarif, type DepAuditRecommendation, type DepAuditReport, type DepAuditVulnerability } from "./core/dep-audit.js";
|
|
29
29
|
export { initGitHooks, removeGitHooks, type InitGitHooksResult, type RemoveGitHooksResult } from "./core/init-git-hooks.js";
|
|
30
30
|
export declare function runCheck(targetPath: string, options?: CheckOptions): Promise<CheckResult>;
|
package/dist/index.js
CHANGED
|
@@ -22,10 +22,10 @@ export { buildDoctorNpmPackageReport, renderDoctorNpmPackageReport, renderDoctor
|
|
|
22
22
|
export { buildDoctorRiskDiffReport, renderDoctorRiskDiffReport, renderDoctorRiskDiffReportJson } from "./core/risk-diff.js";
|
|
23
23
|
export { buildDoctorInspectorReport, renderDoctorInspectorReport, renderDoctorInspectorReportJson } from "./core/inspector-bridge.js";
|
|
24
24
|
export { buildEcosystemAudit, renderEcosystemAudit, renderEcosystemAuditJson } from "./audit/ecosystem-audit.js";
|
|
25
|
-
export { applyPolicyToDoctorConfig, applyPolicyToSecurityAudit, parsePolicyPack, policyEnablesRuntime, policyFailsOnWarnings, policyPackNames } from "./policy/policy-packs.js";
|
|
25
|
+
export { applyPolicyToDepAudit, applyPolicyToDoctorConfig, applyPolicyToSecurityAudit, parsePolicyPack, policyEnablesRuntime, policyFailsOnWarnings, policyPackNames } from "./policy/policy-packs.js";
|
|
26
26
|
export { buildGenericMcpDoctor, renderGenericMcpDoctor, renderGenericMcpDoctorJson } from "./mcp/generic-mcp-doctor.js";
|
|
27
27
|
export { watchPlugin } from "./core/watch-plugin.js";
|
|
28
|
-
export { buildDepAudit, renderDepAudit, renderDepAuditJson } from "./core/dep-audit.js";
|
|
28
|
+
export { buildDepAudit, buildDepAuditRecommendations, renderDepAudit, renderDepAuditJson, renderDepAuditSarif } from "./core/dep-audit.js";
|
|
29
29
|
export { initGitHooks, removeGitHooks } from "./core/init-git-hooks.js";
|
|
30
30
|
export async function runCheck(targetPath, options = {}) {
|
|
31
31
|
return validatePlugin(targetPath, options);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DoctorConfig } from "../core/doctor-config.js";
|
|
2
|
+
import type { DepAuditReport } from "../core/dep-audit.js";
|
|
2
3
|
import type { SecurityAudit } from "../security/security-audit.js";
|
|
3
4
|
export declare const policyPackNames: readonly ["codex-publish", "mcp-strict", "security"];
|
|
4
5
|
export type PolicyPackName = (typeof policyPackNames)[number];
|
|
@@ -7,3 +8,4 @@ export declare function policyEnablesRuntime(policy: PolicyPackName | null): boo
|
|
|
7
8
|
export declare function policyFailsOnWarnings(policy: PolicyPackName | null): boolean;
|
|
8
9
|
export declare function applyPolicyToDoctorConfig(config: DoctorConfig, policy: PolicyPackName | null): DoctorConfig;
|
|
9
10
|
export declare function applyPolicyToSecurityAudit(audit: SecurityAudit, policy: PolicyPackName | null): SecurityAudit;
|
|
11
|
+
export declare function applyPolicyToDepAudit(report: DepAuditReport, policy: PolicyPackName | null): DepAuditReport;
|
|
@@ -31,3 +31,12 @@ export function applyPolicyToSecurityAudit(audit, policy) {
|
|
|
31
31
|
status: "fail"
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
+
export function applyPolicyToDepAudit(report, policy) {
|
|
35
|
+
if (!policyFailsOnWarnings(policy)) {
|
|
36
|
+
return report;
|
|
37
|
+
}
|
|
38
|
+
if (report.status === "warn") {
|
|
39
|
+
return { ...report, status: "fail" };
|
|
40
|
+
}
|
|
41
|
+
return report;
|
|
42
|
+
}
|
|
@@ -251,6 +251,15 @@ export const ruleCatalog = [
|
|
|
251
251
|
fix: "Use HTTPS for remote MCP servers; reserve HTTP for explicit localhost development endpoints.",
|
|
252
252
|
example: '{ "url": "https://example.com/mcp" }'
|
|
253
253
|
},
|
|
254
|
+
{
|
|
255
|
+
id: "plugin.security.mcp_binds_all_interfaces",
|
|
256
|
+
category: "security",
|
|
257
|
+
defaultSeverity: "warn",
|
|
258
|
+
summary: "An MCP server listens on `0.0.0.0` instead of `localhost`.",
|
|
259
|
+
why: "Servers that bind to all interfaces can accept connections from external hosts, which is rarely intended for local MCP development.",
|
|
260
|
+
fix: "Use `127.0.0.1` or `localhost` in server URLs and transport configuration unless external access is explicitly required.",
|
|
261
|
+
example: '{ "url": "http://127.0.0.1:3000/mcp" }'
|
|
262
|
+
},
|
|
254
263
|
{
|
|
255
264
|
id: "plugin.security.prompt_injection_text",
|
|
256
265
|
category: "security",
|
package/dist/run-cli.js
CHANGED
|
@@ -31,7 +31,7 @@ import { applyFixPlan, buildFixPlan, renderApplyFixResult, renderFixPlanJsonRepo
|
|
|
31
31
|
import { renderClientDoctor, renderEnvironmentDoctor, renderEnvironmentDoctorJson } from "./core/environment-doctor.js";
|
|
32
32
|
import { initCiWorkflow } from "./core/init-ci.js";
|
|
33
33
|
import { watchPlugin } from "./core/watch-plugin.js";
|
|
34
|
-
import { buildDepAudit, renderDepAudit, renderDepAuditJson } from "./core/dep-audit.js";
|
|
34
|
+
import { buildDepAudit, renderDepAudit, renderDepAuditJson, renderDepAuditSarif } from "./core/dep-audit.js";
|
|
35
35
|
import { initGitHooks, removeGitHooks } from "./core/init-git-hooks.js";
|
|
36
36
|
import { initPluginPackage, initPluginTemplates, isInitPluginTemplate } from "./core/init-plugin.js";
|
|
37
37
|
import { runCheck } from "./index.js";
|
|
@@ -48,7 +48,7 @@ import { renderSuppressionList, renderSuppressionListJson, renderSuppressionMuta
|
|
|
48
48
|
import { renderRuleExplanation } from "./reporting/render-rule-explanation.js";
|
|
49
49
|
import { renderSarifReport } from "./reporting/render-sarif-report.js";
|
|
50
50
|
import { renderTextReport } from "./reporting/render-text-report.js";
|
|
51
|
-
import { applyPolicyToDoctorConfig, applyPolicyToSecurityAudit, parsePolicyPack, policyEnablesRuntime, policyFailsOnWarnings, policyPackNames } from "./policy/policy-packs.js";
|
|
51
|
+
import { applyPolicyToDepAudit, applyPolicyToDoctorConfig, applyPolicyToSecurityAudit, parsePolicyPack, policyEnablesRuntime, policyFailsOnWarnings, policyPackNames } from "./policy/policy-packs.js";
|
|
52
52
|
import { findRuleDefinition } from "./rules/rule-catalog.js";
|
|
53
53
|
import { buildSecurityAudit, renderSecurityAuditJson, renderSecurityScorecard } from "./security/security-audit.js";
|
|
54
54
|
import { buildTrustScore, renderTrustScore, renderTrustScoreJson } from "./security/trust-score.js";
|
|
@@ -78,7 +78,7 @@ const defaultIo = {
|
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
80
|
function printUsage(io) {
|
|
81
|
-
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] [--require-runtime-approval --runtime-approval-digest <digest>] [--verbose-runtime] [--explain] [--no-animations] [--ascii]\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> [--json] [--output <path>]\n codex-plugin-doctor mcp <path> [--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|runtime-plan <path> [--json|--markdown] [--output <path>]|runtime-policy <path> [--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 [--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 [--upload]|mcp <path>|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 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");
|
|
81
|
+
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] [--require-runtime-approval --runtime-approval-digest <digest>] [--verbose-runtime] [--explain] [--no-animations] [--ascii]\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> [--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|runtime-plan <path> [--json|--markdown] [--output <path>]|runtime-policy <path> [--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 [--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 [--upload]|mcp <path>|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 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");
|
|
82
82
|
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]");
|
|
83
83
|
}
|
|
84
84
|
const suppressUsageText = [
|
|
@@ -1963,16 +1963,38 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1963
1963
|
? remainingArgs
|
|
1964
1964
|
: remainingArgs.slice(1);
|
|
1965
1965
|
const jsonOutput = depsFlags.includes("--json");
|
|
1966
|
+
const sarifOutput = depsFlags.includes("--sarif");
|
|
1967
|
+
const recommendationsOutput = depsFlags.includes("--recommend");
|
|
1966
1968
|
const outputIndex = depsFlags.indexOf("--output");
|
|
1967
1969
|
const outputPath = outputIndex === -1 ? null : depsFlags[outputIndex + 1];
|
|
1970
|
+
const policyIndex = depsFlags.indexOf("--policy");
|
|
1971
|
+
const policyName = policyIndex === -1 ? null : depsFlags[policyIndex + 1];
|
|
1972
|
+
const policy = parsePolicyPack(policyName);
|
|
1968
1973
|
if (outputIndex !== -1 && (!outputPath || outputPath.startsWith("--"))) {
|
|
1969
1974
|
io.writeStderr("Missing path after --output.");
|
|
1970
1975
|
return 2;
|
|
1971
1976
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1977
|
+
if (policyIndex !== -1 && (!policyName || policyName.startsWith("--"))) {
|
|
1978
|
+
io.writeStderr("Missing policy after --policy.");
|
|
1979
|
+
return 2;
|
|
1980
|
+
}
|
|
1981
|
+
if (policyIndex !== -1 && !policy) {
|
|
1982
|
+
io.writeStderr(`Unknown policy: ${policyName}. Supported policies: ${policyPackNames.join(", ")}.`);
|
|
1983
|
+
return 2;
|
|
1984
|
+
}
|
|
1985
|
+
if (jsonOutput && sarifOutput) {
|
|
1986
|
+
io.writeStderr("Use either --json or --sarif, not both.");
|
|
1987
|
+
return 2;
|
|
1988
|
+
}
|
|
1989
|
+
let report = await buildDepAudit(targetPath);
|
|
1990
|
+
if (policy) {
|
|
1991
|
+
report = applyPolicyToDepAudit(report, policy);
|
|
1992
|
+
}
|
|
1993
|
+
const renderedReport = sarifOutput
|
|
1994
|
+
? renderDepAuditSarif(report)
|
|
1995
|
+
: jsonOutput
|
|
1996
|
+
? renderDepAuditJson(report, { recommendations: recommendationsOutput })
|
|
1997
|
+
: renderDepAudit(report, { recommendations: recommendationsOutput });
|
|
1976
1998
|
if (outputPath) {
|
|
1977
1999
|
await writeFile(outputPath, renderedReport, "utf8");
|
|
1978
2000
|
}
|
|
@@ -1982,7 +2004,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1982
2004
|
const auditFlags = maybePath ? [maybePath, ...remainingArgs] : remainingArgs;
|
|
1983
2005
|
const installed = auditFlags.includes("--installed");
|
|
1984
2006
|
if (!installed) {
|
|
1985
|
-
io.writeStderr("Usage: codex-plugin-doctor audit --installed [filter] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\n codex-plugin-doctor audit deps <path> [--json] [--output <path>]");
|
|
2007
|
+
io.writeStderr("Usage: codex-plugin-doctor audit --installed [filter] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\n codex-plugin-doctor audit deps <path> [--policy codex-publish|mcp-strict|security] [--json|--sarif] [--output <path>]");
|
|
1986
2008
|
return 2;
|
|
1987
2009
|
}
|
|
1988
2010
|
const installedIndex = auditFlags.indexOf("--installed");
|
|
@@ -227,6 +227,9 @@ export function auditMcpServerConfig(rootPath, parsedConfig, options = {}) {
|
|
|
227
227
|
if (typeof url === "string" && /^http:\/\//i.test(url)) {
|
|
228
228
|
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.", { serverName, configPath, url }));
|
|
229
229
|
}
|
|
230
|
+
if (typeof url === "string" && /\/\/0\.0\.0\.0[:/]/i.test(url)) {
|
|
231
|
+
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.", { serverName, configPath, url }));
|
|
232
|
+
}
|
|
230
233
|
}
|
|
231
234
|
return findings;
|
|
232
235
|
}
|
package/package.json
CHANGED