← Blog

A repository is not a payload

Hub 2 min read

Four browser extensions had to be turned into downloadable payloads. They live in their own repositories, with the things repositories have: a README, a licence, agent instructions, store artwork, a test directory. None of that belongs in bytes a browser loads.

One of them already had a build.sh saying exactly that, in a comment — ships only runtime files — so the job was mostly making that idea common rather than inventing it. What made it worth writing down is what the shared packer refused.

The refusal#

pack: ~/Sodium/_metadata would be refused by Chrome
      names starting with _ are reserved

Chrome rejects any top-level file or directory whose name starts with an underscore, except _locales. The error, if you get that far, is "Cannot load extension with file or directory name _metadata. Filenames starting with '_' are reserved for use by the system."

_metadata is not source. It is what Chrome writes into a copy it installed itself — a manifest fingerprint and a verified-contents file — and it was sitting in that tree because the tree had come back from an installed profile at some point. Ordinary git status cleanliness would not have flagged it; it is a directory that has been there, harmlessly, for a long time.

Zipped up and shipped, it would have produced an extension that downloads successfully, verifies its checksum successfully, unpacks successfully, and then cannot load. The failure surfaces in a customer's browser, minutes later, with nothing connecting it to the cause.

It is dropped now rather than refused, because refusing asks a human to delete a directory Chrome will simply write again. Any other reserved name still stops the pack, loudly, with the source path in the message.

The check that should have been obvious sooner#

Packing correctly is not the same as being loadable, and there was no reason to guess. The mechanism for loading one was already built — the Hub starts a Chrome over the DevTools pipe and calls Extensions.loadUnpacked — so the packer's output goes through exactly that before anything is published:

chrome: Chrome/152.0.7977.64

  load  antiblur-1.0.0  ->  ikdoijnmddhhkiiipjhiakfcdgjphmnf
  load  argon-0.1.0     ->  amkkhglnoibjoienbahjijmoclmdgaon
  load  sodium-3.4      ->  dodkimmdlaknmmaihamleodckfhlfjmc
  load  typeswap-1.0.0  ->  mmfofikbphjojcplkjdbbmpfkmpblfmk

ALL FOUR LOAD

Sixty lines of throwaway script, run once before a publish that cannot easily be taken back. A payload the browser refuses fails long after the download reported success, and by then it is on somebody else's machine.

A smaller thing, for completeness#

The packer stages with rsync into a directory and zips that, rather than passing a list of exclusions to zip. An exclusion pattern that silently fails to match is a document shipped to a customer. A staging directory can be looked at.