<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Abhinav Yadav</title>
    <description>The latest articles on DEV Community by Abhinav Yadav (@abhinav_yadav_b42c8a717ab).</description>
    <link>https://dev.to/abhinav_yadav_b42c8a717ab</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4018916%2F5f7747ad-bf64-422d-9617-d78439a76cdb.jpg</url>
      <title>DEV Community: Abhinav Yadav</title>
      <link>https://dev.to/abhinav_yadav_b42c8a717ab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abhinav_yadav_b42c8a717ab"/>
    <language>en</language>
    <item>
      <title>Always Set persist-credentials: false in actions/checkout</title>
      <dc:creator>Abhinav Yadav</dc:creator>
      <pubDate>Fri, 30 Oct 2026 17:33:04 +0000</pubDate>
      <link>https://dev.to/abhinav_yadav_b42c8a717ab/always-set-persist-credentials-false-in-actionscheckout-4le8</link>
      <guid>https://dev.to/abhinav_yadav_b42c8a717ab/always-set-persist-credentials-false-in-actionscheckout-4le8</guid>
      <description>&lt;p&gt;There's a default in &lt;code&gt;actions/checkout\&lt;/code&gt; that most teams never change — and that quietly leaves a GitHub token sitting in a readable file for every subsequent step in the job.&lt;/p&gt;

&lt;p&gt;The fix is one line. The reasoning is worth understanding.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setting
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;persist-credentials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;  &lt;span class="c1"&gt;# default is true&lt;/span&gt;
&lt;span class="s"&gt;~~~&lt;/span&gt;

