Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1aaa405
docs: design public directory submission preflight
Esquetta Aug 16, 2026
622e846
docs: clarify app manifest preflight boundary
Esquetta Aug 16, 2026
2884212
chore: update vulnerable nanoid transitive
Esquetta Aug 16, 2026
59844de
feat: add submission listing preflight
Esquetta Aug 17, 2026
94b4a94
fix: block skills-only submission screenshots
Esquetta Aug 17, 2026
5264876
fix: fail closed on submission target input
Esquetta Aug 17, 2026
3897a1c
feat: validate submission branding assets
Esquetta Aug 17, 2026
a4f2000
fix: reject truncated submission images
Esquetta Aug 17, 2026
68fe7ae
fix: harden submission image decoding
Esquetta Aug 17, 2026
fb0859d
fix: bound submission image resources
Esquetta Aug 17, 2026
e753c3e
feat: validate submission skill metadata
Esquetta Aug 17, 2026
0fa89dc
fix: align submission skill metadata schema
Esquetta Aug 17, 2026
0ea08c3
fix: support official skill tool descriptors
Esquetta Aug 17, 2026
42f6e0d
fix: reject mixed skill tool descriptors
Esquetta Aug 17, 2026
9744644
feat: expose submission preflight command
Esquetta Aug 17, 2026
83d465f
fix: scope submission CLI flags
Esquetta Aug 17, 2026
4e59f13
feat: add submission preflight to GitHub Action
Esquetta Aug 17, 2026
add1758
fix: guard submission Action outputs
Esquetta Aug 17, 2026
4f90fdc
test: harden submission preflight boundaries
Esquetta Aug 17, 2026
896baff
fix: redact absolute Action target paths
Esquetta Aug 17, 2026
fa958ab
fix: redact file URI Action targets
Esquetta Aug 17, 2026
03e145c
release: prepare v1.59.0
Esquetta Aug 17, 2026
7c11245
fix: accept standard submission image containers
Esquetta Aug 17, 2026
2a8dd3e
fix: bound submission package inputs
Esquetta Aug 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ All notable changes to `codex-plugin-doctor` are documented here.

This changelog groups the shipped work into product-level release blocks instead of repeating every low-level git diff in isolation.

## [1.59.0] - 2026-08-17

### Added

- added offline `doctor submission <path>` reports in text, JSON, and Markdown, with advisory defaults and `--require-ready` as the strict automatic blocker gate
- added automatic public-directory listing and classification, root `.app.json` boundary checks, bounded branding asset validation, and safe skill and `openai.yaml` identity and metadata validation
- added opt-in GitHub Action submission inputs, artifacts, and step-summary output while preserving existing defaults

### Changed

- kept portal-only review items explicit: automatic passing results still require manual review and do not submit a package or claim directory acceptance

### Security

- bounded raster, SVG, and YAML parsing; canonical containment; no process execution or network access; and redacted evidence with absolute Action paths
- added `yaml` and `fast-xml-parser` parsing dependencies without package scripts; includes the transitive `nanoid` remediation

## [1.58.0] - 2026-08-11

### Added
Expand Down
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,19 @@ version is an immutable collision and must be replaced by a version bump. The
command is advisory: it never authenticates, publishes, or changes npm or
Registry records. See [MCP Registry Publication Preflight](./docs/architecture/mcp-registry-publication-preflight.md).

### Public Directory Submission Preflight

Check a plugin package for deterministic public-directory submission issues without contacting a portal:

```bash
codex-plugin-doctor doctor submission <path>
codex-plugin-doctor doctor submission <path> --json
codex-plugin-doctor doctor submission <path> --markdown
codex-plugin-doctor doctor submission <path> --require-ready
```

The preflight is offline and non-executing: it does not submit a package, make network requests, start MCP servers, verify domains, or handle OAuth credentials. Its automatic `status` is `pass` or `fail`; a passing automatic result remains `manual_review_required` until portal-only review is complete. It never claims directory acceptance. See [Public Directory Submission Preflight](./docs/architecture/public-directory-submission-preflight.md).

Output formats:

