Allow secrets to be shared with forks from trusted Actions

This is a proposal to solve a problem related to https://github.community/t5/GitHub-Actions/Make-secrets-available-to-builds-of-forks/m-p/30678

I think Github Actions did the right thing by making the $GITHUB_TOKEN read-only and secrets unavailable for forks for security reasons, but this can be a big limitation. Short of making a key publically available, parts of the CI process would just have to be disabled.

The main problem is the CI code sits with the repository and thus can be changed by code changes in a forked pull request. This has all kinds of implications. A workaround is to have sensitive parts of the pipeline in a trusted context somehow. Jenkins does this with shared libraries and CircleCI does this with contexts (and maybe could do this with Orbs).

Actions seem to be a good candidate for this type of trusted context. Actions are run in isolated containers with defined inputs which reduces the attack vectors of execution context. Actions can be version-locked and maintained by trusted vendors or in a separate trusted repository. These trusted actions could be granted access to specific secrets (approved in Secrets admin settings). The execution context of actions could not be altered by pull requests from forked repositories. For forked pull requests, the workflow files of the base branch should be used (not the one from the forked branch). This prevents alterations of the CI in a forked pull request.

This would be very useful for 3rd party integrations where a secret key is required (Codecov, ChromaticQA, Cypress, autolabler, etc). It could look something like this:

steps:
  - uses: cypress-io/cypress-run-action@v1
    withSecret:
      record-key: CYPRESS_RECORD_KEY # CYPRESS_RECORD_KEY is the name of the secret

The cypress-io/cypress-run-action would be given the record-key input as a decrypted key from the Secrets of the repository by name. Further security could involve an acceptance of cypress-io/cypress-run-action in the “Settings” admin tab for secret management (though if the workflow is only run with code from the target branch I’m not sure this is strictly necessary). The cypress-io/cypress-run-action would be a wrapper around the following command line:

npm run cypress:run -- --record --key=$INPUT_RECORD_KEY

If each action is run in an isolated container with the workspace being the only shared context, this should be a safe operation. Another action could not leave a process running or modify $PATH to capture keys. Running the action from the base branch for forked repos ensure a forked workflow script could not alter execution.

I see this proposal as very similar to the way Github Apps are treated.

18 Likes

@nicholasboll  Even I have put up this proposal in several discussion in this forum. I just also would like to add one point further to your proposal. It would be great  to have read/write permission for the GITHUB_TOKEN for the trusted GitHub Actions to the PR coming from the forked repository. 

This proposal is already discussed here and here

@ibakshay Thanks for the reply and for the links (the first one didn’t work for me, the second one did). I found a few of the linked issues, but not all in my searches. There is a lot of focus on the GITHUB_TOKEN being a read/write token. I think that should be added to the proposal. I think you should be able to opt-in to sharing a token with write access the same way you could opt-in to sharing secrets with actions.

I talked at length with a number of people about the possible attack vectors this proposal would open up. The primary risk to security is execution of code. A workflow has almost an unlimited execution context (you can run any command in a run step). An action (-uses step) has a very limited and predefined execution context. Security risks should be sufficiently minimized by trusting the execution context of an action and the inputs provided to the action.

Jenkins already does this with shared libraries - only an admin can add a shared library as a trusted execution context. Jenkins will run all code within the shared library outside the security sandbox it reserves for any code run in the Jenkinsfile. It seems like CircleCI could do the same thing with Orbs. They could be trusted execution contexts instead of just convenient wrappers around sharable parts of a CI pipeline.

I don’t know if trusted actions is sufficient.  My workflow currently includes a number of calls to scripts.  Someone could fork my repository, modify the scripts to do something malicious with the secrets exposed to them, and we’re right back to square one.

I think a better solution might be to allow secrets to be shared with trusted users.

Thanks. I have been asking for possible security issues with the proposal. I agree with your assessment for traditional CI systems like TravisCI where you have a config file that makes calls to script files, most of which are in source control and can be modified in the PR.

I agree with you that it is a security issue to share secrets with scripts, but I’m suggesting sharing secrets with Actions.

Unfortunately the naming is confusing - “Github Actions” often is used to describe the whole thing while an “Action” is the core part of a Workflow. The Github CI is called “Actions” or “Github Actions”, but they are also called “Workflows”. Workflow files define one or more actions and event(s) that trigger the workflow. Github Workflows can run Actions or scripts. You either use a name with a run (script) config OR a uses (Action). It used to be that running a script required an action to perform and now it is built in because it is such a basic thing everyone needs.

