Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 11 additions & 6 deletions descriptions/edges/GH_CanAccess.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,33 @@

## General Information

The non-traversable GH_CanAccess edge indicates that a personal access token, app installation, or deploy key has been granted access to a repository or organization. This edge represents the scope of access granted to a non-human credential rather than a direct attack path, providing visibility into which repositories are reachable through that credential. It is non-traversable because credential access does not transitively extend to other principals.
The non-traversable GH_CanAccess edge indicates that a personal access token, app installation, or deploy key has been granted access to a repository, reusable scope, or organization. All-repository app installations and PATs point to an organization-scoped GH_Scope with `scope_type=repository` instead of repeating an edge to every repository. This edge represents access scope rather than a direct attack path.

## Edge Schema

| Source | Destination | Traversable |
| --- | --- | --- |
| `GH_AppInstallation` | `GH_Repository` | `false` |
| `GH_AppInstallation` | `GH_Scope` | `false` |
| `GH_DeployKey` | `GH_Repository` | `false` |
| `GH_PersonalAccessToken` | `GH_Organization` | `false` |
| `GH_PersonalAccessToken` | `GH_Repository` | `false` |
| `GH_PersonalAccessToken` | `GH_Scope` | `false` |

## Diagram

```mermaid
graph LR
n0["GH_AppInstallation"]
n1["GH_Repository"]
n2["GH_DeployKey"]
n3["GH_PersonalAccessToken"]
n4["GH_Organization"]
n2["GH_Scope"]
n3["GH_DeployKey"]
n4["GH_PersonalAccessToken"]
n5["GH_Organization"]
n0 -.->|GH_CanAccess| n1
n2 -.->|GH_CanAccess| n1
n3 -.->|GH_CanAccess| n4
n0 -.->|GH_CanAccess| n2
n3 -.->|GH_CanAccess| n1
n4 -.->|GH_CanAccess| n5
n4 -.->|GH_CanAccess| n1
n4 -.->|GH_CanAccess| n2
```
9 changes: 7 additions & 2 deletions descriptions/edges/GH_CanReadSecret.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@

## General Information

Org role can read an organization secret by creating a repository in scope.
Org role can read organization secrets by creating a repository in the matching organization-secret scope. The collector emits this relationship from the role to a reusable `GH_Scope`; `GH_ScopedTo` expands the scope to its `all` or `private_or_internal` organization secrets. Selected secrets do not receive this derived capability because creating a repository does not add that repository to an arbitrary selected set.

## Edge Schema

| Source | Destination | Traversable |
| --- | --- | --- |
| `GH_OrgRole` | `GH_OrgSecret` | `true` |
| `GH_OrgRole` | `GH_Scope` | `true` |

`GH_OrgRole` to `GH_OrgSecret` remains an accepted legacy schema endpoint. Current OpenHound collections emit `GH_OrgRole` to `GH_Scope` instead.

## Diagram

```mermaid
graph LR
n0["GH_OrgRole"]
n1["GH_OrgSecret"]
n1["GH_Scope"]
n2["GH_OrgSecret"]
n0 -->|GH_CanReadSecret| n1
n1 -->|GH_ScopedTo| n2
```
2 changes: 1 addition & 1 deletion descriptions/edges/GH_CanUseRunner.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

For runner-group-backed access, the traversable GH_CanUseRunner edge is a computed edge representing that a repository or branch can dispatch workflows to a self-hosted runner execution surface under the modeled runner-group controls.

The collector derives this edge from GH_IsEligibleFor rather than directly from repository visibility. It emits GH_CanUseRunner only when the repository is within the runner group's repository-access scope, GitHub Actions is enabled for the repository, and `restricted_to_workflows=false` on the organization-facing runner group. Inherited enterprise-backed access also requires `restricted_to_workflows=false` on the source GH_EnterpriseRunnerGroup. Every collected branch in a repository that satisfies those conditions receives the same edge so branch write paths can reach the execution surface.
The collector derives this edge from the same underlying eligibility policy represented by GH_IsEligibleFor. It emits GH_CanUseRunner only when the repository is within the runner group's repository-access scope, GitHub Actions is enabled for the repository, and `restricted_to_workflows=false` on the organization-facing runner group. Inherited enterprise-backed access also requires `restricted_to_workflows=false` on the source GH_EnterpriseRunnerGroup. Every collected branch in a repository that satisfies those conditions receives the same edge so branch write paths can reach the execution surface.

