Skip to content

Add public directory submission preflight - #27

Merged
Esquetta merged 24 commits into
mainfrom
feature/v1.59-public-directory-preflight
Aug 17, 2026
Merged

Add public directory submission preflight#27
Esquetta merged 24 commits into
mainfrom
feature/v1.59-public-directory-preflight

Conversation

@Esquetta

Copy link
Copy Markdown
Owner

Adds the offline doctor submission preflight, bounded listing/asset/skill validation, opt-in GitHub Action reports, and v1.59.0 release metadata. Manual portal review remains explicit. Verification: npm run release-check passed with 1,271 tests and 6 environment-gated skips, build, package audit, fresh install, and publish dry-run.

@Esquetta
Esquetta merged commit f7d01e4 into main Aug 17, 2026
2 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2a8dd3e5b0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +307 to +311
return Object.entries(value).every(([key, item]) => {
const childContext = context === "css" || key === "style" || key === "@_style"
? "css"
: key === "@_href" || key === "@_xlink:href" ? "href" : undefined;
return inspectSvgValue(item, childContext);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject executable SVG elements and attributes

When a logo or composer icon contains an inline <script>, an onload handler, or foreign HTML content, this walker only applies special validation to style and href keys and accepts every other element and attribute. svgDimensions therefore returns dimensions and the preflight can mark an executable SVG as safe; reject scripting/foreign-content elements and event-handler attributes before approving the asset.

Useful? React with 👍 / 👎.

Comment on lines +228 to +231
} else if (chunkType === "VP8L") {
if (chunkLength < 5 || buffer[payload] !== 0x2f) return null;
const packed = view.getUint32(payload + 1, true);
detected = { width: (packed & 0x3fff) + 1, height: ((packed >>> 14) & 0x3fff) + 1 };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fully decode WebP assets before passing them

When a VP8L file contains only the five-byte signature/dimension header and no encoded image data, this branch still returns dimensions, causing rasterAsset to accept a corrupt WebP. The same malformed asset will fail in a real image decoder or the submission portal, so the preflight should validate the complete bitstream rather than treating its header as a successful decode.

Useful? React with 👍 / 👎.

type TargetType = SubmissionPreflightReport["targetType"];

const packageNamePattern = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
const semverPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject leading-zero numeric prerelease identifiers

When the manifest version is, for example, 1.0.0-01, this regex accepts it even though SemVer forbids leading zeroes in numeric prerelease identifiers. Such a package can receive a passing preflight despite having the invalid semantic version that this check claims to reject, so use strict SemVer parsing or distinguish numeric prerelease identifiers in the pattern.

Useful? React with 👍 / 👎.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add offline public directory submission preflight (v1.59.0)

✨ Enhancement 📝 Documentation 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Adds doctor submission  for offline public-directory preflight (listing, assets, skills).
• Enforces bounded parsing and canonical containment for images, SVG, and YAML.
• Adds opt-in GitHub Action submission reports/outputs and bumps release to v1.59.0.
Diagram

graph TD
  CLI["doctor submission CLI"] --> Preflight["submission-preflight.ts"] --> Ruleset[("submission-ruleset.ts")]
  Preflight --> Listing["Listing/App checks"]
  Preflight --> Assets["submission-assets.ts"]
  Preflight --> Skills["submission-skill-metadata.ts"]
  Preflight --> Report["render-submission-report.ts"] --> Action["GitHub Action opt-in"]
  subgraph Legend
    direction LR
    _m(["Module"]) ~~~ _d[("Static ruleset")] ~~~ _e{{"External"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use an image-metadata library (e.g. image-size/sharp metadata)
  • ➕ Less custom binary parsing code to maintain
  • ➕ Potentially broader format support
  • ➖ Harder to guarantee strict resource bounds (decompression/max output) needed for DoS resistance
  • ➖ Higher supply-chain and audit surface than narrow in-house parsers
  • ➖ May still require significant guardrails around malformed inputs
2. Use a full-featured YAML parser/config (e.g. permissive schemas)
  • ➕ Less custom validation code
  • ➖ Must explicitly block aliases/custom tags and other unsafe features
  • ➖ Increased risk of unexpected constructs sneaking into the supported surface

Recommendation: Given the tool’s stated posture (offline-only, non-executing, bounded, low leakage), the PR’s approach of implementing narrowly scoped, resource-bounded parsers and rejecting unsafe YAML/SVG constructs is appropriate. The main reviewer focus should be on the security invariants (containment, size/entry budgets, decoder correctness) rather than adding more permissive third-party parsing dependencies.

Files changed (29) +3654 / -48

Enhancement (10) +1556 / -3
action.ymlAdd opt-in submission preflight to GitHub Action +66/-3

Add opt-in submission preflight to GitHub Action

• Adds 'submission' and 'require-submission-ready' inputs, emits submission JSON/Markdown report paths as outputs, appends the Markdown report to the step summary when enabled, and redacts absolute/file-URI target paths in the generated Action manifest.

action.yml

output-contract.tsAdd doctor.submission.json output contract schema +76/-0

Add doctor.submission.json output contract schema

• Registers a new public output contract surface for submission preflight JSON, including schema constraints for checks, findings, and manual checklist.

src/core/output-contract.ts

shell-completion.tsAdd doctor submission completions and scope flags +30/-0

Add doctor submission completions and scope flags

• Extends bash/zsh/fish completion generation to include the new 'doctor submission' subcommand and its flags while keeping them scoped to that subcommand.

src/core/shell-completion.ts

submission-assets.tsValidate submission branding assets with bounded decoders +417/-0

Validate submission branding assets with bounded decoders

• Implements validation for required 'logo' and 'composerIcon' assets, including safe path resolution, size limits, extension/content matching, bounded raster dimension decoding (PNG/JPEG/WebP), and safe SVG parsing/sanitization.

src/core/submission-assets.ts

submission-preflight.tsBuild submission preflight report and enforce boundaries +361/-0

Build submission preflight report and enforce boundaries

• Adds a new report model (schemaVersion 1.0.0) and orchestrator that discovers '.codex-plugin/plugin.json', validates listing fields, checks '.app.json' boundaries, runs asset + skills validators, and computes pass/fail + readiness + manual checklist.

src/core/submission-preflight.ts

submission-ruleset.tsAdd frozen submission ruleset and manual checklist +39/-0

Add frozen submission ruleset and manual checklist

• Defines the immutable submission ruleset (limits, categories, sources) and the manual checklist items, including MCP-only applicability.

src/core/submission-ruleset.ts

submission-skill-metadata.tsValidate skills directory and optional openai.yaml agent metadata +348/-0

Validate skills directory and optional openai.yaml agent metadata

• Adds safe directory traversal and bounded reading for SKILL.md frontmatter/body and optional agents/openai.yaml, rejecting unsafe YAML constructs (aliases/tags) and enforcing aggregate byte and entry limits.

src/core/submission-skill-metadata.ts

index.tsExport submission preflight APIs +25/-0

Export submission preflight APIs

• Exports the new submission ruleset, preflight builder, validators, and renderers from the public package surface.

src/index.ts

render-submission-report.tsRender submission preflight reports and exit code +94/-0

Render submission preflight reports and exit code

• Adds JSON/text/Markdown rendering for submission preflight reports and an exit-code helper for '--require-ready' gating.

src/reporting/render-submission-report.ts

run-cli.tsAdd doctor submission CLI command and flags +100/-0

Add doctor submission CLI command and flags

• Adds 'doctor submission' routing, flag parsing for '--json|--markdown|--output|--require-ready', and exact stdout writing to ensure file and stdout bytes match.

src/run-cli.ts

Tests (11) +1570 / -12
action-metadata.test.tsTest Action submission wiring and target path redaction +114/-1

Test Action submission wiring and target path redaction

• Adds tests for new Action inputs/outputs and ensures absolute/file-URI targets are redacted in the Action manifest; verifies installed-cache incompatibility is handled safely.

tests/action-metadata.test.ts

completion.test.tsTest submission completion flag scoping +15/-3

Test submission completion flag scoping

• Updates completion tests to ensure submission flags appear only under 'doctor submission' in bash/zsh/fish scripts.

tests/completion.test.ts

contract-command.test.tsTest output-contract includes doctor.submission.json +28/-0

Test output-contract includes doctor.submission.json

• Adds assertions that the contract command includes the new submission schema and expected required fields/properties.

tests/contract-command.test.ts

public-readiness.test.tsVerify docs describe submission preflight without implying approval +40/-0

Verify docs describe submission preflight without implying approval

• Adds checks that README/docs/action guide/rules catalog describe the offline nature and avoid language implying portal acceptance.

tests/public-readiness.test.ts

release-check.test.tsUpdate release-check expectations to 1.59.0 +2/-2

Update release-check expectations to 1.59.0

• Bumps version assertions to match the new release number.

tests/release-check.test.ts

release-notes.test.tsUpdate release-notes tests for 1.59.0 and pinned examples +20/-4

Update release-notes tests for 1.59.0 and pinned examples

• Updates changelog section indexing and asserts README/Action examples are pinned to the latest release version.

tests/release-notes.test.ts

release-sync.test.tsUpdate release-sync expectations to 1.59.0 +2/-2

Update release-sync expectations to 1.59.0

• Bumps stable release target assertions to the new version.

tests/release-sync.test.ts

submission-assets.test.tsAdd comprehensive submission asset validation tests +357/-0

Add comprehensive submission asset validation tests

• Adds synthetic PNG/JPEG/WebP/SVG fixtures to verify decode/format checks, bounds, and failure modes for malformed or unsafe assets.

tests/submission-assets.test.ts

submission-command.test.tsAdd end-to-end tests for doctor submission outputs and redaction +187/-0

Add end-to-end tests for doctor submission outputs and redaction

• Tests text/JSON/Markdown output, output-file byte equality, exit codes, and redaction of sensitive content from rendered reports.

tests/submission-command.test.ts

submission-preflight.test.tsAdd submission preflight orchestration tests +422/-0

Add submission preflight orchestration tests

• Tests ruleset publication, target classification, listing checks, and aggregate report shape/status computations.

tests/submission-preflight.test.ts

submission-skill-metadata.test.tsAdd skill + agent metadata safety tests +383/-0

Add skill + agent metadata safety tests

• Covers safe directory containment, entry limits, aggregate size budgets, YAML safety constraints, and tool descriptor validation.

tests/submission-skill-metadata.test.ts

Documentation (6) +379 / -27
CHANGELOG.mdAdd v1.59.0 release notes for submission preflight +17/-0

Add v1.59.0 release notes for submission preflight

• Introduces the 1.59.0 changelog entry describing the new submission preflight command, Action opt-in support, and security/bounding guarantees.

CHANGELOG.md

README.mdDocument doctor submission usage and update pinned Action version +16/-3

Document doctor submission usage and update pinned Action version

• Adds a README section describing the offline submission preflight command and updates workflow examples to v1.59.0.

README.md

README.mdLink new submission preflight architecture doc +1/-0

Link new submission preflight architecture doc

• Adds the Public Directory Submission Preflight document to the docs index.

docs/README.md

public-directory-submission-preflight.mdAdd architecture/design spec for submission preflight +256/-0

Add architecture/design spec for submission preflight

• Provides a detailed spec: command surface, offline/non-executing boundary, result model, automatic rules, manual checklist, privacy/evidence redaction, and ruleset governance.

docs/architecture/public-directory-submission-preflight.md

github-action.mdDocument Action submission preflight usage and outputs +43/-24

Document Action submission preflight usage and outputs

• Adds a section describing the opt-in submission reports, readiness gating, and outputs; updates pinned Action examples to v1.59.0.

docs/guides/github-action.md

catalog.mdAdd plugin.submission.* rule catalog section +46/-0

Add plugin.submission.* rule catalog section

• Documents the new submission preflight rule IDs, severities, and meanings for reviewers and users.

docs/rules/catalog.md

Other (2) +149 / -6
package-lock.jsonLockfile update for v1.59.0, new deps, and nanoid remediation +144/-5

Lockfile update for v1.59.0, new deps, and nanoid remediation

• Bumps the root version to 1.59.0 and adds lock entries for 'yaml' and 'fast-xml-parser' (and transitives), including a transitive nanoid vulnerability remediation.

package-lock.json

package.jsonRelease bump to 1.59.0 and add runtime deps +5/-1

Release bump to 1.59.0 and add runtime deps

• Bumps the package version to 1.59.0 and adds 'yaml' and 'fast-xml-parser' runtime dependencies used by submission validation.

package.json

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Duplicate findings dropped 🐞 Bug ≡ Correctness
Description
The submission text/Markdown renderers de-duplicate findings by finding.id, so multiple findings
sharing the same id overwrite each other and the report can hide distinct failures (JSON output is
unaffected). This commonly happens for missing required assets and multiple URL/unknown-field
issues, causing misleading “fix one thing” output.
Code

src/reporting/render-submission-report.ts[R15-17]

+  const findings = new Map<string, SubmissionFinding>(
+    report.findings.map((finding) => [finding.id, finding])
+  );
Evidence
findingsByCheck() collapses findings into a Map keyed by id, but the preflight intentionally emits
repeated ids (e.g., one per invalid URL field, one per unknown interface key, and one per missing
asset field). This means the text/Markdown report cannot faithfully reproduce the original finding
instances and will drop or misattribute evidence.

src/reporting/render-submission-report.ts[11-25]
src/core/submission-preflight.ts[191-205]
src/core/submission-assets.ts[353-356]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`findingsByCheck()` builds a `Map` keyed by `finding.id`, which assumes ids are unique. The submission preflight can legitimately emit multiple findings with the same id (e.g., one per missing asset field), so text/Markdown reports drop/overwrite earlier findings and may repeat the last instance.

### Issue Context
- The JSON renderer (`renderSubmissionPreflightJson`) serializes `report.findings` directly, so it preserves duplicates.
- Only the human-readable renderers (via `findingsByCheck`) are impacted.

### Fix
Update `findingsByCheck()` to preserve *instances*, not just ids. A minimal fix is to map `id -> SubmissionFinding[]` and consume entries in order when iterating `check.findingIds` (queue semantics), rather than mapping `id -> SubmissionFinding`.

### Fix Focus Areas
- src/reporting/render-submission-report.ts[11-25]
- src/core/submission-preflight.ts[191-205]
- src/core/submission-assets.ts[353-356]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Misleading dimension limit 🐞 Bug ◔ Observability
Description
When an asset is outside the allowed dimension range, the finding evidence always reports `limit:
minimumDimension` even when the asset is too large. This produces misleading evidence for oversized
assets and complicates debugging/automation consuming the evidence.
Code

src/core/submission-assets.ts[R347-349]

+  if (dimensions.width < minimumDimension || dimensions.width > maximumDimension) {
+    return finding("plugin.submission.asset.dimensions", "Asset dimensions are outside the allowed range.", { ...evidence, limit: minimumDimension });
+  }
Evidence
The code checks both the lower and upper bound but records only the minimum bound in evidence, so a
too-large image is reported with the wrong limit value even though the decision to fail is correct.

src/core/submission-assets.ts[11-15]
src/core/submission-assets.ts[339-350]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`dimensionFinding()` rejects dimensions below `minimumDimension` or above `maximumDimension`, but the evidence always sets `limit: minimumDimension`. For upper-bound failures, the evidence misrepresents the violated constraint.

### Issue Context
This affects both raster and SVG asset validation paths, since both funnel through `dimensionFinding()`.

### Fix
Emit accurate bounds in evidence, e.g. `{ min: minimumDimension, max: maximumDimension }`, or conditionally set `limit` to the bound that was violated (min when too small, max when too large).

### Fix Focus Areas
- src/core/submission-assets.ts[11-15]
- src/core/submission-assets.ts[339-350]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can route each action level your way: inline, summary, both, or drop

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +15 to +17
const findings = new Map<string, SubmissionFinding>(
report.findings.map((finding) => [finding.id, finding])
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Duplicate findings dropped 🐞 Bug ≡ Correctness

The submission text/Markdown renderers de-duplicate findings by finding.id, so multiple findings
sharing the same id overwrite each other and the report can hide distinct failures (JSON output is
unaffected). This commonly happens for missing required assets and multiple URL/unknown-field
issues, causing misleading “fix one thing” output.
Agent Prompt
### Issue description
`findingsByCheck()` builds a `Map` keyed by `finding.id`, which assumes ids are unique. The submission preflight can legitimately emit multiple findings with the same id (e.g., one per missing asset field), so text/Markdown reports drop/overwrite earlier findings and may repeat the last instance.

### Issue Context
- The JSON renderer (`renderSubmissionPreflightJson`) serializes `report.findings` directly, so it preserves duplicates.
- Only the human-readable renderers (via `findingsByCheck`) are impacted.

### Fix
Update `findingsByCheck()` to preserve *instances*, not just ids. A minimal fix is to map `id -> SubmissionFinding[]` and consume entries in order when iterating `check.findingIds` (queue semantics), rather than mapping `id -> SubmissionFinding`.

### Fix Focus Areas
- src/reporting/render-submission-report.ts[11-25]
- src/core/submission-preflight.ts[191-205]
- src/core/submission-assets.ts[353-356]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +347 to +349
if (dimensions.width < minimumDimension || dimensions.width > maximumDimension) {
return finding("plugin.submission.asset.dimensions", "Asset dimensions are outside the allowed range.", { ...evidence, limit: minimumDimension });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Misleading dimension limit 🐞 Bug ◔ Observability

When an asset is outside the allowed dimension range, the finding evidence always reports `limit:
minimumDimension` even when the asset is too large. This produces misleading evidence for oversized
assets and complicates debugging/automation consuming the evidence.
Agent Prompt
### Issue description
`dimensionFinding()` rejects dimensions below `minimumDimension` or above `maximumDimension`, but the evidence always sets `limit: minimumDimension`. For upper-bound failures, the evidence misrepresents the violated constraint.

### Issue Context
This affects both raster and SVG asset validation paths, since both funnel through `dimensionFinding()`.

### Fix
Emit accurate bounds in evidence, e.g. `{ min: minimumDimension, max: maximumDimension }`, or conditionally set `limit` to the bound that was violated (min when too small, max when too large).

### Fix Focus Areas
- src/core/submission-assets.ts[11-15]
- src/core/submission-assets.ts[339-350]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant