<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Supply Chain Security Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/supply-chain-security/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/supply-chain-security/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Sat, 09 May 2026 18:42:14 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.4</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>Supply Chain Security Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/supply-chain-security/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<item>
		<title>CI/CD Secrets Exposure: How Supply Chain Attacks Target Your Pipeline</title>
		<link>https://linuxcent.com/cicd-secrets-exposure-supply-chain/</link>
					<comments>https://linuxcent.com/cicd-secrets-exposure-supply-chain/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Tue, 16 Jun 2026 02:00:00 +0000</pubDate>
				<category><![CDATA[Purple Team]]></category>
		<category><![CDATA[CI/CD]]></category>
		<category><![CDATA[CircleCI]]></category>
		<category><![CDATA[DevSecOps]]></category>
		<category><![CDATA[GitHub Actions]]></category>
		<category><![CDATA[Secrets Management]]></category>
		<category><![CDATA[Supply Chain Security]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1858</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 11</span> <span class="rt-label rt-postfix">minutes</span></span>CI/CD secrets exposure is structural, not behavioral. How CircleCI and GitHub Actions breaches happened and how pre-commit hooks plus secrets scanning close the path permanently.</p>
<p>The post <a href="https://linuxcent.com/cicd-secrets-exposure-supply-chain/">CI/CD Secrets Exposure: How Supply Chain Attacks Target Your Pipeline</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 11</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><a href="/what-is-purple-team-security/">What is purple team security</a> → <a href="/owasp-top-10-cloud-infrastructure/">OWASP Top 10 mapped to cloud infrastructure</a> → <a href="/cloud-security-breaches-2020-2025/">Cloud security breaches 2020–2025</a> → <a href="/broken-access-control-aws/">Broken access control in AWS</a> → <a href="/mfa-fatigue-attack/">MFA fatigue attacks</a> → <strong>CI/CD secrets exposure</strong></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li><strong>CI/CD secrets exposure</strong> is OWASP A08 + A02: credentials committed to repositories or stored in pipeline environment variables can be exfiltrated when the platform is compromised, and automated scanners find them within seconds of a public commit</li>
<li>The CircleCI breach (January 2023): an engineer&#8217;s laptop was compromised via malware → session token stolen → attacker accessed CircleCI production systems → all customer environment variables (AWS keys, GitHub tokens, SSH keys) exfiltrated</li>
<li>The structural problem: long-lived credentials stored in a CI/CD platform are only as secure as the platform itself — if the platform is compromised, all stored secrets are compromised</li>
<li>The structural fix: <a href="/oidc-workload-identity-eliminate-cloud-access-keys/">OIDC workload identity</a> replaces stored credentials with short-lived tokens issued at job runtime — there is nothing to exfiltrate</li>
<li>Pre-commit hooks and CI-layer secret scanning are detection layers, not structural fixes — they catch accidents, not determined attackers</li>
<li>Automated secret scanners (TruffleHog, Gitleaks) find credentials in public repos within 60–90 seconds of commit</li>
</ul>
<hr />
<blockquote>
<p><strong>OWASP Mapping:</strong> A08 Software and Data Integrity Failures — build pipeline integrity. A02 Cryptographic Failures — secrets stored in ways that allow exfiltration.</p>
</blockquote>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<pre><code class="" data-line="">┌─────────────────────────────────────────────────────────────────────┐
│                  CI/CD SECRETS ATTACK SURFACE                       │
│                                                                     │
│   VECTOR 1: COMMITTED TO VCS                                        │
│   Developer ── git commit ──&#x25b6; .env with AWS_SECRET_KEY              │
│   Automated scanner ──────&#x25b6;  clones within 60 seconds              │
│   Attacker ───────────────&#x25b6;  accesses AWS before dev notices        │
│                                                                     │
│   VECTOR 2: STORED IN CI/CD PLATFORM                                │
│   DevOps ─── configures ──&#x25b6;  AWS_ACCESS_KEY_ID in CircleCI         │
│   Attacker compromises CircleCI → exfiltrates all org env vars      │
│                                                                     │
│   VECTOR 3: IN CONTAINER/PROCESS ENV                                │
│   kubectl exec / docker inspect ──&#x25b6;  printenv shows credentials     │
│   Anyone with container exec access = credential access             │
│                                                                     │
│   VECTOR 4: IN BUILD ARTIFACTS / LOGS                               │
│   Build log: &quot;Using token: ghp_xxxxxxxxxxxx...&quot; → exposed in log   │
│                                                                     │
│   ═══════════════════════════════════════════════════════           │
│   STRUCTURAL FIX: OIDC WORKLOAD IDENTITY                            │
│   No stored credential → nothing to commit, nothing to exfiltrate  │
│   CI job requests token at runtime → 1-hour TTL → expired          │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
</code></pre>
<p><strong>CI/CD secrets exposure</strong> is not primarily a developer discipline problem — it is a structural problem. When credentials are stored in a CI/CD platform, in environment variables, or in version control, the only question is when they will be exposed, not whether. The structural answer replaces stored credentials with dynamically issued, short-lived tokens that cannot be exfiltrated because they don&#8217;t persist.</p>
<hr />
<h2 id="the-25-minute-compromise-how-automated-scanning-works-against-you">The 25-Minute Compromise: How Automated Scanning Works Against You</h2>
<p>At 2:47 AM, a developer committed a <code class="" data-line="">.env</code> file to a public GitHub repository. It contained:</p>
<pre><code class="" data-line="">DATABASE_URL=postgres://admin:prod_p@ssw0rd@db.internal.company.com:5432/customers
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
STRIPE_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
</code></pre>
<p>At 2:48 AM — 60 seconds later — an automated scanner had cloned the repository. These scanners run continuously against GitHub&#8217;s public event stream, looking for credential patterns in new commits, new files, and new repository forks.</p>
<p>At 3:12 AM — 25 minutes after the commit — the database started receiving unusual queries. The automated scanning infrastructure is not operated by individuals manually watching for leaks. It is fully automated: pattern match → clone → test credential validity → if valid, begin exploitation or sell.</p>
<p>GitHub now runs its own secret scanning and immediately invalidates some credential types (GitHub tokens, AWS IAM keys partnered with AWS) when detected in public repositories. This covers a subset of credential types. It does not cover database passwords, service-specific tokens for non-partnered services, or private repository commits that become public via fork.</p>
<hr />
<h2 id="the-circleci-breach-platform-level-credential-exfiltration">The CircleCI Breach: Platform-Level Credential Exfiltration</h2>
<p>The CircleCI breach (January 2023) is the definitive example of CI/CD platform-level secrets exposure. The attack chain:</p>
<pre><code class="" data-line="">1. CircleCI engineer&#039;s laptop compromised via malware (initial vector not fully disclosed)
2. Malware steals a 2FA-authenticated SSO session token
3. Session token valid, not expired
4. Attacker uses session token to authenticate to CircleCI internal systems
5. From internal access, attacker reaches production database
6. Production database contains encrypted customer secrets (environment variables)
7. Database also contains the encryption keys (in accessible internal system)
8. Attacker exfiltrates: encrypted secrets + encryption keys = plaintext secrets
</code></pre>
<p><strong>What was stored in CircleCI environment variables by customers:</strong><br />
&#8211; AWS IAM access key ID and secret access key pairs<br />
&#8211; GitHub personal access tokens and OAuth tokens<br />
&#8211; DockerHub credentials<br />
&#8211; SSH private keys (for deployment access)<br />
&#8211; Heroku API keys<br />
&#8211; Stripe, Twilio, SendGrid API keys<br />
&#8211; Internal service account credentials</p>
<p>CircleCI could not determine which customer secrets were accessed and which were not — they notified all customers to rotate all credentials stored in their system.</p>
<p><strong>The scale of the blast radius:</strong> Any customer who had stored long-lived credentials in CircleCI environment variables was potentially compromised. The credential was valid. The CircleCI platform&#8217;s encryption only protected against offline attacks — an attacker with internal database access and access to the key management system had everything needed to decrypt.</p>
<hr />
<h2 id="red-phase-enumerating-secrets-exposure-in-your-pipeline">Red Phase: Enumerating Secrets Exposure in Your Pipeline</h2>
<h3 id="scanning-repositories-for-committed-secrets">Scanning Repositories for Committed Secrets</h3>
<pre><code class="" data-line=""># Install: pip install trufflehog3 or use the Docker image
docker run --rm \
  -v &quot;$(pwd):/repo&quot; \
  trufflesecurity/trufflehog:latest \
  git file:///repo \
  --json \
  --only-verified \
  2&gt;/dev/null | \
  jq &#039;{
    file: .SourceMetadata.Data.Git.file,
    commit: .SourceMetadata.Data.Git.commit,
    detector: .DetectorName,
    verified: .Verified,
    line: .SourceMetadata.Data.Git.line
  }&#039;
</code></pre>
<pre><code class="" data-line=""># Gitleaks: alternative scanner with SARIF output for CI integration
gitleaks detect \
  --source . \
  --report-format sarif \
  --report-path gitleaks-report.sarif \
  --verbose

# Or: scan entire git history (catches secrets that were committed then deleted)
gitleaks detect \
  --source . \
  --log-opts=&quot;--all&quot; \
  --report-format json \
  --report-path gitleaks-history.json
</code></pre>
<pre><code class="" data-line=""># Scan a specific GitHub organization&#039;s public repositories
# (test your own org before red team exercises)
trufflehog github \
  --org your-github-org \
  --token &quot;${GITHUB_TOKEN}&quot; \
  --json \
  --only-verified \
  2&gt;/dev/null | \
  jq &#039;{
    repo: .SourceMetadata.Data.Github.repository,
    file: .SourceMetadata.Data.Github.file,
    detector: .DetectorName,
    verified: .Verified
  }&#039;
</code></pre>
<h3 id="enumerating-secrets-in-cicd-platform-environment-variables">Enumerating Secrets in CI/CD Platform Environment Variables</h3>
<pre><code class="" data-line=""># GitHub Actions: list secrets defined in a repository
# (shows names only — values are not returned by API, but names reveal what&#039;s stored)
curl -H &quot;Authorization: Bearer ${GITHUB_TOKEN}&quot; \
  -H &quot;Accept: application/vnd.github+json&quot; \
  &quot;https://api.github.com/repos/your-org/your-repo/actions/secrets&quot; | \
  jq &#039;.secrets[] | {name: .name, updated: .updated_at}&#039;

# GitHub Actions: list organization-level secrets
curl -H &quot;Authorization: Bearer ${GITHUB_TOKEN}&quot; \
  -H &quot;Accept: application/vnd.github+json&quot; \
  &quot;https://api.github.com/orgs/your-org/actions/secrets&quot; | \
  jq &#039;.secrets[] | {name: .name, visibility: .visibility, updated: .updated_at}&#039;
</code></pre>
<pre><code class="" data-line=""># Check for credentials in running pod environment variables (Kubernetes)
# This is what an attacker with kubectl exec access would do
kubectl get pods -A -o json | \
  jq -r &#039;.items[] | 
    .metadata.namespace + &quot;/&quot; + .metadata.name + &quot;: &quot; + 
    ([.spec.containers[].env[]? | 
      select(.name | test(&quot;KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|API&quot;; &quot;i&quot;)) |
      .name
    ] | join(&quot;, &quot;))&#039; | \
  grep -v &quot;: $&quot;  # Only show pods with matching env var names
</code></pre>
<h3 id="testing-whether-aws-keys-in-cicd-are-over-permissioned">Testing Whether AWS Keys in CI/CD Are Over-Permissioned</h3>
<pre><code class="" data-line=""># If you find an AWS access key in a scan — test its permissions
# (on your own test account&#039;s keys only)
aws sts get-caller-identity
# Returns: account, user/role ARN, caller ID

# What can this key do?
aws iam simulate-principal-policy \
  --policy-source-arn $(aws sts get-caller-identity --query Arn --output text) \
  --action-names &quot;s3:*&quot; &quot;ec2:*&quot; &quot;iam:*&quot; &quot;sts:AssumeRole&quot; \
  --query &#039;EvaluationResults[?EvalDecision==`allowed`].EvalActionName&#039; \
  --output text
</code></pre>
<hr />
<h2 id="blue-phase-detection-across-the-secret-lifecycle">Blue Phase: Detection Across the Secret Lifecycle</h2>
<h3 id="github-secret-scanning-alerts">GitHub Secret Scanning Alerts</h3>
<pre><code class="" data-line=""># List secret scanning alerts in a repository via GitHub API
curl -H &quot;Authorization: Bearer ${GITHUB_TOKEN}&quot; \
  -H &quot;Accept: application/vnd.github+json&quot; \
  &quot;https://api.github.com/repos/your-org/your-repo/secret-scanning/alerts?state=open&quot; | \
  jq &#039;.[] | {
    type: .secret_type,
    state: .state,
    created: .created_at,
    url: .html_url
  }&#039;
