From 3a0fa104fd19d62d25c626c89501db20d2b14360 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Sun, 15 Mar 2026 08:58:48 -0400 Subject: [PATCH] Add shared get-github-token.sh for all agents Single script at repo root that auto-detects GITHUB_APP_ID_* and GITHUB_PEM_PATH_* env vars, generates a JWT, and exchanges it for a GitHub App installation token. Contains no secrets. Updated all heartbeats to reference the absolute path. Co-Authored-By: Claude Opus 4.6 --- ceo/HEARTBEAT.md | 2 +- cmo/HEARTBEAT.md | 2 +- cto/HEARTBEAT.md | 2 +- engineering/gandalf/HEARTBEAT.md | 2 +- engineering/hugh/HEARTBEAT.md | 2 +- engineering/regina/HEARTBEAT.md | 2 +- get-github-token.sh | 59 ++++++++++++++++++++++++++++++++ marketing/samuel/HEARTBEAT.md | 2 +- 8 files changed, 66 insertions(+), 7 deletions(-) create mode 100755 get-github-token.sh diff --git a/ceo/HEARTBEAT.md b/ceo/HEARTBEAT.md index d152ea5..e46ab3c 100644 --- a/ceo/HEARTBEAT.md +++ b/ceo/HEARTBEAT.md @@ -38,7 +38,7 @@ This repo (`/paperclip/privilegedescalation`) is the canonical source of truth f #### 4a. Authenticate with GitHub and pull latest - export GH_TOKEN=$(bash ./get-github-token.sh) + export GH_TOKEN=$(bash /paperclip/privilegedescalation/get-github-token.sh) git -C /paperclip/privilegedescalation pull origin main #### 4b. Detect changes since last sync diff --git a/cmo/HEARTBEAT.md b/cmo/HEARTBEAT.md index 2ac671b..d2f207e 100644 --- a/cmo/HEARTBEAT.md +++ b/cmo/HEARTBEAT.md @@ -6,7 +6,7 @@ Do these steps in order. Do not skip any. Do not ask for input. ### 0. Authenticate with GitHub - export GH_TOKEN=$(bash ./get-github-token.sh) + export GH_TOKEN=$(bash /paperclip/privilegedescalation/get-github-token.sh) ### 1. Load your operating context diff --git a/cto/HEARTBEAT.md b/cto/HEARTBEAT.md index fed34f8..eba2a50 100644 --- a/cto/HEARTBEAT.md +++ b/cto/HEARTBEAT.md @@ -6,7 +6,7 @@ Do these steps in order. Do not skip any. Do not ask for input. ### 0. Authenticate with GitHub - export GH_TOKEN=$(bash ./get-github-token.sh) + export GH_TOKEN=$(bash /paperclip/privilegedescalation/get-github-token.sh) ### 1. Load your operating context diff --git a/engineering/gandalf/HEARTBEAT.md b/engineering/gandalf/HEARTBEAT.md index fe2e7e6..f41f3b9 100644 --- a/engineering/gandalf/HEARTBEAT.md +++ b/engineering/gandalf/HEARTBEAT.md @@ -6,7 +6,7 @@ Do these steps in order. Do not skip any. Do not ask for input. ### 0. Authenticate with GitHub - export GH_TOKEN=$(bash ./get-github-token.sh) + export GH_TOKEN=$(bash /paperclip/privilegedescalation/get-github-token.sh) ### 1. Load your operating context diff --git a/engineering/hugh/HEARTBEAT.md b/engineering/hugh/HEARTBEAT.md index d883e40..6ef2a7b 100644 --- a/engineering/hugh/HEARTBEAT.md +++ b/engineering/hugh/HEARTBEAT.md @@ -6,7 +6,7 @@ Do these steps in order. Do not skip any. Do not ask for input. ### 0. Authenticate with GitHub - export GH_TOKEN=$(bash ./get-github-token.sh) + export GH_TOKEN=$(bash /paperclip/privilegedescalation/get-github-token.sh) ### 1. Load your operating context diff --git a/engineering/regina/HEARTBEAT.md b/engineering/regina/HEARTBEAT.md index 204cd7e..776d364 100644 --- a/engineering/regina/HEARTBEAT.md +++ b/engineering/regina/HEARTBEAT.md @@ -6,7 +6,7 @@ Do these steps in order. Do not skip any. Do not ask for input. ### 0. Authenticate with GitHub - export GH_TOKEN=$(bash ./get-github-token.sh) + export GH_TOKEN=$(bash /paperclip/privilegedescalation/get-github-token.sh) ### 1. Load your operating context diff --git a/get-github-token.sh b/get-github-token.sh new file mode 100755 index 0000000..11a736f --- /dev/null +++ b/get-github-token.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +set -euo pipefail +# +# Generates a GitHub App installation access token. +# Reads credentials from env vars set in each agent's adapter config: +# GITHUB_APP_ID_ — the GitHub App ID +# GITHUB_PEM_PATH_ — path to the private key PEM file +# +# Usage: export GH_TOKEN=$(bash /paperclip/privilegedescalation/get-github-token.sh) + +# Auto-detect credentials from env (each agent has exactly one of each) +APP_ID=$(printenv | grep '^GITHUB_APP_ID_' | head -1 | cut -d= -f2) +PEM_PATH=$(printenv | grep '^GITHUB_PEM_PATH_' | head -1 | cut -d= -f2) + +if [[ -z "${APP_ID:-}" || -z "${PEM_PATH:-}" ]]; then + echo "Error: GITHUB_APP_ID_* and GITHUB_PEM_PATH_* env vars must be set" >&2 + exit 1 +fi + +if [[ ! -f "$PEM_PATH" ]]; then + echo "Error: PEM file not found at $PEM_PATH" >&2 + exit 1 +fi + +# --- Build JWT (RS256) --- +b64url() { openssl base64 -e -A | tr '+/' '-_' | tr -d '='; } + +NOW=$(date +%s) +HEADER=$(printf '{"alg":"RS256","typ":"JWT"}' | b64url) +PAYLOAD=$(printf '{"iat":%d,"exp":%d,"iss":"%s"}' "$((NOW - 60))" "$((NOW + 600))" "$APP_ID" | b64url) +SIGNATURE=$(printf '%s.%s' "$HEADER" "$PAYLOAD" \ + | openssl dgst -sha256 -sign "$PEM_PATH" | b64url) +JWT="${HEADER}.${PAYLOAD}.${SIGNATURE}" + +# --- Get installation ID (first installation for this app) --- +INSTALLATION_ID=$(curl -sf \ + -H "Authorization: Bearer $JWT" \ + -H "Accept: application/vnd.github+json" \ + https://api.github.com/app/installations \ + | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])") + +if [[ -z "$INSTALLATION_ID" ]]; then + echo "Error: Could not get installation ID for app $APP_ID" >&2 + exit 1 +fi + +# --- Exchange for installation access token --- +TOKEN=$(curl -sf -X POST \ + -H "Authorization: Bearer $JWT" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/app/installations/${INSTALLATION_ID}/access_tokens" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])") + +if [[ -z "$TOKEN" ]]; then + echo "Error: Could not get installation access token" >&2 + exit 1 +fi + +echo "$TOKEN" diff --git a/marketing/samuel/HEARTBEAT.md b/marketing/samuel/HEARTBEAT.md index 4201728..60a30ea 100644 --- a/marketing/samuel/HEARTBEAT.md +++ b/marketing/samuel/HEARTBEAT.md @@ -6,7 +6,7 @@ Do these steps in order. Do not skip any. Do not ask for input. ### 0. Authenticate with GitHub - export GH_TOKEN=$(bash ./get-github-token.sh) + export GH_TOKEN=$(bash /paperclip/privilegedescalation/get-github-token.sh) ### 1. Load your operating context