Suggestion: Encourage reproducible builds to help assess the risk of updating a plugin

The risk of installing/updating plugins

At this point the community’s small and friendly, so I assume people are pretty trusting when downloading new versions of plugins.

That being said, a stolen github account or rogue plugin developer could cause a lot of damage by releasing malicious code. Hopefully with obsidian growing in popularity it doesn’t eventually get targeted

How do reproducible builds help?

Right now any main.js file can be uploaded to github when releasing a plugin update.

A reproducible build would make it easier to verify that this built/minified main.js was actually generated from the source code. You could build it yourself from source and compare the file contents.

By knowing main.js was generated from source you can confidently review the new version’s git diff to make sure it’s safe. A tool could maybe even be created to automate verifying the build and highlight potentially risky changes (like changes to package.json or the build script)

What could reproducible builds look like?

There’d have to be some encouraged conventions, like that running npm run setup or setup.sh will install all dependencies and build the plugin, whether it be with npm, yarn, pnpm, webpack, rollup, etc.

1 Like

You forgot the link: https://reproducible-builds.org/

I think that’s a great idea for any kind of extension or plugin!
For example browser extension builds need to be reproducible and Mozilla is particularly picky about it during their review process!

It’s probably a good idea to have a single package file as the build result - something Obsidian is currently not doing.
In a zip archive, the date is part of the checksum tho, so when reproducing a build, it must be possible to specify the SOURCE_DATE_EPOCH (see: SOURCE_DATE_EPOCH — reproducible-builds.org)

When working on browser extensions, I’ve bundled the SOURCE_DATE_EPOCH also in the zip, so when you want to build the project again, you’d do something like:

SOURCE_DATE_EPOCH=$(cat ../previous-build/SOURCE_DATE_EPOCH) npm run build

And the official release zip package should be identical to the rebuild, and even the checksum of the two zip files would be identical.
It’s then easy for the user to just diff the zip file hash instead of individual files in the build.

1 Like

Yeah I should’ve linked reproducible-builds.org but don’t see the edit button to add it in

A single package file is a good idea. I’m unclear how to workaround the modified time being included in the zip file’s header though

Here’s mozilla extension submission guidelines if anyone’s interested. Sounds like people submit written build instructions that the extension reviewer then manually follows

I linked the first mention of the phrase for you. Let me know if you want it somewhere else.

1 Like

Depends on how you want to zip it, but because Obsidian is a web based project, it makes sense to use a Node.js library for zipping. This way it also work on Win, Linux and Mac the same way.

For example when appending files with archiver the append function takes two arguments, the data source and an entry-data object which allows you to set the date, among other file properties.

Here’s what I’m using for a browser extension:

export async function zip(source, destination, date) {
  console.log(`ZIP ${source} → ${destination}`);

  const files = (await findFiles(source + "/**")).filter((location) => fs.lstatSync(location).isFile());

  return await new Promise((resolve, reject) => {
    const output  = fs.createWriteStream(path.resolve(destination));
    const archive = archiver("zip");

    output.on("close", function() {
      resolve();
    });

    archive.on("error", function (error) {
      reject(error);
    });

    archive.pipe(output);

    files.forEach((file) => {
      archive.append(
        fs.createReadStream(file),
        {
          name: path.relative(source, file),
          date
        }
      );
    });

    archive.finalize();
  });
}
1 Like

That makes sense now, thanks for the links and code example

I suppose the sample plugin template could include a js script like you mentioned for packaging in a consistent way and also writing SOURCE_DATE_EPOCH to a file