Organization and inherited enterprise-backed access terminate at the organization-facing GH_OrgRunnerGroup, then continue through GH_HasRunner for native organization runners or through GH_InheritedFrom and GH_HasRunner for inherited enterprise runners. Repository-scoped runners currently receive GH_CanUseRunner directly from their containing repository and are not part of this runner-group traversability change.

Expand Down
47 changes: 25 additions & 22 deletions descriptions/edges/GH_Contains.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## General Information

The non-traversable GH_Contains edge represents structural containment within the GitHub resource hierarchy. The enterprise contains enterprise teams, roles, managed users, runner groups, and enterprise runners through their groups. The organization serves as a top-level container for users, teams, repositories, roles, secrets, app installations, personal access tokens, and organization runner groups. Native organization runner groups contain organization runners. Repositories contain branches, workflows, branch protection rules, environments, repo-level secrets and variables, and repository-scoped runners. Environments contain environment branch policies, environment-scoped secrets, and environment-scoped variables. This edge is created by the collector to establish the resource hierarchy and is not traversable because containment alone does not imply privilege escalation.
The non-traversable GH_Contains edge represents structural containment within the GitHub resource hierarchy. The enterprise contains enterprise teams, roles, managed users, runner groups, and enterprise runners through their groups. The organization serves as a top-level container for users, teams, repositories, roles, scopes, secrets, app installations, personal access tokens, and organization runner groups. Native organization runner groups contain organization runners. Repositories contain branches, workflows, branch protection rules, environments, repo-level secrets and variables, and repository-scoped runners. Environments contain environment branch policies, environment-scoped secrets, and environment-scoped variables. This edge is created by the collector to establish the resource hierarchy and is not traversable because containment alone does not imply privilege escalation.

## Edge Schema

Expand All @@ -24,6 +24,7 @@ The non-traversable GH_Contains edge represents structural containment within th
| `GH_Organization` | `GH_OrgVariable` | `false` |
| `GH_Organization` | `GH_PersonalAccessToken` | `false` |
| `GH_Organization` | `GH_PersonalAccessTokenRequest` | `false` |
| `GH_Organization` | `GH_Scope` | `false` |
| `GH_Organization` | `GH_SecretScanningAlert` | `false` |
| `GH_Repository` | `GH_Branch` | `false` |
| `GH_Repository` | `GH_BranchProtectionRule` | `false` |
Expand Down Expand Up @@ -59,17 +60,18 @@ graph LR
n15["GH_OrgVariable"]
n16["GH_PersonalAccessToken"]
n17["GH_PersonalAccessTokenRequest"]
n18["GH_SecretScanningAlert"]
n19["GH_Repository"]
n20["GH_Branch"]
n21["GH_BranchProtectionRule"]
n22["GH_DeployKey"]
n23["GH_RepoRunner"]
n24["GH_RepoSecret"]
n25["GH_RepoVariable"]
n26["GH_Workflow"]
n27["GH_WorkflowJob"]
n28["GH_WorkflowStep"]
n18["GH_Scope"]
n19["GH_SecretScanningAlert"]
n20["GH_Repository"]
n21["GH_Branch"]
n22["GH_BranchProtectionRule"]
n23["GH_DeployKey"]
n24["GH_RepoRunner"]
n25["GH_RepoSecret"]
n26["GH_RepoVariable"]
n27["GH_Workflow"]
n28["GH_WorkflowJob"]
n29["GH_WorkflowStep"]
n0 -.->|GH_Contains| n1
n0 -.->|GH_Contains| n2
n0 -.->|GH_Contains| n3
Expand All @@ -87,15 +89,16 @@ graph LR
n4 -.->|GH_Contains| n16
n4 -.->|GH_Contains| n17
n4 -.->|GH_Contains| n18
n19 -.->|GH_Contains| n20
n19 -.->|GH_Contains| n21
n19 -.->|GH_Contains| n22
n19 -.->|GH_Contains| n6
n19 -.->|GH_Contains| n23
n19 -.->|GH_Contains| n24
n19 -.->|GH_Contains| n25
n19 -.->|GH_Contains| n18
n19 -.->|GH_Contains| n26
n26 -.->|GH_Contains| n27
n4 -.->|GH_Contains| n19
n20 -.->|GH_Contains| n21
n20 -.->|GH_Contains| n22
n20 -.->|GH_Contains| n23
n20 -.->|GH_Contains| n6
n20 -.->|GH_Contains| n24
n20 -.->|GH_Contains| n25
n20 -.->|GH_Contains| n26
n20 -.->|GH_Contains| n19
n20 -.->|GH_Contains| n27
n27 -.->|GH_Contains| n28
n28 -.->|GH_Contains| n29
```
5 changes: 4 additions & 1 deletion descriptions/edges/GH_HasSecret.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## General Information

The traversable GH_HasSecret edge represents the relationship between a repository or environment and the secrets accessible within that context. This edge shows which secrets are available in which scopes. Repositories can have access to both organization-level secrets (scoped to selected repositories) and repository-level secrets, while environments expose their own environment-scoped secrets to jobs that target them. This edge is traversable because any principal that can execute a workflow in the relevant context may be able to exfiltrate secret values at runtime, making this a meaningful link in attack path analysis.
The traversable GH_HasSecret edge represents the relationship between a repository or environment and the secrets accessible within that context. Canonical all/private organization-secret availability is factored through a GH_Scope; selected organization secrets and repository/environment secrets remain direct. This edge is traversable because any principal that can execute a workflow in the relevant context may be able to exfiltrate secret values at runtime, making this a meaningful link in attack path analysis.

## Edge Schema

Expand All @@ -11,6 +11,7 @@ The traversable GH_HasSecret edge represents the relationship between a reposito
| `GH_Environment` | `GH_EnvironmentSecret` | `true` |
| `GH_Repository` | `GH_OrgSecret` | `true` |
| `GH_Repository` | `GH_RepoSecret` | `true` |
| `GH_Repository` | `GH_Scope` | `true` |

## Diagram

Expand All @@ -21,7 +22,9 @@ graph LR
n2["GH_Repository"]
n3["GH_OrgSecret"]
n4["GH_RepoSecret"]
n5["GH_Scope"]
n0 -->|GH_HasSecret| n1
n2 -->|GH_HasSecret| n3
n2 -->|GH_HasSecret| n4
n2 -->|GH_HasSecret| n5
```
5 changes: 4 additions & 1 deletion descriptions/edges/GH_HasVariable.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## General Information

The traversable GH_HasVariable edge represents the relationship between a repository or environment and the variables accessible within that context. This edge shows which variables are available in which scopes. Repositories can have access to both organization-level variables (scoped by visibility to all, private, or selected repositories) and repository-level variables defined directly on the repo, while environments expose their own environment-scoped variables to jobs that target them. This edge is traversable because any principal that can execute a workflow in the relevant context may be able to read variable values at runtime, and variables may contain configuration data useful for lateral movement such as deployment URLs, service names, or environment identifiers.
The traversable GH_HasVariable edge represents the relationship between a repository or environment and the variables accessible within that context. Canonical all/private organization-variable availability is factored through a GH_Scope; selected organization variables and repository/environment variables remain direct.

## Edge Schema

Expand All @@ -11,6 +11,7 @@ The traversable GH_HasVariable edge represents the relationship between a reposi
| `GH_Environment` | `GH_EnvironmentVariable` | `true` |
| `GH_Repository` | `GH_OrgVariable` | `true` |
| `GH_Repository` | `GH_RepoVariable` | `true` |
| `GH_Repository` | `GH_Scope` | `true` |

## Diagram

