<?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: Shanjeev Rajendran</title>
    <description>The latest articles on DEV Community by Shanjeev Rajendran (@shanjeev_rajendran).</description>
    <link>https://dev.to/shanjeev_rajendran</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%2F4185670%2Fd1f0c86d-1f3b-428e-8abc-15870a8405df.jpg</url>
      <title>DEV Community: Shanjeev Rajendran</title>
      <link>https://dev.to/shanjeev_rajendran</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shanjeev_rajendran"/>
    <language>en</language>
    <item>
      <title>What Cloakroom would and wouldn't have done in October's incidents</title>
      <dc:creator>Shanjeev Rajendran</dc:creator>
      <pubDate>Wed, 28 Oct 2026 01:48:17 +0000</pubDate>
      <link>https://dev.to/shanjeev_rajendran/what-cloakroom-would-and-wouldnt-have-done-in-octobers-incidents-45h</link>
      <guid>https://dev.to/shanjeev_rajendran/what-cloakroom-would-and-wouldnt-have-done-in-octobers-incidents-45h</guid>
      <description>&lt;p&gt;An API key is a password that also pays the bill: hold a copy and you can run up charges on someone else’s account until they revoke it. In October, developers’ keys leaked four different ways in one month.&lt;/p&gt;

&lt;p&gt;LiteLLM 1.107.0, a popular AI gateway, shipped an update that copied its users’ keys every time Python started; the attacker got in through a master key that had been committed to a public repo. Tensorlake’s npm package 0.5.144 carried code that copied developers’ keys, used them to spread into their other packages, and planted agent config in repos. Railway, a cloud hosting company, was breached and some stored settings were decrypted. OpenCode’s hosted Console exposed the provider keys users had saved in it. I checked which steps Cloakroom touches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step by step
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;stops&lt;/strong&gt; means Cloakroom, as shipped, prevents the step. &lt;strong&gt;reduces&lt;/strong&gt; means it removes one copy or one path but not the step. &lt;strong&gt;none&lt;/strong&gt; means it is outside what a local agent hook can see.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Incident, step&lt;/th&gt;
&lt;th&gt;Cloakroom&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LiteLLM: master key in an agent config file, committed to a public repo, never revoked&lt;/td&gt;
&lt;td&gt;reduces (only if the commit went through an agent or Cloakroom's git hook; unknown)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LiteLLM: key used on their agent API, which returned a GitHub token&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LiteLLM: stolen token sends repo secrets to a webhook through CI&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LiteLLM: release workflow pointed at a lookalike fork&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LiteLLM: wheels run remote code at every Python start&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LiteLLM: stealer reads disk, browsers, cloud metadata&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LiteLLM: encrypted archive sent out&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LiteLLM: Claude Code puts a GitHub token in a response body&lt;/td&gt;
&lt;td&gt;reduces if it passed through a tool result; none if Claude Code added it to its own API request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tensorlake: admin account commits the payload&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tensorlake: &lt;code&gt;npm install&lt;/code&gt; runs the payload&lt;/td&gt;
&lt;td&gt;reduces (narrow: only if the agent runs the install; it masks output, doesn't block)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tensorlake: reads credential files and &lt;code&gt;.env*&lt;/code&gt; files&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tensorlake: plants &lt;code&gt;.claude/settings.json&lt;/code&gt; in repos&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tensorlake: republishes the victim's packages&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Railway: privilege escalation in build infrastructure&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Railway: encrypted variables pulled, some decrypted&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Railway: search for AI provider key names&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenCode: legacy system behind Console reached&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenCode: stored provider keys exposed&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The test
&lt;/h2&gt;

&lt;p&gt;I built a git repo with 11 random dummy secrets in the places the stealers look: a &lt;code&gt;.env&lt;/code&gt;, a gateway config file, an MCP config, npm, AWS and SSH files, and a script that prints a &lt;code&gt;gho_&lt;/code&gt; token the way &lt;code&gt;gh auth status --show-token&lt;/code&gt; does. Claude Code ran the same task twice, plain and with Cloakroom 0.8.0: read the configs, run the script, write a setup file, commit. Then I scanned the model's input, the local session file and git history for the exact dummy values.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;Without Cloakroom&lt;/th&gt;
&lt;th&gt;With Cloakroom&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What the model was given&lt;/td&gt;
&lt;td&gt;11 of 11&lt;/td&gt;
&lt;td&gt;0 of 11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session transcript&lt;/td&gt;
&lt;td&gt;11 of 11&lt;/td&gt;
&lt;td&gt;0 of 11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git history&lt;/td&gt;
&lt;td&gt;1 of 11 (the gateway key)&lt;/td&gt;
&lt;td&gt;0 of 11&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The agent's final answer held none in either run. Cloakroom withheld the script's whole output, so the token never reached the model.&lt;/p&gt;

&lt;p&gt;Cloakroom refused the agent's write of the gateway key into the setup file. The agent used a placeholder and committed that file alone; the commit check rightly passed it, so it never had a key to stop. Read it as "the key didn't get committed", not as a test of the commit check.&lt;/p&gt;

&lt;p&gt;To rerun it (two short runs on your own login): &lt;a href="https://github.com/shanjeevrajendran/cloakroom/blob/main/bench/black-october/run.sh" rel="noopener noreferrer"&gt;&lt;code&gt;bench/black-october/run.sh&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cloakroom would not have stopped any of the four incidents. Every theft step reads disk, a server or CI directly.&lt;/li&gt;
&lt;li&gt;The only steps it touches are upstream copies: a key committed through the agent, and a token passing through tool output into model context and gateway logs. Even the commit step depends on how it was made, which LiteLLM didn't say.&lt;/li&gt;
&lt;li&gt;Neither stealer read agent session transcripts, so keeping secrets out of transcripts would not have changed these thefts either. Both took agent credentials straight from disk, which Cloakroom's placeholders never touch.&lt;/li&gt;
&lt;li&gt;Placeholders are restored to real values in files the agent writes, by design, and Claude Code's session log records what was written (a known limit in the repo's limits file). In this run the agent wrote no values into the setup file, so that path wasn't exercised. A task that copies a key into a new file would put it back on disk and in the transcript.&lt;/li&gt;
&lt;li&gt;The Tensorlake payload is mostly encoded, so what I know about what it reads comes from the plain strings only.&lt;/li&gt;
&lt;li&gt;I found no first-hand statement from OpenCode, only users' issues. Railway's report is preliminary.&lt;/li&gt;
&lt;li&gt;One machine, one model, one prompt: counts can move a little between runs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  In 0.8.0
&lt;/h2&gt;

&lt;p&gt;Two features shipped today (2026-10-27) in Cloakroom 0.8.0 on &lt;a href="https://pypi.org/project/cloakroom/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;. &lt;code&gt;cloakroom exposure&lt;/code&gt; reports which secrets your agents saw. &lt;code&gt;cloakroom scan&lt;/code&gt; checks a repo's agent and editor config (&lt;code&gt;.claude/settings.json&lt;/code&gt; hooks, &lt;code&gt;.mcp.json&lt;/code&gt;, &lt;code&gt;.vscode/tasks.json&lt;/code&gt;) for commands that were added or changed, and a git hook runs it after checkout and merge and warns. Both report; neither blocks.&lt;/p&gt;

&lt;p&gt;The commands are in the &lt;a href="https://github.com/shanjeevrajendran/cloakroom/blob/main/README.md" rel="noopener noreferrer"&gt;README's command reference&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>How I keep secrets and personal data out of Claude Code</title>
      <dc:creator>Shanjeev Rajendran</dc:creator>
      <pubDate>Thu, 15 Oct 2026 00:39:00 +0000</pubDate>
      <link>https://dev.to/shanjeev_rajendran/how-i-keep-secrets-and-personal-data-out-of-claude-code-61n</link>
      <guid>https://dev.to/shanjeev_rajendran/how-i-keep-secrets-and-personal-data-out-of-claude-code-61n</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Claude Code reads whatever a task touches. Ask it to fix a failing job and it opens the &lt;code&gt;.env&lt;/code&gt;, tails the log, greps the support export. Everything it reads goes to the model as context.&lt;/p&gt;

&lt;p&gt;But my working folders hold API keys, database passwords, and support tickets with customers' names, emails and home addresses. I didn't want any of that leaving my machine just because Claude needed to look at a file that happened to contain it.&lt;/p&gt;

&lt;p&gt;Secret scanners help with keys that have a known shape. They don't help with a customer's name in a ticket, or an address in a log line. To a regex, those look like ordinary text.&lt;/p&gt;

&lt;p&gt;So I built Cloakroom: a Claude Code plugin that masks secrets and personal data before the model sees them, and puts the real values back when Claude writes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Claude sees
&lt;/h2&gt;

&lt;p&gt;Claude Code lets a plugin run hooks before and after every tool call. Cloakroom sits on those hooks. When Claude reads a file or runs a command, the output goes through Cloakroom first. It looks for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;secrets with a known shape (cloud keys, tokens, private keys, passwords in URLs and config lines)&lt;/li&gt;
&lt;li&gt;numbers with a checksum (cards, IBANs, SSNs)&lt;/li&gt;
&lt;li&gt;personal data in free text (names, emails, addresses, birth dates), found by a small model that runs locally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each value it finds is swapped for a placeholder. Here's a support note and a config line, on disk and as Claude gets them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# on disk
Nadia Petrov called about a double charge. Reach her at nadia.petrov@gmail.com, 18 Larkspur Lane, Charlotte NC.
DATABASE_URL=postgres://admin:Hunter2x9@10.0.4.12/prod

# what Claude sees
PII_PERSON_1 called about a double charge. Reach her at PII_EMAIL_1, PII_ADDRESS_1.
DATABASE_URL=postgres://admin:PII_SECRET_1@10.0.4.12/prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude can still do the job. It knows there's a person, an email and a password, and where each one sits. It just doesn't know the values. A value keeps the same placeholder for the whole session, so &lt;code&gt;PII_PERSON_1&lt;/code&gt; in one file is the same person in the next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scans and photos
&lt;/h2&gt;

&lt;p&gt;A lot of personal data lives in pictures: a scanned form, a photo of an ID. Cloakroom can't check pixels, so it reads them instead. A scanned PDF page or a photo that Claude opens goes through OCR on your own machine, and Claude gets the masked text instead of the image. The OCR models ship inside the package, so nothing is uploaded. A photo of a form that says &lt;code&gt;Patient: Nadia Petrov DOB 03/14/1986&lt;/code&gt; reaches Claude as &lt;code&gt;Patient: PII_PERSON_1 DOB PII_DATE_1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;OCR misreads digits, so in that text every long run of them is masked as an account number. Handwriting still reads poorly.&lt;/p&gt;

&lt;h2&gt;
  
  
  How restore works
&lt;/h2&gt;

&lt;p&gt;Masking alone breaks things. If Claude writes &lt;code&gt;Hi PII_PERSON_1,&lt;/code&gt; into a reply draft, that's useless to you.&lt;/p&gt;

&lt;p&gt;So the original values go into an encrypted vault for the session, with the key in your OS's secure store (Keychain on macOS, Credential Manager on Windows, Secret Service on Linux). When Claude writes a file or runs a command that contains a placeholder, the hook swaps the real value back in, locally, just before the tool runs. The draft on your disk says &lt;code&gt;Hi Nadia Petrov,&lt;/code&gt;. The model never saw the name.&lt;/p&gt;

&lt;p&gt;Prompts are different. Claude Code lets a hook block a prompt but not rewrite it, so if you paste a key or an email straight into the chat, Cloakroom stops that prompt and tells you what it found. Put the detail in a file and ask Claude to read it instead, or start the prompt with &lt;code&gt;cr!&lt;/code&gt; to send it as is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing false alarms
&lt;/h2&gt;

&lt;p&gt;A detector that masks too much gets switched off. Mine flagged &lt;code&gt;Order 4471&lt;/code&gt; as an account number, which is harmless and gets in the way.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cloakroom dashboard&lt;/code&gt; opens a local page listing every masked call. Label an alert as correct, a false alarm, or something it missed. A false alarm suggests a "never mask this" rule; a miss suggests an "always mask this" one. Approve the rule and that order number reaches Claude as plain text from then on. Rules that would unmask a key, password or card number always wait for your yes, and undo puts everything back.&lt;/p&gt;

&lt;p&gt;The detection model itself never changes. Your labels become rules in front of it, encrypted like the vault.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Regex isn't enough.&lt;/strong&gt; Keys are easy. Names, addresses and free-text personal data need a model. I use a small one that runs on your machine. The cost is a one-time download and some false alarms, which is why the labels and rules exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Restore is the hard part.&lt;/strong&gt; Detection gets the attention, but the real work was making write-back reliable: the same placeholder everywhere in a session, the right shape for each tool's input, and edge cases like Claude's Edit tool checking text against the file before any hook runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fail closed.&lt;/strong&gt; If Cloakroom can't check something (the model still loading, the service down, OCR timing out), tool calls are held back rather than waved through. Prompts fall back to the fast rules, so you're never locked out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encoded output counts.&lt;/strong&gt; A command like &lt;code&gt;base64 .env&lt;/code&gt; or &lt;code&gt;xxd&lt;/code&gt; turns a secret into something no pattern matches. Cloakroom decodes those dumps and checks them too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Know what it isn't.&lt;/strong&gt; Cloakroom stops accidental exposure: the key in a file Claude happened to open, the customer list in a log. It isn't a sandbox, and a model determined to leak data piece by piece is out of scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;It's open source, runs locally, needs no account, and works on macOS, Windows and Linux. Inside Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/plugin marketplace add shanjeevrajendran/cloakroom
/plugin install cloakroom@cloakroom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It protects keys and card numbers within seconds, then downloads the local model in the background for names and addresses.&lt;/p&gt;

&lt;p&gt;I'd like to hear what it misses and what it over-masks: &lt;a href="https://github.com/shanjeevrajendran/cloakroom" rel="noopener noreferrer"&gt;https://github.com/shanjeevrajendran/cloakroom&lt;/a&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>privacy</category>
      <category>security</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
