codex-plugin-doctor 1.32.0 → 1.33.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
@@ -400,9 +400,9 @@ jobs:
400
400
  runs-on: ubuntu-latest
401
401
  steps:
402
402
  - uses: actions/checkout@v5
403
- - uses: Esquetta/CodexPluginDoctor@v1.32.0
403
+ - uses: Esquetta/CodexPluginDoctor@v1.33.0
404
404
  with:
405
- version: "1.32.0"
405
+ version: "1.33.0"
406
406
  path: .
407
407
  runtime: "true"
408
408
  policy: codex-publish
@@ -15,3 +15,4 @@ export interface DepAuditReport {
15
15
  export declare function buildDepAudit(targetPath: string): Promise<DepAuditReport>;
16
16
  export declare function renderDepAudit(report: DepAuditReport): string;
17
17
  export declare function renderDepAuditJson(report: DepAuditReport): string;
18
+ export declare function renderDepAuditSarif(report: DepAuditReport): string;
@@ -171,3 +171,40 @@ export function renderDepAuditJson(report) {
171
171
  audit: report.auditJson
172
172
  }, null, 2);
173
173
  }
174
+ export function renderDepAuditSarif(report) {
175
+ const rules = report.vulnerabilities.map((vuln) => ({
176
+ id: `dep-audit.${vuln.name}`,
177
+ name: vuln.name,
178
+ shortDescription: {
179
+ text: `${vuln.severity} severity vulnerability in ${vuln.name}${vuln.fixAvailable ? " (fix available)" : ""}`
180
+ },
181
+ help: {
182
+ text: vuln.via.length > 0 ? `Via: ${vuln.via.join(", ")}` : "No additional information."
183
+ }
184
+ }));
185
+ const results = report.vulnerabilities.map((vuln, index) => ({
186
+ ruleId: `dep-audit.${vuln.name}`,
187
+ ruleIndex: index,
188
+ level: vuln.severity === "critical" || vuln.severity === "high" ? "error" : "warning",
189
+ message: {
190
+ text: `${vuln.severity}: ${vuln.name} ${vuln.fixAvailable ? "(fix available)" : "(no fix available)"}`
191
+ }
192
+ }));
193
+ const sarif = {
194
+ version: "2.1.0",
195
+ $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
196
+ runs: [
197
+ {
198
+ tool: {
199
+ driver: {
200
+ name: "codex-plugin-doctor (dep-audit)",
201
+ informationUri: "https://github.com/Esquetta/CodexPluginDoctor",
202
+ rules
203
+ }
204
+ },
205
+ results
206
+ }
207
+ ]
208
+ };
209
+ return JSON.stringify(sarif, null, 2);
210
+ }
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, renderDepAudit, renderDepAuditJson, renderDepAuditSarif, 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, 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] [--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,37 @@ 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");
1966
1967
  const outputIndex = depsFlags.indexOf("--output");
1967
1968
  const outputPath = outputIndex === -1 ? null : depsFlags[outputIndex + 1];
1969
+ const policyIndex = depsFlags.indexOf("--policy");
1970
+ const policyName = policyIndex === -1 ? null : depsFlags[policyIndex + 1];
1971
+ const policy = parsePolicyPack(policyName);
1968
1972
  if (outputIndex !== -1 && (!outputPath || outputPath.startsWith("--"))) {
1969
1973
  io.writeStderr("Missing path after --output.");
1970
1974
  return 2;
1971
1975
  }
1972
- const report = await buildDepAudit(targetPath);
1973
- const renderedReport = jsonOutput
1974
- ? renderDepAuditJson(report)
1975
- : renderDepAudit(report);
1976
+ if (policyIndex !== -1 && (!policyName || policyName.startsWith("--"))) {
1977
+ io.writeStderr("Missing policy after --policy.");
1978
+ return 2;
1979
+ }
1980
+ if (policyIndex !== -1 && !policy) {
1981
+ io.writeStderr(`Unknown policy: ${policyName}. Supported policies: ${policyPackNames.join(", ")}.`);
1982
+ return 2;
1983
+ }
1984
+ if (jsonOutput && sarifOutput) {
1985
+ io.writeStderr("Use either --json or --sarif, not both.");
1986
+ return 2;
1987
+ }
1988
+ let report = await buildDepAudit(targetPath);
1989
+ if (policy) {
1990
+ report = applyPolicyToDepAudit(report, policy);
1991
+ }
1992
+ const renderedReport = sarifOutput
1993
+ ? renderDepAuditSarif(report)
1994
+ : jsonOutput
1995
+ ? renderDepAuditJson(report)
1996
+ : renderDepAudit(report);
1976
1997
  if (outputPath) {
1977
1998
  await writeFile(outputPath, renderedReport, "utf8");
1978
1999
  }
@@ -1982,7 +2003,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
1982
2003
  const auditFlags = maybePath ? [maybePath, ...remainingArgs] : remainingArgs;
1983
2004
  const installed = auditFlags.includes("--installed");
1984
2005
  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>]");
2006
+ 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
2007
  return 2;
1987
2008
  }
1988
2009
  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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-plugin-doctor",
3
- "version": "1.32.0",
3
+ "version": "1.33.0",
4
4
  "description": "CLI-first validator for Codex plugins, skills, and MCP package surfaces with runtime MCP protocol validation.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",