Expand All @@ -21,7 +22,9 @@ graph LR
n2["GH_Repository"]
n3["GH_OrgVariable"]
n4["GH_RepoVariable"]
n5["GH_Scope"]
n0 -->|GH_HasVariable| n1
n2 -->|GH_HasVariable| n3
n2 -->|GH_HasVariable| n4
n2 -->|GH_HasVariable| n5
```
5 changes: 4 additions & 1 deletion descriptions/edges/GH_IsEligibleFor.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## General Information

The non-traversable GH_IsEligibleFor edge represents that a repository is within the repository-access scope of an organization runner group.
The non-traversable GH_IsEligibleFor edge represents that a repository is within the repository-access policy of an organization runner group. Canonical `all` and `private_or_internal` eligibility is factored through a GH_Scope; arbitrary selected eligibility remains a direct relationship.

For runner groups, this edge evaluates the group's `visibility`, selected repository assignments, and `allows_public_repositories` setting. It does not prove that workflows in the repository can dispatch to the group's runners, because GitHub Actions may be disabled for the repository or the runner group may be restricted to selected workflows.

Expand All @@ -11,12 +11,15 @@ For runner groups, this edge evaluates the group's `visibility`, selected reposi
| Source | Destination | Traversable |
| --- | --- | --- |
| `GH_Repository` | `GH_OrgRunnerGroup` | `false` |
| `GH_Repository` | `GH_Scope` | `false` |

## Diagram

```mermaid
graph LR
n0["GH_Repository"]
n1["GH_OrgRunnerGroup"]
n2["GH_Scope"]
n0 -.->|GH_IsEligibleFor| n1
n0 -.->|GH_IsEligibleFor| n2
```
20 changes: 20 additions & 0 deletions descriptions/edges/GH_RequestsAccessTo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# GH_RequestsAccessTo

## General Information

The non-traversable GH_RequestsAccessTo edge records access sought by a pending fine-grained personal access token request. It is distinct from GH_CanAccess, which represents access GitHub has already granted. An all-repository request targets the organization's reusable repository/all GH_Scope.

## Edge Schema

| Source | Destination | Traversable |
| --- | --- | --- |
| `GH_PersonalAccessTokenRequest` | `GH_Scope` | `false` |

## Diagram

```mermaid
graph LR
n0["GH_PersonalAccessTokenRequest"]
n1["GH_Scope"]
n0 -.->|GH_RequestsAccessTo| n1
```
29 changes: 29 additions & 0 deletions descriptions/edges/GH_ScopedTo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# GH_ScopedTo

## General Information

The traversable GH_ScopedTo edge enumerates an asset included in a reusable GH_Scope. It carries an incoming capability from a target scope to each asset in the represented set. Repository scopes contain collected repositories, runner-group scopes contain organization-facing runner groups sharing repository eligibility, and organization-secret/variable scopes contain assets sharing repository availability.

## Edge Schema

| Source | Destination | Traversable |
| --- | --- | --- |
| `GH_Scope` | `GH_OrgRunnerGroup` | `true` |
| `GH_Scope` | `GH_OrgSecret` | `true` |
| `GH_Scope` | `GH_OrgVariable` | `true` |
| `GH_Scope` | `GH_Repository` | `true` |

## Diagram

```mermaid
graph LR
n0["GH_Scope"]
n1["GH_OrgRunnerGroup"]
n2["GH_OrgSecret"]
n3["GH_OrgVariable"]
n4["GH_Repository"]
n0 -->|GH_ScopedTo| n1
n0 -->|GH_ScopedTo| n2
n0 -->|GH_ScopedTo| n3
n0 -->|GH_ScopedTo| n4
```
8 changes: 5 additions & 3 deletions descriptions/nodes/GH_AppInstallation.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Represents a GitHub App installed on an organization. App installations have spe

Unlike fine-grained personal access tokens, GitHub does not expose separate organization and repository permission buckets for app installations, so this property remains a single flat permission list.

Each installation is linked to its parent GH_App via a GH_InstalledAs edge. For installations with `repository_selection` set to `all`, GH_CanAccess edges are created to every repository in the organization. For installations with `repository_selection` set to `selected`, repository-level edges cannot be enumerated with a PAT (requires app installation token authentication).
Each installation is linked to its parent GH_App via a GH_InstalledAs edge. Installations with `repository_selection` set to `all` use one GH_CanAccess edge to the organization's repository/all GH_Scope; GH_ScopedTo edges enumerate the repositories in that reusable scope. For installations with `repository_selection` set to `selected`, repository-level edges cannot be enumerated with a PAT (requires app installation token authentication).

## Properties

Expand Down Expand Up @@ -42,8 +42,10 @@ graph LR
n0["GH_App"]
n1["GH_AppInstallation"]
n2["GH_Repository"]
n3["GH_Organization"]
n3["GH_Scope"]
n4["GH_Organization"]
n0 -->|GH_InstalledAs| n1
n1 -.->|GH_CanAccess| n2
n3 -.->|GH_Contains| n1
n1 -.->|GH_CanAccess| n3
n4 -.->|GH_Contains| n1
```
1 change: 1 addition & 0 deletions descriptions/nodes/GH_Environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,6 @@ graph LR
n7 -->|GH_CanDeployToEnvironment| n1
n8 -.->|GH_ApprovesDeploymentTo| n1
n8 -->|GH_CanDeployToEnvironment| n1
n9 -->|GH_CanRequestOIDCTokenFor| n1
n9 -.->|GH_DeploysTo| n1
```
14 changes: 8 additions & 6 deletions descriptions/nodes/GH_OrgRole.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ graph LR
n1["GH_OrgRunnerGroup"]
n2["GH_OrgSecret"]
n3["GH_Organization"]
n4["GH_SecretScanningAlert"]
n5["GH_Team"]
n6["GH_User"]
n4["GH_Scope"]
n5["GH_SecretScanningAlert"]
n6["GH_Team"]
n7["GH_User"]
n0 -->|GH_HasBaseRole| n0
n0 -->|GH_CanCreateRepositoryWithRunnerAccess| n1
n0 -->|GH_CanReadSecret| n2
n0 -.->|GH_AddCollaborator| n3
n0 -.->|GH_CanCreateInternalRepositories| n3
n0 -.->|GH_CanCreatePrivateRepositories| n3
Expand All @@ -45,8 +45,10 @@ graph LR
n0 -.->|GH_ResolveSecretScanningAlerts| n3
n0 -.->|GH_TransferRepository| n3
n0 -.->|GH_ViewSecretScanningAlerts| n3
n0 -->|GH_CanReadSecretScanningAlert| n4
n0 -->|GH_CanReadSecret| n4
n4 -->|GH_ScopedTo| n2
n0 -->|GH_CanReadSecretScanningAlert| n5
n3 -.->|GH_Contains| n0
n5 -->|GH_HasRole| n0
n6 -->|GH_HasRole| n0
n7 -->|GH_HasRole| n0
```
4 changes: 3 additions & 1 deletion descriptions/nodes/GH_OrgRunnerGroup.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Represents a self-hosted runner group visible within a GitHub organization. Organization runner groups may either be native to the organization or inherited from an enterprise runner group.

