<?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: Christian Daniel</title>
    <description>The latest articles on DEV Community by Christian Daniel (@th3chris).</description>
    <link>https://dev.to/th3chris</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%2F3810767%2Fec10f539-703b-4673-ab2f-c76c4d180e08.jpeg</url>
      <title>DEV Community: Christian Daniel</title>
      <link>https://dev.to/th3chris</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/th3chris"/>
    <language>en</language>
    <item>
      <title>How AI agents leak your credentials — and how to stop it</title>
      <dc:creator>Christian Daniel</dc:creator>
      <pubDate>Tue, 23 Jun 2026 08:30:15 +0000</pubDate>
      <link>https://dev.to/th3chris/how-ai-agents-leak-your-credentials-and-how-to-stop-it-124k</link>
      <guid>https://dev.to/th3chris/how-ai-agents-leak-your-credentials-and-how-to-stop-it-124k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://th3chris.com/en/blog/secrets-and-ai-agents" rel="noopener noreferrer"&gt;th3chris.com&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You are pairing with an AI coding agent — Claude Code, Cursor, whatever — in your repo, and for "open the pull request, call the staging API, run the deploy" it needs credentials. Hand it some and the obvious handoff is already the leak — and "then I just won't give it any" does not save you: it then &lt;strong&gt;fetches them itself&lt;/strong&gt;, reads the &lt;code&gt;.env&lt;/code&gt;, queries the vault — and leaks them in the process, without you ever noticing. In both cases the value passes through the AI model, and it is gone.&lt;/p&gt;

