invite-gate
Auto-accept GitHub repository invitations from senders you explicitly trust. Leave every other invitation exactly where it is — pending, untouched, for you to review by hand.
Why this exists
If you're on a team (or a popular open-source project), you can end up with a steady stream of repository invitations: teammates adding you to new repos, bots inviting you to CI/automation repos, org-wide onboarding flows, and occasionally strangers or compromised accounts trying their luck.
The tempting fix is a cron job that blanket-accepts everything. Don't do that. Accepting an invitation from someone you don't know:
- Adds a repository you didn't choose into your GitHub account, which then shows up in your repo list, your notifications, and your contribution graph.
- Exposes you to code and CI/CD workflows you have not reviewed. GitHub Actions in a repo you've joined can run with your account's context in some flows, and a malicious repo is a real attack vector.
- Implies an association. Being a collaborator on a repo is visible, and it can look like an endorsement or affiliation you never intended.
invite-gate solves the actual problem: auto-accept invitations from a
short, explicit list of senders you already trust (teammates, your org, your
own bots), and leave literally everyone else pending so a human looks at it.
There is no "accept all" mode. There never will be — see
Security notes.
Install
# From PyPI
uv tool install invite-gate
# Or straight from GitHub
uv tool install git+https://github.com/bkrabach/invite-gate
You'll also need the GitHub CLI (gh) installed
and authenticated:
gh auth login
invite-gate never asks for or stores a token itself — it shells out to
gh, which manages your credential. See
Security notes for why this matters.
Quickstart
# 1. Check gh is installed, authenticated, and correctly scoped
invite-gate doctor
# 2. Trust one or more senders (creates the allowlist if it doesn't exist yet)
invite-gate allow some-teammate some-org some-app[bot]
# 3. See what's pending right now
invite-gate status
# 4. Run the gate: auto-accept anything from an allowlisted sender,
# leave the rest pending
invite-gate run
# 5. Wire it up to run periodically
invite-gate install-timer
Commands
invite-gate status # human-readable: pending invites split into "would auto-accept" and "needs your review"
invite-gate run [--dry-run] # poll; accept allowlisted; leave rest pending; refresh cache; this is the timer entrypoint
invite-gate motd # ultra-concise cached summary for shell startup; silent+exit 0 when nothing pending
invite-gate accept <ID>... # accept specific invitation id(s)
invite-gate decline <ID>... # decline (DELETE) specific invitation id(s)
invite-gate allow <LOGIN>... # append login(s) to allowlist (idempotent); then accept any pending from them; report what it did
invite-gate list # print current allowlist
invite-gate doctor # check gh presence/auth/scope, config, state dir, cache age -- reports every check
invite-gate install-timer # print (does NOT execute) ready-to-paste systemd/launchd units for scheduling `run`
invite-gate version # print the installed version
invite-gate update [--check] # check PyPI for a newer version and upgrade in place (--check only reports)
Global flags: --json (machine-readable output for status/run),
--version, -h/--help.
status and run both make a live call to GitHub (via gh) and refresh the
local pending cache as a side effect. motd never does — see below.
motd: shell-startup summary
motd is meant to be wired into your shell's startup (.bashrc, .zshrc,
etc.) so you get a one-glance reminder of pending invitations every time you
open a terminal, without adding any latency to shell startup. To keep that
promise, motd never makes a network call — it only reads the cache
file that run and status last wrote. If that cache doesn't exist yet, it
prints nothing and exits 0. If it exists but is more than 24 hours old, it
still prints its summary, with a short note that the cache is stale.
Add a line like this to your shell profile:
invite-gate motd
Example output when there's something to review:
2 GitHub invites need review:
octocat/some-repo from octocat invite-gate accept/decline 12345
acme/widgets from acme-bot invite-gate accept/decline 12346
invite-gate allow <sender> to auto-accept future invites from a sender
Output is capped at 10 rows (… and N more (invite-gate status) beyond
that), uses no color by default, and only uses color when stdout is a TTY
and NO_COLOR is unset.
Config format
The allowlist is a plain text file, one GitHub login per line:
# invite-gate allowlist
some-teammate
some-org
some-app[bot] # bot logins keep their brackets -- they're part of the login
Rules:
#starts a comment; trailing comments (login # note) are fine.- Blank lines are ignored.
- Leading/trailing whitespace is stripped.
- Matching is case-insensitive (GitHub logins are case-insensitive).
- No wildcards. A literal
*in the file is treated as a literal, unmatchable login andinvite-gateprints a warning explaining why — see Security notes.
Running invite-gate allow <login> creates the file (with a helpful
commented template) if it doesn't exist yet, and is idempotent — allowing an
already-allowed login is a no-op for the file, though it still checks for
and accepts any pending invitation from that sender.
A real-name-free example is shipped as
allowlist.example.txt.
File locations (XDG)
invite-gate never hardcodes a home directory; it always resolves paths
through the XDG base directory variables (falling back to their documented
defaults):
| What | Path |
|---|---|
| Allowlist | ${XDG_CONFIG_HOME:-~/.config}/invite-gate/allowlist.txt |
Pending cache (used by motd) |
${XDG_STATE_HOME:-~/.local/state}/invite-gate/pending.json |
| Activity log | ${XDG_STATE_HOME:-~/.local/state}/invite-gate/activity.log |
The activity log records only: timestamp, action, repo full name, sender login, and invitation id. Nothing else, ever — see Security notes.
Scheduling
invite-gate run is the entrypoint you want on a timer. Run:
invite-gate install-timer
This prints (it does not execute) ready-to-paste unit files: a systemd
user service + timer for Linux, and a launchd plist for macOS. It detects
your platform and shows the relevant one first, followed by the exact enable
commands. Review the printed units, adjust the path to the invite-gate
binary if needed, and install them yourself.
By default the timer polls every 15 minutes. Override it with --interval:
invite-gate install-timer --interval 5min
invite-gate install-timer --interval 1h
--interval takes a number plus a required unit -- s/sec/secs/second/
seconds, m/min/mins/minute/minutes, h/hr/hrs/hour/hours,
or d/day/days (case-insensitive, decimals allowed, e.g. 1.5h). A bare
number with no unit is rejected rather than guessed. The minimum is 60
seconds: invite-gate run makes a GitHub API call every time it fires, and
polling faster than once a minute risks tripping GitHub's secondary rate
limits for no benefit. There's no maximum, but GitHub collaborator
invitations expire after 7 days, so an interval measured in days risks
missing them -- keep it well under a day.
Security notes
This tool exists specifically to be conservative about what it auto-actions. The following are load-bearing, not incidental:
-
invite-gatenever handles a GitHub token. Every GitHub call goes through theghCLI (gh api ...). The tool never readsGITHUB_TOKEN/GH_TOKENfrom the environment, never accepts a token as a flag, and never writes one anywhere. Before invokinggh, it explicitly stripsGITHUB_TOKENandGH_TOKENfrom the subprocess environment, soghalways uses its own stored credential (~/.config/gh/hosts.yml) rather than an ambient token that might belong to a different identity. Ifghis missing or unauthenticated,invite-gatefails loudly with an actionable message — it never degrades quietly. -
No wildcard / accept-all, ever. There is no
*support, no--allflag, and no config option that means "accept from everyone." If you put a literal*in your allowlist file,invite-gatetreats it as a literal login string — which no real GitHub account can have — and prints a warning telling you why wildcards are deliberately unsupported. This is intentional and will not change: the entire point of the tool is precise, reviewed trust, not convenience at the cost of blanket acceptance. -
A missing or empty allowlist is not permission to act.
invite-gate runrefuses to accept anything and exits non-zero with a clear message if the allowlist doesn't exist or has no entries. Read-only commands (status,doctor,list) still work fine with no config at all. -
Non-allowlisted invitations are never auto-actioned — not accepted, not declined. They're recorded and surfaced for you to decide on, via
statusormotd. -
motdnever makes a network call. It only reads the cache file written byrun/status, specifically so it's safe and fast to put in your shell startup. Missing cache -> silent, exit 0. Stale cache (>24h) -> still prints, with a short staleness note. -
Nothing sensitive is ever logged or printed. The activity log records only timestamp, action, repo full name, sender login, and invitation id.
-
All writes go to XDG paths. No home directory is ever hardcoded; see File locations.
-
Any API failure exits non-zero. There is no degraded mode, no silent fallback, and no retry-forever loop. If
ghfails, you find out.
Why repo scope?
invite-gate needs to read and act on /user/repository_invitations, which
requires the OAuth repo scope on your gh credential. repo is a broad
scope — it grants full read/write access to your repositories, not just
invitations — because GitHub doesn't expose a narrower scope for the
invitations endpoints. There isn't a way around this without GitHub adding a
finer-grained scope; invite-gate doesn't request anything beyond what gh
itself already asks for when you run gh auth login. invite-gate doctor
checks for this scope explicitly and tells you how to fix it if it's
missing.
Development
git clone https://github.com/bkrabach/invite-gate
cd invite-gate
uv pip install -e ".[dev]"
python -m pytest
All tests mock subprocess calls to gh — the test suite never touches the
network.
License
MIT © 2026 Brian Krabach. See LICENSE.
Metadata
Release files for invite-gate 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| invite_gate-0.3.0.tar.gz | 33.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| invite_gate-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 59.5 kB
Release files / invite_gate-0.3.0.tar.gz
| Download URL | invite_gate-0.3.0.tar.gz |
|---|---|
| Size | 33.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c2a6a7e7173d547f757c493fcc9a21b029f55ba6f463e749e947a8a1fd171118
|
|
BLAKE2b-256 checksum How to use checksums |
972a5068c35a70680706767ae2a263e9ea357f65cd75753674676543315d3102
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.
Transparency logRelease files / invite_gate-0.3.0-py3-none-any.whl
| Download URL | invite_gate-0.3.0-py3-none-any.whl |
|---|---|
| Size | 25.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
658d629dbb46c97bf105bb0db47ea1e2f4f92cf6f6d492556281ec52e87cf553
|
|
BLAKE2b-256 checksum How to use checksums |
963ad7a35fc8b4610336c9ff10a2df9a6918bff002bcc1710fb81a9c3055e38b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.
Transparency logSecurity for invite-gate 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for invite_gate-0.3.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.