Skip to content

Migrating TUTORIALS_GITHUB_TOKEN to a GitHub App ​

Status: Live in DEV (#1154). sap-tutorials-builder App registered; USE_GITHUB_APP=true in DEV CI (repo variable) + runtime (tutorials-srv env). All three PATs (TUTORIALS_GITHUB_TOKEN, GITHUB_DISPATCH_TOKEN, TUTORIALS_POC_DISPATCH_TOKEN) are retained as intentional fail-open fallbacks — App-token mint failure degrades to PAT rather than breaking builds; they are not deleted. PROD activation follows the end-of-July cutover. Driver: SAP org PAT expiry policies are short, and PAT rotation is manual, error-prone, and tied to a single human account. A GitHub App resolves all three.

👉 For the org-admin and repo-maintainer setup steps, see github-app-setup.md. This doc covers the engineering rationale, current state, and migration design.

Current state ​

The token TUTORIALS_GITHUB_TOKEN (a classic PAT) is consumed in exactly four places:

WherePurposeFallback
.github/workflows/rebuild-content.yml:89npm run fetch-tutorials — discovery (GraphQL) + raw markdown + rules.vrnone
.github/workflows/deploy.yml:56MTA build stepgithub.token (the per-job ephemeral token)
scripts/parsers/github.ts:79 (graphqlRequest)All GraphQL discovery + commit metadatareads GITHUB_TOKEN first, then TUTORIALS_GITHUB_TOKEN
scripts/parsers/github.ts:477 (fetchRulesVr)Raw CDN reads of *-Contribution private repossame env-var fallback

All access targets a single org: sap-tutorials. The fetcher reads:

  • Public tutorial repos (e.g. cap-getting-started) — could in principle work unauthenticated, but discovery via GraphQL still requires a token to avoid 5000 → 60/hr rate limit.
  • Private companion repos *-Contribution (e.g. abap-core-development-Contribution) for rules.vr validation quizzes — these do require auth.

Build cadence is on-demand only (workflow_dispatch + tutorial-source repository_dispatch), so call volume is well under any rate limit. The PAT exists to (a) authenticate against private repos and (b) lift the rate-limit ceiling for batched discovery.

Why a GitHub App is a strict upgrade ​

ConcernClassic PAT todayGitHub App
Token lifetimeWhatever the SAP org policy allows; manual rotation when it expires1 hour, auto-issued per workflow run
ScopeBound to the user's account (all repos they can see)Bound to the App's installation; per-repo + per-permission
ContinuityTied to one human; departure breaks the buildOrg-owned; survives any single account
Audit trailActions appear as the userActions appear as the App (clear bot identity)
Rate limit5000 req/hr per user5000 req/hr per installation, separate from human usage
Rotation painManual: regen, paste into Actions secretNone — token is regenerated every run

The "expires every N days, requires human action" failure mode that motivated this research disappears entirely with a GitHub App.

Architecture ​

text
┌─────────────────────────────────────────┐
│ GitHub App: "sap-tutorials-builder"     │
│   Owner: sap-tutorials org              │
│   Permissions: contents:read,           │
│                metadata:read             │
│   Installed on: sap-tutorials org       │
│     repos: tutorials* + *-Contribution  │
└──────────────┬──────────────────────────┘
               │
               │ APP_ID + PRIVATE_KEY (Actions secrets)
               ▼
┌─────────────────────────────────────────┐
│ rebuild-content.yml (per run)           │
│   step 1: actions/create-github-app-    │
│           token@v1                      │
│           → installation token (1h TTL) │
│   step 2: fetch-tutorials with token    │
│           in TUTORIALS_GITHUB_TOKEN     │
└─────────────────────────────────────────┘

The App is created once. Each workflow run mints a fresh token. No long-lived secret ever holds a usable GitHub access token — only the App's RSA private key (which mints tokens but is itself not a token).

Implementation status ​

The workflow change is already merged in .github/workflows/rebuild-content.yml:

  • A Generate GitHub App token step (uses actions/create-github-app-token@v1) gated on the repo variable USE_GITHUB_APP == 'true'.
  • The Fetch tutorials step pulls steps.app-token.outputs.token || secrets.TUTORIALS_GITHUB_TOKEN — App token if generated, PAT otherwise.

This lets the migration land safely with no behaviour change and no parallel-workflow scaffolding. Activation is one repo-variable flip after the org admin completes App registration; rollback is the inverse.

No code changes required. scripts/parsers/github.ts already reads GITHUB_TOKEN first (falling back to TUTORIALS_GITHUB_TOKEN), and installation tokens are standard Bearer tokens — they work transparently for both graphqlRequest() and fetchRulesVr().

deploy.yml:56 is unchanged — its secrets.TUTORIALS_GITHUB_TOKEN || github.token fallback keeps working, and the MTA build step does not need GitHub-org-scoped credentials. Once the PAT is retired, the line can either be deleted (rely solely on github.token) or migrated the same way if private-repo access becomes necessary in that step.

Cutover steps ​

The detailed admin + repo-maintainer runbook lives in github-app-setup.md. At a glance:

  1. Org admin registers sap-tutorials-builder App, generates private key, installs on org.
  2. Repo maintainer adds TUTORIALS_APP_ID + TUTORIALS_APP_PRIVATE_KEY (and optionally TUTORIALS_APP_INSTALLATION_ID) as Actions secrets.
  3. Repo maintainer sets repo variable USE_GITHUB_APP=true.
  4. Manually trigger a rebuild and verify the Generate GitHub App token step runs.
  5. After one successful unattended run: delete TUTORIALS_GITHUB_TOKEN secret and revoke the underlying PAT.

Rollback at any stage = set USE_GITHUB_APP to anything other than true. The PAT path remains intact until step 5.

SAP-specific considerations ​

  • App ownership. The App must be owned by the sap-tutorials org, not a personal account, so it survives any individual leaving SAP. This is the whole point — confirm the SAP GitHub admin team will register it under the org.
  • Approval process. Internal SAP policy may require security/IT review before installing third-party-style Apps, even self-authored ones. Submit early; this is the long pole.
  • Fallback if Apps are blocked. If org policy disallows custom Apps, the next-best step is a fine-grained PAT scoped to the specific repos with Contents: Read. That fixes the over-broad-scope problem of classic PATs but does not fix the rotation problem. Apps remain the strictly better target.
  • Webhook permissions. Don't enable any. The App is a token-minting identity, not an event consumer. Less surface = less review friction.

Risks & open questions ​

RiskMitigation
App approval takes weeks at SAPStart the conversation now; meanwhile keep PAT working. The migration is non-urgent — current build is healthy.
*-Contribution private repos sit in a different orgVerify before App registration. If yes, App needs install on both orgs (or two Apps). Confirmed today: all access is sap-tutorials/* so this should not apply.
Action actions/create-github-app-token@v1 is a third-party actionIt's GitHub-published (actions org), widely used, and the canonical pattern. Pin the major version (@v1), not @latest.
Private-key leakage in logsThe action masks the token output by default. Don't echo it.

Recommendation ​

Proceed. The work is small (one workflow step, three secrets, no code changes), the security gain is real (scoped + auto-rotated + no human dependency), and the SAP-specific pain that drove the question — short PAT expiry — vanishes by construction. Schedule it after the current Gap #4 work lands so the PAT path stays in place as fallback while the App is being approved.