DEV Community

Cover image for Check if you are breaking your admin rules in your GitHub repos
Matty Stratton
Matty Stratton

Posted on

6 3 2 2 3

Check if you are breaking your admin rules in your GitHub repos

Branch protection rules in GitHub are powerful! We can set rules that don't allow folks to commit directly to main, as well as enforce that PRs need to be reviewed and approved, and have required checks before merging.

However, often we allow admin folks on the repo to be able to bypass these restrictions (the infamous "Merge without waiting for requirements to be met (bypass rules)" checkbox), which can be useful - checks can get stuck, sometimes we do need to do an emergency fix, etc.

It can be really helpful to easily track and report on when these protections are bypassed. I'll show you here how to use Tailpipe in combination with your GitHub Audit Logs to find out who has been sneaking past the rules!

In my example, I'll be querying from the audit logs for the DevOpsDays GitHub org, since I'm an admin there and...maybe have broken my own rules a few times :)

Getting started with Tailpipe

Tailpipe is a lightweight and open source tool which you can use to analyze all kinds of logs directly in your terminal, using familar SQL syntax.

Here's how to install Tailpipe and the GitHub plugin:

Install and Configure Tailpipe

If you use Homebrew, it's as simple as

brew install turbot/tap/tailpipe
Enter fullscreen mode Exit fullscreen mode

otherwise, use the install script

sudo /bin/sh -c "$(curl -fsSL https://tailpipe.io/install/tailpipe.sh)"
Enter fullscreen mode Exit fullscreen mode

Install the GitHub Tailpipe plugin

tailpipe plugin install github
Enter fullscreen mode Exit fullscreen mode

Download your GitHub Audit Logs and save them to a directory, perhaps /Users/USERNAME/github_audit_logs

Once you've done this, we need to configure Tailpipe's table partition and data source. Create a file at ~/.tailpipe/config/github.tpc with the following config (replace the path in the third line with the directory you saved the logs to)

partition "github_audit_log" "my_logs" {
  source "file"  {
    paths       = ["/Users/myuser/github_audit_logs"]
    file_layout = "%{DATA}.json.gz"
  }
}
Enter fullscreen mode Exit fullscreen mode

Finally, let's collect the logs. One simple Tailpipe command slurps all the log data into our table:

tailpipe collect github_audit_log
Enter fullscreen mode Exit fullscreen mode

Run some queries

The awesome thing about Tailpipe is that it lets us search and filter our log data using SQL queries (another awesome thing is that this all happens locally, so you don't have to pay for some cloud service to analyze it, or ship your sensitive logs somewhere else).

We can run our queries in a few ways - either run tailpipe query (for the interactive query shell) or you can save them as .sql files and run them using tailpipe query <filename.sql>.

What options do we have?

If I want to see the columns available to me for my queries, when I'm in the query shell, I can use the inspect command, like so:

$ tailpipe query
> .inspect github_audit_log
Column                     Type
action                     varchar
actor                      varchar
actor_id                   bigint
actor_ip                   varchar
actor_location             json
additional_fields          json
business                   varchar
business_id                bigint
created_at                 timestamp
document_id                varchar
external_identity_name_id  varchar
external_identity_username varchar
hashed_token               varchar
operation_type             varchar
org                        varchar
org_id                     varchar
repo                       varchar
              timestamp
token_id                   bigint
token_scopes               varchar
tp_akas                    varchar[]
tp_date                    date
tp_destination_ip          varchar
tp_domains                 varchar[]
tp_emails                  varchar[]
tp_id                      varchar
tp_index                   varchar
tp_ingest_timestamp        timestamp
tp_ips                     varchar[]
tp_partition               varchar
tp_source_ip               varchar
tp_source_location         varchar
tp_source_name             varchar
tp_source_type             varchar
tp_table                   varchar
tp_tags                    varchar[]
tp_timestamp               timestamp
tp_usernames               varchar[]
user                       varchar
user_id                    bigint
Enter fullscreen mode Exit fullscreen mode

Simple query

For starters, let's find out all the times someone bypassed branch rules protection in general. If we run tailpipe query without arguments, it opens the interactive query shell:

$ tailpipe query
> select created_at, actor, repo, action from github_audit_log where action='protected_branch.policy_override' order by created_at desc
+---------------------+--------------+--------------------------------+----------------------------------+
| created_at          | actor        | repo                           | action                           |
+---------------------+--------------+--------------------------------+----------------------------------+
| 2025-03-20 19:59:15 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-20 13:44:00 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-20 13:14:51 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-19 19:41:08 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-19 19:37:01 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-18 18:22:41 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-18 17:23:08 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-17 22:44:35 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-13 15:31:54 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-10 14:54:54 | yvovandoorn  | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-05 18:54:00 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-05 17:25:21 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-04 17:16:41 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-03 23:37:29 | mattstratton | devopsdays/devopsdays-assets   | protected_branch.policy_override |
| 2025-03-02 23:13:30 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-27 15:45:55 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-25 23:27:24 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-22 02:47:13 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-17 11:20:34 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-14 23:09:25 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-12 09:10:46 | yvovandoorn  | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-11 19:26:17 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-10 16:59:19 | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-10 16:08:52 | phrawzty     | devopsdays/devopsdays-web      | protected_branch.policy_override |
(output truncated)
Enter fullscreen mode Exit fullscreen mode

This is a good start; we can see that the most egregious abuser of the admin privilege is, well, me.

Add another column to the query

Now let's find out where it happened. For this, we can add the referrer column to our query.

$ tailpipe query
> select created_at, additional_fields.referrer, actor, repo, action from github_audit_log where action='protected_branch.policy_override' order by created_at desc 
+---------------------+-------------------------------------------------------------------------------------------------+--------------+--------------------------------+----------------------------------+
| created_at          | referrer                                                                                        | actor        | repo                           | action                           |
+---------------------+-------------------------------------------------------------------------------------------------+--------------+--------------------------------+----------------------------------+
| 2025-03-20 19:59:15 | "https://github.com/devopsdays/devopsdays-web/pull/15001"                                       | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-20 13:44:00 | "https://github.com/devopsdays/devopsdays-web/edit/main/content/events/2025-chicago/program.md" | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-20 13:14:51 | "https://github.com/devopsdays/devopsdays-web/pull/14998"                                       | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-19 19:41:08 | "https://github.com/devopsdays/devopsdays-web/pull/14997"                                       | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-19 19:37:01 | "https://github.com/devopsdays/devopsdays-web/pull/14996"                                       | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-18 18:22:41 | <null>                                                                                          | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-18 17:23:08 | <null>                                                                                          | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-17 22:44:35 | "https://github.com/devopsdays/devopsdays-web/pull/14988"                                       | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-13 15:31:54 | "https://github.com/devopsdays/devopsdays-web/pull/14974"                                       | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-10 14:54:54 | "https://github.com/devopsdays/devopsdays-web/pull/14955"                                       | yvovandoorn  | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-05 18:54:00 | "https://github.com/devopsdays/devopsdays-web/edit/main/data/events/2025/chicago/main.yml"      | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-05 17:25:21 | <null>                                                                                          | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-04 17:16:41 | "https://github.com/devopsdays/devopsdays-web/pull/14939"                                       | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-03-03 23:37:29 | "https://github.com/devopsdays/devopsdays-assets/pull/226"                                      | mattstratton | devopsdays/devopsdays-assets   | protected_branch.policy_override |
| 2025-03-02 23:13:30 | "https://github.com/devopsdays/devopsdays-web/pull/14933"                                       | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-27 15:45:55 | <null>                                                                                          | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-25 23:27:24 | <null>                                                                                          | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-22 02:47:13 | <null>                                                                                          | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-17 11:20:34 | <null>                                                                                          | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-14 23:09:25 | "https://github.com/devopsdays/devopsdays-web/pull/14883"                                       | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-12 09:10:46 | <null>                                                                                          | yvovandoorn  | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-11 19:26:17 | "https://github.com/devopsdays/devopsdays-web/pull/14870"                                       | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-10 16:59:19 | <null>                                                                                          | mattstratton | devopsdays/devopsdays-web      | protected_branch.policy_override |
| 2025-02-10 16:08:52 | "https://github.com/devopsdays/devopsdays-web/pull/14858"                                       | phrawzty     | devopsdays/devopsdays-web      | protected_branch.policy_override |


(output truncated)
Enter fullscreen mode Exit fullscreen mode

This shows us the pull requests that were overridden, but also if you look at the second record, it refers to a single file, which means it wasn't a PR...that was a yolo commit to main (made via the GitHub web interface)

A more complicated query

Speaking of that... if we want to just filter based on yolo commit to main? This query works for that, but also uses some more complicated SQL in order to make the output more readable:

select 
  created_at, 
  actor, 
  repo,
  referrer 
from (
  select 
    created_at, 
    action, 
    additional_fields.overridden_codes, 
    additional_fields.referrer as referrer, 
    additional_fields.reasons, 
    action,
    repo,
    actor 
  from 
    github_audit_log 
  where 
    action = 'protected_branch.policy_override' 
    and CONTAINS(additional_fields.reasons,'Changes must be made through a pull request')
  order by 
    created_at desc
  ) 
as s
Enter fullscreen mode Exit fullscreen mode

If we take that query and save it as query.sql, we can run tailpipe at the command line and use that file for the query:

$ tailpipe query query.sql
+---------------------+--------------+---------------------------+-------------------------------------------------------------------------------------------------+
| created_at          | actor        | repo                      | referrer                                                                                        |
+---------------------+--------------+---------------------------+-------------------------------------------------------------------------------------------------+
| 2025-03-20 13:44:00 | mattstratton | devopsdays/devopsdays-web | "https://github.com/devopsdays/devopsdays-web/edit/main/content/events/2025-chicago/program.md" |
| 2025-03-18 18:22:41 | mattstratton | devopsdays/devopsdays-web | <null>                                                                                          |
| 2025-03-05 18:54:00 | mattstratton | devopsdays/devopsdays-web | "https://github.com/devopsdays/devopsdays-web/edit/main/data/events/2025/chicago/main.yml"      |
| 2025-02-27 15:45:55 | mattstratton | devopsdays/devopsdays-web | <null>                                                                                          |
| 2025-02-10 16:59:19 | mattstratton | devopsdays/devopsdays-web | <null>                                                                                          |
| 2025-01-24 15:36:02 | mattstratton | devopsdays/devopsdays-cli | <null>                                                                                          |
| 2024-09-13 20:46:02 | mattstratton | devopsdays/devopsdays-web | <null>                                                                                          |
| 2024-08-29 11:33:06 | mattstratton | devopsdays/devopsdays-web | "https://github.com/devopsdays/devopsdays-web/delete/main/mattytest.txt"                        |
| 2024-08-28 13:59:20 | mattstratton | devopsdays/devopsdays-web | <null>                                                                                          |
+---------------------+--------------+---------------------------+-------------------------------------------------------------------------------------------------+
Enter fullscreen mode Exit fullscreen mode

(The entries with "null" for the referrer mean that the commits were made directly to main outside of the web interface, i.e., I did a git push origin main)

Conclusion

Being able to query and report on GitHub audit actions in a local, fast way (without having to upload our sensitive logs to another service) gives us a lot of flexibility to dig into actions take on our repos!

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (2)

Collapse
 
nickytonline profile image
Nick Taylor

TIL about Tailpipe! Thanks for sharing Matty!

Captain America saluting

Collapse
 
fyodorio profile image
Fyodor

Please don’t show it to my admin 😅

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

DEV is better (more customized, reading settings like dark mode etc) when you're signed in!

Okay