{
    "archive_path": "archive/1774731388.657509",
    "base_url": "www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning",
    "basename": "",
    "bookmarked_date": "2026-03-28 20:56",
    "canonical": {
        "archive_org_path": "https://web.archive.org/web/www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning",
        "dom_path": "output.html",
        "favicon_path": "favicon.ico",
        "git_path": "git/",
        "google_favicon_path": "https://www.google.com/s2/favicons?domain=www.vaines.org",
        "headers_path": "headers.json",
        "htmltotext_path": "htmltotext.txt",
        "index_path": "index.html",
        "media_path": "media/",
        "mercury_path": "mercury/content.html",
        "pdf_path": "output.pdf",
        "readability_path": "readability/content.html",
        "screenshot_path": "screenshot.png",
        "singlefile_path": "singlefile.html",
        "warc_path": "warc/",
        "wget_path": null
    },
    "domain": "www.vaines.org",
    "downloaded_at": "2026-03-28T20:56:35.495581+00:00",
    "downloaded_datestr": "2026-03-28 20:56",
    "extension": "",
    "hash": "1B9PG06YNE2FRFHE3RS1",
    "history": {
        "archive_org": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://web.archive.org/save/https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2026-03-28T20:59:06.156247+00:00",
                "index_texts": null,
                "output": "https://web.archive.org/web/20260328205847/https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/",
                "pwd": "/data/archive/1774731388.657509",
                "schema": "ArchiveResult",
                "start_ts": "2026-03-28T20:58:19.405058+00:00",
                "status": "succeeded"
            }
        ],
        "dom": [
            {
                "cmd": [
                    "/usr/bin/chromium-browser",
                    "--proxy-server=socks5://tor-socks-proxy:9150",
                    "--disable-features=DarkMode",
                    "--run-all-compositor-stages-before-draw",
                    "--hide-scrollbars",
                    "--autoplay-policy=no-user-gesture-required",
                    "--no-first-run",
                    "--use-fake-ui-for-media-stream",
                    "--use-fake-device-for-media-stream",
                    "--simulate-outdated-no-au='Tue, 31 Dec 2099 23:59:59 GMT'",
                    "--headless=new",
                    "--no-sandbox",
                    "--no-zygote",
                    "--disable-dev-shm-usage",
                    "--disable-software-rasterizer",
                    "--disable-sync",
                    "--window-size=1440,2000",
                    "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "--user-data-dir=/data/personas/Default/chrome_profile",
                    "--profile-directory=Default",
                    "--dump-dom",
                    "https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/"
                ],
                "cmd_version": "131.0.6778",
                "end_ts": "2026-03-28T20:57:19.216165+00:00",
                "index_texts": null,
                "output": "output.html",
                "pwd": "/data/archive/1774731388.657509",
                "schema": "ArchiveResult",
                "start_ts": "2026-03-28T20:56:48.031469+00:00",
                "status": "succeeded"
            }
        ],
        "favicon": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--max-time",
                    "60",
                    "--output",
                    "favicon.ico",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://www.google.com/s2/favicons?domain=www.vaines.org"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2026-03-28T20:56:38.616644+00:00",
                "index_texts": null,
                "output": "favicon.ico",
                "pwd": "/data/archive/1774731388.657509",
                "schema": "ArchiveResult",
                "start_ts": "2026-03-28T20:56:35.893174+00:00",
                "status": "succeeded"
            }
        ],
        "git": [],
        "headers": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2026-03-28T20:56:38.997457+00:00",
                "index_texts": null,
                "output": "headers.json",
                "pwd": "/data/archive/1774731388.657509",
                "schema": "ArchiveResult",
                "start_ts": "2026-03-28T20:56:38.720090+00:00",
                "status": "succeeded"
            }
        ],
        "htmltotext": [
            {
                "cmd": [
                    "(internal) archivebox.extractors.htmltotext",
                    "./{singlefile,dom}.html"
                ],
                "cmd_version": "0.8.5rc51",
                "end_ts": "2026-03-28T20:58:09.812156+00:00",
                "index_texts": [
                    "The Comforting Lie Of SHA Pinning (https://www.vaines.org/favicon.ico) The Comforting Lie Of SHA Pinning (https://www.vaines.org/favicon.ico) (https://fonts.gstatic.com) (https://fonts.googleapis.com/css2?family=PT+Sans:wght@400;700&display=swap)  (/) ()     (https://www.vaines.org/) Home  (https://www.vaines.org/posts) Posts  (https://www.vaines.org/talks) Meatspace  (https://www.vaines.org/categories/things/) Things I Made  Other (https://www.vaines.org/about) About Me (https://www.vaines.org/cursed) Cursed Knowledge (https://www.vaines.org/contact) Contact    ()       (https://www.vaines.org/index.xml)   RSS              The Comforting Lie Of SHA Pinning (https://www.vaines.org/author/aiden-vaines) Aiden Vaines | Mar 24, 2026| (https://www.vaines.org/categories/thought-leadership) Thought Leadership (https://www.vaines.org/categories/security) Security (https://www.vaines.org/categories/supply-chain) Supply Chain (https://www.vaines.org/categories/github-actions) GitHub Actions (https://www.vaines.org/categories/security) Security  (The Comforting Lie Of SHA Pinning) In March 2026, Trivy became the latest reminder that software supply chains are, at best, loosely held together with convention and trust. A typosquatting attack slipped malicious code into what looked like a legitimate dependency path. The post-mortems are worth reading, and they all converge on a single recommendation: pin your dependencies . In the GitHub Actions world, that usually translates to use commit SHAs, not tags . There\u2019s a widely held belief that pinning a GitHub Action to a commit SHA gives you immutability, its what Microsoft/GitHub are recommending, and its what Aqua are recommending. After all, a SHA is content-addressed. It cannot be moved. It cannot be re-tagged. It is, in theory, the most stable reference you can use. The problem with that line of thinking is that the resolution of that SHA is not scoped the way most people assume .\u00a0Specifically, GitHub Actions does not meaningfully validate that the commit SHA you reference belongs to the repository you think it does. Wait, what? No, thats not right\u2026 I set up a deliberately small example to test this behaviour. A \u201clegitimate\u201d action: avaines/blog_gh_sha_pinning_action  A consuming application: avaines/blog_gh_sha_pinning_app   The application references the action in the usual way: uses: avaines/blog_gh_sha_pinning_action@<some-sha>       (Working GitHub Action showing \u2018Hello World\u2019) Working GitHub Action showing \u2018Hello World\u2019   So far, so normal. Now introduce an attacker: Fork the action repository to aidenvaines-cgi/blog_gh_sha_pinning_action  Add a malicious step (in my case, just printing output, but in reality this is where you exfiltrate all the fun stuff like secrets and personal data )   (Attacker changes to the GitHub Action in their fork adding malicious content) Attacker changes to the GitHub Action in their fork adding malicious content   Next, create a pull request to the consuming application that appears to simply bump the pinned SHA:  (Attacker raises a PR) Attacker raises a PR   The SHA used in the PR comes from the attacker-controlled fork of the action, despite it still being referenced as avaines/blog_gh_sha_pinning_action  You might reasonably assume one of the following safeguards exists: GitHub validates that the SHA belongs to avaines/blog_gh_sha_pinning_action  Or the workflow fails because the commit cannot be found in the specified repository  Neither is true, and that is madness  The workflow executes successfully!!!!!!!  (GitHub Actions running the malicious workflow) GitHub Actions running the malicious workflow   GitHub resolves the commit SHA, finds a matching object, and executes it, regardless of which fork it originated from. (wft meme) From the platform\u2019s perspective, a fork is a separate repository with a shared object graph/history. When the runner resolves the reference, it ultimately looks up the commit in the Git object database; if that object exists and is reachable, it can be used regardless of which fork introduced it. A commit object is globally identifiable. If the SHA exists anywhere reachable, that is apparently sufficient. The result is that a\u00a0pull request can replace a pinned, trusted action with attacker-controlled code without changing the apparent repository reference. If the reviewer is scanning for obvious changes like owner, repo name, or tag they will see none. Only the SHA changes and with that comes a huge amount of assumed trust. I know that owner, and I know that repository, its just a version bump, and a minor one at that, with that comment next to the tag doing a lot of heavy lifting. We\u2019ve just spent the last few years training people to treat that as best practice .  (GitHub PR Delta) GitHub PR Delta   A lot of the current guidance focuses on avoiding tags because they are mutable, which is true: tags can be moved, and relying on them introduces an entirely different risk. Github already has a \u2018Make tags immutable\u2019 feature, but it\u2019s optional, therefore, neither used nor can it be trusted as the owner (or attacker) could just disable it. Simply switching to SHA pinning does not eliminate the problem, in some respects it makes it worse. Tags are scoped to owner/repository because thats how they work. You could argue its harder to compromise that repository rather than hijack it through a forked repository and then writing actual changes to the repository. Whereas a commit object is content-addressed and can be reachable from multiple repositories that share history (e.g. forks) I believe the industry advice is a bit of an overcorrection, and we\u2019ve replaced one weak guarantee (mutable tags but scoped to repo) with another vastly worse idea in unscoped SHAs. Yes you should check, yes you should validate it, but tags are human readable, SHAs are not and if you ask yourself \u201cDo I always properly check?\u201d do you? because I can\u2019t say I do enough validation 100% of the time. Supply Chain Woes The Trivy incident is not interesting because of the tool. Though it is the thing that\u2019s caused me a lot of bother over the last month, and its symptomatic of the constant supply chain threats we\u2019re seeing everywhere. Late last year NPM was basically a skip fire ((https://www.wiz.io/blog/widespread-npm-supply-chain-attack-breaking-down-impact-scope-across-debug-chalk) https://www.wiz.io/blog/widespread-npm-supply-chain-attack-breaking-down-impact-scope-across-debug-chalk , (https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack) https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack ,\u00a0etc). We\u2019ve delegated so much behaviour to 3rd parties we can\u2019t, and shouldn\u2019t implicitly trust. The (https://www.npmjs.com/package/is-odd) (https://www.npmjs.com/package/is-odd) https://www.npmjs.com/package/is-odd package became a bit of a meme for this exact problem, where tool chains like NPM and GitHub Actions place re-usable custom modules/actions/libraries as an attractive off the shelf solution to solve common problems so you don\u2019t have to. Ironically with the rise of AI, it\u2019s now easier to just vibe code the same functionality yourself that use one of these off the shelf resources, and you can still know exactly as much about how security, your data, and to some extent the functionality works with strangely more ownership and a smaller attack service. GitHub Actions, in particular exacerbates this somewhat as workflows routinely execute third-party code, the secrets are implicitly available to those workflows and there are multiple ways to extract those at runtime with little audit or oversight. Then to top it all off SHAs are not human friendly and tags are not immutable so review processes tend to focus on what changed , not *where it came from. Which is all a fragile house of cards, sometimes I miss Jenkins. If \u201cuse SHAs\u201d is not sufficient, what is? At a minimum, we need to introduce provenance checks . I\u2019ve said above that SHA\u2019s are not human friendly like tags so theres a couple of things we can, and probably should be doing to validate. Obviously that the SHA or tag exists in the right repository and GitHub should enforce tag mutability in my opinion: We tend to describe these incidents as \u201csupply chain attacks\u201d, which is accurate but slightly misleading. It implies a complex and sophisticated multi-stage compromise by 1337 h4x0rz. In reality, the weakest link is often much simpler because humans are a bit shit sometimes. SHA pinning being touted as the solution is just security-theater rather than due diligence in an ecosystem that is not helping either.  References (https://rosesecurity.dev/2026/03/20/typosquatting-trivy.html) https://rosesecurity.dev/2026/03/20/typosquatting-trivy.html  (https://www.wiz.io/blog/trivy-compromised-teampcp-supply-chain-attack) https://www.wiz.io/blog/trivy-compromised-teampcp-supply-chain-attack  (https://www.aquasec.com/blog/trivy-supply-chain-attack-what-you-need-to-know/) https://www.aquasec.com/blog/trivy-supply-chain-attack-what-you-need-to-know/  (https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23) https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23  (https://www.wiz.io/blog/widespread-npm-supply-chain-attack-breaking-down-impact-scope-across-debug-chalk) https://www.wiz.io/blog/widespread-npm-supply-chain-attack-breaking-down-impact-scope-across-debug-chalk  (https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack) https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack      (Disqus)   Please enable JavaScript to view the (https://disqus.com/?ref_noscript) comments powered by Disqus.       Hi, I'm Aiden I\u2019ve been working in IT for a bit over twenty years. I started out doing infrastructure and operations work and gradually took on more responsibility around design, delivery, and technical direction.This site is mostly where I write things down as I go. It\u2019s a mix of experiments, notes, and the occasional reminder that some ideas sound better before you try them. (https://linkedin.com/in/aidenvaines/)     (https://github.com/avaines)     (https://buymeacoffee.com/u6zv4r45ct)     (https://shop.vaines.org)     (https://www.npmjs.com/~aiden.vaines)      (https://www.vaines.org/about/)   Subscribe for updates  (Email Address)  Subscribe    Featured Posts (post-thumb) (https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/) The Comforting Lie Of SHA Pinning  (https://www.vaines.org/author/aiden-vaines) Aiden Vaines | Mar 24, 2026    (post-thumb) (https://www.vaines.org/posts/2026-03-23-preparing-for-quantum-computers-that-may-or-may-not-exist/) Preparing for Quantum Computers That May or May Not Exist  (https://www.vaines.org/author/aiden-vaines) Aiden Vaines | Mar 23, 2026    (post-thumb) (https://www.vaines.org/posts/2026-03-02-terraform-six-years-on/) Terraform Six Years On (Retrospective)  (https://www.vaines.org/author/aiden-vaines) Aiden Vaines | Mar 2, 2026     Socials  (https://linkedin.com/in/aidenvaines/)     (https://github.com/avaines)     (https://buymeacoffee.com/u6zv4r45ct)     (https://shop.vaines.org)     (https://www.npmjs.com/~aiden.vaines)       Tags  (https://www.vaines.org/tags/ai) Ai  (https://www.vaines.org/tags/api) API  (https://www.vaines.org/tags/architecture) Architecture  (https://www.vaines.org/tags/automation) Automation  (https://www.vaines.org/tags/aws) Aws  (https://www.vaines.org/tags/bedrock) Bedrock  (https://www.vaines.org/tags/catchphrase) Catchphrase  (https://www.vaines.org/tags/cli) CLI  (https://www.vaines.org/tags/code-structure) Code structure  (https://www.vaines.org/tags/copilot) Copilot  (https://www.vaines.org/tags/deployment) Deployment  (https://www.vaines.org/tags/event) Event  (https://www.vaines.org/tags/fastapi) Fastapi  (https://www.vaines.org/tags/framework) Framework  (https://www.vaines.org/tags/game) Game  (https://www.vaines.org/tags/gameshow) Gameshow  (https://www.vaines.org/tags/hardware) Hardware  (https://www.vaines.org/tags/homeassistant) Homeassistant  (https://www.vaines.org/tags/hugo) Hugo  (https://www.vaines.org/tags/itv) Itv  (https://www.vaines.org/tags/javascript) Javascript  (https://www.vaines.org/tags/lambda) Lambda  (https://www.vaines.org/tags/machine-learning) Machine learning  (https://www.vaines.org/tags/microservices) Microservices  (https://www.vaines.org/tags/npm) Npm  (https://www.vaines.org/tags/open-source) Open source  (https://www.vaines.org/tags/opinion) Opinion  (https://www.vaines.org/tags/packaging) Packaging  (https://www.vaines.org/tags/powershell) Powershell  (https://www.vaines.org/tags/prompt-engineering) Prompt engineering  (https://www.vaines.org/tags/python) Python  (https://www.vaines.org/tags/react) React  (https://www.vaines.org/tags/s3) S3  (https://www.vaines.org/tags/scripting) Scripting  (https://www.vaines.org/tags/serverless) Serverless  (https://www.vaines.org/tags/static-site-generator) Static site generator  (https://www.vaines.org/tags/strandsagents) Strandsagents  (https://www.vaines.org/tags/synonyms) Synonyms  (https://www.vaines.org/tags/technology) Technology  (https://www.vaines.org/tags/terraform) Terraform  (https://www.vaines.org/tags/time-tracking) Time tracking  (https://www.vaines.org/tags/visualization) Visualization  (https://www.vaines.org/tags/webapp) Webapp  (https://www.vaines.org/tags/word-game) Word game  (https://www.vaines.org/tags/writing) Writing            (https://www.vaines.org/) ()   (https://www.vaines.org/about/) About  (https://www.vaines.org/privacy/) Privacy  (https://buymeacoffee.com/u6zv4r45ct) Buy me a coffee    (https://linkedin.com/in/aidenvaines/)     (https://github.com/avaines)     (https://buymeacoffee.com/u6zv4r45ct)     (https://shop.vaines.org)     (https://www.npmjs.com/~aiden.vaines)        Copyright \u00a9 2025 - Aiden Vaines \u00b7 All rights reserved    (/widgets/carousel/auto-carousel.css) \u00d7   ()                  "
                ],
                "output": "htmltotext.txt",
                "pwd": "/data/archive/1774731388.657509",
                "schema": "ArchiveResult",
                "start_ts": "2026-03-28T20:58:09.317350+00:00",
                "status": "succeeded"
            }
        ],
        "media": [
            {
                "cmd": [
                    "/usr/local/bin/yt-dlp",
                    "--restrict-filenames",
                    "--trim-filenames",
                    "128",
                    "--write-description",
                    "--write-info-json",
                    "--write-annotations",
                    "--write-thumbnail",
                    "--no-call-home",
                    "--write-sub",
                    "--write-auto-subs",
                    "--convert-subs=srt",
                    "--yes-playlist",
                    "--continue",
                    "--no-abort-on-error",
                    "--ignore-errors",
                    "--geo-bypass",
                    "--add-metadata",
                    "--format=(bv*+ba/b)[filesize<=750m][filesize_approx<=?750m]/(bv*+ba/b)",
                    "--skip-download",
                    "--cache-dir=/data/yt-dlp-cache/",
                    "--cookies=/data/yt-dlp-cache/cookies.txt",
                    "--proxy=socks5://tor-socks-proxy:9150",
                    "--no-playlist",
                    "https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/"
                ],
                "cmd_version": "2024.10.7",
                "end_ts": "2026-03-28T20:58:19.236266+00:00",
                "index_texts": [],
                "output": "media/",
                "pwd": "/data/archive/1774731388.657509",
                "schema": "ArchiveResult",
                "start_ts": "2026-03-28T20:58:12.685904+00:00",
                "status": "succeeded"
            }
        ],
        "mercury": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/postlight-parser",
                    "https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/"
                ],
                "cmd_version": "2.2.3",
                "end_ts": "2026-03-28T20:58:08.596163+00:00",
                "index_texts": null,
                "output": "mercury/",
                "pwd": "/data/archive/1774731388.657509",
                "schema": "ArchiveResult",
                "start_ts": "2026-03-28T20:58:00.339456+00:00",
                "status": "succeeded"
            }
        ],
        "pdf": [],
        "readability": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/readability-extractor",
                    "/tmp/tmp7wsp_yvm",
                    "https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/"
                ],
                "cmd_version": "0.0.11",
                "end_ts": "2026-03-28T20:57:32.639716+00:00",
                "index_texts": [
                    "In March 2026, Trivy became the latest reminder that software supply chains are, at best, loosely held together with convention and trust.A typosquatting attack slipped malicious code into what looked like a legitimate dependency path. The post-mortems are worth reading, and they all converge on a single recommendation: pin your dependencies. In the GitHub Actions world, that usually translates to use commit SHAs, not tags.There\u2019s a widely held belief that pinning a GitHub Action to a commit SHA gives you immutability, its what Microsoft/GitHub are recommending, and its what Aqua are recommending. After all, a SHA is content-addressed. It cannot be moved. It cannot be re-tagged. It is, in theory, the most stable reference you can use. The problem with that line of thinking is that the resolution of that SHA is not scoped the way most people assume.\u00a0Specifically, GitHub Actions does not meaningfully validate that the commit SHA you reference belongs to the repository you think it does.Wait, what? No, thats not right\u2026I set up a deliberately small example to test this behaviour.A \u201clegitimate\u201d action: avaines/blog_gh_sha_pinning_actionA consuming application: avaines/blog_gh_sha_pinning_appThe application references the action in the usual way:uses: avaines/blog_gh_sha_pinning_action@<some-sha>\nWorking GitHub Action showing \u2018Hello World\u2019So far, so normal.Now introduce an attacker:Fork the action repository to aidenvaines-cgi/blog_gh_sha_pinning_actionAdd a malicious step (in my case, just printing output, but in reality this is where you exfiltrate all the fun stuff like secrets and personal data)Attacker changes to the GitHub Action in their fork adding malicious contentNext, create a pull request to the consuming application that\u00a0appears to simply bump the pinned SHA:Attacker raises a PRThe SHA used in the PR comes from the attacker-controlled fork of the action, despite it still being referenced as\u00a0avaines/blog_gh_sha_pinning_actionYou might reasonably assume one of the following safeguards exists:GitHub validates that the SHA belongs to avaines/blog_gh_sha_pinning_actionOr the workflow fails because the commit cannot be found in the specified repositoryNeither is true, and that is madnessThe workflow executes successfully!!!!!!!GitHub Actions running the malicious workflowGitHub resolves the commit SHA, finds a matching object, and executes it, regardless of which fork it originated from.From the platform\u2019s perspective, a fork is a separate repository with a shared object graph/history. When the runner resolves the reference, it ultimately looks up the commit in the Git object database; if that object exists and is reachable, it can be used regardless of which fork introduced it. A commit object is globally identifiable. If the SHA exists anywhere reachable, that is apparently sufficient.The result is that a\u00a0pull request can replace a pinned, trusted action with attacker-controlled code without changing the apparent repository reference.If the reviewer is scanning for obvious changes like owner, repo name, or tag they will see none.Only the SHA changes and with that comes a huge amount of assumed trust. I know that owner, and I know that repository, its just a version bump, and a minor one at that, with that comment next to the tag doing a lot of heavy lifting. We\u2019ve just spent the last few years training people to treat that as\u00a0best practice.GitHub PR DeltaA lot of the current guidance focuses on avoiding tags because they are mutable, which is true: tags can be moved, and relying on them introduces an entirely different risk. Github already has a \u2018Make tags immutable\u2019 feature, but it\u2019s optional, therefore, neither used nor can it be trusted as the owner (or attacker) could just disable it.Simply switching to SHA pinning does not eliminate the problem, in some respects it makes it worse. Tags are scoped to owner/repository because thats how they work. You could argue its harder to compromise that repository rather than hijack it through a forked repository and then writing actual changes to the repository. Whereas a commit object is content-addressed and can be reachable from multiple repositories that share history (e.g. forks)I believe the industry advice is a bit of an overcorrection, and we\u2019ve replaced one weak guarantee (mutable tags but scoped to repo) with another vastly worse idea in unscoped SHAs. Yes you should check, yes you should validate it, but tags are human readable, SHAs are not and if you ask yourself \u201cDo I always properly check?\u201d do you? because I can\u2019t say I do enough validation 100% of the time.Supply Chain WoesThe Trivy incident is not interesting because of the tool. Though it is the thing that\u2019s caused me a lot of bother over the last month, and its symptomatic of the constant supply chain threats we\u2019re seeing everywhere. Late last year NPM was basically a skip fire (https://www.wiz.io/blog/widespread-npm-supply-chain-attack-breaking-down-impact-scope-across-debug-chalk, https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack,\u00a0etc). We\u2019ve delegated so much behaviour to 3rd parties we can\u2019t, and shouldn\u2019t implicitly trust. The https://www.npmjs.com/package/is-odd package became a bit of a meme for this exact problem, where tool chains like NPM and GitHub Actions place re-usable custom modules/actions/libraries as an attractive off the shelf solution to solve common problems so you don\u2019t have to.Ironically with the rise of AI, it\u2019s now easier to just vibe code the same functionality yourself that use one of these off the shelf resources, and you can still know exactly as much about how security, your data, and to some extent the functionality works with strangely more ownership and a smaller attack service.GitHub Actions, in particular exacerbates this somewhat as workflows routinely execute third-party code, the secrets are implicitly available to those workflows and there are multiple ways to extract those at runtime with little audit or oversight. Then to top it all off SHAs are not human friendly and tags are not immutable so review processes tend to focus on what changed, not *where it came from. Which is all a fragile house of cards, sometimes I miss Jenkins.If \u201cuse SHAs\u201d is not sufficient, what is?At a minimum, we need to introduce provenance checks. I\u2019ve said above that SHA\u2019s are not human friendly like tags so theres a couple of things we can, and probably should be doing to validate. Obviously that the SHA or tag exists in the right repository and GitHub should enforce tag mutability in my opinion:We tend to describe these incidents as \u201csupply chain attacks\u201d, which is accurate but slightly misleading. It implies a complex and sophisticated multi-stage compromise by 1337 h4x0rz. In reality, the weakest link is often much simpler because humans are a bit shit sometimes. SHA pinning being touted as the solution is just security-theater rather than due diligence in an ecosystem that is not helping either."
                ],
                "output": "readability/",
                "pwd": "/data/archive/1774731388.657509",
                "schema": "ArchiveResult",
                "start_ts": "2026-03-28T20:57:23.013203+00:00",
                "status": "succeeded"
            }
        ],
        "screenshot": [],
        "singlefile": [],
        "title": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2026-03-28T20:57:21.477660+00:00",
                "index_texts": null,
                "output": "The Comforting Lie Of SHA Pinning",
                "pwd": "/data/archive/1774731388.657509",
                "schema": "ArchiveResult",
                "start_ts": "2026-03-28T20:57:21.215688+00:00",
                "status": "succeeded"
            }
        ],
        "wget": []
    },
    "icons": null,
    "is_archived": true,
    "is_static": false,
    "latest": {
        "archive_org": "https://web.archive.org/web/20260328205847/https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/",
        "dom": "output.html",
        "favicon": "favicon.ico",
        "git": null,
        "media": "media/",
        "pdf": null,
        "screenshot": null,
        "singlefile": null,
        "title": "The Comforting Lie Of SHA Pinning",
        "warc": null,
        "wget": null
    },
    "link_dir": "/data/archive/1774731388.657509",
    "newest_archive_date": "2026-03-28T20:58:19.405058+00:00",
    "num_failures": 0,
    "num_outputs": 9,
    "oldest_archive_date": "2026-03-28T20:56:35.893174+00:00",
    "path": "/posts/2026-03-24-the-comforting-lie-of-sha-pinning/",
    "schema": "Link",
    "scheme": "https",
    "snapshot_abid": "snp_01KMV3QSRZ08B7737E01BZWVYP",
    "snapshot_id": "015a6d10-e6d4-4ae0-bee2-978617fe6fd6",
    "sources": [
        "/data/sources/1774731387-import.txt"
    ],
    "tags": null,
    "tags_str": "",
    "timestamp": "1774731388.657509",
    "title": "The Comforting Lie Of SHA Pinning",
    "url": "https://www.vaines.org/posts/2026-03-24-the-comforting-lie-of-sha-pinning/"
}