Secure Communication Between Actions and App

I maintain a GitHub App with the following flow:

  • A GitHub Action is added to a workflow
  • This action runs a task, and uploads results to the App
  • The App performs more complex analysis and interacts with GitHub

Ideally, I’d like a way to verify that the upload in the second step comes from a GitHub process - basically, a shared secret, similar to how I can verify webhook payloads. I don’t want to require users of the App to manually configure an organization-level secret, and most are hesitant to grant the “secrets” permission required to setup such a secret via API upon installation. It would be great if Apps automatically had a shared secret associated with an installation, or similar.

Does such functionality already exist, or if not, is there somewhere other than here I should be starting a request/discussion about servicing this use case?

I don’t think this functionality exists, but that’s an interesting idea… and I think there may be options to achieve this now.

The first option is to use the GITHUB_TOKEN associated with the Run, as this token will remain active while the Run is in progress. The upload endpoint of your App would accept the GITHUB_TOKEN from the Action and use it to verify access to the target repository.

POST https://example.com/uploads

{
  "repository": "examples/example-repository",
  "token": "${{ github.token }}",
  "data": {}, // your upload data
}

Then you can use the 200 or 401 response of this request to pass or fail the check.

curl --request GET \
  --url https://api.github.com/repos/:repository \
  --header 'Authorization: token :github_token' \
  --header 'Content-Type: application/json'

The downside of this approach is that your validation of an upload must be synchronous because the token will expire the moment the Run completes – and so doing it in the background is not an option.

Another option could be to implement an intermediary step: accept uploads without any authentication, but hold the uploads for processing until a GitHub Webhook provides you information required to determine the upload was genuine.

For example, workflow_run could be used in the following way:

  1. Your Action, in a Workflow, uploads a result to your App with a commit, repository or Check Run identifier – whichever is most useful to you
  2. Your App adds the uploaded result to a “queue” of uploads “pending validation”
  3. Your App listens for a workflow_run Webhook event
  4. When a workflow_run Webhook event arrives, the App extracts the commit information from the signed Webhook and uses it to approve any matching uploads

For example, if I submit an upload for examples/my-repository@a5b457d and then you receive a workflow_run webhook for that commit, you can say with reasonable confidence that the upload came from GitHub. You could take it a step further and use the Webhook information to retrieve the Workflow yaml and verify that it did indeed use your Action to upload the file – probably overkill, but helpful if you’d like to also protect against authorised users misbehaving.

Does that help, or do you have any considerations these options miss?

1 Like

The workflow_run approach is certainly interesting, and may actually help a lot, thank you! (though I’d still advocate for a little more formalized approach like I outlined)

Using GITHUB_TOKEN had occurred to me, but I was hesitant to send a secret like that in a request body or header, outside the system it’s meant to be encapsulated by. If there’s an argument for this being ok, we actually already have places in accepting the upload that attempts verification for systems that allow for it that such an operation could be slotted into

I agree! Hopefully, someone from GitHub sees this but their Roadmap is very full so it’s probably worth moving forward with a workaround if you need to deliver this in the next few quarters.

I think there are arguments in favour and arguments against. The key argument against this is that these tokens are not designed to be used externally and a security audit from an especially cautious customer might flag it as a concern, “why is this Action exfiltrating our tokens?”.

Personally, though, I think there is a strong justification for using these tokens externally if done so with security in mind. The tokens are described as a way to “authenticate on behalf of GitHub Actions” which is the intent of your implementation. The permissions configuration for these tokens enables a Workflow administrator (i.e: your customer) to confidently restrict the scope of the token down to the same level that a GitHub App has by default.

jobs:
  do-app-task:
    runs-on: ubuntu-latest
    permissions:
      metadata: read
    steps:
      - uses: app/action@v1
        with:
          token: ${{ github.token }}

From the GitHub Apps Permissions documentation (emphasis mine):

The metadata permission provides access to a collection of read-only endpoints with metadata for various resources. These endpoints do not leak sensitive private repository information.

My conclusion is that it’s justifiable, the key points that lead me to that conclusion:

  1. The tokens immediately expire at the end of a Workflow Run, this is behaviour controlled by GitHub and so a customer is not dependent on you to be a responsible custodian of the tokens
  2. GitHub are very conscious of the threat that these tokens present, so much so there is security research published about secure use of these tokens and this approach does not contradict any of that advice
  3. The token permissions can be restricted by the Workflow administrator to provide the same level of permissions that a GitHub App has already
  4. The metadata endpoints available are explicitly described by GitHub as not leaking sensitive data – something that should give users confidence
1 Like

That is a very comprehensive analysis, thank you! based on what you’d provided, I’m leaning towards using the token, at least as a first iteration to improve upon the current situation.