Skip to content

github-backup: drainer needs an owner allowlist — public App admits foreign installs #3326

Closed

Description

@dezren39

Problem

The backup GitHub App developing-today-backup (ID 5562183) was made public on 2026-10-31 — GitHub does not allow a private App to be installed by organizations, and org installs were a hard requirement. A side effect: anyone can now install the App on their own repositories.

Their webhook deliveries verify against the App secret (it is genuinely our App), reach the Worker queue, and bin/drain_queue.py currently acts on any queued event whose repo appears in plan.json — and files fetch attempts / state for anything else it is handed. A foreign install could make the laptop drainer clone/fetch strangers' repositories into ~/backups/github, burning disk, bandwidth, and the App installation rate limit, and polluting the backup tree.

Fix

Drop foreign events at the drainer door, before any fetch work:

Tests

Extend tests/test_drain_queue.py: allowed owner proceeds exactly as today; foreign owner dropped + acked + logged (even if the repo is in plan.json); missing-owner payload dropped; repository-owner precedence over installation account; #3219 failure semantics unchanged for allowed owners.

Deploy / proof

Merge → pull on the laptop primary → just github-backup-units → one drain run Result=success → hand-inject one foreign event through the Worker and show the DRAIN-DROP line with no clone attempted.

Refs #3215 (system issue). Approved by Drew 2026-10-31 23:08 CDT.

Activity

  1. added a commit that references this issue on Nov 1, 2026
  2. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Done — implemented, merged, deployed, and proven live.

    PR: #3328 (merge 72e347609).

    Change: bin/drain_queue.py checks each queued event's owner against config.json owners before any fetch work (repository.owner.login → full_name owner segment → installation.account.login; case-insensitive; fails closed). Foreign/owner-less events are dropped: acked as handled-by-policy, one DRAIN-DROP log line each, counted as dropped_foreign in the run summary, never counted toward the #3219 DRAIN-CHRONIC streak.

    Tests: 54/54 in tests/test_drain_queue.py (22 new). Repro first: the new foreign-owner case run against the pre-fix drainer reached fetch (the hole); closed after. Sibling suites 18/18 + 9/9.

    Deploy: laptop primary pulled to the merge, just github-backup-units refreshed, drain timer healthy.

    Live proof (2026-10-31 23:12 CDT): hand-injected a signed webhook for stranger-danger/evil-repo through the Worker (202 queued). Drain run Result=success; summary dropped_foreign: 1, acked: 1 (the other 28 queued events were allowed-owner repos inside their coalesce windows, untouched). fetch.log:

    2026-10-31T23:12:48-0500 DRAIN-DROP foreign owner=stranger-danger repo=stranger-danger/evil-repo delivery=1793506360616-bcc9742f-5119-4c22-b34e-957c7557c0b2
    

    No ~/backups/github/stranger-danger exists — no clone was attempted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions