Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5894eae
feat: add app installation plugin for managing GitHub App repo access
decyjphr Jun 25, 2026
a51ab87
fix: use enterprise installation token for app installation API calls
decyjphr Jun 25, 2026
8fee0dd
perf: cache enterprise installation ID to avoid repeated lookups
decyjphr Jun 25, 2026
d0be177
Wire syncAppInstallations into syncSelectedRepos for delta processing
decyjphr Jun 26, 2026
24fb684
Compute repository_unselection by diffing previous vs current configs
decyjphr Jun 26, 2026
4b2cec7
Fix delta bugs: org-all precedence + repo config path; update schema
decyjphr Jun 26, 2026
b731d54
Address app installation review: remove bad drift handler, fix orderi…
decyjphr Jun 26, 2026
9a25bdc
Skip redundant app installation churn for unchanged delta
decyjphr Jun 26, 2026
eacef7f
Use correct Enterprise Org Installations API (names, toggle, PATCH ad…
decyjphr Jun 26, 2026
c55645e
Add verification for app-installations plugin functionality
decyjphr Jul 6, 2026
c43625a
Refactor enterprise installation handling: add getEnterpriseAppClient…
decyjphr Jul 6, 2026
1364b09
Add ADR 0001: app installation management plugin
decyjphr Jul 6, 2026
99cd47e
Enhance app installations plugin: add subject handling and improve re…
decyjphr Jul 6, 2026
47a8d11
ADR updates
decyjphr Jul 6, 2026
7832694
Refactor enterprise installation handling: update caching logic and i…
decyjphr Jul 7, 2026
366522b
Add functions to list installations and find enterprise installation
decyjphr Jul 7, 2026
b4f00b2
Potential fix for pull request finding
decyjphr Jul 7, 2026
b6b87a0
Resolved CCR PR review
decyjphr Jul 7, 2026
d22a410
Removed repository_selection as a field in the settings.yml
decyjphr Jul 7, 2026
31652a7
Enhance app_installations handling: add detection for layered configu…
decyjphr Jul 7, 2026
1b63531
syncSelectedRepos() repoConfigs often contains only the *last* proces…
decyjphr Jul 7, 2026
26b5010
Handle authentication errors by clearing cached enterprise installati…
decyjphr Jul 7, 2026
60c4b7a
Map and Array mismatch will cause delta-based sync to skip real add/r…
decyjphr Jul 7, 2026
19d911b
Fix error that makes the app_installations phase silently no-op (and …
decyjphr Jul 7, 2026
97e2b81
protective to change to lowercase
decyjphr Jul 7, 2026
4e8240c
cleaned up test
decyjphr Jul 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 102 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,8 @@ plugins for a given scope. Each entry is either:

Valid plugin names: `repository`, `labels`, `collaborators`, `teams`,
`milestones`, `branches`, `autolinks`, `validator`, `rulesets`, `environments`,
`custom_properties`, `custom_repository_roles`, `variables`, `archive`.
`custom_properties`, `custom_repository_roles`, `variables`, `archive`,
`app_installations`.

#### Strip matrix (which source layers are removed before merge)

Expand Down Expand Up @@ -661,6 +662,105 @@ additive_plugins:
- collaborators
```

### App installation management (`app_installations`)

Most safe-settings plugins target a **repository**. The `app_installations`
plugin is different: its target is a **GitHub App installation**. It lets you
declaratively manage *which repositories a GitHub App can access* (the app's
`repository_selection`), using the same `org` → `suborg` → `repo` config
hierarchy you already use for repository settings.

This is useful for controlling, as code, which repos apps such as Copilot,
Dependabot, or your own internal apps are installed on across the org.

#### Prerequisites

- Safe-settings must be installed on the **enterprise** with the **Enterprise
organization installations** permission (see the
[Enterprise organization installations API](https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/organization-installations)).
Managing app installations requires an enterprise-level token; the regular
org installation token is not sufficient. If safe-settings is not installed
on the enterprise with this permission, app installation sync is reported as
an error and skipped.
- The enterprise slug is read from the webhook event payload
(`payload.enterprise.slug`); no extra environment variable is required.

#### How repository selection is resolved

The config layer where `app_installations` is declared determines which repos
are selected for the app:

| Layer | File | Repos selected for the app |
| --- | --- | --- |
| Org | `settings.yml` | All repos in the org (`repository_selection: all`) |
| Suborg | `suborgs/*.yml` | Repos matching the suborg's targeting (`suborgrepos`, `suborgteams`, `suborgproperties`) |
| Repo | `repos/<repo>.yml` | That specific repo |

> [!important]
> An app configured with `repository_selection: all` at the **org** level takes
> precedence. Suborg/repo-level selections for that same app are ignored, and
> repos are never removed from it by incremental (suborg/repo) changes — it is
> reconciled only by the full (scheduled) sync.

#### Examples

Org-level `settings.yml` — give an app access to **all** repos in the org:

```yaml
app_installations:
- app_slug: my-internal-app
repository_selection: all
```

Suborg-level `suborgs/backend.yml` — give an app access to the repos targeted
by this suborg (here, all repos with the `Team=backend` custom property):

```yaml
suborgproperties:
- Team: backend
app_installations:
- app_slug: my-internal-app
```

Repo-level `repos/my-repo.yml` — add this specific repo to the app:

```yaml
app_installations:
- app_slug: my-internal-app
```

Removing an app from a suborg/repo config (or changing the suborg's targeting)
removes the affected repos from that app on the next sync, unless another layer
still selects them.

#### Sync behavior

- **Incremental (delta) sync** runs when a `suborgs/*.yml` or `repos/*.yml`
file changes. Only the apps affected by the changed file are reconciled: the
previous version of the file is compared with the new one to compute repos to
add (`repository_selection`) and repos to remove (`repository_unselection`).
Removals are applied before additions, so a repo removed by one config and
added by another ends up present.
- **Full sync** runs on the schedule (cron), on manual sync, and when
`settings.yml` changes. It recomputes the full desired state for every managed
app across all layers and reconciles it against the live installation state.
This is the mechanism that corrects any configuration drift.
- Add/remove operations are automatically batched in chunks of 50 repos (the
API limit).

> [!note]
> Drift on managed apps is reconciled by the **full (cron) sync**, not by
> webhooks. A GitHub App only receives `installation` repository events for its
> *own* installation, so safe-settings cannot detect — via webhooks — when a
> human changes another app's repository access. Keep the scheduled sync enabled
> for timely drift correction.

#### Disabling and additive mode

`app_installations` honors both [`disable_plugins`](#disabling-plugins-disable_plugins)
and [`additive_plugins`](#additive-plugins-additive_plugins). In additive mode
the plugin only **adds** repos to installations and never removes them.

### The Settings Files

The settings files can be used to set the policies at the `org`, `suborg` or `repo` level.
Expand All @@ -680,6 +780,7 @@ The following can be configured:
- `Repository name validation` using regex pattern
- `Rulesets`
- `Environments` - wait timer, required reviewers, prevent self review, protected branches deployment branch policy, custom deployment branch policy, variables, deployment protection rules
- `App installations` - which repositories a GitHub App installation can access (see [App installation management](#app-installation-management-app_installations))

See [`docs/sample-settings/settings.yml`](docs/sample-settings/settings.yml) for a sample settings file.

Expand Down
73 changes: 39 additions & 34 deletions app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ default_events:
# the value (for example, write).
# Valid values are `read`, `write`, and `none`
default_permissions:
repository_custom_properties: write
organization_custom_properties: admin

# Workflows, workflow runs and artifacts. (needed to read environments when repo is private or internal)
# https://developer.github.com/v3/apps/permissions/#repository-permissions-for-actions
actions: read

# Manage Actions variables.
# https://docs.github.com/en/rest/actions/variables?apiVersion=2022-11-28
actions_variables: write

# Repository creation, deletion, settings, teams, and collaborators.
# https://developer.github.com/v3/apps/permissions/#permission-on-administration
administration: write
Expand All @@ -54,6 +55,10 @@ default_permissions:
# https://developer.github.com/v3/apps/permissions/#permission-on-deployments
# deployments: read

enterprise_organization_installations: write

enterprise_organization_installation_repositories: write

# Manage repository environments.
# https://developer.github.com/v3/apps/permissions/#repository-permissions-for-environments
environments: write
Expand All @@ -62,38 +67,14 @@ default_permissions:
# https://developer.github.com/v3/apps/permissions/#permission-on-issues
issues: write

# Search repositories, list collaborators, and access repository metadata.
# https://developer.github.com/v3/apps/permissions/#metadata-permissions
metadata: read

# Retrieve Pages statuses, configuration, and builds, as well as create new builds.
# https://developer.github.com/v3/apps/permissions/#permission-on-pages
# pages: read

# Pull requests and related comments, assignees, labels, milestones, and merges.
# https://developer.github.com/v3/apps/permissions/#permission-on-pull-requests
pull_requests: write

# Manage the post-receive hooks for a repository.
# https://developer.github.com/v3/apps/permissions/#permission-on-repository-hooks
# repository_hooks: read

# Manage repository projects, columns, and cards.
# https://developer.github.com/v3/apps/permissions/#permission-on-repository-projects
# repository_projects: read

# Retrieve security vulnerability alerts.
# https://developer.github.com/v4/object/repositoryvulnerabilityalert/
# vulnerability_alerts: read

# Commit statuses.
# https://developer.github.com/v3/apps/permissions/#permission-on-statuses
statuses: write

# Organization members and teams.
# https://developer.github.com/v3/apps/permissions/#permission-on-members
members: write

# Search repositories, list collaborators, and access repository metadata.
# https://developer.github.com/v3/apps/permissions/#metadata-permissions
metadata: read

# View and manage users blocked by the organization.
# https://developer.github.com/v3/apps/permissions/#permission-on-organization-user-blocking
# organization_user_blocking: read
Expand Down Expand Up @@ -122,9 +103,33 @@ default_permissions:
# https://docs.github.com/en/enterprise-cloud@latest/rest/authentication/permissions-required-for-github-apps?apiVersion=2026-03-10#organization-permissions-for-custom-repository-roles
organization_custom_roles: write

# Manage Actions variables.
# https://docs.github.com/en/rest/actions/variables?apiVersion=2022-11-28
actions_variables: write
organization_custom_properties: admin

# Retrieve Pages statuses, configuration, and builds, as well as create new builds.
# https://developer.github.com/v3/apps/permissions/#permission-on-pages
# pages: read

# Pull requests and related comments, assignees, labels, milestones, and merges.
# https://developer.github.com/v3/apps/permissions/#permission-on-pull-requests
pull_requests: write

repository_custom_properties: write

# Manage the post-receive hooks for a repository.
# https://developer.github.com/v3/apps/permissions/#permission-on-repository-hooks
# repository_hooks: read

# Manage repository projects, columns, and cards.
# https://developer.github.com/v3/apps/permissions/#permission-on-repository-projects
# repository_projects: read

# Retrieve security vulnerability alerts.
# https://developer.github.com/v4/object/repositoryvulnerabilityalert/
# vulnerability_alerts: read

# Commit statuses.
# https://developer.github.com/v3/apps/permissions/#permission-on-statuses
statuses: write


# The name of the GitHub App. Defaults to the name specified in package.json
Expand Down
Loading
Loading