Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recipe for caching untrusted PRs? #49

Open
Kha opened this issue Jul 21, 2021 · 4 comments
Open

Recipe for caching untrusted PRs? #49

Kha opened this issue Jul 21, 2021 · 4 comments
Labels
question Further information is requested

Comments

@Kha
Copy link

Kha commented Jul 21, 2021

As the readme rightly points out,

Pull requests do not have access to secrets so read access to a public binary cache will work, but pushing will be disabled since there is no signing key.

This begs the question: if one wanted to cache builds of PRs from arbitrary forks, what's the best way to do that? And what are caveats/security implications of doing so?

Off the top of my head, I can think of two approaches:

  • the simple approach would be to use the pull_request_target trigger to build the PR via something like nix build .?rev=${{ github.event.pull_request.head.sha }} (entirely untested), relying on hermetic evaluation to ensure that the PR code cannot access any secrets. This would fail if the build relies on scripts that are run outside of a Nix build, and the PR modified some of them.
  • a different approach following GitHub's own suggestions would be to build the PR in an untrusted context, put the resulting Nix closure in an artifact, and restore and push that closure in a trusted workflow job. A little more involved, but once someone figures out a generic recipe, that shouldn't be too hard to set up either.

In either case, one might want to be mindful of the possibility of cache poisoning. I haven't seen too much discussion about this in the Nix community, but I would assume that Nix' hashing scheme is strong enough to make that a non-issue in practice. Still, concerned users might want to set up a separate cache for untrusted PRs and e.g. only activate it selectively on the cmdline (keeping in mind that the results of such a temporary build will, of course, remain in the local Nix store until they are GC'ed).

@domenkozar
Copy link
Member

This begs the question: if one wanted to cache builds of PRs from arbitrary forks, what's the best way to do that? And what are caveats/security implications of doing so?

Is this about caching all PRs or just specific ones?

If you don't need to cache everything and can ask the user for a bit of setup,
I'd suggest using github username for the cache for forks and users can set up their cache and secrets for it.

the simple approach would be to use the pull_request_target trigger to build the PR via something like nix build .?rev=${{ github.event.pull_request.head.sha }} (entirely untested)

That's exploitable, as soon as you run the builds from PRs, they can extract secrets (in this case cachix and all others).

@Kha
Copy link
Author

Kha commented Jul 21, 2021

Is this about caching all PRs or just specific ones?

All PRs. Basically, everything the CI builds should be cached - both for speeding up CI rebuilds as well as instantaneous local builds when reviewing PRs.

That's exploitable, as soon as you run the builds from PRs, they can extract secrets (in this case cachix and all others).

Via which channel? pull_request_target runs in the context of the target repo, so the executed workflow file is not from the PR. The only thing the PR could influence is Nix evaluation & building. Flakes & sandboxing are supposed to make these hermetic, though admittingly the focus there is mostly correctness and not security against adversarial input afaik...

edit: I guess fixed-output derivations with impureEnvVars might be a potential side channel? I would hope the secrets are not available in the daemon's environment, but that's a lot of assumptions, so perhaps the second approach is the way to go after all.

@domenkozar
Copy link
Member

Is this about caching all PRs or just specific ones?

All PRs. Basically, everything the CI builds should be cached - both for speeding up CI rebuilds as well as instantaneous local builds when reviewing PRs.

That's exploitable, as soon as you run the builds from PRs, they can extract secrets (in this case cachix and all others).

Via which channel? pull_request_target runs in the context of the target repo, so the executed workflow file is not from the PR. The only thing the PR could influence is Nix evaluation & building. Flakes & sandboxing are supposed to make these hermetic, though admittingly the focus there is mostly correctness and not security against adversarial input afaik...

You could use Environments to create two workflows with each own environment/secrets. Then each would use a different cache and in case the PR one gets abused, your main cache is still fine.

edit: I guess fixed-output derivations with impureEnvVars might be a potential side channel? I would hope the secrets are not available in the daemon's environment, but that's a lot of assumptions, so perhaps the second approach is the way to go after all.

As long as your workflow only runs Nix it should be fine, but as soon as it runs an impure script it's easy to compromise it.

@Kha
Copy link
Author

Kha commented Jul 22, 2021

You could use Environments to create two workflows with each own environment/secrets. Then each would use a different cache and in case the PR one gets abused, your main cache is still fine.

Nice, that does sound like the easiest approach. Though I'll have to move the existing repo secrets to the new environment...

@domenkozar domenkozar transferred this issue from cachix/cachix-action Jul 22, 2021
@domenkozar domenkozar added the question Further information is requested label Nov 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants