Skip to content

fix(store): scan .zip bundles, and let owners re-scan - #44

Merged
ralyodio merged 1 commit into
mainfrom
fix/scan-zip-bundles
Jul 25, 2026
Merged

fix(store): scan .zip bundles, and let owners re-scan#44
ralyodio merged 1 commit into
mainfrom
fix/scan-zip-bundles

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Every listing on the store shows "unscanned" — including the only one live. The footer meanwhile promises "every listing carries a vu1nz.com scan."

Two causes, both in the publish path

  1. The gating scanner only ran on a .crx. A .crx is a signed header wrapped around a ZIP, and most publishers upload the bare .zip. Those skipped the gate entirely and fell through to the async vu1nz path.
  2. The async path can't produce a verdict either. enqueueScan records skipped when VU1NZ_API_URL is unset — and it is unset in production (68 vars on the service, neither VU1NZ_* present). No vu1nz scan API is deployed either: vu1nz.com/scan/extension and /api/scan/extension both 404.

So the fallback for the unscannable case was itself unscannable.

Changes

  • artifactToZip() accepts a .crx or a bare .zip, so scanArtifact() runs on whatever was actually published. scanCrx/fetchCrx remain as aliases.
  • The publish gate now scans crx || bundle. This tightens the gate, so I verified it against the real live bundle first — coinpay-wallet.zip scans clean, 0 findings at every severity, so no existing publisher is blocked.
  • POST /extensions/:id/rescan (owner) re-runs the scan against the artifact already on file, so listings published before this can earn a badge with no re-upload and no version bump.
  • "🛡 Re-scan" button next to Edit listing — owners shouldn't need curl, same lesson as feat(store): let publishers update their listing #43.

Note on VU1NZ_API_URL

Left unset deliberately: the in-process scanner needs no external service, and there is nothing deployed to point it at. Wiring vu1nz later is now enrichment rather than the only path to a badge.

Verification

tsc --noEmit clean; vitest run src/store → 37/37 (4 new covering bare-zip scanning, crx/zip verdict parity, a critical finding inside a plain zip, and rejection of non-archive bytes); node --check on the frontend.

🤖 Generated with Claude Code

Every listing on the store reads "unscanned", including the only one
live. Two causes, both in the publish path:

1. The gating scanner only ran when a publisher supplied a .crx. A .crx
   is just a signed header wrapped around a ZIP, and most publishers
   upload the bare .zip — those skipped the gate entirely and fell
   through to the async vu1nz path.
2. That async path records 'skipped' when VU1NZ_API_URL is unset, which
   it is in production (no vu1nz scan API is deployed — vu1nz.com/scan/*
   404s). So the fallback could never produce a verdict either.

Net effect: the store advertises "every listing carries a vu1nz.com
scan" while nothing had ever been scanned.

- artifactToZip() accepts a .crx or a bare .zip, so scanArtifact() works
  on whatever was actually published. scanCrx/fetchCrx stay as aliases.
- The publish gate now scans crx || bundle. Verified against the live
  coinpay-wallet bundle: clean, 0 findings at every severity, so this
  tightens the gate without blocking a real publisher.
- POST /extensions/:id/rescan re-runs the scan against the artifact
  already on file, so listings published before this can earn a badge
  without a re-upload or version bump — and a "🛡 Re-scan" button next
  to Edit listing, since owners shouldn't need curl for it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio
ralyodio merged commit 014872f into main Jul 25, 2026
6 checks passed
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