Skip to content

Back to the catalog

Staying Evergreen: How we automated secure access and repo hygiene in a shared org
github.blogarticle

Staying Evergreen: How we automated secure access and repo hygiene in a shared org

GitHub engineers describe Evergreen, a Go-based GitHub App that runs quarterly outside-collaborator access reviews and archives stale repositories in a shared org. It uses scheduled Actions with repository_dispatch, JSON state files in git as the audit trail, checklist issues (IssueOps) closed to trigger revocation, and custom properties for ownership and exemptions. Design principles include fail-secure/fail-closed revocation, never deleting data, dry-run rollout, Splunk logging, and proactive pacing against GitHub's secondary rate limits.

TL;DR GitHub's Evergreen app automates quarterly outside-collaborator access reviews and stale-repo archival using IssueOps, fail-secure defaults, and git-backed audit state.
Document

What it is

Evergreen is a small Go webhook server, packaged as a GitHub App, that GitHub runs against a shared org where employees work with customers and partners. It does two jobs: a quarterly review of outside-collaborator access, and flagging, warning and archiving repos with no human activity past a threshold. The post presents it as a pattern you can rebuild from native GitHub features.

How it works

  • Clock and memory: a controller repo holds four scheduled Actions workflows that fire repository_dispatch events quarterly. Each workflow also supports workflow_dispatch for manual runs. Reviews open two weeks before cleanup, and a built-in grace period stops a stray manual run from fast-forwarding to revocation.
  • State: one JSON file per repo at state/repos/<repo_id>.json. It records the access snapshot, review open date, deadline, who decided and the outcome. git log on that file answers "why did this person lose access".
  • Decision channel: each repo gets a checklist issue. Checked collaborators stay; closing the issue fires the issues.closed webhook, and unchecked ones are revoked. A receipt comment lists what was kept and removed.
  • Authorization: if the closer is not an authorized employee, the app reopens the issue and posts an explanation.
  • Owner resolution: use the evergreen_repo_owner custom property (a @user or @org/team) if set. Otherwise take recent issue and commit authors and cc those who are on the access review team.
flowchart TD
    A[Review issue closed] --> B{Closer authorized?}
    B -- No --> C[Reopen + explain]
    B -- Yes --> D[Keep checked, revoke rest]
    E[Deadline passes, no response] --> F[Revoke all in snapshot]
    D --> G{All removals succeeded?}
    F --> G
    G -- Yes --> H[Record revoked, post receipt]
    G -- No --> I[Stay pending, issue open, retry next run]

What to do

If you copy the pattern:

  • Fail closed on partial failure. Record "revoked" only after every removal succeeds. Otherwise leave the review pending and the issue open so the next run retries.
  • Write every change into the issue. Restoring access then means re-adding the listed handles.
  • Provide opt-outs via org-owner-set custom properties: evergreen_exempt_from_access_review and evergreen_exempt_from_archive.
  • Roll out in order: 1. Ship a DRY_RUN mode that logs every intended write, and run it against the real org first. 2. Announce repeatedly before enabling. 3. Send structured logfmt logs to Splunk. The authors say this caught empty collaborator lists, unusual permissions and repos with issues disabled.
  • Pace for rate limits. Issue and comment creation hits GitHub's secondary content-creation limits of 80 requests/minute and 500/hour. Evergreen batches repos, spaces content-creating calls by a couple of seconds, sleeps between batches, and counts only repos that actually create content. The authors chose proactive avoidance over reactive back-off.

Caveats

  • The post gives no code, repo link or configuration for Evergreen. It describes the design only, so you would be building it yourself.
  • Authorization is described only as "Hubbers" (employees). How the check is implemented is not stated.
  • Archiving is never destructive: nothing is deleted. The post does not give the staleness threshold or the exact reminder timing.

Organisations named here

  • IssueOps
  • outside collaborators
  • repository_dispatch

Technologies named here

  • Evergreen
  • GitHub Actions scheduled workflows
  • GitHub custom properties
  • GitHub issues and webhooks
  • DRY_RUN mode
  • GitHub
  • GitHub Actions
  • Go
  • Splunk
  • GitHub Copilot

Techniques named here

  • Quarterly outside collaborator access review
  • Fail secure
  • Fail closed
  • Authorization check
  • Owner resolution fallback chain