</code></pre>
<h3 id="cloudtrail-detecting-api-activity-from-cicd-credentials">CloudTrail: Detecting API Activity from CI/CD Credentials</h3>
<p>When a CI/CD credential is used by an attacker, the CloudTrail events show unusual patterns:</p>
<pre><code class="" data-line=""># Find API calls from CI/CD credentials outside normal working hours
# or from unexpected IPs (attacker using the stolen key)
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=Username,AttributeValue=ci-deploy-user \
  --start-time &quot;$(date -d &#039;7 days ago&#039; --iso-8601=seconds)&quot; \
  --query &#039;Events[].{Time:EventTime,Name:EventName,IP:CloudTrailEvent}&#039; \
  --output json | \
  jq &#039;.[] | {
    time: .Time,
    event: .Name,
    ip: (.IP | fromjson | .sourceIPAddress),
    user_agent: (.IP | fromjson | .userAgent)
  }&#039; | \
  jq &#039;select(.ip | test(&quot;^(10\\.|172\\.(1[6-9]|2[0-9]|3[01])\\.|192\\.168\\.)&quot;) | not)&#039;
  # Filter: events from non-RFC1918 IPs (outside your known CI/CD IP ranges)
</code></pre>
<h3 id="siem-query-credential-used-in-multiple-regions-simultaneously">SIEM Query: Credential Used in Multiple Regions Simultaneously</h3>
<p>A credential being used from multiple regions simultaneously is a strong indicator of compromise:</p>
<pre><code class="" data-line="">-- Athena query against CloudTrail logs
-- Detect: same access key used from multiple regions in same hour
SELECT
  userIdentity.accessKeyId,
  userIdentity.userName,
  COUNT(DISTINCT awsRegion) as region_count,
  ARRAY_AGG(DISTINCT awsRegion) as regions,
  COUNT(DISTINCT sourceIPAddress) as ip_count,
  ARRAY_AGG(DISTINCT sourceIPAddress) as source_ips,
  DATE_TRUNC(&#039;hour&#039;, from_iso8601_timestamp(eventTime)) as hour
FROM cloudtrail_logs
WHERE
  userIdentity.type = &#039;IAMUser&#039;
  AND from_iso8601_timestamp(eventTime) &gt; current_timestamp - interval &#039;7&#039; day
GROUP BY
  userIdentity.accessKeyId,
  userIdentity.userName,
  DATE_TRUNC(&#039;hour&#039;, from_iso8601_timestamp(eventTime))
HAVING COUNT(DISTINCT awsRegion) &gt; 2
ORDER BY region_count DESC;
</code></pre>
<h3 id="guardduty-credential-exfiltration-indicators">GuardDuty: Credential Exfiltration Indicators</h3>
<pre><code class="" data-line=""># GuardDuty findings relevant to CI/CD credential compromise
DETECTOR_ID=$(aws guardduty list-detectors --query &#039;DetectorIds[0]&#039; --output text)

aws guardduty list-findings \
  --detector-id &quot;${DETECTOR_ID}&quot; \
  --finding-criteria &#039;{
    &quot;Criterion&quot;: {
      &quot;type&quot;: {
        &quot;Equals&quot;: [
          &quot;UnauthorizedAccess:IAMUser/TorIPCaller&quot;,
          &quot;UnauthorizedAccess:IAMUser/MaliciousIPCaller&quot;,
          &quot;Discovery:IAMUser/AnomalousBehavior&quot;,
          &quot;Exfiltration:IAMUser/AnomalousBehavior&quot;,
          &quot;CredentialAccess:IAMUser/AnomalousBehavior&quot;
        ]
      }
    }
  }&#039; \
  --query &#039;FindingIds&#039; --output text | \
  xargs -n 10 aws guardduty get-findings \
    --detector-id &quot;${DETECTOR_ID}&quot; \
    --finding-ids | \
  jq &#039;.Findings[] | {type: .Type, user: .Resource.AccessKeyDetails.UserName, severity: .Severity}&#039;
</code></pre>
<hr />
<h2 id="purple-phase-the-structural-fix">Purple Phase: The Structural Fix</h2>
<h3 id="fix-1-oidc-workload-identity-eliminate-stored-credentials">Fix 1: OIDC Workload Identity — Eliminate Stored Credentials</h3>
<p>This is the structural solution. Instead of storing an AWS IAM access key in your CI/CD platform, the CI/CD job authenticates to AWS using an OIDC token issued by the CI/CD provider. AWS validates the token against a pre-configured trust policy and issues temporary credentials valid for the duration of the job.</p>
<p>The <a href="/oidc-workload-identity-eliminate-cloud-access-keys/">OIDC workload identity approach eliminates static cloud access keys</a> entirely — there is no secret to commit, no secret to exfiltrate from the CI/CD platform, and no long-lived credential to rotate on breach.</p>
<p><strong>GitHub Actions with AWS OIDC — complete setup:</strong></p>
<pre><code class="" data-line=""># .github/workflows/deploy.yml
name: Deploy to AWS

on:
  push:
    branches: [main]

permissions:
  id-token: write   # Required for OIDC token request
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy-role
          role-session-name: github-actions-${{ github.run_id }}
          aws-region: us-east-1
          # No AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY needed

      - name: Deploy
        run: aws s3 sync ./dist s3://your-bucket/
</code></pre>
<p><strong>AWS IAM trust policy for GitHub Actions OIDC:</strong></p>
<pre><code class="" data-line="">{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {
        &quot;Federated&quot;: &quot;arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com&quot;
      },
      &quot;Action&quot;: &quot;sts:AssumeRoleWithWebIdentity&quot;,
      &quot;Condition&quot;: {
        &quot;StringEquals&quot;: {
          &quot;token.actions.githubusercontent.com:aud&quot;: &quot;sts.amazonaws.com&quot;
        },
        &quot;StringLike&quot;: {
          &quot;token.actions.githubusercontent.com:sub&quot;: &quot;repo:your-org/your-repo:ref:refs/heads/main&quot;
        }
      }
    }
  ]
}
</code></pre>
<pre><code class="" data-line=""># Create the OIDC provider in AWS (one-time setup)
aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list &quot;6938fd4d98bab03faadb97b34396831e3780aea1&quot;

# Create the IAM role with the trust policy above
aws iam create-role \
  --role-name github-actions-deploy-role \
  --assume-role-policy-document file://github-actions-trust-policy.json

# Attach a least-privilege policy to the role
aws iam attach-role-policy \
  --role-name github-actions-deploy-role \
  --policy-arn arn:aws:iam::123456789012:policy/deploy-policy