Native organization runner groups contain GH_OrgRunner nodes directly. Direct memberships also emit GH_HasRunner to represent the traversable capability hop from the group to its runners. Inherited organization runner groups do not directly contain organization runners; instead, they link to the source GH_EnterpriseRunnerGroup through GH_InheritedFrom and gain access to the enterprise runners contained there. GH_IsEligibleFor edges from repositories describe repository access policy scope, while GH_CanUseRunner edges identify repositories and branches that can dispatch workflows to the group under the collected Actions and workflow-restriction settings.
Native organization runner groups contain GH_OrgRunner nodes directly. Direct memberships also emit GH_HasRunner to represent the traversable capability hop from the group to its runners. Inherited organization runner groups do not directly contain organization runners; instead, they link to the source GH_EnterpriseRunnerGroup through GH_InheritedFrom and gain access to the enterprise runners contained there. GH_IsEligibleFor uses shared runner-group scopes for canonical policies and direct edges for selected policies, while GH_CanUseRunner identifies repositories and branches that can dispatch workflows to the group under collected Actions and workflow-restriction settings.

## Properties

Expand Down Expand Up @@ -42,6 +42,7 @@ graph LR
n4["GH_OrgRunner"]
n5["GH_Organization"]
n6["GH_Repository"]
n7["GH_Scope"]
n0 -->|GH_CanUseRunner| n1
n2 -->|GH_CanCreateRepositoryWithRunnerAccess| n1
n1 -->|GH_InheritedFrom| n3
Expand All @@ -50,4 +51,5 @@ graph LR
n5 -.->|GH_Contains| n1
n6 -->|GH_CanUseRunner| n1
n6 -.->|GH_IsEligibleFor| n1
n7 -->|GH_ScopedTo| n1
```
Loading
Loading