- human text output
Expand Down Expand Up @@ -480,9 +493,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: Esquetta/CodexPluginDoctor@v1.58.0
- uses: Esquetta/CodexPluginDoctor@v1.59.0
with:
version: "1.58.0"
version: "1.59.0"
path: .
runtime: "true"
policy: codex-publish
Expand All @@ -494,7 +507,7 @@ jobs:
review-bundle-verify: "true"
```

The action writes `codex-plugin-doctor-summary.md`, `codex-plugin-doctor-report.json`, `codex-plugin-doctor-action-manifest.json`, optional `codex-plugin-doctor.sarif`, optional validation corpus and quality metrics reports, optional `output-contract.json`, and optional signed `review-bundle/` files to `codex-plugin-doctor-reports`, appends the Markdown report to the GitHub Actions step summary, uploads the report directory as an artifact, and then returns the real validation exit code. Review bundle generation requires a signing key environment variable such as `CODEX_PLUGIN_DOCTOR_SIGNING_KEY`. For runtime probing, SARIF output, corpus quality regression gates, corpus and contract artifacts, review bundle artifacts, installed plugin cache checks, CI policy presets, and pinned release examples, see [GitHub Action Usage](./docs/guides/github-action.md).
The action writes `codex-plugin-doctor-summary.md`, `codex-plugin-doctor-report.json`, `codex-plugin-doctor-action-manifest.json`, optional `codex-plugin-doctor.sarif`, optional validation corpus and quality metrics reports, optional `output-contract.json`, and optional signed `review-bundle/` files to `codex-plugin-doctor-reports`, appends the Markdown report to the GitHub Actions step summary, uploads the report directory as an artifact, and then returns the real validation exit code. Set `submission: "true"` to add offline submission preflight reports; set `require-submission-ready: "true"` only with that opt-in to make automatic blockers fail the job. Review bundle generation requires a signing key environment variable such as `CODEX_PLUGIN_DOCTOR_SIGNING_KEY`. For runtime probing, SARIF output, corpus quality regression gates, corpus and contract artifacts, review bundle artifacts, installed plugin cache checks, CI policy presets, and pinned release examples, see [GitHub Action Usage](./docs/guides/github-action.md).

To self-test this repository after cloning it:

Expand Down
69 changes: 66 additions & 3 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ inputs:
description: Fail unless the configured local Registry metadata receives a pass result.
required: false
default: "false"
submission:
description: Generate offline public directory submission preflight JSON and Markdown reports.
required: false
default: "false"
require-submission-ready:
description: Fail unless the offline submission preflight is automatically ready; this does not replace manual review.
required: false
default: "false"
installed:
description: Validate plugins from the local Codex plugin cache.
required: false
Expand Down Expand Up @@ -169,6 +177,12 @@ outputs:
registry-report-path:
description: Path to the MCP Registry readiness JSON report when registry-metadata is configured.
value: ${{ steps.run-doctor.outputs.registry-report-path }}
submission-json-path:
description: Path to the offline submission preflight JSON report when submission is enabled.
value: ${{ steps.run-doctor.outputs.submission-json-path }}
submission-summary-path:
description: Path to the offline submission preflight Markdown report when submission is enabled.
value: ${{ steps.run-doctor.outputs.submission-summary-path }}
review-bundle-path:
description: Path to the generated review bundle directory when review-bundle is enabled.
value: ${{ steps.run-doctor.outputs.review-bundle-path }}
Expand All @@ -193,6 +207,8 @@ runs:
REQUIRE_REMOTE_RELIABILITY_INPUT: ${{ inputs['require-remote-reliability'] }}
REGISTRY_METADATA_INPUT: ${{ inputs['registry-metadata'] }}
REQUIRE_REGISTRY_READINESS_INPUT: ${{ inputs['require-registry-readiness'] }}
SUBMISSION_INPUT: ${{ inputs.submission }}
REQUIRE_SUBMISSION_READY_INPUT: ${{ inputs['require-submission-ready'] }}
CORPUS_METRICS_MANIFEST_INPUT: ${{ inputs['corpus-metrics-manifest'] }}
CORPUS_METRICS_BASELINE_INPUT: ${{ inputs['corpus-metrics-baseline'] }}
CORPUS_METRICS_FAIL_ON_REGRESSION_INPUT: ${{ inputs['corpus-metrics-fail-on-regression'] }}
Expand All @@ -209,10 +225,16 @@ runs:
output_contract_path="$report_dir/output-contract.json"
action_manifest_path="$report_dir/codex-plugin-doctor-action-manifest.json"
registry_report_path="$report_dir/mcp-registry-readiness.json"
submission_json_path="$report_dir/codex-plugin-doctor-submission.json"
submission_summary_path="$report_dir/codex-plugin-doctor-submission.md"
review_bundle_path="$report_dir/${{ inputs['review-bundle-dir'] }}"
review_bundle_verification_path="$report_dir/review-bundle-verification.json"
status_file="${RUNNER_TEMP:-.}/codex-plugin-doctor-status"
submission_state_file="${RUNNER_TEMP:-.}/codex-plugin-doctor-submission-ran"
status=0
submission_ran=false
submission_json_output=""
submission_summary_output=""
doctor_version="$(codex-plugin-doctor --version)"

mkdir -p "$report_dir"
Expand Down Expand Up @@ -348,6 +370,24 @@ runs:
run_doctor "MCP Registry readiness" "${registry_args[@]}"
fi

if [[ "$REQUIRE_SUBMISSION_READY_INPUT" == "true" && "$SUBMISSION_INPUT" != "true" ]]; then
echo "require-submission-ready requires submission." >&2
record_status 2
elif [[ "$SUBMISSION_INPUT" == "true" && "${{ inputs.installed }}" == "true" ]]; then
echo "Submission preflight requires a single package path, not installed-cache mode." >&2
record_status 2
elif [[ "$SUBMISSION_INPUT" == "true" ]]; then
submission_ran=true
submission_args=(doctor submission "${{ inputs.path }}" --json --output "$submission_json_path")
if [[ "$REQUIRE_SUBMISSION_READY_INPUT" == "true" ]]; then
submission_args+=(--require-ready)
fi
run_doctor "submission preflight" "${submission_args[@]}"
run_doctor "submission summary" doctor submission "${{ inputs.path }}" --markdown --output "$submission_summary_path"
submission_json_output="$submission_json_path"
submission_summary_output="$submission_summary_path"
fi

if [[ "${{ inputs['review-bundle'] }}" == "true" ]]; then
signing_key_env="${{ inputs['signing-key-env'] }}"

Expand Down Expand Up @@ -395,6 +435,7 @@ runs:
export CODEX_PLUGIN_DOCTOR_ACTION_CORPUS_METRICS_DIFF="$([[ -n "$CORPUS_METRICS_BASELINE_INPUT" ]] && echo true || echo false)"
export CODEX_PLUGIN_DOCTOR_ACTION_CONTRACT="${{ inputs.contract }}"
export CODEX_PLUGIN_DOCTOR_ACTION_REGISTRY="$([[ -n "$REGISTRY_METADATA_INPUT" ]] && echo true || echo false)"
export CODEX_PLUGIN_DOCTOR_ACTION_SUBMISSION="$submission_ran"
export CODEX_PLUGIN_DOCTOR_ACTION_REVIEW_BUNDLE="${{ inputs['review-bundle'] }}"
export CODEX_PLUGIN_DOCTOR_ACTION_REVIEW_BUNDLE_VERIFY="${{ inputs['review-bundle-verify'] }}"
export CODEX_PLUGIN_DOCTOR_ACTION_SUMMARY_PATH="$summary_path"
Expand All @@ -405,12 +446,19 @@ runs:
export CODEX_PLUGIN_DOCTOR_ACTION_CORPUS_METRICS_DIFF_PATH="$corpus_metrics_diff_path"
export CODEX_PLUGIN_DOCTOR_ACTION_CONTRACT_PATH="$output_contract_path"
export CODEX_PLUGIN_DOCTOR_ACTION_REGISTRY_PATH="$registry_report_path"
export CODEX_PLUGIN_DOCTOR_ACTION_SUBMISSION_JSON_PATH="$submission_json_output"
export CODEX_PLUGIN_DOCTOR_ACTION_SUBMISSION_SUMMARY_PATH="$submission_summary_output"
export CODEX_PLUGIN_DOCTOR_ACTION_REVIEW_BUNDLE_PATH="$review_bundle_path"
export CODEX_PLUGIN_DOCTOR_ACTION_REVIEW_BUNDLE_VERIFICATION_PATH="$review_bundle_verification_path"
node <<'NODE'
const fs = require("node:fs");
const path = require("node:path");

const enabled = (name) => process.env[name] === "true";
const displayTargetPath = (value) => {
if (/^file:/iu.test(value) || path.isAbsolute(value) || path.win32.isAbsolute(value)) return "[absolute-path-redacted]";
return value === "" ? "" : path.posix.normalize(value.replace(/\\/gu, "/"));
};
const report = (key, enabledEnv, pathEnv) => ({
enabled: enabled(enabledEnv),
path: process.env[pathEnv] || ""
Expand All @@ -424,7 +472,7 @@ runs:
reportDirectory: process.env.CODEX_PLUGIN_DOCTOR_ACTION_REPORT_DIR || "",
artifactName: process.env.CODEX_PLUGIN_DOCTOR_ACTION_ARTIFACT_NAME || "",
target: {
path: process.env.CODEX_PLUGIN_DOCTOR_ACTION_PATH || "",
path: displayTargetPath(process.env.CODEX_PLUGIN_DOCTOR_ACTION_PATH || ""),
installed: enabled("CODEX_PLUGIN_DOCTOR_ACTION_INSTALLED"),
runtime: enabled("CODEX_PLUGIN_DOCTOR_ACTION_RUNTIME")
},
Expand All @@ -437,6 +485,8 @@ runs:
corpusMetricsDiff: report("corpusMetricsDiff", "CODEX_PLUGIN_DOCTOR_ACTION_CORPUS_METRICS_DIFF", "CODEX_PLUGIN_DOCTOR_ACTION_CORPUS_METRICS_DIFF_PATH"),
contract: report("contract", "CODEX_PLUGIN_DOCTOR_ACTION_CONTRACT", "CODEX_PLUGIN_DOCTOR_ACTION_CONTRACT_PATH"),
registryReport: report("registryReport", "CODEX_PLUGIN_DOCTOR_ACTION_REGISTRY", "CODEX_PLUGIN_DOCTOR_ACTION_REGISTRY_PATH"),
submissionJson: report("submissionJson", "CODEX_PLUGIN_DOCTOR_ACTION_SUBMISSION", "CODEX_PLUGIN_DOCTOR_ACTION_SUBMISSION_JSON_PATH"),
submissionSummary: report("submissionSummary", "CODEX_PLUGIN_DOCTOR_ACTION_SUBMISSION", "CODEX_PLUGIN_DOCTOR_ACTION_SUBMISSION_SUMMARY_PATH"),
reviewBundle: report("reviewBundle", "CODEX_PLUGIN_DOCTOR_ACTION_REVIEW_BUNDLE", "CODEX_PLUGIN_DOCTOR_ACTION_REVIEW_BUNDLE_PATH"),
reviewBundleVerification: report("reviewBundleVerification", "CODEX_PLUGIN_DOCTOR_ACTION_REVIEW_BUNDLE_VERIFY", "CODEX_PLUGIN_DOCTOR_ACTION_REVIEW_BUNDLE_VERIFICATION_PATH")
}
Expand All @@ -450,6 +500,7 @@ runs:
NODE

printf "%s" "$status" > "$status_file"
printf "%s" "$submission_ran" > "$submission_state_file"

{
echo "status=$status"
Expand All @@ -463,6 +514,8 @@ runs:
echo "output-contract-path=$output_contract_path"
echo "action-manifest-path=$action_manifest_path"
echo "registry-report-path=$registry_report_path"
echo "submission-json-path=$submission_json_output"
echo "submission-summary-path=$submission_summary_output"
echo "review-bundle-path=$review_bundle_path"
echo "review-bundle-verification-path=$review_bundle_verification_path"
} >> "$GITHUB_OUTPUT"
Expand All @@ -475,19 +528,29 @@ runs:

report_dir="${{ inputs['output-dir'] }}"
summary_path="$report_dir/codex-plugin-doctor-summary.md"
submission_summary_path="$report_dir/codex-plugin-doctor-submission.md"
status_file="${RUNNER_TEMP:-.}/codex-plugin-doctor-status"
submission_state_file="${RUNNER_TEMP:-.}/codex-plugin-doctor-submission-ran"
status="unknown"
submission_ran=false

if [[ -f "$status_file" ]]; then
status="$(cat "$status_file")"
fi

if [[ -f "$submission_state_file" ]]; then
submission_ran="$(cat "$submission_state_file")"
fi

if [[ -n "${GITHUB_STEP_SUMMARY:-}" && -f "$summary_path" ]]; then
cat "$summary_path" >> "$GITHUB_STEP_SUMMARY"
exit 0
fi

if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then
if [[ -n "${GITHUB_STEP_SUMMARY:-}" && "$submission_ran" == "true" && -f "$submission_summary_path" ]]; then
cat "$submission_summary_path" >> "$GITHUB_STEP_SUMMARY"
fi

if [[ -n "${GITHUB_STEP_SUMMARY:-}" && ! -f "$summary_path" && ( "$submission_ran" != "true" || ! -f "$submission_summary_path" ) ]]; then
{
echo "## Codex Plugin Doctor"
echo ""
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This directory contains public documentation for users, contributors, and securi
- [Remote MCP Transport Reliability](architecture/remote-mcp-transport-reliability.md)
- [MCP Registry Readiness](architecture/mcp-registry-readiness.md)
- [MCP Registry Publication Preflight](architecture/mcp-registry-publication-preflight.md)
- [Public Directory Submission Preflight](architecture/public-directory-submission-preflight.md)
- [Real-World Corpus Quality Metrics](architecture/real-world-corpus-quality-metrics.md)
- [Corpus Metrics Regression Diff](architecture/corpus-metrics-regression-diff.md)

Expand Down
Loading