Back to Articles

GitOops: Mapping GitHub CI/CD Attack Paths Like Active Directory

[ View on GitHub ]

GitOops: Mapping GitHub CI/CD Attack Paths Like Active Directory

Hook

Your GitHub organization probably has dozens of ways for a developer with read access to trigger workflows that deploy to production. GitOops will show you every single one of them in a graph database.

Context

Red teamers have spent years using Bloodhound to visualize Active Directory attack paths—those multi-hop chains where a low-privilege user can escalate to Domain Admin through nested group memberships, ACLs, and Kerberos delegation. But as organizations shifted to cloud-native infrastructure, a new attack surface emerged that traditional security tools completely missed: CI/CD pipelines.

GitHub Actions workflows are effectively serverless functions with access to secrets, cloud credentials, and deployment permissions. A developer who can trigger a workflow that has access to production AWS credentials essentially has production access, even if their user account doesn't. Manual auditing of these relationships is impossible at scale—a 200-repository organization with dozens of workflows, teams, and environment configurations creates thousands of potential attack paths. GitOops applies the Bloodhound methodology to this problem, treating GitHub organizations as graphs where nodes are users, repositories, workflows, and secrets, and edges represent permissions and access relationships.

Technical Insight

GitOops is built in Go and structured around two core operations: enumeration and graph construction. The enumeration phase uses GitHub's REST and GraphQL APIs to collect organization structure, extracting users, teams, repositories, branch protection rules, GitHub Actions workflows, environment variables, and secrets metadata. The tool authenticates with a personal access token that needs repo, read:org, and read:user scopes at minimum.

The collected data gets transformed into nodes and relationships for a Bolt-compatible graph database like Neo4j. Here's how the graph model works: Users and Teams become identity nodes, Repositories become asset nodes, Workflows become execution context nodes, and Secrets/Variables become credential nodes. The edges capture relationships like MemberOf, CanPush, CanTrigger, HasAccess, and UsesSecret.

A typical Neo4j query after GitOops ingestion might look like this:

// Find all paths where a user can trigger a workflow with production secrets
MATCH path = (u:User)-[:MemberOf*0..3]->(t:Team)-[:CanPush]->(r:Repository)
             -[:Contains]->(w:Workflow)-[:UsesSecret]->(s:Secret)
WHERE s.environment = 'production'
RETURN path

This query reveals multi-hop scenarios invisible in GitHub's UI: a user belongs to a team, that team can push to a repository, that repository contains a workflow, and that workflow accesses production secrets. Each hop is a potential escalation point.

The workflow parsing logic is particularly sophisticated. GitOops doesn't just enumerate workflow files—it parses the YAML to understand triggers (on: push, on: pull_request, workflow_dispatch), identifies which branches can trigger execution, and extracts environment variable and secrets references from the env: blocks and ${{ secrets.NAME }} syntax. This creates edges like:

(workflow:Workflow {name: 'deploy.yml', trigger: 'push'})
  -[:UsesSecret]->(secret:Secret {name: 'AWS_ACCESS_KEY'})
  -[:ScopedTo]->(env:Environment {name: 'production'})

The tool also models GitHub's environment protection rules. If a repository has an environment called production with required reviewers, GitOops captures both the workflow's access to that environment AND the users who can approve deployments, creating edges that show who can authorize production access even if they can't directly trigger workflows.

From a defensive perspective, you can query for overprivileged access patterns:

// Find workflows that can be triggered by pull_request events from forks
MATCH (w:Workflow)-[:UsesSecret]->(s:Secret)
WHERE w.trigger CONTAINS 'pull_request' 
  AND NOT w.trigger CONTAINS 'pull_request_target'
RETURN w.repository, w.name, collect(s.name) as exposed_secrets

This specific query identifies a critical vulnerability: workflows triggered by pull_request from forks can't access secrets by default, but misconfigurations happen, and this query finds them.

The enumeration process is rate-limit aware, respecting GitHub's API limits with configurable throttling. For large organizations with thousands of repositories, GitOops supports incremental updates—only fetching changed resources rather than re-enumerating everything, which is essential for organizations that want to run this regularly as part of security monitoring.

Gotcha

GitOops requires substantial GitHub permissions, and running it against a large organization will consume significant API quota—potentially thousands of requests. This isn't a tool you can run stealthily; if you're on a red team engagement, the security team will likely notice the API activity patterns in their audit logs. GitHub Enterprise Cloud with SAML SSO or advanced security features may also block automated enumeration tools.

The graph model is powerful but incomplete. GitOops doesn't capture every attack vector in a modern DevOps environment: it won't map relationships between GitHub and external systems like AWS IAM roles, Kubernetes clusters, or third-party CI/CD tools like Jenkins that might be triggered by GitHub webhooks. You're getting a GitHub-centric view, which means cross-platform attack paths require manual investigation. The tool also doesn't analyze workflow code for vulnerabilities—it maps access paths, not code execution flaws. A workflow that's vulnerable to command injection won't be flagged unless you manually review the workflow YAML. Finally, the graph is a point-in-time snapshot. GitHub permissions change constantly as developers join teams, repositories get created, and workflows are modified. Running GitOops once gives you a security assessment; continuous monitoring requires scheduled execution and delta analysis, which the tool doesn't handle automatically.

Verdict

Use GitOops if you're conducting security assessments of GitHub organizations with complex CI/CD pipelines, especially during red team engagements or security audits where you need to demonstrate non-obvious privilege escalation paths. It's essential for organizations with hundreds of repositories where manual permission auditing is impossible, and invaluable for demonstrating to leadership why seemingly low-risk permissions (like read access to a public repository) can chain into production compromise. Skip it if your organization uses GitLab, Bitbucket, or other Git hosting platforms, since the tool is GitHub-specific. Also skip it if you need real-time alerting on permission changes—this is a reconnaissance tool, not a SIEM. For continuous monitoring, you'd need to build orchestration around GitOops yourself or use broader infrastructure mapping tools like Netflix's Cartography, though you'll lose the CI/CD-specific attack path intelligence.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/cybersecurity/ovotech-gitoops.svg)](https://starlog.is/api/badge-click/cybersecurity/ovotech-gitoops)