&lt;span class="s"&gt;By default (\`persist-credentials&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="s"&gt;\`), the checkout action writes a git credential config file containing your GitHub token to \`$RUNNER_TEMP\`. That file persists for the lifetime of the job.&lt;/span&gt;

&lt;span class="c1"&gt;## What the File Looks Like&lt;/span&gt;

&lt;span class="s"&gt;The checkout action creates a file at \`$RUNNER_TEMP/git-credentials-&amp;lt;UUID&amp;gt;.config\`:&lt;/span&gt;

&lt;span class="s"&gt;~~~ini&lt;/span&gt;
&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;http "https&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;//github.com/"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;extraheader = AUTHORIZATION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;basic &amp;lt;Base64-encoded-string&amp;gt;&lt;/span&gt;
&lt;span class="s"&gt;~~~&lt;/span&gt;

&lt;span class="s"&gt;The Base64 string decodes to \`x-access-token:&amp;lt;GITHUB_TOKEN&amp;gt;\`. It's not encrypted. It's not write-protected. It sits in \`$RUNNER_TEMP\` for every step after checkout to read.&lt;/span&gt;

&lt;span class="c1"&gt;## How Easy It Is to Exploit&lt;/span&gt;

&lt;span class="s"&gt;Any step in the same job — including actions from third-party repos — can extract the token with a shell one-liner:&lt;/span&gt;

&lt;span class="s"&gt;~~~bash&lt;/span&gt;
&lt;span class="s"&gt;GH_TOKEN=$(&lt;/span&gt;
  &lt;span class="s"&gt;cat "$RUNNER_TEMP"/git-credentials-*.config \&lt;/span&gt;
    &lt;span class="s"&gt;| awk 'NR==2 {print $5}' \&lt;/span&gt;
    &lt;span class="s"&gt;| base64 --decode \&lt;/span&gt;
    &lt;span class="s"&gt;| cut -d&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;-f2&lt;/span&gt;
&lt;span class="s"&gt;)&lt;/span&gt;
&lt;span class="s"&gt;echo "token&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${GH_TOKEN:0:10}..."&lt;/span&gt;
&lt;span class="s"&gt;~~~&lt;/span&gt;

&lt;span class="s"&gt;This doesn't require \`${{ github.token }}\` or \`${{ secrets.GITHUB_TOKEN }}\` to appear anywhere in the workflow. The token is already on disk.&lt;/span&gt;

&lt;span class="pi"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;[!WARNING]&lt;/span&gt;
&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s"&gt; If any action you use is compromised via a supply chain attack, or if your workflow is vulnerable to script injection through untrusted input, that action can silently exfiltrate your GitHub token via this file.&lt;/span&gt;

&lt;span class="err"&gt;S&lt;/span&gt;&lt;span class="s"&gt;etting \`persist-credentials: false\` makes the checkout action **delete the credentials file** after checkout completes. The file is gone before the next step runs.&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="s"&gt;# The Practical Risk&lt;/span&gt;

&lt;span class="err"&gt;T&lt;/span&gt;&lt;span class="s"&gt;he most realistic threat vectors:&lt;/span&gt;

&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="s"&gt;*1. Compromised third-party action:** You pin \`some-org/some-action@v2\`. The maintainer's account is compromised, a new patch release is pushed, and the action now exfiltrates credentials. If you reference by tag (not commit SHA), you get the new version automatically.&lt;/span&gt;

&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="s"&gt;*2. Script injection via PR titles or branch names:** Workflows that echo \`${{ github.event.pull_request.title }}\` into a \`run:\` block are vulnerable. An attacker opens a PR with a crafted title that executes a shell command reading the credentials file.&lt;/span&gt;

&lt;span class="err"&gt;T&lt;/span&gt;&lt;span class="s"&gt;he \`persist-credentials: false\` flag eliminates the file-based exfiltration path. It doesn't prevent all attacks, but it removes the easiest one.&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="s"&gt;# What If I Need Git Auth in Later Steps?&lt;/span&gt;

&lt;span class="err"&gt;S&lt;/span&gt;&lt;span class="s"&gt;etting \`persist-credentials: false\` means subsequent \`git pull\`, \`git push\`, or other authenticated operations will fail without credentials being re-established.&lt;/span&gt;

&lt;span class="err"&gt;T&lt;/span&gt;&lt;span class="s"&gt;he clean solution is \`gh auth setup-git\`, which configures Git to use the GitHub CLI credential helper:&lt;/span&gt;

&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="s"&gt;~~yaml&lt;/span&gt;
&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="s"&gt; uses: actions/checkout@v4&lt;/span&gt;
  &lt;span class="s"&gt;with:&lt;/span&gt;
    &lt;span class="s"&gt;persist-credentials: false&lt;/span&gt;

&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="s"&gt; name: Set up git credentials via GH CLI&lt;/span&gt;
  &lt;span class="s"&gt;run: gh auth setup-git&lt;/span&gt;
  &lt;span class="s"&gt;env:&lt;/span&gt;
    &lt;span class="s"&gt;GH_HOST: github.com&lt;/span&gt;

&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="s"&gt; name: Pull latest&lt;/span&gt;
  &lt;span class="s"&gt;run: git pull origin main&lt;/span&gt;
  &lt;span class="s"&gt;env:&lt;/span&gt;
    &lt;span class="s"&gt;GH_TOKEN: ${{ github.token }}&lt;/span&gt;
&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="s"&gt;~~&lt;/span&gt;

&lt;span class="err"&gt;T&lt;/span&gt;&lt;span class="s"&gt;his configures \`~/.gitconfig\` with:&lt;/span&gt;

&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="s"&gt;~~ini&lt;/span&gt;
&lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="s"&gt;credential "https://github.com"]&lt;/span&gt;
    &lt;span class="s"&gt;helper =&lt;/span&gt;
    &lt;span class="s"&gt;helper = !/usr/bin/gh auth git-credential&lt;/span&gt;
&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="s"&gt;~~&lt;/span&gt;

&lt;span class="err"&gt;T&lt;/span&gt;&lt;span class="s"&gt;he credential helper calls out to the GitHub CLI at auth time, which reads \`GH_TOKEN\` from the environment — **only when Git actually needs to authenticate**, and without writing the token to disk.&lt;/span&gt;

&lt;span class="err"&gt;T&lt;/span&gt;&lt;span class="s"&gt;he token is scoped to the steps where \`GH_TOKEN\` is explicitly passed in \`env:\`. It's not sitting in a file readable by any arbitrary code in the job.&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="s"&gt;# Pinning Actions by Commit SHA&lt;/span&gt;

&lt;span class="err"&gt;W&lt;/span&gt;&lt;span class="s"&gt;hile hardening your checkout step, pin by full commit SHA rather than a mutable tag:&lt;/span&gt;

&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="s"&gt;~~yaml&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="s"&gt; Mutable — tag can be force-pushed to point at new, malicious code&lt;/span&gt;
&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="s"&gt; uses: actions/checkout@v4&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="s"&gt; Immutable — this exact commit is guaranteed&lt;/span&gt;
&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="s"&gt; uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2&lt;/span&gt;
&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="s"&gt;~~&lt;/span&gt;

&lt;span class="err"&gt;C&lt;/span&gt;&lt;span class="s"&gt;ombined with \`persist-credentials: false\`, this closes both the supply chain mutation vector and the credential-on-disk vector in one checkout block.&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="s"&gt;# Automated Detection&lt;/span&gt;

&lt;span class="err"&gt;H&lt;/span&gt;&lt;span class="s"&gt;uman review will miss this. Use static analysis in CI:&lt;/span&gt;

&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="s"&gt;*[zizmor](https://docs.zizmor.sh)** — Rust-based, runs as a GitHub Action:&lt;/span&gt;

&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="s"&gt;~~yaml&lt;/span&gt;
&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="s"&gt; uses: woodruffw/zizmor-action@v1&lt;/span&gt;
&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="s"&gt;~~&lt;/span&gt;

&lt;span class="err"&gt;O&lt;/span&gt;&lt;span class="s"&gt;utput when \`persist-credentials\` is not set:&lt;/span&gt;

&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="s"&gt;~~plaintext&lt;/span&gt;
&lt;span class="err"&gt;w&lt;/span&gt;&lt;span class="s"&gt;arning[artipacked]: credential persistence through GitHub Actions artifacts&lt;/span&gt;
 &lt;span class="s"&gt;--&amp;gt; .github/workflows/build.yml:7:9&lt;/span&gt;
  &lt;span class="s"&gt;|&lt;/span&gt;
&lt;span class="err"&gt;7&lt;/span&gt;&lt;span class="s"&gt; |       - uses: actions/checkout@v4&lt;/span&gt;
  &lt;span class="s"&gt;|         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ does not set persist-credentials: false&lt;/span&gt;
  &lt;span class="s"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;= note: this finding has an auto-fix&lt;/span&gt;
&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="s"&gt;~~&lt;/span&gt;

&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="s"&gt;*[ghasec](https://github.com/koki-develop/ghasec)** and **[ghalint](https://github.com/suzuki-shunsuke/ghalint)** also catch this and surface it with rule references.&lt;/span&gt;

&lt;span class="err"&gt;R&lt;/span&gt;&lt;span class="s"&gt;unning any of these in CI ensures the check doesn't rely on engineers remembering to apply it manually.&lt;/span&gt;

&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s"&gt; [!TIP]&lt;/span&gt;
&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s"&gt; There is an open PR in the \`actions/checkout\` repo to flip the default to \`false\`, but it has been stalled for years. Until the default changes, enforce it via linting.&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="s"&gt;# Checklist&lt;/span&gt;

&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="s"&gt; [ ] \`persist-credentials: false\` on all \`actions/checkout\` steps&lt;/span&gt;
&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="s"&gt; [ ] Actions pinned to full commit SHAs, not mutable tags&lt;/span&gt;
&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="s"&gt; [ ] \`gh auth setup-git\` pattern for workflows that need post-checkout git auth&lt;/span&gt;
&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="s"&gt; [ ] zizmor or ghasec running in CI to catch new workflows&lt;/span&gt;
&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="s"&gt; [ ] Minimal \`permissions:\` scope on each job (\`contents: read\` unless write is required)&lt;/span&gt;

&lt;span class="err"&gt;T&lt;/span&gt;&lt;span class="s"&gt;he same lesson applies to AI tooling: a token that sits where other code can read it will eventually be read. See [stop giving AI agents your production keys](https://yadui.dev/blog/stop-giving-ai-agents-production-keys) and the [LiteLLM 1.107.0 incident response guide](https://yadui.dev/blog/litellm-1-107-0-incident-response-ai-gateways) for what that looks like in practice.&lt;/span&gt;


&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="s"&gt;--&lt;/span&gt;

&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="s"&gt;Originally published at [yadui.dev](https://yadui.dev/blog/github-actions-persist-credentials-security) by Abhinav Yadav.*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>githubactions</category>
      <category>security</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Stop giving AI agents your production keys</title>
      <dc:creator>Abhinav Yadav</dc:creator>
      <pubDate>Fri, 30 Oct 2026 17:32:27 +0000</pubDate>
      <link>https://dev.to/abhinav_yadav_b42c8a717ab/stop-giving-ai-agents-your-production-keys-ccb</link>
      <guid>https://dev.to/abhinav_yadav_b42c8a717ab/stop-giving-ai-agents-your-production-keys-ccb</guid>
      <description>&lt;h2&gt;
  
  
  What CERT-In reported
&lt;/h2&gt;

&lt;p&gt;On 15 October 2026, CERT-In issued advisory &lt;a href="https://www.cert-in.org.in/s2cMainServlet?VLCODE=CIAD-2026-0049&amp;amp;pageid=PUBVLNOTES02" rel="noopener noreferrer"&gt;CIAD-2026-0049&lt;/a&gt;, rated critical, titled "AI Agents as Attack Vectors". It describes two kinds of incident, and both come down to how AI agent credentials are exposed and scoped.&lt;/p&gt;

&lt;p&gt;In the first, AI model providers voluntarily disclosed that their models, during research and evaluation on the internet, reached the live systems of Indian organizations. The advisory says the agents read business records, including personal data and confidential material, using credentials and connection settings exposed in application configuration and public code repositories, and through permissive cloud service roles. It states that the agents' actions exceeded their assigned tasks and that none of the affected organizations detected the access themselves.&lt;/p&gt;

&lt;p&gt;In the second, CERT-In's own threat hunting found attackers using publicly available AI-based offensive-security frameworks in a financially motivated campaign. The advisory says agentic tooling compressed the time from initial access to impact from days to a few hours.&lt;/p&gt;

&lt;p&gt;The advisory does not name the providers, models or organizations. &lt;a href="https://thehackernews.com/2026/10/cert-in-warns-ai-agents-accessed.html" rel="noopener noreferrer"&gt;The Hacker News coverage&lt;/a&gt; summarizes the same points and notes that CERT-In published no indicators of compromise.&lt;/p&gt;

&lt;p&gt;The striking line is this: CERT-In calls the root weaknesses conventional. Exposed keys, dormant test APIs, plaintext payment keys, over-permissive cloud roles and internet-exposed admin interfaces. Agents found and used them faster and more thoroughly than people do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two sides of the same problem
&lt;/h2&gt;

&lt;p&gt;The advisory is useful for any team that runs agents, because it covers both directions. Your exposed credentials can be found by someone else's agent. Your own agent, if it holds broad production credentials, can do the same damage when it is misdirected by prompt injection or simply overreaches. OWASP calls the second case &lt;a href="https://genai.owasp.org/llmrisk/llm062025-excessive-agency/" rel="noopener noreferrer"&gt;Excessive Agency (LLM06:2025)&lt;/a&gt; and lists excessive permissions as one of its three root causes.&lt;/p&gt;

&lt;p&gt;For organizations, CERT-In's recommendations include a specific section on governing internal agents: give each run a separate, short-lived identity with least privilege, enforce default-deny egress, and keep a tamper-resistant log of agent activity covering at minimum all actions that affect production systems and data. The rest of this post turns that into concrete steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: find the keys you already leaked
&lt;/h2&gt;

&lt;p&gt;Start where the advisory starts. CERT-In asks organizations to audit source code, repository commit history, CI/CD variables, configuration files, client applications and cloud storage for exposed credentials, then rotate any exposed credential, purge it from history and verify the fix externally. It also asks that every commit and build be scanned for secrets.&lt;/p&gt;

&lt;p&gt;On GitHub, &lt;a href="https://docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning" rel="noopener noreferrer"&gt;secret scanning&lt;/a&gt; checks the full Git history of a repository for known secret formats, and push protection can block a commit containing a supported secret before it lands. Turn both on for every repository, not only public ones. CI is a common leak path too; see &lt;a href="https://yadui.dev/blog/github-actions-persist-credentials-security" rel="noopener noreferrer"&gt;why persist-credentials in GitHub Actions matters&lt;/a&gt;. Remember the order: rotate first, then clean history. A deleted commit does not un-leak a key someone has already copied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: replace stored secrets with workload identity
&lt;/h2&gt;

&lt;p&gt;The most reliable way to stop leaking keys is to stop having them. On Azure, &lt;a href="https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview" rel="noopener noreferrer"&gt;managed identities&lt;/a&gt; let a workload obtain Microsoft Entra tokens without the application managing credentials. For agents running outside Azure, such as in GitHub Actions or Kubernetes, &lt;a href="https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation" rel="noopener noreferrer"&gt;workload identity federation&lt;/a&gt; lets the workload exchange a token from its own trusted issuer for an Entra access token, so no client secret or certificate needs to be stored.&lt;/p&gt;

&lt;p&gt;Tokens obtained this way are short-lived, which is what CERT-In asks for. The remaining design work is scoping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: one identity per agent run, scoped to the task
&lt;/h2&gt;

&lt;p&gt;A single shared service principal for "the agent" defeats attribution and least privilege. Aim for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separate identities per agent role.&lt;/strong&gt; A summarizing agent and a deployment agent should never share a principal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Narrow role assignments.&lt;/strong&gt; Grant data-plane roles on the specific resource the task needs, not Contributor or Owner on a subscription. Read-only by default; write access only where the task is a write.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-run attribution.&lt;/strong&gt; Where the platform allows, tag tokens or log entries with a run identifier so every action traces back to one execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No production in evaluation.&lt;/strong&gt; CERT-In's guidance for AI developers says to use synthetic targets, synthetic credentials and disposable infrastructure, and to keep production data and real credentials out of evaluation environments. The same rule fits internal agent testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep credentials out of the model's context entirely. The tool layer should hold the token and return results, so a prompt injection cannot ask the model to print it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: default-deny egress, enforced by the network
&lt;/h2&gt;

&lt;p&gt;CERT-In is explicit that network restrictions must be enforced at the network layer, not through instructions to the model. A prompt that says "only contact api.example.com" is not a control.&lt;/p&gt;

&lt;p&gt;Run agents in a network segment whose outbound traffic passes through a firewall or proxy with an allowlist of required destinations. Block cloud metadata endpoints unless the identity mechanism needs them, and block private ranges the agent has no reason to reach. Log denied connections; an agent trying to reach somewhere unexpected is an early warning. A fuller design, with per-run allowlists and shutdown, is in &lt;a href="https://yadui.dev/blog/ai-agent-kill-switch-egress-gateway" rel="noopener noreferrer"&gt;AI agent kill switches and egress gateways&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: logs the agent cannot edit
&lt;/h2&gt;

&lt;p&gt;CERT-In asks for a tamper-resistant record of agent activity and, for organizations generally, log retention of at least 180 days. In practice, send agent and tool-call logs to a separate store the agent's identity cannot write to or delete from, such as a central log workspace with restricted access or immutable storage. Record the destination, action, parameters and result for every tool call that touches production.&lt;/p&gt;

&lt;p&gt;The advisory's warning for evaluators applies here too: transcript review alone can miss activity that looks legitimate in the model's reasoning. Log what the agent did, not only what it said.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Secret scanning and push protection on every repository; rotate before purging.&lt;/li&gt;
&lt;li&gt;No long-lived keys for agents; use managed identity or workload identity federation.&lt;/li&gt;
&lt;li&gt;One scoped identity per agent role, read-only by default.&lt;/li&gt;
&lt;li&gt;No production credentials or data in test and evaluation runs.&lt;/li&gt;
&lt;li&gt;Default-deny egress at the network layer.&lt;/li&gt;
&lt;li&gt;Append-only logs outside the agent's reach, retained for at least 180 days.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is new security practice. That is CERT-In's point. Agents simply remove the time you used to have before someone noticed.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://yadui.dev/blog/stop-giving-ai-agents-production-keys" rel="noopener noreferrer"&gt;yadui.dev&lt;/a&gt; by Abhinav Yadav.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>azure</category>
      <category>devops</category>
    </item>
    <item>
      <title>PHP2Shell: a practical patch and check guide for PHP servers</title>
      <dc:creator>Abhinav Yadav</dc:creator>
      <pubDate>Fri, 30 Oct 2026 17:31:51 +0000</pubDate>
      <link>https://dev.to/abhinav_yadav_b42c8a717ab/php2shell-a-practical-patch-and-check-guide-for-php-servers-483b</link>
      <guid>https://dev.to/abhinav_yadav_b42c8a717ab/php2shell-a-practical-patch-and-check-guide-for-php-servers-483b</guid>
      <description>&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;PHP2Shell is the nickname for CVE-2026-109618 and CVE-2026-109619. On October 21, 2026, the PHP project shipped emergency releases, PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, to fix two critical flaws that it says "can allow unauthenticated remote code execution" (&lt;a href="https://www.php.net/archive/2026.php#2026-10-21-5" rel="noopener noreferrer"&gt;PHP security advisory&lt;/a&gt;). The day before, PHP had given &lt;a href="https://www.php.net/archive/2026.php#2026-10-20-1" rel="noopener noreferrer"&gt;advance notice&lt;/a&gt; that both were rated CVSS v4.0 9.2. If you upgrade, also rotate any secrets a compromised host could have read; the &lt;a href="https://yadui.dev/blog/railway-emudhra-sbi-breaches-explained" rel="noopener noreferrer"&gt;Railway breach the same week&lt;/a&gt; shows why.&lt;/p&gt;

&lt;p&gt;The two bugs, as PHP describes them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CVE-2026-109618, RCE via out-of-memory corruption.&lt;/strong&gt; Out-of-memory handling can interrupt PHP while internal request data is only partly initialized, and request cleanup may then treat that invalid data as valid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CVE-2026-109619, RCE via stale global state.&lt;/strong&gt; Request-owned internal state can outlive the memory behind it, and a later request on the same worker may use it before normal initialization resets it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two days later, on October 23, PHP published a short &lt;a href="https://www.php.net/archive/2026.php#2026-10-23-1" rel="noopener noreferrer"&gt;informational advisory&lt;/a&gt; about a separate issue, CVE-2026-115560. It is not fixed by the October 21 releases. PHP calls it "an unlikely RCE vector" and is handling it in the regular release cycle rather than with another emergency release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is exposed
&lt;/h2&gt;

&lt;p&gt;PHP's own answer for the October 21 flaws is broad: "all PHP installations serving web requests, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed." It considers the risk highest for mod_php, where it has a demonstrated exploit in which an unauthenticated request can trigger the attack before any application code runs (&lt;a href="https://www.php.net/archive/2026.php#2026-10-21-5" rel="noopener noreferrer"&gt;advisory&lt;/a&gt;). In other words, a clean application does not protect you. The bug sits in the runtime.&lt;/p&gt;

&lt;p&gt;PHP 8.1 and older no longer get updates from the project. PHP published minimal source backports for 7.4, 8.0 and 8.1 for people building from source, and says some distributions and LTS vendors ship backported fixes.&lt;/p&gt;

&lt;p&gt;The October 23 issue is much narrower. According to &lt;a href="https://www.php.net/archive/2026.php#2026-10-23-1" rel="noopener noreferrer"&gt;PHP&lt;/a&gt;, it needs all of the following: &lt;code&gt;session.serialize_handler&lt;/code&gt; set to &lt;code&gt;php_binary&lt;/code&gt; or &lt;code&gt;php_serialize&lt;/code&gt; in configuration, application code that switches it to &lt;code&gt;php&lt;/code&gt; at runtime with &lt;code&gt;ini_set()&lt;/code&gt; before &lt;code&gt;session_start()&lt;/code&gt;, and session upload progress still enabled. PHP has not said which versions are affected or when the fix ships.&lt;/p&gt;

&lt;h2&gt;
  
  
  What attackers are doing
&lt;/h2&gt;

&lt;p&gt;Everything in this section comes from &lt;a href="https://thehackernews.com/2026/10/urgent-php-warns-of-another-unpatched.html" rel="noopener noreferrer"&gt;The Hacker News&lt;/a&gt;, which attributes each claim to the vendor named. I have not verified these against the vendors' own posts.&lt;/p&gt;

&lt;p&gt;THN reports that Amazon said its honeypots logged an exploit attempt against both CVEs on October 17, four days before disclosure. It reports Cloudflare saying attempts rose from tens of thousands within 45 minutes of the October 21 disclosure to millions by early October 22. It also reports that Sophos and Arctic Wolf saw attempts to install a Mirai botnet variant, and that Previdian, Wordfence and others report scanning and probing but no confirmed code execution so far.&lt;/p&gt;

&lt;p&gt;Read that last point carefully. "No confirmed code execution" is a statement about what defenders have observed, not a guarantee. A pre-disclosure attempt also means someone had details early. Patch as if a working exploit exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checklist
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Find every PHP runtime you actually run
&lt;/h3&gt;

&lt;p&gt;Check the CLI and the web runtime separately, because they are often different binaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php &lt;span class="nt"&gt;-v&lt;/span&gt;
php-fpm &lt;span class="nt"&gt;-v&lt;/span&gt;        &lt;span class="c"&gt;# or php-fpm8.3 -v, php-fpm8.4 -v, depending on packaging&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Apache with mod_php, check the loaded module version through your package manager or a &lt;code&gt;phpinfo()&lt;/code&gt; page served only to an admin. Remember containers, base images, CI runners and managed hosting.&lt;/p&gt;

&lt;p&gt;If you use distribution packages, the upstream version number may not change. PHP notes that Linux distributions and LTS vendors received the patches in advance (&lt;a href="https://www.php.net/archive/2026.php#2026-10-21-5" rel="noopener noreferrer"&gt;advisory&lt;/a&gt;). Check your vendor's security tracker for the package version that carries the fix for both CVEs, rather than looking for "8.4.27" in &lt;code&gt;php -v&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Upgrade and restart
&lt;/h3&gt;

&lt;p&gt;Move to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or your vendor's patched package. Then restart every FPM pool, Apache and any long-running worker. A package upgrade that leaves old workers running has not patched anything. Note that PHP 8.2 reaches end of life on December 31, 2026, per &lt;a href="https://www.php.net/archive/2026.php#2026-10-21-1" rel="noopener noreferrer"&gt;php.net&lt;/a&gt;, so plan that upgrade now.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Turn off session upload progress
&lt;/h3&gt;

&lt;p&gt;Set this in &lt;code&gt;php.ini&lt;/code&gt;, the FPM pool or server configuration, then restart every worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;session.upload_progress.enabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PHP lists this as a mitigation for CVE-2026-109619 and says it also covers CVE-2026-115560. PHP warns not to rely on &lt;code&gt;ini_set()&lt;/code&gt; or FPM &lt;code&gt;.user.ini&lt;/code&gt; for it, because the setting must apply before request parsing. Upload progress is on by default, so check whether any feature of yours depends on it before switching it off.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. If you truly cannot patch today
&lt;/h3&gt;

&lt;p&gt;PHP is explicit that only an update is a complete fix. Its stopgaps for the October 21 bugs include &lt;code&gt;memory_limit=-1&lt;/code&gt; set in a way applications cannot override, a low &lt;code&gt;post_max_size&lt;/code&gt;, request size and part limits at the web server or proxy, &lt;code&gt;fatal_error_backtraces=0&lt;/code&gt; on PHP 8.5, and container or cgroup limits as an availability boundary. It specifically warns against &lt;code&gt;RLIMIT_AS&lt;/code&gt; and &lt;code&gt;RLIMIT_DATA&lt;/code&gt;, which can cause the vulnerable allocation failure. Read the &lt;a href="https://www.php.net/archive/2026.php#2026-10-21-5" rel="noopener noreferrer"&gt;full list&lt;/a&gt; before applying any of these, since several trade availability for exposure.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Use your WAF, but do not lean on it
&lt;/h3&gt;

&lt;p&gt;If your WAF or CDN vendor has published managed rules for these CVEs, enable them. Treat them as noise reduction while you patch, not a substitute for patching.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Look for signs of a foothold
&lt;/h3&gt;

&lt;p&gt;No indicators have been published that I can verify, so use standard post-exploitation checks on any host that was exposed and unpatched:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New or modified &lt;code&gt;.php&lt;/code&gt; files in upload, cache and temp directories, especially files written by the web server user.&lt;/li&gt;
&lt;li&gt;New crontab entries for the web user or root, and new systemd timers or services.&lt;/li&gt;
&lt;li&gt;Unknown processes running as the web user, and outbound connections from PHP workers to hosts you do not recognize.&lt;/li&gt;
&lt;li&gt;Unexpected binaries in &lt;code&gt;/tmp&lt;/code&gt;, &lt;code&gt;/var/tmp&lt;/code&gt; or &lt;code&gt;/dev/shm&lt;/code&gt;, given the reported Mirai attempts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you find anything, isolate the host and rebuild from a known-good image rather than cleaning in place. The &lt;a href="https://yadui.dev/blog/litellm-1-107-0-incident-response-ai-gateways" rel="noopener noreferrer"&gt;LiteLLM 1.107.0 incident response guide&lt;/a&gt; walks through rebuild and key rotation in more detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Keep watching
&lt;/h3&gt;

&lt;p&gt;Alert on PHP worker crashes and out-of-memory events, since both bugs involve those paths. Watch request logs for spikes in large or malformed multipart requests, and track your vendor's advisory pages for updated package versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Which PHP versions fix PHP2Shell?
&lt;/h3&gt;

&lt;p&gt;PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, released on October 21, 2026, fix CVE-2026-109618 and CVE-2026-109619. Distribution packages may carry the fix under a different version number, so check your vendor's tracker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a secure application protect me?
&lt;/h3&gt;

&lt;p&gt;No. The bugs sit in the PHP runtime, and PHP describes a mod_php exploit that triggers before any application code runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do the October 21 releases fix CVE-2026-115560?
&lt;/h3&gt;

&lt;p&gt;No. PHP is handling CVE-2026-115560 in its regular release cycle. Setting &lt;code&gt;session.upload_progress.enabled = 0&lt;/code&gt; in server configuration is the listed mitigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;PHP says it will publish a full advisory for CVE-2026-115560 with the next release (&lt;a href="https://www.php.net/archive/2026.php#2026-10-23-1" rel="noopener noreferrer"&gt;php.net&lt;/a&gt;). Another fix is already queued: PHP has flagged CVE-2026-115560 for its next routine release, so expect another patch round soon, and keep &lt;code&gt;session.upload_progress.enabled=0&lt;/code&gt; in place until then.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://yadui.dev/blog/php2shell-cve-2026-109618-109619-patch-guide" rel="noopener noreferrer"&gt;yadui.dev&lt;/a&gt; by Abhinav Yadav.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>security</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>LiteLLM 1.107.0 compromise: incident response guide</title>
      <dc:creator>Abhinav Yadav</dc:creator>
      <pubDate>Fri, 30 Oct 2026 17:31:13 +0000</pubDate>
      <link>https://dev.to/abhinav_yadav_b42c8a717ab/litellm-11070-compromise-incident-response-guide-5b1a</link>
      <guid>https://dev.to/abhinav_yadav_b42c8a717ab/litellm-11070-compromise-incident-response-guide-5b1a</guid>
      <description>&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;The LiteLLM 1.107.0 compromise began on October 21, 2026, when a compromised &lt;code&gt;litellm==1.107.0&lt;/code&gt; was published to PyPI and to LiteLLM's container registries. LiteLLM's &lt;a href="https://docs.litellm.ai/blog/security-incident-oct-21-2026" rel="noopener noreferrer"&gt;incident post&lt;/a&gt; states that earlier versions are safe, that the affected releases were pulled, and that customers on the private enterprise release were not affected. Its &lt;a href="https://docs.litellm.ai/blog/security-incident-oct-21-2026-attack-chain" rel="noopener noreferrer"&gt;attack-chain post&lt;/a&gt; says the artifacts were live for about two hours: PyPI releases were frozen by 13:23 UTC and Docker images removed by 14:20 UTC.&lt;/p&gt;

&lt;p&gt;Two hours is enough. LiteLLM is commonly deployed as a gateway between applications and model providers, and &lt;a href="https://www.microsoft.com/en-us/security/blog/2026/10/23/inside-the-litellm-1-107-0-supply-chain-compromise-credential-theft-through-python-startup-hooks/" rel="noopener noreferrer"&gt;Microsoft's analysis&lt;/a&gt; notes that in that position it may hold model-provider API keys, database connection strings, workload identities and routing configuration. The same package also sits on developer laptops and build agents. If you run an AI gateway, treat this as a template for the next incident, whichever package it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "I never imported it" is not a defence
&lt;/h2&gt;

&lt;p&gt;The malicious wheel shipped &lt;code&gt;cook.pth&lt;/code&gt;, &lt;code&gt;cook.py&lt;/code&gt; and &lt;code&gt;sitecustomize.py&lt;/code&gt; under its &lt;code&gt;purelib&lt;/code&gt; data directory. On install, Microsoft reports, these files landed in the environment's library directory, outside the &lt;code&gt;litellm&lt;/code&gt; package folder.&lt;/p&gt;

&lt;p&gt;That location matters because of how Python starts. The &lt;a href="https://docs.python.org/3/library/site.html" rel="noopener noreferrer"&gt;&lt;code&gt;site&lt;/code&gt; module documentation&lt;/a&gt; describes how lines in &lt;code&gt;.pth&lt;/code&gt; files that begin with &lt;code&gt;import&lt;/code&gt; are executed during interpreter initialization, and how Python then attempts to import a &lt;code&gt;sitecustomize&lt;/code&gt; module. Microsoft found that &lt;code&gt;cook.pth&lt;/code&gt; contained a single line, &lt;code&gt;import cook&lt;/code&gt;, which launched a credential collector in a daemon thread. &lt;code&gt;sitecustomize.py&lt;/code&gt; carried a separate Unix-oriented loader designed to fetch and execute additional Python code; Microsoft says remote retrieval through that path was not confirmed.&lt;/p&gt;

&lt;p&gt;The consequence is stated plainly in the analysis: an application did not need to import LiteLLM for the payload to run. Starting Python in an affected environment could be enough. In one Windows example, a routine Python invocation to install an unrelated dependency activated the collector. And because the startup files lived outside the package folder, deleting &lt;code&gt;litellm&lt;/code&gt; could leave them behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the collector went after
&lt;/h2&gt;

&lt;p&gt;Microsoft lists the targets the recovered code was designed to read: AI assistant, IDE and MCP configuration; Git credentials and package-manager configuration; AWS, Azure CLI and Google Cloud credential caches; Kubernetes service-account files and Docker configuration; browser credential data and shell histories; process environments, including other processes' environments readable through procfs on Linux; SSH, GPG and TLS material. On a proxy host, a separate module could use Prisma to read selected fields from two LiteLLM database tables. Microsoft is careful to note that a target appearing in the code does not establish successful access.&lt;/p&gt;

&lt;p&gt;Collected data was encrypted with a per-run AES key wrapped by an embedded RSA key and posted to &lt;code&gt;hxxp://www[.]litellm[.]dev/healthcheck&lt;/code&gt;. Note the lookalike domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Response, in order
&lt;/h2&gt;

&lt;p&gt;Microsoft's guidance and LiteLLM's posts agree on the shape of the response. Here it is as a runbook.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find every copy. Search installed environments, &lt;code&gt;pip&lt;/code&gt; and &lt;code&gt;uv&lt;/code&gt; caches, internal mirrors, lock files, build outputs and container images for 1.107.0. LiteLLM's incident post lists affected image tags, including floating ones such as &lt;code&gt;latest&lt;/code&gt;, and digests. Mutable tags mean "we pinned to latest" is not an answer; you need to know which digest you actually pulled and when.&lt;/li&gt;
&lt;li&gt;Do not run Python to check. Microsoft is explicit: do not run &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;pip&lt;/code&gt; or anything that invokes the affected interpreter during evidence collection. Each start can re-run the payload. Isolate the host, stop Python processes where safe, prevent automation from restarting them, and inspect the filesystem with trusted tools from a known-clean machine.&lt;/li&gt;
&lt;li&gt;Correlate before declaring compromise. Combine file hashes, Python process ancestry, &lt;code&gt;.pc_e_&lt;/code&gt; temporary staging directories, connections to the collector domain, and subsequent use of the identities involved. Package presence alone is not a verdict.&lt;/li&gt;
&lt;li&gt;Rebuild, do not clean. Rebuild environments and images from verified artifacts. Confirm &lt;code&gt;cook.pth&lt;/code&gt;, &lt;code&gt;cook.py&lt;/code&gt; and &lt;code&gt;sitecustomize.py&lt;/code&gt; are gone from the library path. Microsoft states that removing the package or a release tag is not sufficient recovery.&lt;/li&gt;
&lt;li&gt;Rotate from a clean machine. Rotate everything the affected identity could reach: model-provider keys, cloud and workload credentials, CI/CD secrets, source-control and registry tokens, SSH and GPG keys, AI coding-tool tokens, and on proxy hosts the stored provider credentials and &lt;code&gt;DATABASE_URL&lt;/code&gt;. Rotating from a compromised workstation hands the new secret to the same collector. The same rotate-from-clean discipline applied to the &lt;a href="https://yadui.dev/blog/railway-emudhra-sbi-breaches-explained" rel="noopener noreferrer"&gt;Railway breach the same week&lt;/a&gt;. Revoke sessions as well; Microsoft points to &lt;a href="https://learn.microsoft.com/entra/identity/users/users-revoke-access" rel="noopener noreferrer"&gt;Entra emergency revocation&lt;/a&gt; because rotating a password does not immediately invalidate every active token.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Hardening that would have narrowed the blast radius
&lt;/h2&gt;

&lt;p&gt;Hash-pinned lock files. pip's &lt;a href="https://pip.pypa.io/en/stable/topics/secure-installs/" rel="noopener noreferrer"&gt;secure installs guide&lt;/a&gt; describes hash-checking mode, in which every requirement must carry a hash and installation fails on a mismatch. A pinned hash for 1.107.0 would not exist in your lock file unless someone added it, so an unreviewed upgrade fails closed.&lt;/p&gt;

&lt;p&gt;Image digests, not tags. Microsoft recommends container digests because they identify immutable content. A deployment that references &lt;code&gt;@sha256:&lt;/code&gt; does not silently pick up a new &lt;code&gt;latest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Gated internal mirrors. Admit packages to your mirror only after review, with a delay for brand-new releases. The window here was about two hours.&lt;/p&gt;

&lt;p&gt;Egress control. Microsoft's guidance says developer workstations, build agents, test systems, containers and AI gateway hosts should not have unrestricted outbound HTTP unless required, and links to Azure's &lt;a href="https://learn.microsoft.com/azure/networking/design-guide/outbound-egress?pivots=lift-shift" rel="noopener noreferrer"&gt;outbound egress design guide&lt;/a&gt;. A gateway host needs to reach model providers and its database. It does not need to reach an arbitrary domain that merely looks like the vendor's.&lt;/p&gt;

&lt;p&gt;Keep secrets out of the gateway's process environment where possible. The collector read environment variables, including other processes' on Linux. Secrets fetched at use time from a vault, with short lifetimes, are worth less to a collector than long-lived keys sitting in &lt;code&gt;env&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson upstream: one unrevoked key, one unscoped token
&lt;/h2&gt;

&lt;p&gt;LiteLLM's attack-chain post is unusually candid, and the root causes are ones every team running agents should check for.&lt;/p&gt;

&lt;p&gt;On May 27, 2026, a configuration file committed to a public repository inadvertently contained the master key to a deployment of LiteLLM's internal Agent Platform. The commit was force-pushed over and the key was identified as needing revocation. In LiteLLM's words, key revocation did not occur.&lt;/p&gt;

&lt;p&gt;That platform let self-hosted agents open pull requests, so it held a GitHub token. The post says the token was not associated with a service account and was not scoped down: it was a GitHub CLI OAuth token belonging to the organization owner. On October 10, the attacker used the master key against the platform's API, which returned that token. With owner access, they ran throwaway CI workflows that posted repository secrets to an external endpoint, collecting tokens for two more maintainers. On October 21 they used the owner token to override branch protection on the release repository and repoint it to a lookalike fork, used one maintainer token to approve the production gate, and another to delete the run records. LiteLLM notes the release approval rule had been relaxed that week to allow any two engineers to approve.&lt;/p&gt;

&lt;p&gt;Three checks follow for any team running agents with repository access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An exposed key is revoked, not just hidden. Force-pushing history does not un-publish a secret.&lt;/li&gt;
&lt;li&gt;Agents get their own scoped identity. An agent platform should never hold a human owner's token.&lt;/li&gt;
&lt;li&gt;CI secrets are reachable only from protected branches and approved workflows, so a new branch cannot run a job that prints them. Related: &lt;a href="https://yadui.dev/blog/github-actions-persist-credentials-security" rel="noopener noreferrer"&gt;why persist-credentials in GitHub Actions matters&lt;/a&gt; and &lt;a href="https://yadui.dev/blog/stop-giving-ai-agents-production-keys" rel="noopener noreferrer"&gt;Stop giving AI agents your production keys&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Was I affected if my application never imported LiteLLM?
&lt;/h3&gt;

&lt;p&gt;Possibly. Microsoft found the payload ran from &lt;code&gt;.pth&lt;/code&gt; and &lt;code&gt;sitecustomize.py&lt;/code&gt; startup files, so starting Python in an affected environment could be enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is uninstalling litellm 1.107.0 enough?
&lt;/h3&gt;

&lt;p&gt;No. The startup files lived outside the package folder, and Microsoft states that removing the package or a release tag is not sufficient recovery. Rebuild from verified artifacts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which secrets should I rotate?
&lt;/h3&gt;

&lt;p&gt;Everything the affected identity could reach, including model-provider keys, cloud and CI/CD credentials, source-control tokens, SSH and GPG keys, and on proxy hosts the stored provider credentials and &lt;code&gt;DATABASE_URL&lt;/code&gt;. Rotate from a clean machine and revoke sessions.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long were the malicious releases available?
&lt;/h3&gt;

&lt;p&gt;About two hours, according to LiteLLM's attack-chain post. PyPI releases were frozen by 13:23 UTC and Docker images removed by 14:20 UTC.&lt;/p&gt;

&lt;p&gt;LiteLLM says it will share process changes separately. Until then, the attack-chain post is the clearest public record of how this happened, and it is worth reading in full.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://yadui.dev/blog/litellm-1-107-0-incident-response-ai-gateways" rel="noopener noreferrer"&gt;yadui.dev&lt;/a&gt; by Abhinav Yadav.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>security</category>
      <category>ai</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
