<?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>DEV Community: Noel Himer</title>
    <description>The latest articles on DEV Community by Noel Himer (@unbearablelabs).</description>
    <link>https://dev.to/unbearablelabs</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%2F3957067%2F7aeaf394-3ab1-4e17-bab7-3ebcea759137.png</url>
      <title>DEV Community: Noel Himer</title>
      <link>https://dev.to/unbearablelabs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/unbearablelabs"/>
    <language>en</language>
    <item>
      <title>If you use Trivy or KICS in CI, read this</title>
      <dc:creator>Noel Himer</dc:creator>
      <pubDate>Thu, 28 May 2026 17:44:24 +0000</pubDate>
      <link>https://dev.to/unbearablelabs/if-you-use-trivy-or-kics-in-ci-read-this-4d91</link>
      <guid>https://dev.to/unbearablelabs/if-you-use-trivy-or-kics-in-ci-read-this-4d91</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;75 of 76 trivy-action tags hijacked in five days. The pattern, three checks, and what to automate.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Hey —&lt;/p&gt;

&lt;p&gt;Between March 19 and March 24, 2026, the "TeamPCP" actor force-pushed mutable tags on three popular security-tool repos. &lt;strong&gt;75 of 76 &lt;code&gt;trivy-action&lt;/code&gt; tags&lt;/strong&gt; plus 7 &lt;code&gt;setup-trivy&lt;/code&gt; tags went first. Four days later, &lt;strong&gt;all 91 Checkmarx KICS action tags&lt;/strong&gt; were repointed. The same group landed malicious LiteLLM builds on PyPI on the 24th. Every CI pipeline pinned to &lt;code&gt;@v0&lt;/code&gt;, &lt;code&gt;@main&lt;/code&gt;, or &lt;code&gt;@latest&lt;/code&gt; on those actions ran attacker code on its next build.&lt;/p&gt;

&lt;p&gt;The injected payload was not subtle: it scraped the hosted GitHub runner's process memory for variables marked &lt;code&gt;isSecret: true&lt;/code&gt;, swept the filesystem for SSH keys and cloud credentials, encrypted everything with AES-256-CBC + RSA-4096, and exfiltrated it.&lt;/p&gt;

&lt;p&gt;If you used Trivy or KICS in CI without a SHA pin, &lt;strong&gt;assume those secrets are gone.&lt;/strong&gt; Rotate, then come back to this email.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mechanism in one paragraph
&lt;/h2&gt;

