<?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: crimsondhaks</title>
    <description>The latest articles on DEV Community by crimsondhaks (@crimsondhaks).</description>
    <link>https://dev.to/crimsondhaks</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4106097%2F9945f0bc-0adc-4065-9510-24aaba0c3933.png</url>
      <title>DEV Community: crimsondhaks</title>
      <link>https://dev.to/crimsondhaks</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/crimsondhaks"/>
    <language>en</language>
    <item>
      <title>The pin you stopped reading</title>
      <dc:creator>crimsondhaks</dc:creator>
      <pubDate>Wed, 02 Sep 2026 11:57:10 +0000</pubDate>
      <link>https://dev.to/crimsondhaks/the-pin-you-stopped-reading-16i</link>
      <guid>https://dev.to/crimsondhaks/the-pin-you-stopped-reading-16i</guid>
      <description>&lt;p&gt;In March 2025 an attacker compromised &lt;code&gt;tj-actions/changed-files&lt;/code&gt; and rewrote its&lt;br&gt;
release tags to point at malicious code. The injected code dumped runner memory&lt;br&gt;
into workflow logs, which on a public repository means printing your secrets&lt;br&gt;
where anyone can read them. Around 23,000 repositories depended on that action,&lt;br&gt;
most of them through a mutable tag like &lt;code&gt;@v45&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Repositories that had pinned the action to a commit SHA were unaffected. That is&lt;br&gt;
worth sitting with for a moment, because it is rare for a security practice to&lt;br&gt;
draw such a clean line through a real incident. The advice worked exactly as&lt;br&gt;
advertised.&lt;/p&gt;

&lt;p&gt;So we all pinned. And then we quietly undid it.&lt;/p&gt;
&lt;h2&gt;
  
  
  What pinning actually buys you
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;uses: actions/checkout@v5&lt;/code&gt; means "run whatever &lt;code&gt;v5&lt;/code&gt; points at when this&lt;br&gt;
workflow starts". The tag is a pointer the upstream author can move at any time,&lt;br&gt;
and nothing about your repository changes when they do. You are trusting the tag&lt;br&gt;
to keep meaning what it meant when you wrote the line.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09&lt;/code&gt; means "run&lt;br&gt;
this exact commit". Nobody can repoint it. If the upstream repository is&lt;br&gt;
compromised tomorrow, your workflow keeps running the code you reviewed.&lt;/p&gt;

&lt;p&gt;That is the entire mechanism. It is not clever, and that is its strength.&lt;/p&gt;
&lt;h2&gt;
  
  
  The part nobody mentions
&lt;/h2&gt;

&lt;p&gt;A pin is a snapshot, and snapshots go stale. The action you froze in March has&lt;br&gt;
since fixed bugs, gained features, and patched its own vulnerabilities, and you&lt;br&gt;
are running none of it. So you enable Dependabot, which is exactly the right&lt;br&gt;
call — an un-updated pin is its own risk.&lt;/p&gt;

&lt;p&gt;Now look at what arrives in your pull request queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
&lt;/span&gt;&lt;span class="gi"&gt;+ uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What changed? You cannot tell. Not because you are careless — because the diff&lt;br&gt;
contains no information. Two hex strings differ. That is the whole change.&lt;/p&gt;

&lt;p&gt;GitHub's own engineering blog describes a repository where&lt;br&gt;
&lt;a href="https://github.blog/security/supply-chain-security/tame-dependabot-group-your-updates-slow-the-cadence-keep-security-fast/" rel="noopener noreferrer"&gt;roughly one in six commits were Dependabot version bumps&lt;/a&gt;,&lt;br&gt;
61 of them in twelve months, sometimes several in a single day. Nobody&lt;br&gt;
meaningfully reviews an opaque hash 61 times a year. You approve it, because the&lt;br&gt;
alternative is manually diffing a third-party repository before every merge, and&lt;br&gt;
nobody has budgeted for that.&lt;/p&gt;

&lt;p&gt;One maintainer put it plainly on her own blog, admitting she&lt;br&gt;
&lt;a href="https://some-natalie.dev/blog/github-actions-changes/" rel="noopener noreferrer"&gt;blindly accepts Dependabot pull requests and noting this negates the benefit of pinning&lt;/a&gt;.&lt;br&gt;
That is not a confession of bad practice. It is an accurate description of what&lt;br&gt;
the workflow makes people do.&lt;/p&gt;

&lt;p&gt;So the pin is still in the file. The review it depends on is gone. You are&lt;br&gt;
running unreviewed third-party code with your credentials, with a line in your&lt;br&gt;
YAML that looks like diligence.&lt;/p&gt;
&lt;h2&gt;
  
  
  It gets slightly worse
&lt;/h2&gt;

&lt;p&gt;Two details make this sharper than it first appears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependabot Alerts do not cover SHA-pinned actions.&lt;/strong&gt; Users have been&lt;br&gt;
&lt;a href="https://github.com/orgs/community/discussions/154189" rel="noopener noreferrer"&gt;asking GitHub to fix this asymmetry&lt;/a&gt;&lt;br&gt;
for a while. You are told to pin, then told to enable version updates to combat&lt;br&gt;
staleness, and the alerting that would tell you a pinned action has a known&lt;br&gt;
vulnerability does not apply to the form you were told to use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The version comment is not verified by anything.&lt;/strong&gt; Tooling writes bumps like&lt;br&gt;
this:&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09&lt;/span&gt; &lt;span class="c1"&gt;# v5.1.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That line makes two claims. First, run this commit — GitHub enforces it. Second,&lt;br&gt;
this commit is v5.1.0 — nothing enforces it at all. It is a comment. Your eyes&lt;br&gt;
read &lt;code&gt;v5.1.0&lt;/code&gt; and move on, and the only thing GitHub cares about is the hash.&lt;/p&gt;

&lt;p&gt;Those two claims agreeing is precisely what a retagged release breaks. In the&lt;br&gt;
tj-actions incident the tags were moved; the SHAs were not. If a pin's comment&lt;br&gt;
says &lt;code&gt;v5.1.0&lt;/code&gt; and the &lt;code&gt;v5.1.0&lt;/code&gt; tag now resolves to a different commit, something&lt;br&gt;
worth knowing has happened — either your pin is stale, or the tag moved under&lt;br&gt;
you. That check takes one API call, and almost nobody performs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is not a niche concern
&lt;/h2&gt;

&lt;p&gt;GitHub reported&lt;br&gt;
&lt;a href="https://github.blog/news-insights/product-news/lets-talk-about-github-actions/" rel="noopener noreferrer"&gt;11.5 billion Actions minutes used in 2025, up 35% year over year&lt;/a&gt;.&lt;br&gt;
Datadog's security research found that&lt;br&gt;
&lt;a href="https://securitylabs.datadoghq.com/articles/case-for-github-actions-security/" rel="noopener noreferrer"&gt;two out of three organizations have at least one vulnerability in an Actions workflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And the incidents kept coming after tj-actions. In May 2026 a threat group&lt;br&gt;
&lt;a href="https://dev.to/unbearablelabs/two-supply-chain-attacks-in-one-week-heres-what-to-actually-fix-in-your-ci-2knc"&gt;compromised 5,561 public repositories in about six hours&lt;/a&gt;&lt;br&gt;
by pushing workflows carrying dormant &lt;code&gt;workflow_dispatch&lt;/code&gt; backdoors. TanStack's&lt;br&gt;
router was breached through&lt;br&gt;
&lt;a href="https://www.copilotkit.ai/blog/tanstack-supply-chain-attack-and-how-to-lock-down-github-actions" rel="noopener noreferrer"&gt;cache poisoning that led to OIDC token theft and 84 malicious npm packages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Attackers moved to CI because CI holds credentials, runs on every push, and is&lt;br&gt;
reviewed less carefully than application code. Workflow files are code with&lt;br&gt;
production access that we treat as configuration.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources above were paraphrased; content was rephrased for compliance with&lt;br&gt;
licensing restrictions.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What would actually help
&lt;/h2&gt;

&lt;p&gt;Not "review your Dependabot PRs more carefully." That advice has been available&lt;br&gt;
the whole time and it loses to arithmetic: 61 bumps a year against a finite&lt;br&gt;
attention budget.&lt;/p&gt;

&lt;p&gt;What helps is making the diff contain information again. When a pin moves, the&lt;br&gt;
questions worth answering are mechanical, and mechanical questions can be&lt;br&gt;
answered mechanically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the new commit exist in any tag or branch upstream, or is it floating
outside published history?&lt;/li&gt;
&lt;li&gt;If the pin claims a version, does that tag still resolve to this commit?&lt;/li&gt;
&lt;li&gt;Did the action's declared interface change — inputs, outputs, required flags?&lt;/li&gt;
&lt;li&gt;Did its execution model change, for instance a JavaScript action becoming a
Docker action that can pull an arbitrary image?&lt;/li&gt;
&lt;li&gt;Did it gain network calls, secret references, or dynamic code execution that
it did not have before?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that requires judgment. It requires fetching two revisions and&lt;br&gt;
comparing them, which is a thing a computer should do for you before you click&lt;br&gt;
approve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two honest caveats
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No static check can be certain.&lt;/strong&gt; An action that builds a resource name at&lt;br&gt;
runtime could reach anything, and a tool claiming certainty there is lying. The&lt;br&gt;
right behaviour is to say what could not be determined rather than stay quiet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A clean report is not a safety guarantee.&lt;/strong&gt; It means the reviewable surface&lt;br&gt;
did not change in a way worth flagging. A sufficiently careful attacker can stay&lt;br&gt;
below that line. This raises the cost of an attack; it does not eliminate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The point
&lt;/h2&gt;

&lt;p&gt;Pinning to a SHA is still correct. Keep doing it.&lt;/p&gt;

&lt;p&gt;But recognise what it is: a mechanism that converts a trust problem into a&lt;br&gt;
review problem. It only pays out if the review happens, and we built a workflow&lt;br&gt;
that guarantees it does not. The pin in your YAML is not protection. It is a&lt;br&gt;
place where protection could go, if something were reading it.&lt;/p&gt;




&lt;p&gt;I wrote &lt;a href="https://github.com/helgafinn/action-diff" rel="noopener noreferrer"&gt;action-diff&lt;/a&gt; to do the&lt;br&gt;
comparison above — it reads both revisions of a bumped action and reports what&lt;br&gt;
changed, including whether the pin's claimed version still resolves to the&lt;br&gt;
commit it names. It is MIT licensed and runs as a GitHub Action or a CLI.&lt;/p&gt;

&lt;p&gt;Use it, use something else, or write your own. The tooling matters much less&lt;br&gt;
than noticing that a practice we all adopted has quietly stopped working.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>security</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
  </channel>
</rss>
