Skip to main content

Registering the gate GitHub App (spike)

Who does this: a SecurityV0 org admin. One sitting, roughly fifteen minutes. What it unblocks: platform #1885 and #1900 — the spike that pins the deployment_protection_rule payload, re-run and concurrency semantics, and receipt latency. Until it happens, the provider-shaped fields in the design pack cannot be frozen and gate_decisions is being designed against a documented guess.


Is this the App customers will install? No — and deliberately so

Register a throwaway, private App now. Register a separate, clean App for customers later.

The eventual customer shape is one public App, not one App per customer. That is already baked into the design: gate_installations maps (installation.id, repository.id) → tenant_id, which is the single-App-multi-tenant shape. GitHub also removes the alternative — "GitHub Apps can only review their own custom deployment protection rules" (REST: Review custom deployment protection rules), so the App that receives the webhook is the only App that can release the hold. There is no splitting it into a read App and a write App.

Three reasons not to promote the spike App to that role:

  • Its private key will have been handled loosely while 1Password is unprovisioned.
  • The App name and slug become customer-visible branding on the installation consent screen.
  • Its permission set will churn during the spike.

App names are globally unique, so giving the spike an obviously-throwaway name also keeps the real name free.


What you do not need: a running webhook receiver

GitHub's App settings carry Advanced → Recent Deliveries, which shows the complete request headers and full JSON body of every delivery — whether or not anything answered it. That satisfies the payload-capture, re-run-semantics and concurrency criteria on its own.

Only the latency measurement needs a live endpoint, and that is a separate sitting. So the webhook URL below is a deliberate placeholder.

This also exploits a property the architecture already relies on: GitHub permits redelivery of failed deliveries (it never auto-retries successful-but-unhandled ones). Deliveries that fail against the placeholder are not lost — they can be replayed at a real receiver later.


Registration

Go to https://github.com/organizations/SecurityV0/settings/apps/new. Register under the org, not a personal account, so ownership survives.

FieldValue
GitHub App namesv0-gate-spike
Homepage URLhttps://securityv0.com
Webhook → Active✅ leave checked
Webhook URLhttps://example.invalid/gate — placeholder, see above
Webhook secretgenerate a random string and keep it
Where can this GitHub App be installed?Only on this account

Repository permissions:

PermissionLevelWhy
DeploymentsRead and writeThe load-bearing one. GitHub's review endpoint requires it (Permissions required for GitHub Apps). There is no narrower scope — the same scope also permits creating and deleting deployments. That is an accepted-risk item in the adapter threat model, mitigated by process and credential isolation, not by a second App identity
ActionsRead-onlyThe webhook carries no run_id object and no workflow_run object, so run attempt and workflow identity cost one extra GET /actions/runs/{run_id}

Subscribe to events:Deployment protection rule

If that checkbox is not in the list, set the Deployments permission first — the event list is filtered by the permissions you have selected.

Then Create GitHub App.


After creation

  1. Generate a private key → downloads a .pem. Move it outside any git repo (~/.config/sv0/gate-spike.pem is fine). Never commit, paste or print it.
  2. Note the App ID from the General page. This one is safe to share.
  3. Put the webhook secret in sv0-platform/.env as GITHUB_GATE_WEBHOOK_SECRET.
  4. Install App → SecurityV0 → Only select repositoriesenterprise-nimbus-media-insights.

Enabling the rule

In SecurityV0/enterprise-nimbus-media-insightsSettings → Environments → production:

  1. Under Deployment protection rules, sv0-gate-spike now appears. Enable it.
  2. Deselect "Allow administrators to bypass configured protection rules."

That second step is not optional bookkeeping. Admin bypass is on by default (Reviewing deployments), and while it is on, any claim that the gate holds is false. Verify it, do not assume it.

Leave production-infra ungated. One gated environment answers all three unknowns and keeps the infrastructure phase runnable.


Two cautions

A held job waits 30 days. "When a custom deployment protection rule is triggered it will wait for up to 30 days for a webhook event response before it times out and the workflow job fails." With a placeholder URL nothing will ever answer, so cancel the run from the Actions tab once the delivery has been read.

Permissions can be added later — the installation simply prompts to accept the new scope. The set above is a starting point, not a commitment.

MEASURED 2026-07-28 — the drift-detection poll needs no extra permission. This previously said the poll in architecture §4.3 (confirming the rule is still installed and admin bypass still off) may need administration: read, pinned against the live API rather than guessed. It has now been pinned. With an installation token carrying only actions: read, metadata: read, deployments: write, both of these returned 200 against SecurityV0/enterprise-nimbus-media-insights:

  • GET /repos/{owner}/{repo}/environments/{env} — returns can_admins_bypass
  • GET /repos/{owner}/{repo}/environments/{env}/deployment_protection_rules

So the third leg of item 19 is implementable on the permission set above. Do not add administration: read.

Read the right endpoint when you check whether the rule is on. The environment object's protection_rules array never lists App-installed custom rules — those live only on GET /repos/{owner}/{repo}/environments/{env}/deployment_protection_rules. An environment can look completely unguarded in the first response and be fully gated. This trips people up; check the second endpoint before concluding anything. Measured 2026-08-05 on the promotion repo: one enabled custom rule from this App on production, with admin bypass already deselected on both environments.