</code></pre>
<h3 id="fix-2-pre-commit-hooks-catch-accidents-before-they-reach-vcs">Fix 2: Pre-Commit Hooks — Catch Accidents Before They Reach VCS</h3>
<p>Pre-commit hooks don&#8217;t stop a determined attacker. They catch accidents — the developer who forgets to move a <code class="" data-line="">.env</code> file to <code class="" data-line="">.gitignore</code> before staging all files.</p>
<pre><code class="" data-line=""># Install pre-commit framework
pip install pre-commit

# .pre-commit-config.yaml in your repository root
cat &gt; .pre-commit-config.yaml &lt;&lt; &#039;EOF&#039;
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.4
    hooks:
      - id: gitleaks
        name: Detect hardcoded secrets
        entry: gitleaks protect --staged --redact --verbose
        language: golang
        pass_filenames: false

  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: detect-private-key
      - id: check-added-large-files
        args: [&#039;--maxkb=1000&#039;]
EOF

# Install the hooks in the local repository
pre-commit install

# Test against staged files
pre-commit run --all-files
</code></pre>
<h3 id="fix-3-ci-layer-secret-scanning-block-before-merge">Fix 3: CI-Layer Secret Scanning — Block Before Merge</h3>
<pre><code class="" data-line=""># GitHub Actions: secret scanning as a required status check
# .github/workflows/secret-scan.yml
name: Secret Scan

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  secret-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Full history for git log scanning

      - name: Run TruffleHog
        uses: trufflesecurity/trufflehog@main
        with:
          path: ./
          base: ${{ github.event.repository.default_branch }}
          head: HEAD
          extra_args: --only-verified --json
</code></pre>
<pre><code class="" data-line=""># GitLab CI: secret detection built-in template
include:
  - template: Security/Secret-Detection.gitlab-ci.yml

secret_detection:
  stage: test
  variables:
    SECRET_DETECTION_HISTORIC_SCAN: &quot;true&quot;  # Scan full history
</code></pre>
<h3 id="fix-4-audit-and-rotate-existing-cicd-platform-secrets">Fix 4: Audit and Rotate Existing CI/CD Platform Secrets</h3>
<p>After implementing OIDC, the migration path for existing stored credentials:</p>
<pre><code class="" data-line="">#!/bin/bash
# Purple Team EP06 — CI/CD Secrets Migration Audit
# Identifies AWS IAM keys stored in CI/CD that should be replaced with OIDC

echo &quot;=== AWS IAM Keys Potentially Stored in CI/CD ===&quot;
echo &quot;--- Keys not used from expected CI/CD IPs in last 30 days ---&quot;

# Get all IAM access keys
aws iam list-users --query &#039;Users[].UserName&#039; --output text | tr &#039;\t&#039; &#039;\n&#039; | \
  while read user; do
    keys=$(aws iam list-access-keys --user-name &quot;$user&quot; \
      --query &#039;AccessKeyMetadata[?Status==`Active`].{Key:AccessKeyId,Created:CreateDate}&#039; \
      --output json)

    if [ &quot;$(echo &quot;$keys&quot; | jq length)&quot; -gt 0 ]; then
      echo &quot;&quot;
      echo &quot;User: $user&quot;
      echo &quot;$keys&quot; | jq -r &#039;.[] | &quot;  Key: &quot; + .Key + &quot; | Created: &quot; + .Created&#039;

      # Check last used
      echo &quot;$keys&quot; | jq -r &#039;.[].Key&#039; | while read key_id; do
        last_used=$(aws iam get-access-key-last-used --access-key-id &quot;$key_id&quot; \
          --query &#039;AccessKeyLastUsed.{Date:LastUsedDate,Service:ServiceName,Region:Region}&#039; \
          --output json)
        echo &quot;  Last used: $(echo &quot;$last_used&quot; | jq -r &#039;.Date // &quot;Never&quot;&#039;) | Service: $(echo &quot;$last_used&quot; | jq -r &#039;.Service // &quot;N/A&quot;&#039;)&quot;
      done
    fi
  done

echo &quot;&quot;
echo &quot;=== MIGRATION CHECKLIST ===&quot;
echo &quot;  1. For each CI/CD IAM key above:&quot;
echo &quot;     a. Identify which CI/CD platform uses it&quot;
echo &quot;     b. Set up OIDC trust policy for that platform&quot;
echo &quot;     c. Update pipeline to use OIDC (no stored key)&quot;
echo &quot;     d. Disable and then delete the IAM key&quot;
echo &quot;     e. Verify pipelines still work&quot;
</code></pre>
<hr />
<h2 id="run-this-in-your-own-environment-secrets-exposure-audit">Run This in Your Own Environment: Secrets Exposure Audit</h2>
<pre><code class="" data-line="">#!/bin/bash
# Purple Team EP06 — CI/CD Secrets Exposure Audit
# Run from your workstation with git and trufflehog installed

echo &quot;=== 1. Scan Local Repository for Committed Secrets ===&quot;
if command -v trufflehog &gt; /dev/null 2&gt;&amp;1; then
  trufflehog git file://$(pwd) --only-verified --json 2&gt;/dev/null | \
    jq &#039;{file: .SourceMetadata.Data.Git.file, detector: .DetectorName}&#039; || \
    echo &quot;  No verified secrets found in git history&quot;
else
  echo &quot;  Install trufflehog: pip install trufflehog3&quot;
fi

echo &quot;&quot;
echo &quot;=== 2. Check for .env Files in Git History ===&quot;
git log --all --full-history -- &quot;*.env&quot; &quot;**/.env&quot; &quot;.env.*&quot; 2&gt;/dev/null | \
  grep &quot;^commit&quot; | head -5 | \
  while read _ commit; do
    echo &quot;  .env file committed: $commit&quot;
    git show &quot;$commit&quot; --stat | head -3
  done

echo &quot;&quot;
echo &quot;=== 3. Check Running Pods for Credential Env Vars (Kubernetes) ===&quot;
if command -v kubectl &gt; /dev/null 2&gt;&amp;1; then
  kubectl get pods -A -o json 2&gt;/dev/null | \
    jq -r &#039;.items[] | 
      .metadata.namespace + &quot;/&quot; + .metadata.name + &quot;: &quot; + 
      ([.spec.containers[].env[]? | 
        select(.name | test(&quot;KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL&quot;; &quot;i&quot;)) |
        .name
      ] | join(&quot;, &quot;))&#039; | \
    grep -v &quot;: $&quot; | head -20
else
  echo &quot;  kubectl not found&quot;
fi

echo &quot;&quot;
echo &quot;=== 4. GitHub Actions Secrets Inventory ===&quot;
if [ -n &quot;${GITHUB_TOKEN}&quot; ]; then
  REPO=&quot;your-org/your-repo&quot;  # Update this
  curl -s -H &quot;Authorization: Bearer ${GITHUB_TOKEN}&quot; \
    -H &quot;Accept: application/vnd.github+json&quot; \
    &quot;https://api.github.com/repos/${REPO}/actions/secrets&quot; | \
    jq &#039;.secrets[] | {name: .name, updated: .updated_at}&#039;
else
  echo &quot;  Set GITHUB_TOKEN to enumerate repository secrets&quot;
fi
</code></pre>
<hr />
<h2 id="common-mistakes-when-addressing-cicd-secrets-exposure"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Common Mistakes When Addressing CI/CD Secrets Exposure</h2>
<p><strong>Treating secret scanning as the primary control.</strong> TruffleHog and Gitleaks catch what gets committed. They do not prevent the CircleCI attack class — an attacker who compromises the CI/CD platform itself bypasses all scanning controls. Scanning is detection; OIDC workload identity is prevention.</p>
<p><strong>Rotating compromised keys without checking CloudTrail for use.</strong> When a secret is exposed, the first question is not &#8220;rotate it&#8221; — it is &#8220;was it used?&#8221; Check CloudTrail for any API activity from the key between the suspected exposure time and the rotation. If the key was used, you have an active incident, not just a credential rotation task.</p>
<p><strong>Using OIDC trust policies that are too broad.</strong> The GitHub Actions OIDC trust policy in the fix section uses a <code class="" data-line="">StringLike</code> condition on the <code class="" data-line="">sub</code> claim to scope to a specific repository and branch. If you use <code class="" data-line="">StringLike: &quot;*&quot;</code> instead, any GitHub Actions job in any repository can assume your role. Always scope OIDC trust policies to the specific repository, branch, and environment that needs the access.</p>
<p><strong>Not scanning git history — only the working tree.</strong> Secrets that were committed and then deleted are still in git history. <code class="" data-line="">git rm</code> removes the file from the working tree but not from the object store. TruffleHog and Gitleaks scan history by default when given the <code class="" data-line="">--all</code> flag. Scanning only the current working tree misses all historical exposures.</p>
<p><strong>Forgetting third-party GitHub Actions.</strong> The supply chain attack surface includes the Actions you reference in your workflows. An Action pinned to a mutable tag (<code class="" data-line="">@main</code>, <code class="" data-line="">@v1</code>) can be changed by the maintainer. Pin to a specific commit SHA and verify the Action&#8217;s provenance.</p>
<pre><code class="" data-line=""># Vulnerable: mutable tag
- uses: aws-actions/configure-aws-credentials@v4

# Secure: pinned SHA
- uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e831c1e4c763fe4
</code></pre>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<table>
<thead>
<tr>
<th>Secret Storage Pattern</th>
<th>Risk Level</th>
<th>Structural Fix</th>
</tr>
</thead>
<tbody>
<tr>
<td>.env file committed to public repo</td>
<td>Critical</td>
<td>Pre-commit hook + OIDC</td>
</tr>
<tr>
<td>.env file committed to private repo</td>
<td>High</td>
<td>Git history purge + pre-commit hook + OIDC</td>
</tr>
<tr>
<td>Long-lived key in CI/CD env var</td>
<td>High</td>
<td>OIDC workload identity</td>
</tr>
<tr>
<td>Long-lived key in K8s Secret</td>
<td>High</td>
<td>Pod identity / IRSA / Workload Identity</td>
</tr>
<tr>
<td>Secret in build log output</td>
<td>Medium</td>
<td>Mask secrets in CI configuration</td>
</tr>
<tr>
<td>Secret in container env var</td>
<td>Medium</td>
<td>Vault agent / CSI secrets driver</td>
</tr>
<tr>
<td>Key referenced via AWS Secrets Manager</td>
<td>Low (if scoped)</td>
<td>Use for remaining static secrets</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li><strong>CI/CD secrets exposure</strong> is structural: long-lived credentials in a CI/CD platform are only as secure as that platform — the CircleCI breach proved that encryption alone is insufficient if the attacker can access the keys</li>
<li>Automated secret scanners find publicly committed credentials within 60–90 seconds — rotation must happen faster than that or assume compromise</li>
<li>Pre-commit hooks and CI secret scanning catch accidents; they do not prevent determined attackers who compromise the platform itself</li>
<li><a href="/oidc-workload-identity-eliminate-cloud-access-keys/">OIDC workload identity</a> is the structural fix: no stored credential means no credential to exfiltrate</li>
<li>When rotating a compromised key, check CloudTrail for usage between exposure and rotation before closing the incident</li>
<li>OIDC trust policies must be scoped to specific repositories and branches — a wildcard trust policy recreates the exposure in a different form</li>
<li>Pin third-party GitHub Actions to commit SHAs, not mutable tags — mutable tags are a supply chain attack surface</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP07 covers SSRF to cloud metadata: how an SSRF vulnerability in any application layer becomes a straight line to IAM credentials when IMDSv2 is not enforced. The Capital One breach anatomy — WAF SSRF → EC2 metadata → IAM role credentials → 100 million S3 records — in full technical detail, with the simulation commands and the one-line enforcement fix. If you&#8217;ve addressed identity and secrets, the network attack paths are where EP07 through EP10 focus.</p>
<p>Get EP07 in your inbox when it publishes → <a href="#subscribe">subscribe at linuxcent.com</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fcicd-secrets-exposure-supply-chain%2F&amp;linkname=CI%2FCD%20Secrets%20Exposure%3A%20How%20Supply%20Chain%20Attacks%20Target%20Your%20Pipeline" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fcicd-secrets-exposure-supply-chain%2F&amp;linkname=CI%2FCD%20Secrets%20Exposure%3A%20How%20Supply%20Chain%20Attacks%20Target%20Your%20Pipeline" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fcicd-secrets-exposure-supply-chain%2F&amp;linkname=CI%2FCD%20Secrets%20Exposure%3A%20How%20Supply%20Chain%20Attacks%20Target%20Your%20Pipeline" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fcicd-secrets-exposure-supply-chain%2F&amp;linkname=CI%2FCD%20Secrets%20Exposure%3A%20How%20Supply%20Chain%20Attacks%20Target%20Your%20Pipeline" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fcicd-secrets-exposure-supply-chain%2F&amp;linkname=CI%2FCD%20Secrets%20Exposure%3A%20How%20Supply%20Chain%20Attacks%20Target%20Your%20Pipeline" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fcicd-secrets-exposure-supply-chain%2F&amp;linkname=CI%2FCD%20Secrets%20Exposure%3A%20How%20Supply%20Chain%20Attacks%20Target%20Your%20Pipeline" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fcicd-secrets-exposure-supply-chain%2F&amp;linkname=CI%2FCD%20Secrets%20Exposure%3A%20How%20Supply%20Chain%20Attacks%20Target%20Your%20Pipeline" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fcicd-secrets-exposure-supply-chain%2F&#038;title=CI%2FCD%20Secrets%20Exposure%3A%20How%20Supply%20Chain%20Attacks%20Target%20Your%20Pipeline" data-a2a-url="https://linuxcent.com/cicd-secrets-exposure-supply-chain/" data-a2a-title="CI/CD Secrets Exposure: How Supply Chain Attacks Target Your Pipeline"></a></p><p>The post <a href="https://linuxcent.com/cicd-secrets-exposure-supply-chain/">CI/CD Secrets Exposure: How Supply Chain Attacks Target Your Pipeline</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/cicd-secrets-exposure-supply-chain/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1858</post-id>	</item>
		<item>
		<title>Security Hardens: Supply Chain, Pod Security, and the API Cleanup (2020–2022)</title>
		<link>https://linuxcent.com/kubernetes-pod-security-supply-chain/</link>
					<comments>https://linuxcent.com/kubernetes-pod-security-supply-chain/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Wed, 01 Apr 2026 19:31:04 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Cloud Native]]></category>
		<category><![CDATA[DevSecOps]]></category>
		<category><![CDATA[Kubernetes Security]]></category>
		<category><![CDATA[Pod Security]]></category>
		<category><![CDATA[Sigstore]]></category>
		<category><![CDATA[Supply Chain Security]]></category>
		<guid isPermaLink="false">https://linuxcent.com/kubernetes-pod-security-supply-chain/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span>PSP was deprecated. Pod Security Standards replaced it. Sigstore arrived. Trace how v1.19–v1.23 hardened the Kubernetes supply chain — and what SolarWinds forced the industry to do.</p>
<p>The post <a href="https://linuxcent.com/kubernetes-pod-security-supply-chain/">Security Hardens: Supply Chain, Pod Security, and the API Cleanup (2020–2022)</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;<br />
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;<br />
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;<br />
    font-size:.88em;line-height:1.6;border-left:4px solid #555}<br />
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}<br />
pre code{background:transparent;padding:0;color:inherit}<br />
pre[data-lang="bash"],pre[data-lang="sh"],<br />
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}<br />
pre[data-lang="yaml"],pre[data-lang="json"],<br />
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}<br />
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],<br />
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}<br />
pre[data-lang="text"],pre[data-lang="output"],<br />
pre[data-lang="console"]{border-left-color:#888}<br />
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;<br />
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;<br />
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;<br />
    transition:opacity .15s,background .15s;line-height:1.6}<br />