&lt;p&gt;Here is the uncomfortable part: in most agent setups the credential leaves your infrastructure and lands with the model provider or a service it runs — stored, out of your control. (Self-hosted or enterprise models change how it's stored and trained on — the value is still out of your hands.) For most companies that is also a compliance problem. Here is why it happens, and how to close it for good.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — AI agents leak credentials by &lt;em&gt;reading&lt;/em&gt; them as output that gets shipped to the model provider. The fix is to let an agent &lt;em&gt;use&lt;/em&gt; a secret without ever &lt;em&gt;seeing&lt;/em&gt; it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why the obvious setup leaks
&lt;/h2&gt;

&lt;p&gt;The mechanism is simple — and sneaky: &lt;strong&gt;the moment the agent &lt;em&gt;reads&lt;/em&gt; the value&lt;/strong&gt; — from &lt;code&gt;.env&lt;/code&gt;, a vault, or your message — &lt;strong&gt;it becomes part of the conversation the agent sends to the AI provider.&lt;/strong&gt; The token is now in someone else's logs. Deleting the chat afterwards does nothing; it was transmitted the moment it appeared.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpjzxpt0n2ig3tv9jhntr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpjzxpt0n2ig3tv9jhntr.png" alt="How a secret leaks — it flows from the secret store to the agent, into the tool output, and on to the model, where it is gone" width="800" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You cannot fix this by telling the AI to "please be careful." An instruction in a prompt is advice, and advice gets ignored — by this agent, the next one, or some tool wrapped around it. The fix has to be structural: the secret must be &lt;em&gt;impossible&lt;/em&gt; to see, not merely discouraged.&lt;/p&gt;

&lt;p&gt;This is not hypothetical. In early 2026, security researchers found that 7% of the skills on ClawHub — the extension marketplace for the AI agent platform OpenClaw — leaked credentials in exactly this way; I broke that incident down in &lt;a href="https://th3chris.com/en/blog/openclaw-credential-leak" rel="noopener noreferrer"&gt;the OpenClaw leak&lt;/a&gt;. This is no OpenClaw special case: the same mechanism hits every skill, every MCP tool, and every command Claude Code or Cursor runs — the moment an extension reads a secret, it is in the model's context.&lt;/p&gt;

&lt;p&gt;And it is not only agents fetching secrets: millions of developers — and "vibe coders" — paste API keys straight into the prompt every day. The same leak, done by hand — out of convenience and a naive trust that it'll be fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using is not seeing
&lt;/h2&gt;

&lt;p&gt;Here is the whole solution in one line: &lt;strong&gt;an agent has to &lt;em&gt;use&lt;/em&gt; a credential, but it never has to &lt;em&gt;see&lt;/em&gt; it.&lt;/strong&gt; Those are two different things, and keeping them apart is the entire trick.&lt;/p&gt;

&lt;p&gt;Concretely, the secret goes into the &lt;em&gt;program&lt;/em&gt; the agent runs — never into the &lt;em&gt;text&lt;/em&gt; the AI model reads. The shell sits in between: it hands the value to a program as an environment variable, while the agent only ever sees the command it typed.&lt;/p&gt;

&lt;p&gt;One scope note up front: this is about &lt;em&gt;accidental&lt;/em&gt; leaking in day-to-day use — the model never gets to see the value. A fully compromised agent with arbitrary shell execution is a different threat model; more on that below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does the secret live?
&lt;/h2&gt;

&lt;p&gt;Keeping a secret out of the AI's sight only works if it lives somewhere a tool can fetch it on demand — not hardcoded in the repo, not pasted into a config the agent reads. That somewhere is a &lt;strong&gt;secrets manager&lt;/strong&gt; (a vault): one place that stores credentials encrypted, hands them to authorized programs, and records who touched what.&lt;/p&gt;

&lt;p&gt;I use &lt;strong&gt;&lt;a href="https://infisical.com" rel="noopener noreferrer"&gt;Infisical&lt;/a&gt;&lt;/strong&gt; — open source and self-hostable (the secrets stay on my own infrastructure), with first-class &lt;strong&gt;machine identities&lt;/strong&gt;: headless, short-lived-token auth built for exactly this agent-and-CI use. Its real payoff for &lt;em&gt;this&lt;/em&gt; problem: Infisical does three things in one — &lt;strong&gt;fetch&lt;/strong&gt; the secret, &lt;strong&gt;put it in the program's environment&lt;/strong&gt;, and &lt;strong&gt;run the command&lt;/strong&gt; — and the value never appears on stdout. That chain (fetch → env → run) is exactly what a vault like HashiCorp Vault, &lt;code&gt;pass&lt;/code&gt; or AWS Secrets Manager does &lt;em&gt;not&lt;/em&gt; do for you: those only hand back the &lt;strong&gt;value&lt;/strong&gt;, so you wire the chain yourself as a short wrapper script. Both reach the same result; Infisical is just the path of least resistance.&lt;/p&gt;

&lt;h2&gt;
  
  
  In practice: it's mostly one command
&lt;/h2&gt;

&lt;p&gt;Once the secret lives in Infisical, here is what that looks like in practice: the agent should list your open pull requests — to do that it runs GitHub's CLI &lt;code&gt;gh pr list&lt;/code&gt;, and &lt;code&gt;gh&lt;/code&gt; needs your GitHub token. Instead of handing it the token, you let Infisical run the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;infisical run &lt;span class="nt"&gt;--env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;prod &lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/git &lt;span class="nt"&gt;--&lt;/span&gt; gh &lt;span class="nb"&gt;pr &lt;/span&gt;list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step by step: &lt;code&gt;infisical run&lt;/code&gt; fetches the secrets from the &lt;code&gt;/git&lt;/code&gt; folder (environment &lt;code&gt;prod&lt;/code&gt;) and &lt;strong&gt;starts a new process&lt;/strong&gt; for the command after the &lt;code&gt;--&lt;/code&gt; (here &lt;code&gt;gh pr list&lt;/code&gt;) — with the secrets in &lt;em&gt;its&lt;/em&gt; environment. Each secret becomes an environment variable with the same name as its key in Infisical: the &lt;code&gt;/git&lt;/code&gt; folder holds a secret called &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;, so the process sees &lt;code&gt;$GITHUB_TOKEN&lt;/code&gt;. The GitHub CLI &lt;code&gt;gh&lt;/code&gt; reads that variable on its own — the way many tools pick up their token from a known env var — and does its job. The key point: the secret lives inside that child process, not with the agent, and its value never shows up on stdout — so never in the transcript the agent sends to the AI. That is the fetch → into the env → run chain from above, packed into one command. For most teams, that &lt;em&gt;is&lt;/em&gt; the solution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwhb23nlntprctew9og1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwhb23nlntprctew9og1.jpg" alt="Infisical project overview: the /git folder with secrets GITHUB_TOKEN, GITLAB_HOST and GITLAB_TOKEN, values masked" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In Infisical: the &lt;code&gt;/git&lt;/code&gt; folder holds a secret named &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; — that exact key becomes &lt;code&gt;$GITHUB_TOKEN&lt;/code&gt; in the process (the values stay masked).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I wrap it in a &lt;code&gt;get-secret&lt;/code&gt; helper so the call is short and the auth is automatic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;get-secret &lt;span class="nb"&gt;exec &lt;/span&gt;git &lt;span class="nt"&gt;--&lt;/span&gt; gh &lt;span class="nb"&gt;pr &lt;/span&gt;list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood that is just &lt;code&gt;infisical run&lt;/code&gt; with a machine-identity token pulled from the OS keychain — so every call skips passing &lt;code&gt;--token&lt;/code&gt;, &lt;code&gt;--projectId&lt;/code&gt; and &lt;code&gt;--domain&lt;/code&gt; by hand. For &lt;em&gt;using&lt;/em&gt; a secret you could equally type the bare &lt;code&gt;infisical run&lt;/code&gt;; here the wrapper is pure convenience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That closes the leak:&lt;/strong&gt; the secret is used, never seen — not by the model, not in the transcript. I checked it adversarially: fetch the real value, capture everything the command prints, search that output for the secret — over &lt;code&gt;get-secret exec&lt;/code&gt; it shows up nowhere an agent could read it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzn4odfuo2ojjk4ji07lm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzn4odfuo2ojjk4ji07lm.png" alt="How it stays safe — infisical run injects the token into a new child process; the AI agent only ever sees the command and the result, never the value" width="799" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading single values (with a guardrail)
&lt;/h2&gt;

&lt;p&gt;That covers &lt;em&gt;using&lt;/em&gt;. But sometimes you only want to &lt;em&gt;look at&lt;/em&gt; a value (debugging, a quick check) — or you ask the agent for it directly, e.g. "show me the staging DB password." For that there's a read path, and the catch: &lt;code&gt;infisical secrets get GITHUB_TOKEN&lt;/code&gt; prints the value in plaintext to stdout. Fine for a human at a terminal; for an agent, that stdout is the transcript again.&lt;/p&gt;

&lt;p&gt;So we put a small guardrail in front of it: print the value only when a human is actually watching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# get-secret &amp;lt;folder&amp;gt;/&amp;lt;NAME&amp;gt; — print a value, but only to a human&lt;/span&gt;
&lt;span class="nv"&gt;val&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;infisical secrets get &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;prod &lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/&lt;/span&gt;&lt;span class="nv"&gt;$folder&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--plain&lt;/span&gt; &lt;span class="nt"&gt;--silent&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# real terminal -&amp;gt; a human; a pipe -&amp;gt; an agent&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; 1 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$val&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'[redacted len=%s]\n'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${#&lt;/span&gt;&lt;span class="nv"&gt;val&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"reveal in a terminal: get-secret &lt;/span&gt;&lt;span class="nv"&gt;$folder&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt; --show"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;[ -t 1 ]&lt;/code&gt; checks whether stdout is a real terminal. An agent's stdout is normally a pipe — so it gets &lt;code&gt;[redacted len=40]&lt;/code&gt; instead of the value, plus a hint for how a human reveals it in full at the terminal (&lt;code&gt;--show&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So there is no misunderstanding: this is a guardrail, not a wall — and not the actual prevention.&lt;/strong&gt; That lives in &lt;code&gt;exec&lt;/code&gt;; an agent doesn't need the raw read path to &lt;em&gt;use&lt;/em&gt; a secret at all. The guardrail only catches the most common reflex — "fetch the value and print it" — and it sits on &lt;em&gt;this&lt;/em&gt; wrapper alone. Anyone with shell access and the token can call the raw CLI directly and get the value (or allocate a pseudo-terminal, which makes &lt;code&gt;[ -t 1 ]&lt;/code&gt; true). A &lt;strong&gt;real wall&lt;/strong&gt; only comes from not giving the agent the token in the first place — e.g. a sandbox that knows only the gated wrapper, not the raw CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does the agent know to use &lt;code&gt;exec&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;It doesn't, on its own — you tell it once. In the agent's instructions — &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt;, a skill, or the system prompt — you write: "use &lt;code&gt;get-secret exec&lt;/code&gt; for secrets, never fetch the raw value."&lt;/p&gt;

&lt;p&gt;Wait: isn't that just another prompt rule, the kind I said above don't work as a boundary? It is — but this one has a safety net. Follow it, and everything runs leak-free through &lt;code&gt;exec&lt;/code&gt;. Reach for the read path instead, and you get &lt;code&gt;[redacted]&lt;/code&gt;. The rule only picks the &lt;em&gt;convenient&lt;/em&gt; path; that the obvious mistake gives nothing away is the structure's job (env injection + the gate). That's the difference between &lt;em&gt;asking&lt;/em&gt; for behaviour and &lt;em&gt;backstopping&lt;/em&gt; it.&lt;/p&gt;

&lt;h2&gt;
  
  
  If your vault has no &lt;code&gt;run&lt;/code&gt; command
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;infisical run&lt;/code&gt; does the injection step for you: fetch the secrets, put them in the environment, run the command, never touch stdout. Vaults like &lt;a href="https://developer.hashicorp.com/vault" rel="noopener noreferrer"&gt;HashiCorp Vault&lt;/a&gt;, &lt;a href="https://www.passwordstore.org/" rel="noopener noreferrer"&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://aws.amazon.com/secrets-manager/" rel="noopener noreferrer"&gt;AWS Secrets Manager&lt;/a&gt; only hand you the &lt;em&gt;value&lt;/em&gt; — so you write that one step yourself. It is a handful of lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# run-with-secret &amp;lt;cmd...&amp;gt; — load a secret from any vault into the env, then run the command.&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;vault kv get &lt;span class="nt"&gt;-field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;token secret/git&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;   &lt;span class="c"&gt;# or: pass show git/token, aws secretsmanager get-secret-value …&lt;/span&gt;
&lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;run-with-secret gh pr list&lt;/code&gt; passes the token to &lt;code&gt;gh&lt;/code&gt; through the environment via command substitution — it never crosses stdout, so the agent sees only the command, never the value. More secrets? More &lt;code&gt;export&lt;/code&gt; lines. That is exactly what &lt;code&gt;get-secret exec&lt;/code&gt; does under the hood; Infisical just saves you from writing it.&lt;/p&gt;

&lt;p&gt;So the split is simple. The &lt;strong&gt;read gate&lt;/strong&gt; above you build once, whatever vault you use. The &lt;strong&gt;injection&lt;/strong&gt; is free with Infisical (&lt;code&gt;infisical run&lt;/code&gt;) and a few lines with anything else. The safety never lived in the vault — it lives in how you hand the value to the agent: into the process, never into the prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing a new secret
&lt;/h2&gt;

&lt;p&gt;Writing a &lt;em&gt;new&lt;/em&gt; secret is the asymmetric direction: the value has to enter from somewhere, and if a person pastes it into chat for the agent to store, that paste is already the leak. So the write path takes values only over stdin or a file, never typed into a command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VALUE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | set-secret git/TOKEN -      &lt;span class="c"&gt;# wraps `infisical secrets set`, reads stdin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Get the scripts
&lt;/h2&gt;

&lt;p&gt;The whole thing is, in the end, a short shell script: &lt;code&gt;infisical run&lt;/code&gt; to use secrets, a TTY-gated &lt;code&gt;infisical secrets get&lt;/code&gt; to read one, and for auth a headless &lt;strong&gt;machine identity&lt;/strong&gt; pulled from the OS keychain (no interactive login). Here are the two wrappers to download: &lt;a href="https://th3chris.com/scripts/get-secret" rel="noopener noreferrer"&gt;get-secret&lt;/a&gt; and &lt;a href="https://th3chris.com/scripts/set-secret" rel="noopener noreferrer"&gt;set-secret&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Install (macOS; needs the &lt;code&gt;infisical&lt;/code&gt; CLI and &lt;code&gt;jq&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.local/bin
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://th3chris.com/scripts/get-secret &lt;span class="nt"&gt;-o&lt;/span&gt; ~/.local/bin/get-secret
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://th3chris.com/scripts/set-secret &lt;span class="nt"&gt;-o&lt;/span&gt; ~/.local/bin/set-secret
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/.local/bin/get-secret ~/.local/bin/set-secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scripts assume macOS — credentials live in the Keychain. The pattern itself is platform-independent: on Linux/Windows just swap the &lt;code&gt;security&lt;/code&gt; call (step 3) for your OS secret store (libsecret/&lt;code&gt;pass&lt;/code&gt;, Windows Credential Manager), or pass the machine identity via an &lt;code&gt;INFISICAL_TOKEN&lt;/code&gt; env var.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup (one-time):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the &lt;code&gt;infisical&lt;/code&gt; CLI and &lt;code&gt;jq&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a &lt;strong&gt;machine identity&lt;/strong&gt; in Infisical (Org → &lt;em&gt;Access Control → Identities&lt;/em&gt; → Universal Auth), generate a &lt;strong&gt;client secret&lt;/strong&gt;, and give the identity read access to the project (write access too, for &lt;code&gt;set-secret&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Put the client ID and secret in the keychain (service name = your &lt;code&gt;KEYCHAIN_SVC&lt;/code&gt;) so it never lands in shell history:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   security add-generic-password &lt;span class="nt"&gt;-s&lt;/span&gt; infisical-machine-identity &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt;
   &lt;span class="c"&gt;# at the prompt, enter:  CLIENT_ID:CLIENT_SECRET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Fill in the &lt;code&gt;CONFIG&lt;/code&gt; block at the top of the script — just &lt;code&gt;INFISICAL_DOMAIN&lt;/code&gt; (your instance, or &lt;code&gt;https://app.infisical.com&lt;/code&gt;) and &lt;code&gt;KEYCHAIN_SVC&lt;/code&gt; (the name from step 3). Project and environment do &lt;em&gt;not&lt;/em&gt; go here — they live per repo (step 5).&lt;/li&gt;
&lt;li&gt;Bind each repo to a project — a &lt;code&gt;.infisical.json&lt;/code&gt; with &lt;code&gt;workspaceId&lt;/code&gt; (the project ID from the project URL / &lt;em&gt;Project Settings&lt;/em&gt;) and &lt;code&gt;defaultEnvironment&lt;/code&gt; (env slug, e.g. &lt;code&gt;prod&lt;/code&gt;):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'{"workspaceId":"&amp;lt;projectId&amp;gt;","defaultEnvironment":"prod"}'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .infisical.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;code&gt;infisical init&lt;/code&gt; does this interactively, but needs a login session; with a machine-identity-only setup you write the non-sensitive file by hand.)&lt;/p&gt;

&lt;p&gt;After that, &lt;code&gt;get-secret exec &amp;lt;folder&amp;gt; -- &amp;lt;cmd&amp;gt;&lt;/code&gt; just works in the repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple projects?&lt;/strong&gt; You don't copy the script per project — it lives once, globally, in your PATH (&lt;code&gt;~/.local/bin&lt;/code&gt;). Which Infisical project applies is recorded in each repo's &lt;code&gt;.infisical.json&lt;/code&gt;: the wrapper reads the &lt;code&gt;workspaceId&lt;/code&gt; from it and passes it as &lt;code&gt;--projectId&lt;/code&gt;. That step is necessary because Infisical's machine-identity auth &lt;em&gt;always&lt;/em&gt; requires an explicit project ID — the CLI's &lt;code&gt;.infisical.json&lt;/code&gt; auto-detection only kicks in for an interactive login. So &lt;code&gt;get-secret exec git -- …&lt;/code&gt; hits the right project in each repo — no flag, no second copy; if the file is missing, the wrapper stops with a hint.&lt;/p&gt;

&lt;p&gt;This is a workstation setup. &lt;strong&gt;In CI there is no keychain&lt;/strong&gt; — there you pass the token via &lt;code&gt;INFISICAL_TOKEN&lt;/code&gt; from a masked CI variable, or use OIDC auth. And: Universal Auth with a static client secret is the &lt;em&gt;simplest&lt;/em&gt;, not the &lt;em&gt;safest&lt;/em&gt; option — rotate it like any credential, or use OIDC/native identities where the runtime supports them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Still: rotate your credentials&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No gate is 100% protection — it prevents the &lt;em&gt;accidental&lt;/em&gt; leak during development, but it does not make a credential un-leakable. So treat your tokens accordingly: rotate them on a schedule, and treat anything that &lt;em&gt;could&lt;/em&gt; have been exposed as exposed. Defense in depth, not a substitute for rotation.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Most companies solve this today with prompt rules — "please don't print secrets." The problem: a prompt rule is not a security boundary. If you want to run AI agents in production, you need &lt;strong&gt;technical boundaries instead of behavioral ones&lt;/strong&gt; — exactly the kind this article describes. Drawing those boundaries cleanly, so automation moves fast without quietly creating risk, is part of what I do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://th3chris.com/en/projektanfrage" rel="noopener noreferrer"&gt;Let's talk about it.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>My coworker wanted mood-based music. I built it before the beer got warm.</title>
      <dc:creator>Christian Daniel</dc:creator>
      <pubDate>Sat, 07 Mar 2026 01:13:57 +0000</pubDate>
      <link>https://dev.to/th3chris/my-coworker-wanted-mood-based-music-i-built-it-before-the-beer-got-warm-3h10</link>
      <guid>https://dev.to/th3chris/my-coworker-wanted-mood-based-music-i-built-it-before-the-beer-got-warm-3h10</guid>
      <description>&lt;p&gt;&lt;a href="https://mood.th3chris.com/en" rel="noopener noreferrer"&gt;moodplay&lt;/a&gt; uses your webcam to detect emotions and plays a matching Spotify playlist — no login, no setup, no ads - just 4 fun.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>showdev</category>
      <category>sideprojects</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
