diff --git a/decisions/ADR-003_Acceptable_use_of_GitHub_PAT_and_Apps_for_authN_and_authZ.md b/decisions/ADR-003_Acceptable_use_of_GitHub_PAT_and_Apps_for_authN_and_authZ.md
new file mode 100644
index 00000000..ccedd9b1
--- /dev/null
+++ b/decisions/ADR-003_Acceptable_use_of_GitHub_PAT_and_Apps_for_authN_and_authZ.md
@@ -0,0 +1,244 @@
+# ADR-003: Acceptable use of GitHub authentication and authorisation mechanisms
+
+>| | |
+>| ------------ | --- |
+>| Date | `04/09/2023` |
+>| Status | `RFC` |
+>| Deciders | `Engineering` |
+>| Significance | `Construction techniques` |
+>| Owners | `Amaan Ibn-Nasar, Jacob Gill, Dan Stefaniuk` |
+
+---
+
+- [ADR-003: Acceptable use of GitHub authentication and authorisation mechanisms](#adr-003-acceptable-use-of-github-authentication-and-authorisation-mechanisms)
+ - [Context](#context)
+ - [Decision](#decision)
+ - [Assumptions](#assumptions)
+ - [Drivers](#drivers)
+ - [Options](#options)
+ - [Outcome](#outcome)
+ - [Built-in authentication using `GITHUB_TOKEN` secret](#built-in-authentication-using-github_token-secret)
+ - [GitHub PAT (fine-grained Personal Access Token)](#github-pat-fine-grained-personal-access-token)
+ - [GitHub App](#github-app)
+ - [Rationale](#rationale)
+ - [Notes](#notes)
+ - [GitHub App setup](#github-app-setup)
+ - [Recommendation for GitHub Admins](#recommendation-for-github-admins)
+ - [Diagram](#diagram)
+ - [Context diagram showing the GitHub App setup](#context-diagram-showing-the-github-app-setup)
+ - [Authentication flow diagram](#authentication-flow-diagram)
+ - [Limitations](#limitations)
+ - [Examples of acquiring access token](#examples-of-acquiring-access-token)
+ - [Actions](#actions)
+ - [Tags](#tags)
+ - [Footnotes](#footnotes)
+
+## Context
+
+As teams increasingly adopt GitHub and invest in refining development processes, there is a growing need to facilitate automated bot access to repositories, for tasks such as managing Pull Requests or integrating self-hosted runners with preferred Cloud providers. While GitHub's official documentation provides detailed technical instructions, it might not always offer a clear and holistic understanding of the platform's authentication and authorisation mechanisms. This document seeks to bridge that gap. It elucidates not just the "_how_" but also the "_why_", "_when_", and "_what_" behind these mechanisms, aiming to promote both effective and secure usage.
+
+## Decision
+
+### Assumptions
+
+_A **GitHub App** is a type of integration that you can build to interact with and extend the functionality of GitHub. You can build a GitHub App to provide flexibility and reduce friction in your processes, without needing to sign in a user or create a service account._ [^1]
+
+_**Personal access tokens** are an alternative to using passwords for authentication to GitHub when using the GitHub API or the command line. Personal access tokens are intended to access GitHub resources on behalf of yourself._ [^2]
+
+_When you enable GitHub Actions, GitHub installs a GitHub App on your repository. The **GITHUB_TOKEN** secret is a GitHub App installation access token. You can use the installation access token to authenticate on behalf of the GitHub App installed on your repository._ [^3]
+
+### Drivers
+
+The aim of this decision record, or more precisely, this guide, is to provide clear guidelines on the appropriate use of GitHub's authentication and authorisation mechanisms. Our objective is to ensure that any automated process utilises correct authentication when executing GitHub Actions and Workflows. These processes underpin the implementation of the CI/CD (Continuous Integration and Continuous Delivery) pipeline. By adhering to these guidelines, we can maintain robust, secure and effective operations.
+
+### Options
+
+There are three options available to support automated GitHub Action and Workflow authentication processes:
+
+1. [Built-in authentication](https://docs.github.com/en/actions/security-guides/automatic-token-authentication) using `GITHUB_TOKEN` secret
+
+ - ➕ **No set-up required**. It works effortlessly, even for forked repositories.
+ - ➕ **The token can only access the repository containing the workflow file**. This token cannot be used to access other private repositories.
+ - ➖ **The token can only access a repository containing the workflow file**. If you need to access other private repositories or require write access to other public repositories this token will not be sufficient.
+ - ➖ **The token cannot trigger other workflows**. If you have a workflow that creates a release and another workflow that runs when someone creates a release, the first workflow will not trigger the second workflow if it utilises this token based mechanism for authentication.
+
+2. [GitHub PAT](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) (fine-grained Personal Access Token)
+
+ - ➕ **Simple to set up**. You can create a [fine-grained personal access token](https://github.com/settings/tokens?type=beta) with a repository scope. Classic personal access token should never be used.
+ - ➕ **GitHub PAT provides a more fine-grained permission model** than the built-in `GITHUB_TOKEN`
+ - ➕ **The token can trigger other workflows**.
+ - ➖ **It is bound to a person**. The owner of the token leaving the organisation can cause your workflow to break.
+
+3. [GitHub App](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps)
+
+ - ➕ **You can control which repositories your token has access to** by installing the GitHub App to selected repositories.
+ - ➕ **An organisation can own multiple GitHub Apps** and they do not consume a team seat.
+ - ➕ **GitHub App provides a more fine-grained permission model** than the built-in `GITHUB_TOKEN`
+ - ➕ **The token can trigger other workflows**.
+ - ➖ **Not very well documented**. Despite the extensive content of the GitHub documentation, it does not effectively communicate the pros & cons, use-cases and comparison of each authentication method. This was one of the reasons we created this ADR.
+ - ➖ **The setup is a bit more complicated**.
+
+### Outcome
+
+#### Built-in authentication using `GITHUB_TOKEN` secret
+
+A `GITHUB_TOKEN` is automatically generated and used within GitHub Action and Workflow for tasks related to the current repository such as creating or updating issues, pushing commits, etc.
+
+- **Scope**: The `GITHUB_TOKEN` is automatically created by GitHub in each run of a GitHub Action and Workflow, with its scope restricted to the repository initiating the workflow. The permissions of the `GITHUB_TOKEN` are limited to read and write access to the repository files, with an exception of write access to the `.github/workflows` directory.
+- **Life Span**: The `GITHUB_TOKEN` has a temporary lifespan automatically expiring after the completion of the job that initiated its creation.
+
+This method enables basic operations expected from the repository pipeline, like accessing GitHub secret variables.
+
+#### GitHub PAT (fine-grained Personal Access Token)
+
+Use personal access token when:
+
+- **Scripted access**: When you are writing scripts that automate tasks related to your repositories PATs can be a good choice. These tokens can authenticate your script with GitHub allowing it to perform various operations like cloning repositories, creating issues, or fetching data from the API. Since PATs can act with nearly all the same scopes as a user, they can be a versatile tool for script-based interactions with your repositories.
+
+- **Command-line access**: If you are directly using the GitHub API from the command-line (e.g. with `curl`), PATs provide a convenient way to authenticate. They allow you to perform a wide range of actions, including getting the number of stars on a repository, posting a comment on an issue or triggering a new build or deployment. In this use case a common task that a contributor has to perform daily can be automated using a PAT generated with a scope specifically for it.
+
+- **Two-Factor Authentication (2FA)**: If you have enabled 2FA for added account security, performing `https` Git operations like clone, fetch, pull or push will require a PAT instead of a password. This helps ensure that operations remain secure even from the command-line.
+
+Do not use it when:
+
+- **Sharing your account**: PATs should never be used to provide access to your GitHub account to others. Instead, use GitHub's built-in features for collaboration and access management, such as adding collaborators to repositories or using organisations and teams.
+
+- **Public repositories or code**: PATs provide broad access to your account, so you should never embed them in your code, especially if that code is public. This could allow someone to take control of your account, modify your repositories or steal your data. The [scan secrets functionality](https://github.com/nhs-england-tools/repository-template/blob/main/docs/user-guides/Scan_secrets.md) that is part of this repository template should prevent you from doing so anyway.
+
+- **Broad permissions**: While PATs can have broad permissions, you should aim to restrict each token's scope to what is necessary for its purpose. For instance, a token used only for reading repository metadata does not need write or admin access.
+
+- **Long-term usage without rotation**: To limit potential exposure of your PAT, it is recommended to periodically change or "rotate" your tokens. This is a common security best practice for all kinds of secret keys or tokens.
+
+This method of authentication and authorisation using the fine-grained PAT for the purpose of automation should mostly be used by the GitHub organisation owners, administrators and maintainers.
+
+#### GitHub App
+
+Use app when:
+
+- **Acting on behalf of a user or an organisation**: GitHub Apps can be installed directly onto an organisation or a user account and can access specific repositories. They act as separate entities and do not need a specific user to authenticate actions, thus separating the app's actions from individual users and preventing user-related issues (like a user leaving the organisation) from disrupting the app's operation. In this model, a GitHub App can act on behalf of a user to perform actions that the user has permissions for. For example, if a GitHub App is used to manage issues in a repository, it can act on behalf of a user to open, close, or comment on issues. The actions the app can perform are determined by the user's permissions and the permissions granted to the app during its installation.
+
+- **When you need fine-grained permissions**: GitHub Apps provide more detailed control over permissions than the classic PAT, which should no longer be used. You can set access permissions on a per-resource basis (issues, pull requests, repositories, etc.). This allows you to follow the principle of least privilege, granting your app only the permissions it absolutely needs.
+
+- **Webhook events**: GitHub Apps can be configured to receive a variety of webhook events. Unlike personal tokens, apps can receive granular event data and respond accordingly. For instance, an app can listen for `push` events to trigger a CI/CD pipeline or `issue_comment` events to moderate comments.
+
+- **Server-to-server communication**: Unlike users, GitHub Apps have their own identities and can perform actions directly on a repository without a user action triggering them. They are associated with the GitHub account (individual or organisation) that owns the app, not necessarily the account that installed the app. In this model the GitHub App can perform actions based on the permissions it was given during setup. These permissions are separate from any user permissions and allow the app to interact with the GitHub API directly. For example, an app might be set up to automatically run a test suite whenever code is pushed to a repository. This action would happen regardless of which user pushed the code.
+
+This method of authentication and authorisation is intended for the engineering teams to implement and support automated processes. Setting up the [GitHub OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps) access is outside the scope of this document as this mechanism should not be employed in the context of development process automation.
+
+### Rationale
+
+This guide describes the essence of the fundamental aspects of GitHub authentication and authorisation mechanisms along with the common use cases identified by the GitHub organisation administrators of the NHS England.
+
+## Notes
+
+### GitHub App setup
+
+To be executed by a GitHub organisation administrator:
+
+- Identify the GitHub repository name for which the team has requested a GitHub App integration
+- Create a shared email address [england.[repository-name]-app@nhs.net](england.[repository-name]-app@nhs.net) by filling in the `New shared mailbox request` form using the Internal Portal (ServiceNow)
+ - Delegate access to this mailbox for the GitHub organisation owners, administrators and the engineering team
+- Create a GitHub bot account named `[repository-name]-app` using the email address mentioned above. The bot account should not be added to the organisation; therefore, **no GitHub seat will be taken**. It serves as an identity, but authentication and authorisation are handled via the GitHub App. This avoids granting the bot admin permissions to the repository, enabling commits to be signed by that bot account. Access is controlled solely through the GitHub App.
+ - Use the `nhs.net` email address as the default and only email
+ - Set the email address as private
+ - Make profile private and hide any activity
+ - Block command line pushes that expose email
+ - Set up commit signing
+ - Flag unsigned commits as unverified
+- [Register new GitHub App](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app) under the `[repository-name]-app` bot account with a name following a pattern that could include information like `[Team] [Repository Name] [Purpose]`, which would help to search for and identify owners of the app once it is installed within the GitHub organisation
+ - Make note of the `App ID`
+ - Generate and store securely a `Private key` for the app
+ - Provide a `Homepage URL` to the repository this app will operate on
+ - Make this app `public`
+ - Set the relevant `Repository permissions` based on the team's requirements. There should be no organisation or account permissions set at all
+
+To be executed by a GitHub organisation owner:
+
+- Install the `[Team] [Repository Name] [Purpose]` app on the GitHub organisation and set repository access to the `[repository-name]` only
+
+#### Recommendation for GitHub Admins
+
+It is advisable to create a separate bot account for each service or programme. This approach fosters responsible ownership practices. It also allows the team to use the bot's identity for signing commits and integrating their service with other SaaS products, such as SonarCloud, without relying on individual team member accounts. Exceptions can be made on a case-by-case basis, allowing for the use of a central organisation account instead.
+
+### Diagram
+
+#### Context diagram showing the GitHub App setup
+
+```mermaid
+C4Context
+ Enterprise_Boundary(b0, "Internal Developer Platform, part of the NHS England CoE") {
+
+ Boundary(b1, "Service", "boundary") {
+ System(repo, "Repository", "Repository
[repository-name]")
+ System(github_app_runner, "GitHub App (runner)", "Bot app runner
for the repository")
+ }
+ Rel(repo, github_app_runner, "Is managed by")
+
+ Boundary(b2, "Bot", "boundary") {
+ System(email_account, "NHSmail shared account", "Bot email
england.[repository-name]-app@nhs.net")
+ System(github_account, "GitHub account", "Bot user
[repository-name]-app
(not assigned to any org)")
+ System(github_app_registration, "GitHub App (registration)", "Bot app registration
'[Team] [Repository Name] [Purpose]'")
+ }
+ Rel(github_account, email_account, "Belongs to")
+ Rel(github_app_registration, github_account, "Is registered by")
+
+ Boundary(b3, "GitHub Admins", "boundary") {
+ System(github_org, "GitHub organisation", "Org")
+ System(github_app_installation, "GitHub App (installation)", "Bot app installation
for the repository")
+ }
+ Rel(github_app_installation, github_org, "Is installed within")
+
+ Rel(repo, github_org, "Belongs to")
+ Rel(repo, github_account, "Can accept contributions from")
+ Rel(github_app_runner, github_app_installation, "Authenticates via")
+ Rel(github_app_installation, github_app_registration, "Is an app installation of")
+
+ UpdateElementStyle(repo, $bgColor="grey")
+ UpdateElementStyle(github_app_runner, $bgColor="grey")
+ }
+```
+
+Please see the above being implemented for the _update from template_ capability:
+
+- [Repository and GitHub App (runner)](https://github.com/nhs-england-tools/update-from-template-app) for the "Update from Template" app. The runner is built on a GitHub Action but it can be a serverless workload or self-hosted compute
+- [GitHub account (bot)](https://github.com/update-from-template-app) linked to an `nhs.net` email address, but not part of any GitHub organisation
+- [GitHub App (registration)](https://github.com/apps/nhs-england-update-from-template) to be installed within the GitHub organisations in use, e.g. `nhs-england-tools`
+
+#### Authentication flow diagram
+
+The diagram below represents all the steps needed for an app implementation (aka app runner) to be authenticated and authorised to perform operations defined by the GitHub App registration and installation.
+
+```mermaid
+graph LR
+ A[Initialisation] -- App ID, App PK --> B[Generate JWT]
+ B -- JWT, Org name --> C[Get installation ID]
+ C -- JWT, Installation ID --> D[Generate Access Token]
+ D -- GITHUB_TOKEN --> E[Perform actions]
+```
+
+### Limitations
+
+- Only 100 app registrations are allowed per user or organisation, but there is [no limit on the number of installed apps](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app#about-registering-github-apps)
+- [Access rate limits apply](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/rate-limits-for-github-apps) depending on the number of repositories or users within organisation
+- The app name cannot exceed 34 characters
+
+### Examples of acquiring access token
+
+- [Bash](./assets/ADR-003/examples/bash/README.md)
+- [Golang](./assets/ADR-003/examples/golang/README.md)
+- [Node.js TypeScript (Octokit)](./assets/ADR-003/examples/nodejs/README.md) - This is our preferred method for implementing GitHub Apps. It is supported by the Octokit library, which is an official client for the GitHub API.
+- [Python](./assets/ADR-003/examples/python/README.md)
+
+## Actions
+
+- [ ] Provide an example of commit signing by bot in the unattended mode, i.e. include a link on how this is implemented in the [Update from Template](https://github.com/nhs-england-tools/update-from-template-app/blob/c1b87f3aaa568caf4a8bfdd5b07d0c4ef88a2e4a/entrypoint.sh#L81) app.
+
+## Tags
+
+`#maintainability, #security`
+
+## Footnotes
+
+[^1]: [About creating GitHub Apps](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps)
+[^2]: [Managing your personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)
+[^3]: [Publishing and installing a package with GitHub Actions](https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions)
diff --git a/decisions/assets/ADR-003/examples/bash/README.md b/decisions/assets/ADR-003/examples/bash/README.md
new file mode 100644
index 00000000..90e56cf5
--- /dev/null
+++ b/decisions/assets/ADR-003/examples/bash/README.md
@@ -0,0 +1,32 @@
+# Example: Get GitHub App access token in Bash
+
+Dependencies are `openssl`, `curl`, `jq` and `gh`.
+
+Prepare environment:
+
+```bash
+export GITHUB_APP_ID=...
+export GITHUB_APP_PK_FILE=...
+export GITHUB_ORG="nhs-england-tools"
+```
+
+Run script:
+
+```bash
+$ cd docs/adr/assets/ADR-003/examples/bash
+$ ./script.sh
+GITHUB_TOKEN=ghs_...
+```
+
+Check the token:
+
+```bash
+$ GITHUB_TOKEN=ghs_...; echo "$GITHUB_TOKEN" | gh auth login --with-token
+$ gh auth status
+github.com
+ ✓ Logged in to github.com as nhs-england-update-from-template[bot] (keyring)
+ ✓ Git operations for github.com configured to use https protocol.
+ ✓ Token: ghs_************************************
+```
+
+See the [example (script.sh)](./script.sh) implementation. This script has been written to illustrate the concept in a clear and simple way. It is not a production ready code.
diff --git a/decisions/assets/ADR-003/examples/bash/script.sh b/decisions/assets/ADR-003/examples/bash/script.sh
new file mode 100755
index 00000000..7dd83464
--- /dev/null
+++ b/decisions/assets/ADR-003/examples/bash/script.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+function main() {
+
+ if [[ -z "$GITHUB_APP_ID" || -z "$GITHUB_APP_PK_FILE" || -z "$GITHUB_ORG" ]]; then
+ echo "Environment variables GITHUB_APP_ID, GITHUB_APP_PK_FILE and GITHUB_ORG must be passed to this program."
+ exit 1
+ fi
+
+ jwt_token=$(get-jwt-token)
+ installation_id=$(get-installation-id)
+ access_token=$(get-access-token)
+
+ echo "GITHUB_TOKEN=$access_token"
+}
+
+function get-jwt-token() {
+
+ header=$(echo -n '{"alg":"RS256","typ":"JWT"}' | base64 | tr -d '=' | tr -d '\n=' | tr -- '+/' '-_')
+ payload=$(echo -n '{"iat":'"$(date +%s)"',"exp":'$(($(date +%s)+600))',"iss":"'"$GITHUB_APP_ID"'"}' | base64 | tr -d '\n=' | tr -- '+/' '-_')
+ signature=$(echo -n "$header.$payload" | openssl dgst -binary -sha256 -sign "$GITHUB_APP_PK_FILE" | openssl base64 | tr -d '\n=' | tr -- '+/' '-_')
+
+ echo "$header.$payload.$signature"
+}
+
+function get-installation-id() {
+
+ installations_response=$(curl -sX GET \
+ -H "Authorization: Bearer $jwt_token" \
+ -H "Accept: application/vnd.github.v3+json" \
+ https://api.github.com/app/installations)
+
+ echo "$installations_response" | jq '.[] | select(.account.login == "'"$GITHUB_ORG"'") .id'
+}
+
+function get-access-token() {
+
+ token_response=$(curl -sX POST \
+ -H "Authorization: Bearer $jwt_token" \
+ -H "Accept: application/vnd.github.v3+json" \
+ "https://api.github.com/app/installations/$installation_id/access_tokens")
+
+ echo "$token_response" | jq .token -r
+}
+
+main
diff --git a/decisions/assets/ADR-003/examples/golang/README.md b/decisions/assets/ADR-003/examples/golang/README.md
new file mode 100644
index 00000000..87071afc
--- /dev/null
+++ b/decisions/assets/ADR-003/examples/golang/README.md
@@ -0,0 +1,32 @@
+# Example: Get GitHub App access token in Golang
+
+Dependencies are listed in the `go.mod` file.
+
+Prepare environment:
+
+```bash
+export GITHUB_APP_ID=...
+export GITHUB_APP_PK_FILE=...
+export GITHUB_ORG="nhs-england-tools"
+```
+
+Run script:
+
+```bash
+$ cd docs/adr/assets/ADR-003/examples/golang
+$ go run main.go
+GITHUB_TOKEN=ghs_...
+```
+
+Check the token:
+
+```bash
+$ GITHUB_TOKEN=ghs_...; echo "$GITHUB_TOKEN" | gh auth login --with-token
+$ gh auth status
+github.com
+ ✓ Logged in to github.com as nhs-england-update-from-template[bot] (keyring)
+ ✓ Git operations for github.com configured to use https protocol.
+ ✓ Token: ghs_************************************
+```
+
+See the [example (main.go)](./main.go) implementation. This script has been written to illustrate the concept in a clear and simple way. It is not a production ready code.
diff --git a/decisions/assets/ADR-003/examples/golang/go.mod b/decisions/assets/ADR-003/examples/golang/go.mod
new file mode 100644
index 00000000..4c39b3e8
--- /dev/null
+++ b/decisions/assets/ADR-003/examples/golang/go.mod
@@ -0,0 +1,10 @@
+module github-app-get-tokent
+
+go 1.21.0
+
+require (
+ github.com/go-resty/resty/v2 v2.7.0
+ github.com/golang-jwt/jwt v3.2.2+incompatible
+)
+
+require golang.org/x/net v0.7.0 // indirect
diff --git a/decisions/assets/ADR-003/examples/golang/go.sum b/decisions/assets/ADR-003/examples/golang/go.sum
new file mode 100644
index 00000000..030b827f
--- /dev/null
+++ b/decisions/assets/ADR-003/examples/golang/go.sum
@@ -0,0 +1,12 @@
+github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
+github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
+github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
+github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/decisions/assets/ADR-003/examples/golang/main.go b/decisions/assets/ADR-003/examples/golang/main.go
new file mode 100644
index 00000000..42553cf7
--- /dev/null
+++ b/decisions/assets/ADR-003/examples/golang/main.go
@@ -0,0 +1,88 @@
+package main
+
+import (
+ "crypto/x509"
+ "encoding/json"
+ "encoding/pem"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "time"
+
+ "github.com/go-resty/resty/v2"
+ "github.com/golang-jwt/jwt"
+)
+
+type Installation struct {
+ ID int `json:"id"`
+ Account struct {
+ Login string `json:"login"`
+ } `json:"account"`
+}
+
+func main() {
+
+ ghAppId := os.Getenv("GITHUB_APP_ID")
+ ghAppPkFile := os.Getenv("GITHUB_APP_PK_FILE")
+ ghOrg := os.Getenv("GITHUB_ORG")
+
+ if ghAppId == "" || ghAppPkFile == "" || ghOrg == "" {
+ log.Fatalf("Environment variables GITHUB_APP_ID, GITHUB_APP_PK_FILE and GITHUB_ORG must be passed to this program.")
+ }
+
+ jwtToken := getJwtToken(ghAppId, ghAppPkFile)
+ installationId := getInstallationId(jwtToken, ghOrg)
+ accessToken := getAccessToken(jwtToken, installationId)
+
+ fmt.Printf("GITHUB_TOKEN=%s\n", accessToken)
+}
+
+func getJwtToken(ghAppId string, ghAppPkFile string) string {
+
+ pemContent, _ := ioutil.ReadFile(ghAppPkFile)
+ block, _ := pem.Decode(pemContent)
+ privateKey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
+ token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
+ "iat": time.Now().Unix(),
+ "exp": time.Now().Add(10 * time.Minute).Unix(),
+ "iss": ghAppId,
+ })
+ jwtToken, _ := token.SignedString(privateKey)
+
+ return jwtToken
+}
+
+func getInstallationId(jwtToken string, ghOrg string) int {
+
+ client := resty.New()
+ resp, _ := client.R().
+ SetHeader("Authorization", "Bearer "+jwtToken).
+ SetHeader("Accept", "application/vnd.github.v3+json").
+ Get("https://api.github.com/app/installations")
+
+ var installations []Installation
+ json.Unmarshal(resp.Body(), &installations)
+ installationId := 0
+ for _, installation := range installations {
+ if installation.Account.Login == ghOrg {
+ installationId = installation.ID
+ }
+ }
+
+ return installationId
+}
+
+func getAccessToken(jwtToken string, installationId int) string {
+
+ client := resty.New()
+ resp, _ := client.R().
+ SetHeader("Authorization", "Bearer "+jwtToken).
+ SetHeader("Accept", "application/vnd.github.v3+json").
+ Post(fmt.Sprintf("https://api.github.com/app/installations/%d/access_tokens", installationId))
+
+ var result map[string]interface{}
+ json.Unmarshal(resp.Body(), &result)
+
+ return result["token"].(string)
+}
diff --git a/decisions/assets/ADR-003/examples/nodejs/.gitignore b/decisions/assets/ADR-003/examples/nodejs/.gitignore
new file mode 100644
index 00000000..3db80f74
--- /dev/null
+++ b/decisions/assets/ADR-003/examples/nodejs/.gitignore
@@ -0,0 +1,134 @@
+yarn.lock
+
+SEE: https://github.com/github/gitignore/blob/main/Node.gitignore
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
diff --git a/decisions/assets/ADR-003/examples/nodejs/README.md b/decisions/assets/ADR-003/examples/nodejs/README.md
new file mode 100644
index 00000000..95c0ef6f
--- /dev/null
+++ b/decisions/assets/ADR-003/examples/nodejs/README.md
@@ -0,0 +1,29 @@
+# Example: Get GitHub App access token in Node.js TypeScript (using Octokit)
+
+Dependencies are listed in the `package.json` file.
+
+Prepare environment:
+
+```bash
+export GITHUB_APP_ID=...
+export GITHUB_APP_PK_FILE=...
+export GITHUB_ORG="nhs-england-tools"
+```
+
+Run script:
+
+```bash
+$ cd docs/adr/assets/ADR-003/examples/nodejs
+$ yarn install
+$ yarn start
+[
+ {
+ name: 'repository-template',
+ full_name: 'nhs-england-tools/repository-template',
+ private: false,
+ owner: {
+ login: 'nhs-england-tools',
+ ...
+```
+
+See the [example (main.ts)](./main.ts) implementation. This script has been written to illustrate the concept in a clear and simple way. It is not a production ready code.
diff --git a/decisions/assets/ADR-003/examples/nodejs/main.ts b/decisions/assets/ADR-003/examples/nodejs/main.ts
new file mode 100644
index 00000000..e3a72ff6
--- /dev/null
+++ b/decisions/assets/ADR-003/examples/nodejs/main.ts
@@ -0,0 +1,57 @@
+import { Octokit } from "octokit";
+import { createAppAuth } from "@octokit/auth-app";
+import * as fs from "fs";
+
+export const getOctokit = async (
+ appId: string,
+ privateKey: string,
+ orgName: string
+): Promise => {
+ const appOctokit = new Octokit({
+ authStrategy: createAppAuth,
+ auth: {
+ appId,
+ privateKey,
+ },
+ });
+ const installations = await appOctokit.request("GET /app/installations");
+ for (const d of installations.data) {
+ //@ts-ignore
+ if (d.account.login === orgName) {
+ const installationId = d.id;
+ const installationOctokit = new Octokit({
+ authStrategy: createAppAuth,
+ auth: {
+ appId,
+ privateKey,
+ installationId,
+ },
+ });
+ return installationOctokit;
+ }
+ }
+
+ throw new Error(`No installation found for organization ${orgName}`);
+};
+
+const ghAppId = process.env.GITHUB_APP_ID;
+const ghAppPkFile = process.env.GITHUB_APP_PK_FILE;
+const ghOrg = process.env.GITHUB_ORG;
+
+(async () => {
+ if (!ghAppId || !ghAppPkFile || !ghOrg) {
+ throw new Error(
+ "Environment variables GITHUB_APP_ID, GITHUB_APP_PK_FILE, and GITHUB_ORG must be passed to this program."
+ );
+ }
+ const octokit = await getOctokit(
+ ghAppId,
+ fs.readFileSync(ghAppPkFile, "utf8"),
+ ghOrg
+ );
+ const repos = await octokit.request("GET /orgs/{org}/repos", {
+ org: ghOrg,
+ });
+
+ console.log(repos.data);
+})();
diff --git a/decisions/assets/ADR-003/examples/nodejs/package.json b/decisions/assets/ADR-003/examples/nodejs/package.json
new file mode 100644
index 00000000..0a867c4a
--- /dev/null
+++ b/decisions/assets/ADR-003/examples/nodejs/package.json
@@ -0,0 +1,14 @@
+{
+ "main": "main.ts",
+ "scripts": {
+ "start": "ts-node main.ts"
+ },
+ "dependencies": {
+ "@octokit/auth-app": "^6.0.0",
+ "octokit": "^3.1.0"
+ },
+ "devDependencies": {
+ "ts-node": "^10.9.1",
+ "typescript": "^5.2.2"
+ }
+}
diff --git a/decisions/assets/ADR-003/examples/nodejs/tsconfig.json b/decisions/assets/ADR-003/examples/nodejs/tsconfig.json
new file mode 100644
index 00000000..25105290
--- /dev/null
+++ b/decisions/assets/ADR-003/examples/nodejs/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "target": "ES6",
+ "module": "commonjs",
+ "strict": true,
+ "esModuleInterop": true
+ },
+ "include": ["*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/decisions/assets/ADR-003/examples/python/README.md b/decisions/assets/ADR-003/examples/python/README.md
new file mode 100644
index 00000000..36d0e188
--- /dev/null
+++ b/decisions/assets/ADR-003/examples/python/README.md
@@ -0,0 +1,33 @@
+# Example: Get GitHub App access token in Python
+
+Dependencies are listed in the `requirements.txt` file.
+
+Prepare environment:
+
+```bash
+export GITHUB_APP_ID=...
+export GITHUB_APP_PK_FILE=...
+export GITHUB_ORG="nhs-england-tools"
+```
+
+Run script:
+
+```bash
+$ cd docs/adr/assets/ADR-003/examples/python
+$ pip install -r requirements.txt
+$ python main.py
+GITHUB_TOKEN=ghs_...
+```
+
+Check the token:
+
+```bash
+$ GITHUB_TOKEN=ghs_...; echo "$GITHUB_TOKEN" | gh auth login --with-token
+$ gh auth status
+github.com
+ ✓ Logged in to github.com as nhs-england-update-from-template[bot] (keyring)
+ ✓ Git operations for github.com configured to use https protocol.
+ ✓ Token: ghs_************************************
+```
+
+See the [example (main.py)](./main.py) implementation. This script has been written to illustrate the concept in a clear and simple way. It is not a production ready code.
diff --git a/decisions/assets/ADR-003/examples/python/main.py b/decisions/assets/ADR-003/examples/python/main.py
new file mode 100644
index 00000000..4968d385
--- /dev/null
+++ b/decisions/assets/ADR-003/examples/python/main.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import jwt
+import os
+import requests
+import time
+
+
+def main():
+
+ gh_app_id = os.environ.get("GITHUB_APP_ID")
+ gh_app_pk_file = os.environ.get("GITHUB_APP_PK_FILE")
+ gh_org = os.environ.get("GITHUB_ORG")
+
+ if not gh_app_id or not gh_app_pk_file or not gh_org:
+ raise ValueError("Environment variables GITHUB_APP_ID, GITHUB_APP_PK_FILE and GITHUB_ORG must be passed to this program.")
+
+ jwt_token = get_jwt_token(gh_app_id, gh_app_pk_file)
+ installation_id = get_installation_id(jwt_token, gh_org)
+ access_token = get_access_token(jwt_token, installation_id)
+
+ print(f"GITHUB_TOKEN={access_token}")
+
+
+def get_jwt_token(gh_app_id, gh_app_pk_file):
+
+ with open(gh_app_pk_file, "rb") as file:
+ private_key = file.read()
+ payload = {"iat": int(time.time()), "exp": int(time.time()) + 600, "iss": gh_app_id}
+ jwt_token = jwt.encode(payload, private_key, algorithm="RS256")
+
+ return jwt_token
+
+
+def get_installation_id(jwt_token, gh_org):
+
+ headers = {
+ "Authorization": f"Bearer {jwt_token}",
+ "Accept": "application/vnd.github.v3+json",
+ }
+ url = "https://api.github.com/app/installations"
+ response = requests.get(url, headers=headers)
+
+ installation_id = None
+ for installation in response.json():
+ if installation["account"]["login"] == gh_org:
+ installation_id = installation["id"]
+ break
+
+ return installation_id
+
+
+def get_access_token(jwt_token, installation_id):
+
+ headers = {
+ "Authorization": f"Bearer {jwt_token}",
+ "Accept": "application/vnd.github.v3+json",
+ }
+ url = f"https://api.github.com/app/installations/{installation_id}/access_tokens"
+ response = requests.post(url, headers=headers)
+
+ return response.json().get("token")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/decisions/assets/ADR-003/examples/python/requirements.txt b/decisions/assets/ADR-003/examples/python/requirements.txt
new file mode 100644
index 00000000..0e4c971b
--- /dev/null
+++ b/decisions/assets/ADR-003/examples/python/requirements.txt
@@ -0,0 +1,2 @@
+PyJWT==2.8.0
+requests==2.31.0
diff --git a/practices/security.md b/practices/security.md
index d47d3815..81e91a64 100644
--- a/practices/security.md
+++ b/practices/security.md
@@ -83,7 +83,7 @@ The remainder of this page gives more detailed and specific recommendations to b
- Be wary of any 3rd party JavaScript included on the page, e.g. for A/B testing, analytics
- Pin dependencies at known versions to avoid unexpected updates
- Scan dependencies for vulnerabilities, e.g. using [OWASP Dependency Check](https://owasp.org/www-project-dependency-check/) or [Snyk](https://snyk.io/)
- - Scan running software, e.g. using [OWASP ZAP](https://owasp.org/www-project-zap/)
+ - Scan running software, e.g. using [OWASP ZAP](https://www.zaproxy.org/)
- **Automate** security testing — on every build if practical
- Generate test data in a way that avoids including personally identifiable information
- When granting roles to CI/CD tools, use different roles for the different stages in the deployment pipeline — for example so that a deployment meant for a development account cannot be performed against a production account