pre:hover .lc-copy-btn{opacity:1}<br />
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}<br />
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}<br />
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;<br />
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;<br />
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}<br />
pre:hover .lc-lang-badge{opacity:1}<br />
table{border-collapse:collapse;width:100%;margin:16px 0}<br />
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}<br />
th{background:#f0f0f0;font-weight:600}<br />
tr:nth-child(even){background:#fafafa}<br />
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<hr />
<h2 id="introduction">Introduction</h2>
<p>The 2020–2022 period redefined what &#8220;secure Kubernetes&#8221; meant. A global pandemic moved workloads to cloud-native infrastructure faster than security practices could follow. SolarWinds happened. Log4Shell happened. The software supply chain became a crisis.</p>
<p>At the same time, the Kubernetes project was doing something it had been reluctant to do: removing APIs and features, including PodSecurityPolicy — the primary security primitive that most enterprise clusters depended on. The replacement was simpler, but the migration was not.</p>
<hr />
<h2 id="kubernetes-119-lts-behavior-ingress-stable-august-2020">Kubernetes 1.19 — LTS Behavior, Ingress Stable (August 2020)</h2>
<p>1.19 extended the support window to one year (from nine months). This was an acknowledgment that enterprise organizations couldn&#8217;t upgrade four times per year — a common complaint from operations teams.</p>
<ul>
<li><strong>Ingress</strong> graduated to stable: <code class="" data-line="">networking.k8s.io/v1</code> — after years as a beta resource, Ingress finally had a stable API</li>
<li><strong>Immutable ConfigMaps and Secrets</strong> to beta: Configuration protection becomes broadly available</li>
<li><strong>EndpointSlices</strong> to GA: The replacement for Endpoints — shards pod-to-service mappings to avoid the single large Endpoints object that caused control plane stress at scale (10,000+ endpoints for a single service)</li>
<li><strong>Structured logging</strong> (alpha): Machine-parseable log output from Kubernetes control plane components — a prerequisite for reliable SIEM integration</li>
</ul>
<pre><code class="" data-line=""># EndpointSlice: distributed representation of service endpoints
kubectl get endpointslices -n production -l kubernetes.io/service-name=api-service
NAME                  ADDRESSTYPE   PORTS   ENDPOINTS                                   AGE
api-service-abc12     IPv4          8080    10.0.1.5,10.0.1.6,10.0.1.7 + 47 more...   2d
api-service-def34     IPv4          8080    10.0.2.1,10.0.2.2,10.0.2.3 + 47 more...   2d
</code></pre>
<hr />
<h2 id="kubernetes-120-dockershim-deprecated-december-2020">Kubernetes 1.20 — Dockershim Deprecated (December 2020)</h2>
<p>The announcement in 1.20 that the Docker shim was deprecated caused more panic than any previous Kubernetes deprecation. The message was misread by many as &#8220;Kubernetes is dropping Docker support&#8221; — the PR catastrophe that followed required the Kubernetes blog to publish a dedicated clarification post.</p>
<p>The reality: <strong>Docker-built images continued to work on Kubernetes.</strong> What was being removed was the code in the kubelet that talked directly to Docker&#8217;s daemon using a non-standard interface, rather than through the Container Runtime Interface (CRI). Docker images conform to the OCI (Open Container Initiative) image specification — they run on any CRI-compliant runtime.</p>
<p>The migration path:<br />
&#8211; <strong>containerd</strong>: The runtime that Docker itself used internally. Moving to containerd meant removing the Docker layer entirely — the kubelet talks directly to containerd via CRI<br />
&#8211; <strong>CRI-O</strong>: An OCI-focused runtime designed specifically for Kubernetes, minimal and purpose-built</p>
<pre><code class="" data-line=""># Before (Docker socket): kubelet → dockershim → Docker daemon → containerd → runc
# After (direct CRI):     kubelet → containerd → runc
#                    or:  kubelet → CRI-O → runc

# Check runtime in use on a node
kubectl get node worker-1 -o jsonpath=&#039;{.status.nodeInfo.containerRuntimeVersion}&#039;
# containerd://1.6.4
</code></pre>
<p>Also in 1.20:<br />
&#8211; <strong>API Priority and Fairness</strong> beta: Rate-limit API server requests by priority — prevents a runaway controller from starving other API clients<br />
&#8211; <strong>CronJobs</strong> stable: Scheduled jobs graduate after years in beta<br />
&#8211; <strong>Volume snapshot</strong> stable</p>
<hr />
<h2 id="the-solarwinds-context-december-2020">The SolarWinds Context (December 2020)</h2>
<p>The SolarWinds supply chain attack, disclosed in December 2020, didn&#8217;t directly target Kubernetes. But it accelerated an existing conversation in the cloud-native community: if the build pipeline is compromised, signed binaries mean nothing. If the image registry is compromised, admission control on image names means nothing.</p>
<p>The attack catalyzed work on several fronts:<br />
&#8211; <strong>Sigstore</strong>: An open-source project (Google, Red Hat, Purdue University) for signing and verifying software artifacts including container images<br />
&#8211; <strong>SLSA (Supply chain Levels for Software Artifacts)</strong>: A framework for incrementally improving supply chain security, from basic build provenance to hermetic builds with verified dependencies<br />
&#8211; <strong>SBOM (Software Bill of Materials)</strong>: A machine-readable inventory of software components in an image — required by US Executive Order 14028 (May 2021) for software sold to the federal government</p>
<hr />
<h2 id="kubernetes-121-podsecuritypolicy-deprecation-april-2021">Kubernetes 1.21 — PodSecurityPolicy Deprecation (April 2021)</h2>
<p>PodSecurityPolicy was deprecated in 1.21, announcing its removal in 1.25. The deprecation was contentious — PSP was the only built-in mechanism for enforcing pod security constraints, and every security-conscious cluster depended on it, despite its many flaws.</p>
<p>The replacement approach: <strong>Pod Security Standards</strong> — three predefined security profiles:</p>
<table>
<thead>
<tr>
<th>Profile</th>
<th>Description</th>
<th>Use Case</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Privileged</strong></td>
<td>No restrictions</td>
<td>System-level workloads, trusted components</td>
</tr>
<tr>
<td><strong>Baseline</strong></td>
<td>Prevents known privilege escalations</td>
<td>General application workloads</td>
</tr>
<tr>
<td><strong>Restricted</strong></td>
<td>Hardened; follows current best practices</td>
<td>High-security workloads</td>
</tr>
</tbody>
</table>
<p>Other 1.21 highlights:<br />
&#8211; <strong>CronJobs</strong> stable<br />
&#8211; <strong>Immutable ConfigMaps and Secrets</strong> stable<br />
&#8211; <strong>Graceful node shutdown</strong> beta: The kubelet gracefully terminates pods when a node shuts down (not just when the kubelet stops)<br />
&#8211; <strong>PodDisruptionBudget</strong> stable</p>
<hr />
<h2 id="kubernetes-122-the-great-api-removal-august-2021">Kubernetes 1.22 — The Great API Removal (August 2021)</h2>
<p>1.22 was the most disruptive Kubernetes release for operations teams since 1.0. Several long-lived beta APIs were removed:</p>
<table>
<thead>
<tr>
<th>Removed API</th>
<th>Replacement</th>
<th>Used By</th>
</tr>
</thead>
<tbody>
<tr>
<td>networking.k8s.io/v1beta1 Ingress</td>
<td>networking.k8s.io/v1</td>
<td>Every ingress resource</td>
</tr>
<tr>
<td>batch/v1beta1 CronJob</td>
<td>batch/v1</td>
<td>Every scheduled job</td>
</tr>
<tr>
<td>apiextensions.k8s.io/v1beta1 CRD</td>
<td>apiextensions.k8s.io/v1</td>
<td>Every CRD definition</td>
</tr>
<tr>
<td>rbac.authorization.k8s.io/v1beta1</td>
<td>rbac.authorization.k8s.io/v1</td>
<td>RBAC resources</td>
</tr>
</tbody>
</table>
<p>Teams with Helm charts, Terraform modules, and CI/CD pipelines built against beta API versions had to update their manifests. This was the moment that finally drove home the message: beta APIs in Kubernetes are not stable — they will be removed.</p>
<p>Also in 1.22:<br />
&#8211; <strong>Server-Side Apply</strong> stable: Apply semantics moved server-side — field ownership tracking, conflict detection, and merge strategies are handled by the API server rather than client-side kubectl<br />
&#8211; <strong>Memory manager</strong> stable: Better NUMA-aware memory allocation for latency-sensitive workloads<br />
&#8211; <strong>Bound Service Account Token Volumes</strong> stable: Time-limited, audience-bound tokens for pods — replacing the long-lived, cluster-wide service account tokens that were a persistent security concern</p>
<pre><code class="" data-line=""># Bound service account token — expires, audience-restricted
# Projected volume mounts a time-limited token (default 1h expiry)
volumes:
- name: token
  projected:
    sources:
    - serviceAccountToken:
        audience: api
        expirationSeconds: 3600
        path: token
</code></pre>
<p>The bound token change was significant from a security perspective: previously, a service account token extracted from a pod would be valid indefinitely, for any audience. Projected tokens expire and are tied to a specific audience.</p>
<hr />
<h2 id="pod-security-admission-kubernetes-122-ga-in-125">Pod Security Admission (Kubernetes 1.22, GA in 1.25)</h2>
<p>The replacement for PodSecurityPolicy was <strong>Pod Security Admission</strong> — an admission controller built into the API server (no webhook required) that enforces the three Pod Security Standards at the namespace level:</p>
<pre><code class="" data-line=""># Namespace-level security enforcement
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: v1.25
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: v1.25
</code></pre>
<p>The three modes:<br />
&#8211; <strong>enforce</strong>: Reject pods that violate the policy<br />
&#8211; <strong>audit</strong>: Allow the pod but add an audit annotation<br />
&#8211; <strong>warn</strong>: Allow the pod and send a warning to the client</p>
<p>Pod Security Admission is deliberately simpler than PSP. It does less — it enforces three fixed profiles, not arbitrary rules. For arbitrary policy, you still need OPA/Gatekeeper or Kyverno. But the simplicity means it works reliably, with no authorization edge cases.</p>
<hr />
<h2 id="kubernetes-123-dual-stack-stable-hpa-v2-stable-december-2021">Kubernetes 1.23 — Dual-Stack Stable, HPA v2 Stable (December 2021)</h2>
<ul>
<li><strong>IPv4/IPv6 dual-stack</strong> stable: Pods and Services can have both IPv4 and IPv6 addresses — critical for organizations running mixed-stack networks or migrating from IPv4 to IPv6</li>
<li><strong>HPA v2</strong> stable: Horizontal Pod Autoscaler with support for multiple metrics (CPU, memory, custom metrics from Prometheus, external metrics). Scale on Prometheus metrics, not just CPU:</li>
</ul>
<pre><code class="" data-line="">apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: 1000m
</code></pre>
<ul>
<li><strong>FlexVolume</strong> deprecated (in favor of CSI): Another step in the driver out-of-tree migration</li>
</ul>
<hr />
<h2 id="the-log4shell-moment-december-2021">The Log4Shell Moment (December 2021)</h2>
<p>Log4Shell (CVE-2021-44228) hit on December 9, 2021. The vulnerability allowed unauthenticated remote code execution in any Java application using Log4j 2.x. The blast radius was enormous — Log4j was in everything.</p>
<p>For Kubernetes operators, Log4Shell crystallized several operational realities:</p>
<p><strong>Inventory problem</strong>: Do you know which of your pods is running a Java application? Do you know which version of Log4j it includes? Without an SBOM pipeline and admission-time image scanning, you probably don&#8217;t have a reliable answer.</p>
<p><strong>Patch velocity problem</strong>: Once you know which images are vulnerable, how quickly can you rebuild and redeploy? Organizations with GitOps pipelines and image update automation (Flux&#8217;s image reflector, ArgoCD Image Updater) could respond in hours. Organizations without this infrastructure measured response time in days.</p>
<p><strong>Runtime detection problem</strong>: Can you detect exploitation attempts in real time? Falco rules for Log4Shell JNDI lookup patterns were available within hours of disclosure — but only organizations already running Falco could use them.</p>
<p>Log4Shell made the case for supply chain security, image scanning, SBOM generation, and runtime detection tooling more effectively than any conference talk.</p>
<hr />
<h2 id="sigstore-and-the-supply-chain-response">Sigstore and the Supply Chain Response</h2>
<p>In 2021, Sigstore reached a point where its tooling — <strong>cosign</strong> (image signing), <strong>rekor</strong> (transparency log), <strong>fulcio</strong> (keyless signing via OIDC) — was production-ready.</p>
<p>The keyless signing model was significant: instead of managing long-lived signing keys (which themselves become a supply chain risk), fulcio issues short-lived certificates tied to an OIDC identity (a GitHub Actions workflow, a GitLab CI job). The signature proves that a specific workflow built the image.</p>
<pre><code class="" data-line=""># Sign an image as part of CI (keyless, OIDC-based)
cosign sign --yes ghcr.io/org/app:v1.0.0

# Verify before deploying
cosign verify \
  --certificate-identity-regexp &quot;https://github.com/org/app/.github/workflows/build.yml&quot; \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/org/app:v1.0.0
</code></pre>
<p>Policy engines (OPA/Gatekeeper, Kyverno) could be configured to reject pods using unsigned or unverified images at admission time — closing the loop from build provenance to runtime enforcement.</p>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Dockershim deprecation in 1.20 was about removing the non-standard interface, not about dropping Docker image compatibility — containers built with Docker run on containerd or CRI-O without changes</li>
<li>The API removals in 1.22 were operationally painful but necessary — beta APIs in Kubernetes are not production-stable commitments</li>
<li>Pod Security Admission (PSP&#8217;s replacement) trades power for reliability — three fixed profiles enforced at the namespace level, built into the API server, no authorization edge cases</li>
<li>SolarWinds and Log4Shell made supply chain security a board-level concern; Sigstore, SBOM, and admission-time image verification moved from &#8220;nice to have&#8221; to operational requirements</li>
<li>Bound service account tokens (1.22 stable) addressed a persistent security gap: pod tokens that expire and are audience-restricted rather than long-lived cluster-wide credentials</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p><a href="ep04-operator-era.md">← EP04: The Operator Era</a> | <a href="ep06-runtime-reckoning.md">EP06: The Runtime Reckoning →</a></p>
<p><em>Series: Kubernetes: From Borg to Platform Engineering | linuxcent.com</em></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-pod-security-supply-chain%2F&amp;linkname=Security%20Hardens%3A%20Supply%20Chain%2C%20Pod%20Security%2C%20and%20the%20API%20Cleanup%20%282020%E2%80%932022%29" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-pod-security-supply-chain%2F&amp;linkname=Security%20Hardens%3A%20Supply%20Chain%2C%20Pod%20Security%2C%20and%20the%20API%20Cleanup%20%282020%E2%80%932022%29" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-pod-security-supply-chain%2F&amp;linkname=Security%20Hardens%3A%20Supply%20Chain%2C%20Pod%20Security%2C%20and%20the%20API%20Cleanup%20%282020%E2%80%932022%29" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-pod-security-supply-chain%2F&amp;linkname=Security%20Hardens%3A%20Supply%20Chain%2C%20Pod%20Security%2C%20and%20the%20API%20Cleanup%20%282020%E2%80%932022%29" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-pod-security-supply-chain%2F&amp;linkname=Security%20Hardens%3A%20Supply%20Chain%2C%20Pod%20Security%2C%20and%20the%20API%20Cleanup%20%282020%E2%80%932022%29" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-pod-security-supply-chain%2F&amp;linkname=Security%20Hardens%3A%20Supply%20Chain%2C%20Pod%20Security%2C%20and%20the%20API%20Cleanup%20%282020%E2%80%932022%29" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-pod-security-supply-chain%2F&amp;linkname=Security%20Hardens%3A%20Supply%20Chain%2C%20Pod%20Security%2C%20and%20the%20API%20Cleanup%20%282020%E2%80%932022%29" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fkubernetes-pod-security-supply-chain%2F&#038;title=Security%20Hardens%3A%20Supply%20Chain%2C%20Pod%20Security%2C%20and%20the%20API%20Cleanup%20%282020%E2%80%932022%29" data-a2a-url="https://linuxcent.com/kubernetes-pod-security-supply-chain/" data-a2a-title="Security Hardens: Supply Chain, Pod Security, and the API Cleanup (2020–2022)"></a></p><p>The post <a href="https://linuxcent.com/kubernetes-pod-security-supply-chain/">Security Hardens: Supply Chain, Pod Security, and the API Cleanup (2020–2022)</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/kubernetes-pod-security-supply-chain/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1653</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: linuxcent.com @ 2026-08-22 11:00:31 by W3 Total Cache
-->