This document is the source of truth for the repository-level GitHub Apps,
personal access token, and branch-protection settings described below. It does
not inventory the complete runtime authentication posture. The current Worker,
GitHub-environment, deploy-key, OAuth, and AWS credential boundaries are in
INFRASTRUCTURE.md. Read both documents before changing
the authentication posture; neither records secret values.
The benchmark repository leanprover/lean-eval has its own
docs/ci-secrets.md covering its lean-eval-regenerator App; that one
is unrelated to this pipeline.
| Item | Type | Stored as | Used by |
|---|---|---|---|
lean-eval-bot |
GitHub App | LEAN_EVAL_BOT_CLIENT_ID, LEAN_EVAL_BOT_PRIVATE_KEY |
submission.yml (fetch); protected deploy-worker.yml (broker-secret convergence) |
lean-eval-recorder |
GitHub App | LEAN_EVAL_RECORDER_CLIENT_ID, LEAN_EVAL_RECORDER_PRIVATE_KEY |
submission.yml (record) |
lean-eval-archiver |
GitHub App | LEAN_EVAL_ARCHIVER_CLIENT_ID, LEAN_EVAL_ARCHIVER_PRIVATE_KEY |
submission.yml (archive) |
LEADERBOARD_WRITE_TOKEN |
Fine-grained PAT | LEADERBOARD_WRITE_TOKEN |
submission.yml (leaderboard redeploy dispatch) |
Ruleset main protection |
Repository Ruleset | (config in this file, applied via API) | branch protection on main |
Ruleset Protect staging Results |
Repository Ruleset | (config in this file, applied via API) | branch protection on staging-results |
The three Apps in this document are owned by the personal account kim-em;
Kim Morrison is their current credential custodian. For any App private-key
rotation, generate one replacement key while the old key remains valid,
replace only that App's *_PRIVATE_KEY repository secret, verify the matching
protected workflow path with intake disabled or in staging, and then delete the
old key in the App settings. Immediate revocation is deletion of the affected
App key followed by removal or replacement of its repository secret. Do not
reuse one App's key for another App. No alternate App custodian is currently
recorded. No current key expiry, key ID, or fingerprint is recorded: GitHub's
repository-secret and public App APIs do not expose which App key a stored
private-key secret contains. Do not infer those facts from a repository-secret
creation or update timestamp.
To check the live state at any time:
gh secret list -R leanprover/lean-eval-submissions
for app in lean-eval-bot lean-eval-recorder lean-eval-archiver; do
gh api "/apps/$app" --jq '{slug, id, client_id}'
done
gh api /repos/leanprover/lean-eval-submissions/rules/branches/main --jq '[.[].type]'Used by .github/workflows/submission.yml
to mint installation tokens that fetch submission source from contributor
repositories (which may be private).
- Owner account:
kim-em(User account). - App ID:
3346375(public identifier used for installation metadata). - Webhook: deactivated.
- Repository permissions:
Contents: Read
- Where can this GitHub App be installed: Any account. Contributors install it on their own submission repos so the workflow can clone them.
LEAN_EVAL_BOT_CLIENT_ID— the app's Client ID. It is public, but stored as a secret to keep the workflow's app inputs together.LEAN_EVAL_BOT_PRIVATE_KEY— the full PEM contents of a private key generated for the app.
.github/workflows/submission.yml,
in the Mint lean-eval-bot installation token step, via
actions/create-github-app-token. The minted token is scoped to the
single Fetch submission step and is used to clone the contributor's
submission source.
Protected .github/workflows/deploy-worker.yml
also pipes these existing secrets directly into each private broker as
LEGACY_SOURCE_APP_ID and LEGACY_SOURCE_APP_PRIVATE_KEY. That broker mints a
separate repository-scoped contents/metadata-read token only to prove the exact
repository and commit before server intake mutates State.
The issue template
.github/ISSUE_TEMPLATE/submit.yml
instructs contributors to install this app on their submission repo.
This is the same App that previously served leanprover/lean-eval's
submission workflow. The migration step is to install it on
leanprover/lean-eval-submissions and copy its secrets here. A
from-scratch rebuild:
- As the desired app owner, visit https://github.com/settings/apps/new.
- Fill in:
- Name:
lean-eval-bot - Homepage URL:
https://github.com/leanprover/lean-eval-submissions - Webhook → Active: unchecked
- Repository permissions → Contents: Read
- Where can this GitHub App be installed: Any account
- Name:
- Save → record both the App ID and Client ID.
- Generate a private key, download the
.pem. - Install the app on
leanprover/lean-eval-submissions(so the workflow has an installation to mint tokens against). - Set the secrets:
gh secret set LEAN_EVAL_BOT_CLIENT_ID -R leanprover/lean-eval-submissions --body <CLIENT_ID> gh secret set LEAN_EVAL_BOT_PRIVATE_KEY -R leanprover/lean-eval-submissions < path/to/key.pem
Used by .github/workflows/submission.yml
to push the record: commit (a results-store update) directly to this
repo's main or isolated staging-results branch, bypassing the matching
branch ruleset.
- Owner account:
kim-em(User account). - App ID:
3769615(public identifier used by the ruleset bypass actor). - Webhook: deactivated.
- Repository permissions:
Contents: Read and write
- Where can this GitHub App be installed: Any account. (Required so
the org can install it; the only intended installation is on
leanprover/lean-eval-submissionsitself.) - Installed on:
leanprover/lean-eval-submissionsonly (single-repo installation).
This is a distinct App from lean-eval-bot, on purpose:
lean-eval-bot is installed on arbitrary contributor repositories, so it
must stay Contents: Read only. A write-capable App must never be
installable on third-party repos.
LEAN_EVAL_RECORDER_CLIENT_ID— the app's Client ID. It is public, but stored as a secret to keep the workflow's app inputs together.LEAN_EVAL_RECORDER_PRIVATE_KEY— the full PEM contents of a private key generated for the app.
.github/workflows/submission.yml,
in the Mint lean-eval-recorder installation token step. The token
authenticates the results-store/ checkout so the push-retry loop's
git push origin HEAD:main lands on protected main.
GITHUB_TOKEN-authored pushes cannot bypass branch protection (no actor
can put github-actions[bot] itself in the bypass list at workflow
granularity). A dedicated app's principal can be in the bypass list (see
the Ruleset section below); the bypass is then narrow because only this
workflow has the app's secrets.
- As the desired app owner, visit https://github.com/settings/apps/new.
- Fill in:
- Name:
lean-eval-recorder - Homepage URL:
https://github.com/leanprover/lean-eval-submissions - Webhook → Active: unchecked
- Repository permissions → Contents: Read and write (everything else stays "No access")
- Where can this GitHub App be installed: Any account
- Name:
- Save → record both the App ID (for the ruleset bypass actor) and the Client ID (for token minting).
- Generate a private key, download the
.pem. - Install the app on
leanprover/lean-eval-submissionsonly. - Set the secrets:
gh secret set LEAN_EVAL_RECORDER_CLIENT_ID -R leanprover/lean-eval-submissions --body <CLIENT_ID> gh secret set LEAN_EVAL_RECORDER_PRIVATE_KEY -R leanprover/lean-eval-submissions < path/to/key.pem
- Add the App ID to the
mainandProtect staging Resultsrulesets' bypass lists (see Ruleset section below).
Used by .github/workflows/submission.yml
to push age-encrypted submission tarballs and unencrypted metadata
sidecars to leanprover/lean-eval-audit.
See docs/audit-archive.md for the design.
- Owner account:
kim-em(User account). - App ID:
3856297(public identifier used for installation metadata). - Webhook: deactivated.
- Repository permissions:
Contents: Read and write
- Where can this GitHub App be installed: Only on this account.
- Installed on:
leanprover/lean-eval-auditonly (single-repo installation, scoped viarepositories: lean-eval-auditin the workflow'sactions/create-github-app-tokenstep).
This is a distinct App from lean-eval-bot and lean-eval-recorder,
on purpose:
lean-eval-botis installed on arbitrary contributor repositories so it must stayContents: Readonly — bumping it to write would silently expand the trust boundary for every submitter who installed it.lean-eval-recorderwrites the leaderboard results store, a public repo; the audit archive is a private repo with a different access policy. Keeping the apps separate means an audit-archive compromise cannot also rewrite the leaderboard, and vice versa.
LEAN_EVAL_ARCHIVER_CLIENT_ID— the app's Client ID. It is public, but stored as a secret to keep the workflow's app inputs together.LEAN_EVAL_ARCHIVER_PRIVATE_KEY— the full PEM contents of a private key generated for the app.
.github/workflows/submission.yml,
in the Mint lean-eval-archiver installation token step of the
archive job. The minted token authenticates the
scripts/archive_submission.py push invocation, which writes one
ciphertext file and one sidecar JSON to lean-eval-audit via the
GitHub Contents API.
The archive job runs on a separate runner from the one that elaborates
untrusted Lean (the evaluate job), so the write-capable installation
token is never co-resident with an attacker-influenced process. See
docs/audit-archive.md > "Threat model".
- As the desired app owner, visit https://github.com/settings/apps/new.
- Fill in:
- Name:
lean-eval-archiver - Homepage URL:
https://github.com/leanprover/lean-eval-audit - Webhook → Active: unchecked
- Repository permissions → Contents: Read and write (everything else stays "No access")
- Where can this GitHub App be installed: Only on this account
- Name:
- Save → record both the App ID and Client ID.
- Generate a private key, download the
.pem. - Install the app on
leanprover/lean-eval-auditonly. - Set the secrets:
gh secret set LEAN_EVAL_ARCHIVER_CLIENT_ID -R leanprover/lean-eval-submissions --body <CLIENT_ID> gh secret set LEAN_EVAL_ARCHIVER_PRIVATE_KEY -R leanprover/lean-eval-submissions < path/to/key.pem
Fine-grained Personal Access Token used by
.github/workflows/submission.yml
to fire a repository_dispatch (event_type: results-advanced) at
https://github.com/leanprover/lean-eval-leaderboard after a result is
recorded, so the leaderboard site redeploys with the new result.
This is the same PAT leanprover/lean-eval and leanprover/lean-eval-leaderboard
already use for their leaderboard interactions; the submission pipeline
just needs its own copy of the secret.
LEADERBOARD_WRITE_TOKENinleanprover/lean-eval-submissions.
The token must be a fine-grained PAT with
leanprover/lean-eval-leaderboard selected and Contents: Read and write — that permission is what the repository_dispatch REST endpoint
authorizes on (per
https://docs.github.com/en/rest/repos/repos#create-a-repository-dispatch-event).
- Open https://github.com/settings/personal-access-tokens/new.
- Resource owner: leanprover (requires org-owner approval).
- Repository access: Only select repositories →
leanprover/lean-eval-leaderboard. - Repository permissions: Contents: Read and write.
- Save the token, then write it to this repo:
gh secret set LEADERBOARD_WRITE_TOKEN -R leanprover/lean-eval-submissions --body <TOKEN>
When rotating the PAT, update the copies in leanprover/lean-eval and
leanprover/lean-eval-leaderboard together (see those repos' own docs).
Replace all three copies before revoking the old token in its issuing GitHub
account, then verify one dispatch from each caller. The repository APIs expose
the secret names but not the PAT issuer or expiry; those two current facts are
not recoverable from this repository and must not be guessed. Record them in
the credential inventories during the next reviewed, packet-bound PAT rotation.
main is protected by a Repository Ruleset. The lean-eval-recorder app
is on the bypass list so the record job can push results directly;
everyone and everything else goes through a PR with a passing verify
check.
gh api /repos/leanprover/lean-eval-submissions/rulesets \
--jq '.[] | {id, name, target, enforcement}'
gh api "/repos/leanprover/lean-eval-submissions/rulesets/<ID>" \
--jq '{name, bypass_actors}'The <RECORDER_APP_ID> placeholder is the lean-eval-recorder App ID
(currently 3769615, verified by the live-state command above).
integration_id: 15368 pins the verify status check to the GitHub
Actions app, so a hostile third-party app can't satisfy it with a bogus
check of the same name.
{
"name": "main protection",
"target": "branch",
"enforcement": "active",
"conditions": { "ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] } },
"rules": [
{ "type": "deletion" },
{ "type": "non_fast_forward" },
{ "type": "pull_request",
"parameters": {
"required_approving_review_count": 0,
"dismiss_stale_reviews_on_push": false,
"require_code_owner_review": false,
"require_last_push_approval": false,
"required_review_thread_resolution": false } },
{ "type": "required_status_checks",
"parameters": {
"strict_required_status_checks_policy": false,
"required_status_checks": [ { "context": "verify", "integration_id": 15368 } ] } }
],
"bypass_actors": [
{ "actor_id": <RECORDER_APP_ID>, "actor_type": "Integration", "bypass_mode": "always" }
]
}- Make sure the
lean-eval-recorderapp exists, is installed on the repo, and you know its App ID. - Save the payload above to
/tmp/main-ruleset.json, substituting the App ID. - Apply:
gh api -X POST /repos/leanprover/lean-eval-submissions/rulesets \ --input /tmp/main-ruleset.json
Create staging-results from the reviewed main commit before the first
staging intake run. Ruleset Protect staging Results targets only
refs/heads/staging-results, forbids deletion and non-fast-forward updates,
requires ordinary writers to use a pull request, and gives the same
lean-eval-recorder integration the sole always-bypass. It deliberately has
no required status check: recorder commits contain staging data, not reviewed
code, and never deploy the public leaderboard. The live ruleset ID created on
2026-08-23 is 21220656; reconstruct by copying the main protection payload,
changing the name and include condition, and omitting required_status_checks.
- The
recordjob's results push tomaindoes not drive the leaderboard implicitly: that redeploy is driven by an explicitresults-advancedrepository_dispatch. A Results-only push does select unfilteredci.ymland the reviewer-gated, deployment-free immutable-tag promoter so the final accepted-result commit can become an exact historical-inventory cutoff; it does not select the Worker deployment workflow. - Anyone who can land a PR that modifies
submission.ymlto push arbitrary content tomaincould, after merge, exfiltrate that capability. This is the same trust boundary as merging any PR.