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 |
|
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 |
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.
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.
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,secretsunless 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