Example of a script:

steps:
  - name: My Script
    run: ./scripts/my-script.sh

Example of an action:

steps:
  - uses: chromaui/action@v1
    with:
      token: ${{ secrets.GITHUB_TOKEN }}
      appCode: ${{ secrets.CHROMATIC_TOKEN }}

When I say “Allow secrets to be shared with trusted Actions”, I don’t mean a shell script. I mean an Action (using the “uses” instead of the “run”). An Action is a Github org + repo (The chromaui/action@v1 is really a link to https://github.com/chromaui/action and Github will look up the tag or release branches for the version). An Action is an isolated environment where the execution context is contained withing the Action itself. An Action execution context is all files provided by the action + the cloned repository mounted as a workspace. The Action cannot run arbitrary scripts in your workspace unless it was specificly designed to do so (bad idea).

Now there are public Actions and local Actions. Local Actions can be modified by a PR. My suggestion is loading Actions and Workflows from the base branch (branch to be merged - usually “master”) in the case of a forked PR. That means PR workflow changes can only be tested by PRs from collaborators (those with write access). Workflow changes in forked PRs won’t be run during the PR phase and would have to be merged by collaborators to ever be run.

It is still possible to run script files from local Actions. My suggestion is to not do that because the surface area of the execution context becomes very difficult to determine. The use-case is for public actions that repo collaborators choose to trust. Like a code-coverage Action, ChromaticQA action, Cypress Action, etc. Right now this is possible with Github Apps because of the special permissioning model. If you use Github Apps, allowing trusted permissioning to Actions isn’t much of a jump.

CircleCI has strategies for allowing parts of a workflow to be trusted where trust is granted by a collaborator. I think this is too manual. I think public Actions can be trusted just like public Github Apps.

4 Likes

Just putting in a plug for this feature.  Most things that I want to automate with actions require more privileges than actions currently have.  In particular, I’d like trusted actions to be able to:

  • add collaborators to a repository
  • add reviewers to pull requests (like CODEOWNERS files, but I want more flexibility and more automation than that offers)
  • reformat code and push a commit to the originating branch for a PR
  • upload code coverage data to sites like codecov.io

I would have to create bots or custom applications to do this right now, but it would be far simpler to do this with trusted actions.

6 Likes

@nicholasboll great explanation and I really hope the GitHub Team will take your proposal into consideration. 

1 Like

All the actions in a job are run within the same VM image, with each action altering the environment of the next to some extent.  This is most obvious with the “actions/checkout” action, which makes the the contents of the repository available to subsequent actions.

If an action or “run:” step could leave a process running in the background, then it could likely wait for a the new action process to be created and then snoop its “INPUT_*” environment variables for the secrets.

Alternatively, if you know that the action managing the secrets will run some subprocess, you could potentially prepend a directory to $PATH so that it instead invokes a malicious version of that program that can access the secrets.

So it is not enough to just check that the step invoking the action that consumes secrets has been unchanged: you need to worry about every step prior to it.  And if one of those steps invokes the project’s build system, it might not even be necessary to modify the workflow yaml file at all.

@jhenstridge 

Github Actions documentation confirms a job is run on the same VM: https://help.github.com/en/actions/automating-your-workflow-with-github-actions/core-concepts-for-github-actions#step

The documentation states the shared context is the workspace on the filesystem (mounted for containers).

Each action runs in its own code. I’m hoping this code is isolated from other actions where the workspace is the only shared context.

What is unclear is if each step is run on a fresh execution context. Each action can certainly require its own container, which in theory should give it an isolated context. Regardless of an action requesting a container, I hope that each action runs in an isolate execution context. If not, your vulerabilities are valid. If Github Actions share execution context, that was a trade-off of security for speed and simplicity - pull requests from forks are simply doomed.

I’m not sure if you’re suggesting a script could modify a workflow yaml file. Usually in CI systems, the workflow file is read and stored in memory prior to executing anything. Modifying the file would do nothing.

There is certainly an issue with an run command where it would be next to impossible to predict where run code could live in a repo. That’s an issue now and in any CI environment that allows scripts to be run. Secrets couldn’t be shared with run. If execution contexts are isolated between actions, I don’t see how there would be an execution level vulnerability. A run command could modify the file system, but actions shouldn’t use input from the file system - only inputs from with.