Skip to content

feat: add v1.60 submission archive preflight - #28

Merged
Esquetta merged 20 commits into
mainfrom
feature/v1.60-submission-archive-preflight
Aug 25, 2026
Merged

feat: add v1.60 submission archive preflight#28
Esquetta merged 20 commits into
mainfrom
feature/v1.60-submission-archive-preflight

Conversation

@Esquetta

Copy link
Copy Markdown
Owner

Summary

  • add bounded, no-extraction Submission Archive Preflight for existing ZIP packages
  • expose CLI, JSON contract, shell completion, and opt-in GitHub Action archive reports
  • add ZIP64, descriptor, path, type, CRC, budget, privacy, and Action behavior regression coverage
  • prepare package and changelog for v1.60.0

Test Plan

  • npm test (1,379 passed; 9 skipped)
  • npm run build
  • npm run release-check
  • npm audit --omit=dev --audit-level=low
  • repeated malformed archive corpus and Action behavior harness

@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: 0272677618

ℹ️ 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 +637 to +639
if ([...paths.keys()].some((existing) => existing.startsWith(`${normalized.path}/`) || normalized.path.startsWith(`${existing}/`))) {
report.findings.push(archiveFinding("plugin.submission.archive.path_conflict", "Archive entry paths conflict.", { path: normalized.path }));
continue;

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 Allow directory entries to contain descendants

When a ZIP contains explicit directory records, this prefix check treats the normal directory/child relationship as a path conflict. Archives produced by common recursive ZIP tools include entries such as pkg/, pkg/.codex-plugin/, and their files, so otherwise valid submission archives are rejected before preflight; only a non-directory parent should conflict with descendants.

Useful? React with 👍 / 👎.

let rootLayout: SubmissionArchiveRootLayout = "unavailable";
let submission: SubmissionPreflightReport | null = null;

if (!archiveFindings.some((finding) => finding.severity === "fail") && inspection.reader !== null) {

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 Fail when compression prevents package validation

When any regular entry uses a well-formed unsupported compression method, inspection deliberately returns no failure but also sets reader to null. This condition then skips root, manifest, asset, and skill validation without adding a blocker, so even an archive with no manifest can report status: "pass", and --require-ready exits 0; unavailable compression coverage should not silently bypass the remaining automatic readiness checks.

Useful? React with 👍 / 👎.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add offline Submission Archive Preflight for existing ZIP packages (v1.60)

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

Grey Divider

AI Description

• Add a bounded, no-extraction ZIP preflight (doctor submission archive ) for skills-only
 submissions.
• Reuse existing listing/asset/skill validation via a new shared SubmissionPackageReader
 abstraction.
• Expose JSON/Markdown reporting through CLI, output contract, shell completion, and an opt-in
 GitHub Action mode.
• Add regression suites for ZIP boundaries, Action behavior, and release-ready v1.60.0
 docs/versioning.
Diagram

graph TD
  CLI["CLI: doctor submission archive"] --> Preflight["submission-archive-preflight.ts"] --> Reader["submission-archive-reader.ts"] --> ZipFile[("ZIP file")]
  Preflight --> PkgReader["SubmissionPackageReader"] --> Assets["submission-assets.ts"]
  PkgReader --> Skills["submission-skill-metadata.ts"]
  Preflight --> Render["render-submission-archive-report.ts"] --> Action["action.yml"]
  subgraph Legend
    direction LR
    _svc([Component]) ~~~ _db[(Data store)] ~~~ _ext{{External surface}}
  end
Loading
High-Level Assessment

The PR’s approach is appropriate: a custom bounded ZIP validation layer (instead of shelling out or trusting a generic unzip path) enables hard resource budgets and cross-validation (local vs central headers, descriptor handling, overlap detection) before exposing content. Reusing listing/asset/skill logic via SubmissionPackageReader avoids duplicating validation across directory vs archive flows, keeping behavior aligned.

Files changed (37) +4517 / -261

Enhancement (7) +1250 / -0
output-contract.tsAdd doctor.submission.archive.json schema +62/-0

Add doctor.submission.archive.json schema

• Adds a public output schema for archive preflight JSON including archive metadata, summary, findings, coverage, and manual checklist.

src/core/output-contract.ts

shell-completion.tsAdd completion for submission archive subcommand +23/-0

Add completion for submission archive subcommand

• Adds 'archive' as a submission target and enables file completion for the ZIP argument across bash/zsh/fish.

src/core/shell-completion.ts

submission-archive-preflight.tsAdd archive preflight orchestration and report building +316/-0

Add archive preflight orchestration and report building

• Implements archive root discovery, manifest parsing, exclusion warnings (MCP/apps/screenshots), aggregation of nested submission checks, and exit-code gating via '--require-ready'.

src/core/submission-archive-preflight.ts

submission-archive-reader.tsAdd bounded ZIP structural inspector and safe content reader +688/-0

Add bounded ZIP structural inspector and safe content reader

• Implements no-extraction ZIP inspection with size/entry budgets, ZIP64 and descriptor handling, header consistency checks, CRC validation, path/type safety, and overlap detection; only exposes a reader after passing safety gates.

src/core/submission-archive-reader.ts

index.tsExport archive preflight/report APIs +11/-0

Export archive preflight/report APIs

• Exports archive preflight builders/exit-code helper and archive report renderers from the public package API.

src/index.ts

render-submission-archive-report.tsAdd archive report renderers +61/-0

Add archive report renderers

• Implements JSON/text/Markdown rendering for archive preflight reports with escaping and summary sections.

src/reporting/render-submission-archive-report.ts

run-cli.tsAdd doctor submission archive CLI command and dispatch +89/-0

Add doctor submission archive CLI command and dispatch

• Adds argument parsing and dispatch for 'doctor submission archive <zip>' with legacy fallback when an 'archive/' directory exists and no target is provided.

src/run-cli.ts

Refactor (4) +553 / -205
submission-assets.tsRefactor asset validation to use package readers +64/-31

Refactor asset validation to use package readers

• Adds 'validateSubmissionAssetsFromReader' and routes directory validation through 'createDirectorySubmissionPackageReader', replacing direct filesystem reads.

src/core/submission-assets.ts

submission-package-reader.tsAdd SubmissionPackageReader abstraction for safe bounded reads +361/-0

Add SubmissionPackageReader abstraction for safe bounded reads

• Introduces a directory-backed reader with normalized package paths, symlink-safe canonical containment checks, bounded file reads, and concurrency-bounded directory listing.

src/core/submission-package-reader.ts

submission-preflight.tsExport submission listing validator and target type +4/-3

Export submission listing validator and target type

• Exports 'validateSubmissionListing' and 'SubmissionTargetType' for reuse by archive preflight.

src/core/submission-preflight.ts

submission-skill-metadata.tsRefactor skill metadata validation to use package readers +124/-171

Refactor skill metadata validation to use package readers

• Adds 'validateSubmissionSkillMetadataFromReader' and refactors skill/agent validation to use reader list/stat/read, preserving containment and budget checks for directory and archive targets.

src/core/submission-skill-metadata.ts

Tests (16) +2069 / -20
action-archive-behavior.test.tsAdd Action archive-mode behavior harness +249/-0

Add Action archive-mode behavior harness

• End-to-end tests for the composite Action script in archive mode, validating outputs, step summary behavior, and exclusion/mutual-exclusion rules.

tests/action-archive-behavior.test.ts

action-metadata.test.tsExtend Action metadata tests for archive mode +35/-3

Extend Action metadata tests for archive mode

• Adds assertions for submission-archive inputs/outputs and mutual exclusion error messaging; normalizes newlines for stable comparisons.

tests/action-metadata.test.ts

submission-memory-reader.tsAdd in-memory SubmissionPackageReader test helper +108/-0

Add in-memory SubmissionPackageReader test helper

• Implements a MemorySubmissionPackageReader used to test validators without filesystem access.

tests/helpers/submission-memory-reader.ts

zip-fixture.tsAdd deterministic ZIP fixture builder +148/-0

Add deterministic ZIP fixture builder

• Implements a minimal ZIP writer supporting ZIP64 and data descriptors to generate both valid and malformed archives for reader tests.

tests/helpers/zip-fixture.ts

public-readiness.test.tsUpdate public readiness assertions +14/-1

Update public readiness assertions

• Adjusts readiness expectations to reflect added public artifacts/docs.

tests/public-readiness.test.ts

release-check.test.tsUpdate release-check expectations for 1.60.0 +2/-2

Update release-check expectations for 1.60.0

• Updates tests to expect the new version/release metadata.

tests/release-check.test.ts

release-notes.test.tsUpdate release-notes expectations for 1.60.0 +12/-10

Update release-notes expectations for 1.60.0

• Updates changelog/release-notes assertions for the new release entry.

tests/release-notes.test.ts

release-sync.test.tsUpdate release-sync expectations for 1.60.0 +2/-2

Update release-sync expectations for 1.60.0

• Updates version sync checks across package/changelog for v1.60.0.

tests/release-sync.test.ts

submission-archive-command.test.tsAdd CLI and API tests for submission archive +133/-0

Add CLI and API tests for submission archive

• Tests JSON output redaction and file writing, verifies contract publication, API exports, and completion wiring.

tests/submission-archive-command.test.ts

submission-archive-completion.test.tsAdd completion tests for archive target +22/-0

Add completion tests for archive target

• Verifies archive-aware bash/zsh completion behavior and flag scoping.

tests/submission-archive-completion.test.ts

submission-archive-dispatch.test.tsAdd dispatch tests for legacy 'archive' directory target +51/-0

Add dispatch tests for legacy 'archive' directory target

• Ensures 'doctor submission archive' remains a valid legacy directory submission target when 'archive/' exists; verifies archive-aware file completion snippets.

tests/submission-archive-dispatch.test.ts

submission-archive-preflight.test.tsAdd archive preflight integration tests +266/-0

Add archive preflight integration tests

• Covers valid archive aggregation, root discovery/layout rules, exclusion findings, and fail/warn conditions in the archive preflight report.

tests/submission-archive-preflight.test.ts

submission-archive-reader.test.tsAdd ZIP reader regression suite +460/-0

Add ZIP reader regression suite

• Covers stored/deflate, EOCD comments, empty zips, malformed/truncated/multi-disk/encrypted cases, CRC and header mismatches, and budget enforcement.

tests/submission-archive-reader.test.ts

submission-assets.test.tsUpdate asset tests for reader-based validation +130/-1

Update asset tests for reader-based validation

• Adapts tests to exercise asset validation via the new reader-based pathway.

tests/submission-assets.test.ts

submission-package-reader.test.tsAdd tests for directory SubmissionPackageReader +276/-0

Add tests for directory SubmissionPackageReader

• Verifies listing order, non-recursive listing, bounded reads, and safe handling of symlinks/unsafe paths.

tests/submission-package-reader.test.ts

submission-skill-metadata.test.tsUpdate skill metadata tests for reader-based validation +161/-1

Update skill metadata tests for reader-based validation

• Adapts existing skill/agent validation tests to the reader-based implementation and updated path containment logic.

tests/submission-skill-metadata.test.ts

Documentation (6) +469 / -28
CHANGELOG.mdDocument v1.60.0 archive preflight release notes +17/-0

Document v1.60.0 archive preflight release notes

• Adds a 1.60.0 entry describing the new archive preflight, advisory vs strict gating, and offline safety guarantees.

CHANGELOG.md

README.mdDocument archive CLI usage and bump Action version references +8/-2

Document archive CLI usage and bump Action version references

• Adds 'doctor submission archive <zip>' usage examples and describes no-extraction behavior; updates Action examples to v1.60.0.

README.md

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

Link new archive preflight architecture doc

• Adds a documentation index entry for Public Directory Archive Preflight.

docs/README.md

public-directory-archive-preflight.mdAdd architecture/design doc for archive preflight +365/-0

Add architecture/design doc for archive preflight

• New design document covering purpose, command surface, target boundary, result contract, and safety guarantees for the archive preflight.

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

github-action.mdDocument Action archive mode and bump version examples +47/-26

Document Action archive mode and bump version examples

• Adds an Archive Submission Preflight section describing inputs/outputs and mutual exclusion rules; updates version examples to v1.60.0.

docs/guides/github-action.md

catalog.mdAdd archive rule catalog entries +31/-0

Add archive rule catalog entries

• Documents 'plugin.submission.archive.*' rule IDs including severity and meaning, and clarifies advisory vs strict behavior.

docs/rules/catalog.md

Other (4) +176 / -8
action.ymlAdd opt-in submission-archive Action input/outputs +52/-3

Add opt-in submission-archive Action input/outputs

• Adds 'submission-archive' input, report outputs for archive JSON/Markdown, mutual exclusion with directory submission mode, and step-summary appending for archive reports.

action.yml

package-lock.jsonLockfile update for v1.60.0 and ZIP reader deps +56/-3

Lockfile update for v1.60.0 and ZIP reader deps

• Updates versions and adds lock entries for yauzl/iconv-lite and @types/yauzl.

package-lock.json

package.jsonBump to 1.60.0 and add ZIP reader dependencies +5/-2

Bump to 1.60.0 and add ZIP reader dependencies

• Bumps version to 1.60.0; adds 'yauzl' and 'iconv-lite' plus '@types/yauzl'.

package.json

submission-archive-ruleset.tsAdd archive ruleset metadata and portal coverage table +63/-0

Add archive ruleset metadata and portal coverage table

• Defines ruleset version/limits and marks portal rules as automatic/manual/unavailable based on public references.

src/core/submission-archive-ruleset.ts

@Esquetta
Esquetta merged commit 6cf8c39 into main Aug 25, 2026
2 checks passed
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. Directory entries reject descendants 🐞 Bug ≡ Correctness
Description
The prefix-conflict check treats an explicit directory entry such as plugin/ as conflicting with
plugin/.codex-plugin/plugin.json, so ordinary ZIPs produced with directory records fail preflight.
The documented rule only forbids files from containing children, but the implementation applies it
to directories too.
Code

src/core/submission-archive-reader.ts[R637-639]

+      if ([...paths.keys()].some((existing) => existing.startsWith(`${normalized.path}/`) || normalized.path.startsWith(`${existing}/`))) {
+        report.findings.push(archiveFinding("plugin.submission.archive.path_conflict", "Archive entry paths conflict.", { path: normalized.path }));
+        continue;
Evidence
The reader records directory entries and then rejects every prefix relationship without checking
entry kinds. The architecture explicitly says only a path that is both file/directory or a file with
children is invalid, and any fail finding prevents exposing the reader and running root discovery.

src/core/submission-archive-reader.ts[621-646]
src/core/submission-archive-reader.ts[678-679]
docs/architecture/public-directory-archive-preflight.md[183-188]
src/core/submission-archive-preflight.ts[270-278]

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

## Issue description
Explicit ZIP directory entries are currently treated as conflicting with their children, causing valid archives to fail.

## Issue Context
Only a file/descendant conflict or a file-directory collision should be rejected; a directory and its descendants are valid.

## Fix Focus Areas
- src/core/submission-archive-reader.ts[633-646]
- tests/submission-archive-reader.test.ts[128-140]

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


2. Compression suppresses root blockers 🐞 Bug ≡ Correctness
Description
Any well-formed unsupported-compression member leaves inspection.reader null, and
buildSubmissionArchivePreflight then skips even path-only root discovery. An archive containing
only unsupported.bin therefore reports status: "pass" instead of the automatic
missing-root/manifest blocker its already-parsed entry metadata proves.
Code

src/core/submission-archive-preflight.ts[R270-272]

+  if (!archiveFindings.some((finding) => finding.severity === "fail") && inspection.reader !== null) {
+    const root = discoverRoot(inspection.entries);
+    archiveFindings.push(...root.findings);
Evidence
Unsupported methods produce coverage only and return with a null reader. The preflight gates root
discovery on that reader, then derives pass/fail solely from findings, while discoverRoot itself
only consumes entry metadata and would detect an empty or manifest-less root.

src/core/submission-archive-reader.ts[658-679]
tests/submission-archive-reader.test.ts[106-114]
src/core/submission-archive-preflight.ts[102-149]
src/core/submission-archive-preflight.ts[270-300]

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

## Issue description
Unsupported compression disables all root and nested structural checks and can make an invalid package pass.

## Issue Context
Root discovery only needs validated entry metadata; content-dependent checks may remain unavailable when no reader can be exposed.

## Fix Focus Areas
- src/core/submission-archive-preflight.ts[262-290]
- src/core/submission-archive-reader.ts[658-679]
- tests/submission-archive-preflight.test.ts[68-125]

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


3. Central records can hide 🐞 Bug ⛨ Security
Description
EOCD validation accepts a declared central-directory size smaller than the gap to metadata, while
the later walk never verifies its final offset or yielded count against the declared directory
extent. A ZIP can therefore declare one visible entry while placing additional central records in
the accepted slack, allowing the preflight to pass without inspecting every archive member.
Code

src/core/submission-archive-reader.ts[R402-404]

+      if (disk !== 0 || centralDisk !== 0 || entriesOnDisk !== entryCount
+        || centralStart32 > eocdOffset
+        || centralSize32 > eocdOffset - centralStart32) return null;
Evidence
Metadata parsing only rejects a central size larger than the available gap and retains no central
end. The loop advances centralEntryOffset, but post-loop validation checks only local intervals;
this contradicts the documented guarantees to validate central-directory boundaries and stream every
entry.

src/core/submission-archive-reader.ts[382-433]
src/core/submission-archive-reader.ts[583-592]
src/core/submission-archive-reader.ts[670-679]
docs/architecture/public-directory-archive-preflight.md[118-130]

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

## Issue description
The archive parser does not prove that iterated central entries exactly consume the declared central-directory range.

## Issue Context
Validate both classic and ZIP64 directory boundaries and ensure iteration count/end offset agree before exposing a reader.

## Fix Focus Areas
- src/core/submission-archive-reader.ts[382-433]
- src/core/submission-archive-reader.ts[583-592]
- src/core/submission-archive-reader.ts[670-679]
- tests/submission-archive-reader.test.ts[280-310]

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



Remediation recommended

4. Path conflicts scale quadratically 🐞 Bug ➹ Performance
Description
For every entry, the reader materializes all prior keys and scans them with prefix comparisons,
making conflict validation O(entries² × path length). With the allowed 5,000 entries and ZIP names
up to the format limit, a sub-100-MB adversarial archive can force hundreds of millions of
comparisons over long common prefixes and stall the preflight.
Code

src/core/submission-archive-reader.ts[R637-639]

+      if ([...paths.keys()].some((existing) => existing.startsWith(`${normalized.path}/`) || normalized.path.startsWith(`${existing}/`))) {
+        report.findings.push(archiveFinding("plugin.submission.archive.path_conflict", "Archive entry paths conflict.", { path: normalized.path }));
+        continue;
Evidence
The configured entry bound is 5,000, path validation bounds only segment count, and the changed line
spreads and scans every existing map key for each new entry. This yields quadratic pair checks with
potentially long strings inside the archive's 100-MB compressed-file allowance.

src/core/submission-archive-reader.ts[12-16]
src/core/submission-archive-reader.ts[202-211]
src/core/submission-archive-reader.ts[570-578]
src/core/submission-archive-reader.ts[633-646]

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

## Issue description
Path conflict validation performs a full scan of all prior paths for every archive entry.

## Issue Context
The archive permits 5,000 entries and does not impose a byte-length cap on each path, so conflict checks must scale with entries and segments rather than all path pairs.

## Fix Focus Areas
- src/core/submission-archive-reader.ts[202-211]
- src/core/submission-archive-reader.ts[577-646]
- tests/submission-archive-reader.test.ts[128-140]

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


5. Rejects unsigned CRC collisions 🐞 Bug ≡ Correctness
Description
An unsigned ZIP data descriptor whose CRC32 is 0x08074b50 is treated as if it carried the optional
descriptor signature, shifting both size reads by four bytes. The expected-size comparison then
reports descriptor_invalid for an otherwise valid archive.
Code

src/core/submission-archive-reader.ts[R354-355]

+    const signed = descriptor.readUInt32LE(0) === 0x08074b50;
+    const base = signed ? 4 : 0;
Evidence
The descriptor layout is selected solely from its first 32-bit word. For an unsigned descriptor that
word is its CRC; once it equals the signature constant, base becomes 4 and the
compressed/uncompressed sizes are read at the wrong offsets, which must fail against
central-directory sizes. A null local header is subsequently surfaced as descriptor_invalid.

src/core/submission-archive-reader.ts[349-367]
src/core/submission-archive-reader.ts[610-620]

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

## Issue description
An unsigned ZIP data descriptor may have CRC32 `0x08074b50`, the same value as the optional descriptor signature. The parser uses that first word alone to select the signed layout, then reads the descriptor's size fields from shifted offsets and rejects the entry.

## Issue Context
This preflight is intended to accept well-formed existing ZIP packages, including descriptors without the optional signature.

## Fix Focus Areas
- src/core/submission-archive-reader.ts[349-367]

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


Grey Divider

Context sources
Review mode: 🧠 Deep: This introduces substantial archive parsing and security validation logic across CLI, contracts, package readers, and GitHub Action behavior, with 94 independent hunks and many easy-to-miss failure modes.

Grey Divider

Tip of the day
💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +637 to +639
if ([...paths.keys()].some((existing) => existing.startsWith(`${normalized.path}/`) || normalized.path.startsWith(`${existing}/`))) {
report.findings.push(archiveFinding("plugin.submission.archive.path_conflict", "Archive entry paths conflict.", { path: normalized.path }));
continue;

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. Directory entries reject descendants 🐞 Bug ≡ Correctness

The prefix-conflict check treats an explicit directory entry such as plugin/ as conflicting with
plugin/.codex-plugin/plugin.json, so ordinary ZIPs produced with directory records fail preflight.
The documented rule only forbids files from containing children, but the implementation applies it
to directories too.
Agent Prompt
## Issue description
Explicit ZIP directory entries are currently treated as conflicting with their children, causing valid archives to fail.

## Issue Context
Only a file/descendant conflict or a file-directory collision should be rejected; a directory and its descendants are valid.

## Fix Focus Areas
- src/core/submission-archive-reader.ts[633-646]
- tests/submission-archive-reader.test.ts[128-140]

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

Comment on lines +270 to +272
if (!archiveFindings.some((finding) => finding.severity === "fail") && inspection.reader !== null) {
const root = discoverRoot(inspection.entries);
archiveFindings.push(...root.findings);

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

2. Compression suppresses root blockers 🐞 Bug ≡ Correctness

Any well-formed unsupported-compression member leaves inspection.reader null, and
buildSubmissionArchivePreflight then skips even path-only root discovery. An archive containing
only unsupported.bin therefore reports status: "pass" instead of the automatic
missing-root/manifest blocker its already-parsed entry metadata proves.
Agent Prompt
## Issue description
Unsupported compression disables all root and nested structural checks and can make an invalid package pass.

## Issue Context
Root discovery only needs validated entry metadata; content-dependent checks may remain unavailable when no reader can be exposed.

## Fix Focus Areas
- src/core/submission-archive-preflight.ts[262-290]
- src/core/submission-archive-reader.ts[658-679]
- tests/submission-archive-preflight.test.ts[68-125]

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

Comment on lines +402 to +404
if (disk !== 0 || centralDisk !== 0 || entriesOnDisk !== entryCount
|| centralStart32 > eocdOffset
|| centralSize32 > eocdOffset - centralStart32) return null;

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

3. Central records can hide 🐞 Bug ⛨ Security

EOCD validation accepts a declared central-directory size smaller than the gap to metadata, while
the later walk never verifies its final offset or yielded count against the declared directory
extent. A ZIP can therefore declare one visible entry while placing additional central records in
the accepted slack, allowing the preflight to pass without inspecting every archive member.
Agent Prompt
## Issue description
The archive parser does not prove that iterated central entries exactly consume the declared central-directory range.

## Issue Context
Validate both classic and ZIP64 directory boundaries and ensure iteration count/end offset agree before exposing a reader.

## Fix Focus Areas
- src/core/submission-archive-reader.ts[382-433]
- src/core/submission-archive-reader.ts[583-592]
- src/core/submission-archive-reader.ts[670-679]
- tests/submission-archive-reader.test.ts[280-310]

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

Comment on lines +637 to +639
if ([...paths.keys()].some((existing) => existing.startsWith(`${normalized.path}/`) || normalized.path.startsWith(`${existing}/`))) {
report.findings.push(archiveFinding("plugin.submission.archive.path_conflict", "Archive entry paths conflict.", { path: normalized.path }));
continue;

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

4. Path conflicts scale quadratically 🐞 Bug ➹ Performance

For every entry, the reader materializes all prior keys and scans them with prefix comparisons,
making conflict validation O(entries² × path length). With the allowed 5,000 entries and ZIP names
up to the format limit, a sub-100-MB adversarial archive can force hundreds of millions of
comparisons over long common prefixes and stall the preflight.
Agent Prompt
## Issue description
Path conflict validation performs a full scan of all prior paths for every archive entry.

## Issue Context
The archive permits 5,000 entries and does not impose a byte-length cap on each path, so conflict checks must scale with entries and segments rather than all path pairs.

## Fix Focus Areas
- src/core/submission-archive-reader.ts[202-211]
- src/core/submission-archive-reader.ts[577-646]
- tests/submission-archive-reader.test.ts[128-140]

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

Comment on lines +354 to +355
const signed = descriptor.readUInt32LE(0) === 0x08074b50;
const base = signed ? 4 : 0;

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

5. Rejects unsigned crc collisions 🐞 Bug ≡ Correctness

An unsigned ZIP data descriptor whose CRC32 is 0x08074b50 is treated as if it carried the optional
descriptor signature, shifting both size reads by four bytes. The expected-size comparison then
reports descriptor_invalid for an otherwise valid archive.
Agent Prompt
## Issue description
An unsigned ZIP data descriptor may have CRC32 `0x08074b50`, the same value as the optional descriptor signature. The parser uses that first word alone to select the signed layout, then reads the descriptor's size fields from shifted offsets and rejects the entry.

## Issue Context
This preflight is intended to accept well-formed existing ZIP packages, including descriptors without the optional signature.

## Fix Focus Areas
- src/core/submission-archive-reader.ts[349-367]

ⓘ 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