&lt;p&gt;A GitHub Actions reference like &lt;code&gt;uses: aquasecurity/trivy-action@v0&lt;/code&gt; is just a &lt;strong&gt;pointer&lt;/strong&gt; to a git ref. Anyone with push to that repo — including an attacker who steals a maintainer token — can &lt;code&gt;git tag -f v0 &amp;lt;attacker-commit&amp;gt; &amp;amp;&amp;amp; git push --force&lt;/code&gt; and now every pipeline pinned to &lt;code&gt;v0&lt;/code&gt; builds the attacker's code. Branches are worse. Even semver-style tags like &lt;code&gt;@v2&lt;/code&gt; are mutable. The only ref form that is cryptographically immutable is the &lt;strong&gt;full 40-character commit SHA&lt;/strong&gt;. From the Puma Security writeup:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A commit SHA is a cryptographic hash of the repository state at that point in time. It cannot be moved or reassigned.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the whole defense. The rest is logistics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three checks you can run today
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Grep your workflows for unpinned refs.&lt;/strong&gt; Five minutes, no tooling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rEn&lt;/span&gt; &lt;span class="s1"&gt;'uses: [^@]+@(main|master|v[0-9]+(\.[0-9]+)?)'&lt;/span&gt; .github/workflows/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything that prints is a candidate for repinning. If you have third-party actions there (anything not &lt;code&gt;actions/*&lt;/code&gt; or &lt;code&gt;github/*&lt;/code&gt;), prioritize those first — that's the TeamPCP exposure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Replace high-risk pins with SHA + tag comment.&lt;/strong&gt; The standard form:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aquasecurity/trivy-action@&amp;lt;40-char-sha&amp;gt;&lt;/span&gt;  &lt;span class="c1"&gt;# v0.x.y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub's UI shows the SHA on every release page. Dependabot understands this form and proposes SHA updates with the matching tag comment. You give up zero ergonomics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Audit your top-level &lt;code&gt;permissions:&lt;/code&gt; block.&lt;/strong&gt; A workflow without an explicit &lt;code&gt;permissions:&lt;/code&gt; key inherits &lt;code&gt;contents: write&lt;/code&gt; by default on most repos. Add this near the top:&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;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…then grant per-job writes only where needed. If the TeamPCP payload had landed on a repo using &lt;code&gt;permissions: read-all&lt;/code&gt;, the blast radius would have been the runner secrets — not also the ability to push commits back.&lt;/p&gt;

&lt;p&gt;These three checks take ten minutes total on a single workflow. Most of you have multiple workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I'm wiring it for ongoing use
&lt;/h2&gt;

&lt;p&gt;I shipped &lt;code&gt;github-actions-audit&lt;/code&gt; on Apify Store earlier this month — 13 checks for the published CI attack surface. After TeamPCP I'm extending it with an 8-check &lt;code&gt;supply_chain_advanced&lt;/code&gt; category (&lt;code&gt;GHA-201&lt;/code&gt; through &lt;code&gt;GHA-208&lt;/code&gt;) that catches the specific patterns the attackers exploited: mutable tag refs, &lt;code&gt;pull_request_target&lt;/code&gt; + checkout-by-PR-sha, script injection via &lt;code&gt;${{ github.event.* }}&lt;/code&gt;, untrusted owners, &lt;code&gt;permissions: write-all&lt;/code&gt; defaults.&lt;/p&gt;

&lt;p&gt;The MCP version lets Claude or Cursor agents run the audit against a workflow YAML on demand — paste a file, get back severity, line numbers, and a copy-paste fix snippet. Pricing is unchanged: $0.02 per audit. The extension lands in the same Actor — no migration.&lt;/p&gt;

&lt;p&gt;If you want the CLI-shaped version of the same defense, &lt;strong&gt;zizmor&lt;/strong&gt; by William Woodruff is the open-source linter that pioneered most of these checks; it's how I cross-checked our findings during development. I'd run both: zizmor in pre-commit, the MCP server in agentic flows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Find every unpinned third-party action across your repos&lt;/span&gt;
gh repo list &lt;span class="nt"&gt;--limit&lt;/span&gt; 1000 &lt;span class="nt"&gt;--json&lt;/span&gt; nameWithOwner &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s1"&gt;'.[].nameWithOwner'&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;repo&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== &lt;/span&gt;&lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    gh api &lt;span class="s2"&gt;"repos/&lt;/span&gt;&lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="s2"&gt;/contents/.github/workflows"&lt;/span&gt; &lt;span class="nt"&gt;--jq&lt;/span&gt; &lt;span class="s1"&gt;'.[].path'&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;wf&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
        &lt;/span&gt;gh api &lt;span class="s2"&gt;"repos/&lt;/span&gt;&lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="s2"&gt;/contents/&lt;/span&gt;&lt;span class="nv"&gt;$wf&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--jq&lt;/span&gt; &lt;span class="s1"&gt;'.content'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-En&lt;/span&gt; &lt;span class="s1"&gt;'uses: [^@]+@(main|master|v[0-9]+(\.[0-9]+)?)'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s|^|  &lt;/span&gt;&lt;span class="nv"&gt;$wf&lt;/span&gt;&lt;span class="s2"&gt;:|"&lt;/span&gt;
      &lt;span class="k"&gt;done
  done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's three hours of grunt work compressed into one command. Run it, fix what falls out, sleep better.&lt;/p&gt;

&lt;p&gt;For the MCP-native flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"github-actions-audit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://unbearable-dev--github-actions-audit.apify.actor/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer &amp;lt;YOUR_APIFY_TOKEN&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Claude Desktop, paste a workflow YAML, ask for an audit. The new GHA-201..208 checks are coming in the next push.&lt;/p&gt;

&lt;h2&gt;
  
  
  This week's reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://pumasecurity.io/resources/blog/teampcp-github-actions-supply-chain/" rel="noopener noreferrer"&gt;Puma Security: TeamPCP GitHub Actions supply chain&lt;/a&gt;&lt;/strong&gt; — the technical writeup with timeline + IoCs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/woodruffw/zizmor" rel="noopener noreferrer"&gt;zizmor on GitHub&lt;/a&gt;&lt;/strong&gt; — the linter the audit shop's &lt;code&gt;supply_chain_advanced&lt;/code&gt; checks are modeled on&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/step-security/harden-runner" rel="noopener noreferrer"&gt;StepSecurity Harden Runner&lt;/a&gt;&lt;/strong&gt; — egress filtering on the GitHub-hosted runner; catches the AES/RSA exfil leg of TeamPCP-class attacks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://apify.com/unbearable_dev/github-actions-audit" rel="noopener noreferrer"&gt;apify.com/unbearable_dev/github-actions-audit&lt;/a&gt;&lt;/strong&gt; — the Actor itself, $0.02 per audit&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;built by Noel @ &lt;a href="https://unbearabletechtips.beehiiv.com" rel="noopener noreferrer"&gt;Unbearable TechTips&lt;/a&gt; — practical homelab + agent ops. Reply to this email — I read every one.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="mailto:unbearabledev@gmail.com?subject=Sponsorship%20inquiry"&gt;Sponsor this newsletter&lt;/a&gt; · &lt;a href="https://github.com/UnbearableDev" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://www.youtube.com/@UnbearableTechTips" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>devops</category>
      <category>supplychain</category>
      <category>cicd</category>
    </item>
  </channel>
</rss>
