codex-plugin-doctor 1.11.0 → 1.12.1

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
@@ -358,9 +358,9 @@ jobs:
358
358
  runs-on: ubuntu-latest
359
359
  steps:
360
360
  - uses: actions/checkout@v5
361
- - uses: Esquetta/CodexPluginDoctor@v1.11.0
361
+ - uses: Esquetta/CodexPluginDoctor@v1.12.1
362
362
  with:
363
- version: "1.11.0"
363
+ version: "1.12.1"
364
364
  path: .
365
365
  runtime: "true"
366
366
  policy: codex-publish
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
2
+ import { mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { buildDoctorAttestation, renderDoctorAttestationJson, verifyDoctorAttestation } from "./attestation.js";
5
5
  import { buildDoctorReleaseEvidenceReport, renderDoctorReleaseEvidenceJson, verifyDoctorReleaseEvidence } from "./release-evidence.js";
@@ -50,6 +50,21 @@ function isPathInsideDirectory(candidatePath, directoryPath) {
50
50
  const relativePath = path.relative(directoryPath, candidatePath);
51
51
  return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
52
52
  }
53
+ async function resolveBundleArtifactPath(bundleDirectory, relativePath) {
54
+ const resolvedBundleDirectory = path.resolve(bundleDirectory);
55
+ const artifactPath = path.resolve(resolvedBundleDirectory, relativePath);
56
+ if (!isPathInsideDirectory(artifactPath, resolvedBundleDirectory)) {
57
+ throw new Error("Bundle artifact path resolves outside the bundle directory.");
58
+ }
59
+ const [canonicalBundleDirectory, canonicalArtifactPath] = await Promise.all([
60
+ realpath(resolvedBundleDirectory),
61
+ realpath(artifactPath)
62
+ ]);
63
+ if (!isPathInsideDirectory(canonicalArtifactPath, canonicalBundleDirectory)) {
64
+ throw new Error("Bundle artifact canonical path resolves outside the bundle directory.");
65
+ }
66
+ return canonicalArtifactPath;
67
+ }
53
68
  function bundleStatus(runtimePolicy, releaseEvidence) {
54
69
  if (runtimePolicy.status === "fail" || releaseEvidence.status === "fail") {
55
70
  return "fail";
@@ -171,7 +186,7 @@ export function renderDoctorReviewBundleJson(bundle) {
171
186
  return JSON.stringify(bundle.manifest, null, 2);
172
187
  }
173
188
  async function readBundleJsonFile(bundleDirectory, relativePath) {
174
- return readJsonFile(path.join(bundleDirectory, relativePath));
189
+ return readJsonFile(await resolveBundleArtifactPath(bundleDirectory, relativePath));
175
190
  }
176
191
  async function readBundleDiffSnapshot(bundleDirectory) {
177
192
  const manifestArtifact = await readBundleJsonFile(bundleDirectory, "manifest.json");
@@ -348,15 +363,7 @@ export async function verifyDoctorReviewBundle(bundleDirectory, options) {
348
363
  const files = manifest?.files ?? relativeBundleFiles();
349
364
  for (const [fileKey, relativePath] of Object.entries(files)) {
350
365
  try {
351
- const artifactPath = path.resolve(resolvedBundleDirectory, relativePath);
352
- if (!isPathInsideDirectory(artifactPath, resolvedBundleDirectory)) {
353
- checks.push({
354
- id: `review_bundle.file.${fileKey}`,
355
- status: "fail",
356
- message: `${relativePath} resolves outside the review bundle directory.`
357
- });
358
- continue;
359
- }
366
+ const artifactPath = await resolveBundleArtifactPath(resolvedBundleDirectory, relativePath);
360
367
  const fileStat = await stat(artifactPath);
361
368
  checks.push({
362
369
  id: `review_bundle.file.${fileKey}`,
@@ -427,8 +434,8 @@ export async function verifyDoctorReviewBundle(bundleDirectory, options) {
427
434
  });
428
435
  continue;
429
436
  }
430
- const integrityPath = path.resolve(resolvedBundleDirectory, expected.path);
431
- if (!isPathInsideDirectory(integrityPath, resolvedBundleDirectory)) {
437
+ const resolvedIntegrityPath = path.resolve(resolvedBundleDirectory, expected.path);
438
+ if (!isPathInsideDirectory(resolvedIntegrityPath, resolvedBundleDirectory)) {
432
439
  integrityStatus = "fail";
433
440
  checks.push({
434
441
  id: `review_bundle.integrity.${fileKey}.path`,
@@ -437,6 +444,7 @@ export async function verifyDoctorReviewBundle(bundleDirectory, options) {
437
444
  });
438
445
  continue;
439
446
  }
447
+ const integrityPath = await resolveBundleArtifactPath(resolvedBundleDirectory, expected.path);
440
448
  const content = await readFile(integrityPath);
441
449
  const digest = sha256(content);
442
450
  const matches = digest === expected.digest && content.byteLength === expected.bytes;
@@ -505,7 +513,8 @@ export async function verifyDoctorReviewBundle(bundleDirectory, options) {
505
513
  });
506
514
  }
507
515
  try {
508
- attestation = await verifyDoctorAttestation(path.join(resolvedBundleDirectory, files.attestationJson), targetPath, { signingKey: options.signingKey });
516
+ const attestationPath = await resolveBundleArtifactPath(resolvedBundleDirectory, files.attestationJson);
517
+ attestation = await verifyDoctorAttestation(attestationPath, targetPath, { signingKey: options.signingKey });
509
518
  checks.push({
510
519
  id: "review_bundle.attestation",
511
520
  status: attestation.status,
@@ -522,7 +531,8 @@ export async function verifyDoctorReviewBundle(bundleDirectory, options) {
522
531
  });
523
532
  }
524
533
  try {
525
- releaseEvidence = await verifyDoctorReleaseEvidence(path.join(resolvedBundleDirectory, files.releaseEvidenceJson), {
534
+ const releaseEvidencePath = await resolveBundleArtifactPath(resolvedBundleDirectory, files.releaseEvidenceJson);
535
+ releaseEvidence = await verifyDoctorReleaseEvidence(releaseEvidencePath, {
526
536
  signingKey: options.signingKey,
527
537
  targetPath
528
538
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-plugin-doctor",
3
- "version": "1.11.0",
3
+ "version": "1.12.1",
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",