Risky third-party apps installed on the organization

ID

installed_apps

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

low

Family

SCM

Tags

ASVS50:v15.2.1, cicd-sec-08, cicd-security, reachable, security, spvs10-v1.1.3, supply-chain

Description

Are the third-party apps installed on the organization governed and least-privilege?

Every installed app (a GitHub App, an OAuth app, an Azure DevOps extension, a platform integration) is an external party you have handed a key to your source code and pipelines. This is one of the least-watched parts of supply-chain posture (OWASP CI/CD Top-10 CICD-SEC-8 — Ungoverned Usage of 3rd-Party Services). The check enumerates the apps installed on the organization and reports one finding per risky app, so the org can review them one at a time. Least-privilege, scoped, active apps produce no finding.

An app is reported when it carries at least one of these risk factors:

  • all-repo-access (GitHub) — the app is installed against all repositories, not a selected subset (repository_selection = all), so its blast radius is the whole org.

  • sensitive-permissions (GitHub) / sensitive-scope (Azure DevOps) — the app is granted a write/admin permission on high-impact resources (repository contents, administration, secrets, Actions/workflows, …), or, on Azure, an OAuth scope that grants mutation/admin (e.g. vso.code_write, vso.serviceendpoint_manage, vso.build_execute).

  • stale — the app has not been updated for a long time (default > 365 days), a proxy for an unused, forgotten install.

  • suspended (GitHub) / disabled (Azure DevOps) — the app is suspended/disabled but still installed (a leftover that should be removed).

On Azure DevOps, extensions are org-wide by nature (there is no per-repository selection), so the all-repo-access amplifier does not apply and a sensitive-scope extension tops out at high rather than critical. Microsoft built-in / trusted extensions are first-party and are not reported.
This detector is complementary to app_permissions. app_permissions checks an installed app’s granted permissions against a configured allow-policy; installed_apps is a coarser inventory/posture check (breadth of access, staleness, suspended leftovers). The two are intentionally not merged so their findings and severities stay distinct.

Severity — evaluated per app

The reported severity is computed from each app’s configuration, relative to the detector’s base severity (high):

Severity When (the app’s configuration)

critical (base +1)

Org-wide repository access and a sensitive write/admin permission — broad blast radius × powerful access.

high (base)

One strong factor alone: org-wide repository access, or a sensitive write/admin permission on selected repos.

low (base -1)

A weak factor: the app is stale / unused.

info (base -2)

Risk unclear: a suspended-but-still-installed leftover.

Platform support

Listing installed apps is an organization / account-level operation and requires an org-admin-scoped token (see Token scope below). The primitive is platform-specific:

Platform Inventory primitive Status

GitHub / GitHub Enterprise

GET /orgs/{org}/installations — installed GitHub Apps with their permissions, repository selection, suspended state and timestamps

Supported

GitHub — OAuth apps

Org-level OAuth-app inventory is only exposed through the audit log (GitHub Enterprise Cloud), not via a token-readable REST list

Not supported (documented)

Azure DevOps

Installed extensions with their requested OAuth scopes and install state (extensions are org-wide, not repository-scoped; Microsoft built-in / trusted extensions are treated as first-party and skipped)

Supported

GitLab / GitLab Enterprise

No per-group/project installed-third-party-app inventory: the Applications API lists instance-wide OAuth clients (admin only), and group integrations are first-party service configs the org sets up itself — neither is a third-party grant with repository access

Not supported (documented)

Bitbucket (Cloud / Data Center)

A workspace’s installed Connect apps are shown only in the settings UI; there is no REST endpoint to list them

Not supported (documented)

Impact context

Third-party integrations are a trust extension of your organization: each holds standing credentials into your code and CI. If an integration is compromised, over-permissioned, or simply forgotten, it becomes a path straight into your source and pipelines. This is the class of weakness behind the tj-actions/changed-files cascade and the TeamPCP campaign against security vendors — a trusted third-party integration was subverted and its access became the attacker’s access across everything it could touch. Broad access × sensitive permissions × stale × unverified equals maximum blast radius with minimum oversight.

Example — FAIL
App "acme-ci"
  repository_selection: all
  permissions: { contents: write, administration: write }
  -> critical: a third party can write to EVERY repo and change org settings.
     If acme-ci is breached, the attacker inherits org-wide write.
Example — PASS
App "acme-ci"
  repository_selection: selected   (only the repos it needs)
  permissions: { contents: read, checks: write }
  active, verified publisher
  -> no finding: least privilege, minimal blast radius.

Security

Governing installed apps is basic least-privilege hygiene for the supply chain: grant each integration only the repositories and permissions it needs, remove suspended and unused installs, and review broad grants periodically. An org-wide, write/admin-capable, unmonitored integration is a single point of compromise for the entire organization.

Token scope

Enumerating installed apps requires an organization-scoped token (GitHub admin:org read; Azure DevOps vso.extension — read installed extensions). When the scan token lacks that scope, the platform returns 403/401 and the detector reports nothing — it never emits a false "no apps installed". Provide an org-scoped token to get full coverage.

Mitigation / Fix

GitHub — in Organization settings → Third-party access → GitHub Apps / OAuth app policy:

  • Restrict each installed app to selected repositories rather than all repositories.

  • Reduce granted permissions to the minimum the app needs (avoid administration, contents:write, secrets unless required).

  • Remove suspended installs and apps that are no longer used.

  • Require that only verified publishers' apps can be installed, and review installations periodically.

Azure DevOps — in Organization settings → Extensions:

  • Uninstall extensions that are disabled or no longer used.

  • Prefer extensions from trusted publishers, and review extensions that request write/manage OAuth scopes (vso.*_write, vso.*_manage, vso.build_execute, vso.serviceendpoint_manage).

  • Restrict who can install extensions / request them via the extension request workflow.

Configuration

To change these options you can modify SCANNER_DIR/conf/misconfigurations/installed_apps.yml. The following are the default configuration properties:

properties:
  # An app not updated for more than this many days is flagged as stale/unused (low/info).
  # "last updated" is an approximate proxy for "last used". Set to 0 to disable.
  staleAfterDays: 365
  # Permission keys treated as sensitive when granted at 'write' (an 'admin'-level grant is always
  # sensitive). Org-wide repo access AND a sensitive permission escalates the finding to critical.
  sensitivePermissions:
    - administration
    - organization_administration
    - contents
    - secrets
    - organization_secrets
    - actions
    - workflows
    - members
    - packages
    - security_events
    - deployments