Publishing from pull requests

Volume 6, Issue 10; 18 Sep 2022

Tell me your secrets. You trust me, don’t you?

My preference for managing projects these days is to create a repository (on GitHub or GitLab or your own shared server or anywhere you like really) for the project and then use continuous integration (CI) servers (like CircleCI or GitHub workflows orOn second reading, there’s maybe a bit more background here than I needed. If you’re bored already, just skip to the end. That’s where the good stuff is. GitLab runners or anything you like really) to manage builds and deployments.

This works really well for software projects. Someone (maybe you) forks the project, implements a change on a branch, and creates a pull request (PR) for the branch. The CI server checks that the PR can be merged, runs all of the unit tests against the change, and reports success or failure. You can’t forget to run the tests, or run the tests with some local configuration that you forgot you made, or otherwise mess up and commit untested code.

If the tests pass, and the rest of the approvals happen (whatever they might be), you merge the code into the main branch, and the CI server builds and deploys it. Job done.

Except a lot of my projects are about documents, or have important documentation components. To take a canonical example, let’s consider a repository that contains the sources for a specification: a big, complex document that needs editorial review.

You can (and should!) run automated tests against it. There are lots (and lots) of things that you can automate: is the markup correct, do the cross references work, do the sections have IDs, are the ancillary files correct? The list is bounded only by your imagination and enthusiasm for testing.

But at the end of the day, what your collaborators on the project want to do is read the formatted prose of your changes in whatever format you publish them, and see if they make sense. They can eyeball the diffs in the source (XML or HTML or what-have-you), but that’s not an easy and natural way to review the changes, especially if they’re complicated.

What you’d like to do is publish the formatted output from a PR.

And here we run into a problem. Publishing a file (writing the results of a transformation back to the repository) requires some sort of secret. A secret is just a way of authenticating an action. With very rare exceptions, it isn’t practical to just let everyone in the world have write access to your server.

The CI tool knows the secret, that’s how the “and deploys it” part a few paragraphs back works. Code that’s been merged into the main branch is assumed (not always correctly) to have been reviewed and is considered trusted. When the CI build for the main branch gets to the publishing part, it uses the secret to authenticate.

But PRs are “everyone in the world”. (They don’t have to be, there are other options, for example using a private repository, but the easy, common case for a public-facing project is for the repository to be public and to allow anyone to make a PR.)

If you let a PR have the secret, then a malicious actor could commit a PR that reveals the secret, steal the secret, and use it for whatever nefarious purposes they want.

So PRs can’t know the secrets, so you can’t publish the output of a PR, so your collaborators can’t view the formatted work. Bummer.

One way around this is to setup publishing on your fork. You push the branch from which you will make the PR to your repository. That’s trusted on your repository, assuming you trust yourself, and you can have a (different) secret that publishes your local repository.

That’s how changes that I propose via PRs to get published to

It’s not hard, exactly, to do this, but it’s a bunch of extra configuration that every collaborator has to do, and you have to share the location where the artifacts get published with every collaborator, and they’re different every time. It kind of, sort of works, but not really.

What I really, really want is a way to publish PRs. But I can’t. Because secrets.


I was using CircleCI to publish the QT4 CG specifications. (I really like CircleCI, I’ve used it for a long time, and I pay for the service.) Something went wrong:

Could not find a usable config.yml, you may have revoked
the CircleCI OAuth app. Please sign out of CircleCI and
log back in with your VCS before triggering a new pipeline.

What? It worked yesterday. And turning it off and turning it back on again didn’t help. It’s still not exactly clear what that was about, but I’ll dig in if it happens again somewhere else.

What I did instead was, I switched from using CircleCI to build the specifications to using GitHub workflows. I was already thinking about doing this because I expect the QT4 CG specifications will have more contributors than the other projects I’ve worked on, and I thought it might be easier and simpler to use and explain if I was using the GitHub CI, given that the repository is hosted on GitHub.

And what I discoveredThis is where the good stuff starts. was that it’s possible to build and publish from PRs with GitHub workflows. Hallelujah.

A GitHub workflow can be configured with a pull_request_target event. A “pull request target” event runs whenever a pull request is submitted, and it can access secrets! The trick is that it runs against the base of the PR, not from the PR directly. So it won’t execute any malicious code in the PR. But the workflow script can see the actual PR files if it wants.

That means I can:

  1. Check out the PR.
  2. Copy the markup sources, ancillary files, etc. to a temporary location. I copy everything except the build scripts, basically.
  3. Check out the base again. This is safe code that I trust.
  4. Copy the markup sources from the temporary location onto the safe build environment.
  5. Run the build and publish the results.

It’s a tiny bit fiddly, but I’m the only person who has to manage it. All of the collaborators will just make PRs and magic will happen.

You must be careful not to accidentally copy over anything that could compromise your system, though!

It works and I don’t think I could have made this work from CircleCI (at least not yet, I expect they all fight with each other for feature parity). I don’t know about GitLab runners or other CI tools.

I’m very pleased and fully intend to implement similar functionality in other repositories as time goes by.