<?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: Erkan Ciftci</title>
    <description>The latest articles on DEV Community by Erkan Ciftci (@fxerkan).</description>
    <link>https://dev.to/fxerkan</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%2F4130825%2F1e9474e6-f239-4b7b-83a1-d84087409162.jpg</url>
      <title>DEV Community: Erkan Ciftci</title>
      <link>https://dev.to/fxerkan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fxerkan"/>
    <language>en</language>
    <item>
      <title>Remote Control for API-Based Claude Code: Approve Bedrock, Vertex &amp; Foundry Sessions From Your Phone (Claude Mobile App)</title>
      <dc:creator>Erkan Ciftci</dc:creator>
      <pubDate>Sun, 04 Oct 2026 14:13:10 +0000</pubDate>
      <link>https://dev.to/fxerkan/remote-control-for-api-based-claude-code-approve-bedrock-vertex-foundry-sessions-from-your-4fa6</link>
      <guid>https://dev.to/fxerkan/remote-control-for-api-based-claude-code-approve-bedrock-vertex-foundry-sessions-from-your-4fa6</guid>
      <description>&lt;h3&gt;
  
  
  Claude Code’s Remote Control needs a Claude subscription login, so API-provider sessions are stuck in the terminal — here’s how I gave them one back with hooks, a file queue and a single Python file
&lt;/h3&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%2Fo0ozbdubfmx9o1ta54s3.jpeg" 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%2Fo0ozbdubfmx9o1ta54s3.jpeg" alt="ccBridger: Remote Control for API-based Claude Code sessions (Bedrock, Vertex, Foundry)" width="800" height="336"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ccBridger: Remote Control for API-based Claude Code sessions (Bedrock, Vertex, Foundry)&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TL;DR — Claude Code’s Remote Control needs a Claude subscription login. Sessions that run through an API provider — AWS Bedrock, Google Vertex AI, Microsoft Foundry — don’t get it, so a single permission prompt can stall an hour of work while you are in a meeting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ccBridger&lt;/strong&gt; is a small open-source bridge: hooks record what the session needs, a tiny helper session shows it in the Claude mobile app as a native prompt, and your tap goes back to the terminal.&lt;/p&gt;

&lt;p&gt;No server, no open ports, no additional application, standard library only.&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://github.com/fxerkan/ccbridger" rel="noopener noreferrer"&gt;github.com/fxerkan/ccbridger&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  The problem
&lt;/h4&gt;

&lt;p&gt;A lot of companies run Claude Code through their cloud provider instead of a Claude subscription: &lt;strong&gt;AWS Bedrock, Google Vertex AI&lt;/strong&gt; or &lt;strong&gt;Microsoft Foundry&lt;/strong&gt;. The reasons are sensible: billing lands on the cloud invoice, data stays inside the account, access goes through the provider’s own identity system. I work that way on two client projects, both on Bedrock.&lt;/p&gt;

&lt;p&gt;What you give up is Remote Control. With a subscription login you can open the Claude app on your phone, see the session that is running on your laptop, answer its questions and keep it moving. With an &lt;strong&gt;API provider that door is closed&lt;/strong&gt;. The session lives in the terminal tab where you started it, and nowhere else.&lt;/p&gt;

&lt;p&gt;That sounds like a small inconvenience until you look at how agent work actually goes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You start a long task — a migration, a refactor, an infrastructure change — and go to a meeting.&lt;/li&gt;
&lt;li&gt;Eight minutes in, the agent wants to run a command it isn’t allowed to run on its own. It asks. Nobody answers.&lt;/li&gt;
&lt;li&gt;You come back fifty minutes later to a prompt that says “Do you want to proceed?” and an agent that has done nothing since.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The other failure mode is worse. To avoid the stall, people start sessions with &lt;strong&gt;--dangerously-skip-permissions&lt;/strong&gt;. Now nothing waits for you — including the things that should.&lt;/p&gt;

&lt;p&gt;And it isn’t only permissions. Agents ask real questions too: “The upgrade can’t be rolled back. Test workspace first, or straight to prod?” That is exactly the decision you want to make yourself, and exactly the one that sits unanswered while you are away.&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%2F6lqq11h7lmz752bqbxq0.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%2F6lqq11h7lmz752bqbxq0.png" alt="The same prompt, two outcomes: without the bridge it waits at an empty desk for 48 minutes; with ccBridger the answer costs ten seconds from your phone" width="800" height="371"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The same prompt, two outcomes: without the bridge it waits at an empty desk for 48 minutes; with ccBridger the answer costs ten seconds from your phone&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  An analogy before the tech
&lt;/h4&gt;

&lt;p&gt;Picture a contractor renovating your kitchen while you are at work. They are good and fast, but they have one rule: before knocking down a wall, they ask. If they can only ask by knocking on your study door, the work stops the moment you leave the house. Give them your phone number and the same rule costs you ten seconds instead of an afternoon.&lt;/p&gt;

&lt;p&gt;ccBridger is the phone number. The rule — ask before doing something that matters — stays exactly as it was.&lt;/p&gt;
&lt;h4&gt;
  
  
  Why the obvious answers don’t fit
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;“Just SSH in and attach to tmux.”&lt;/strong&gt; Works, on a laptop keyboard. On a phone, typing into a terminal UI to press 1 is miserable, and you only find out there is something to answer if you go and look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Build a web dashboard.”&lt;/strong&gt; Now there is a server to run, a port to expose, authentication to get right, and a new place where your prompts and commands live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Wrap the CLI.”&lt;/strong&gt; A wrapper has to sit between you and the terminal all the time, and it changes how you start every session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Skip permissions.”&lt;/strong&gt; See above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I wanted was narrower:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep working exactly as before. If I’m at the keyboard, nothing changes. If I’m not, the question finds me — in an app I already have, on an account I already own — and my answer goes back. Nothing to host.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  The idea: borrow the Remote Control you already have
&lt;/h4&gt;

&lt;p&gt;Remote Control doesn’t work for the API-based session. But it works fine for a different session on the same machine that runs on my own Claude login. So the bridge doesn’t try to remote-control that session at all. It starts a tiny second session whose only job is to ask me a question, and lets Remote Control carry that question to my phone.&lt;/p&gt;

&lt;p&gt;Three pieces make that work.&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%2Fr19v53d1psijgimxacde.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%2Fr19v53d1psijgimxacde.png" alt="ccBridger architecture: a permission request leaves the API-based session through hooks, lands in a plain-file queue, and after 15 seconds a relay session running on your own Claude login carries it to the Claude mobile app; your tap maps back to a decision file and unblocks the terminal" width="799" height="534"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ccBridger architecture: a permission request leaves the API-based session through hooks, lands in a plain-file queue, and after 15 seconds a relay session running on your own Claude login carries it to the Claude mobile app; your tap maps back to a decision file and unblocks the terminal&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  1. Hooks on the API-based session
&lt;/h4&gt;

&lt;p&gt;Claude Code runs hooks — shell commands — at defined points.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ccb install&lt;/strong&gt; adds five of them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PermissionRequest&lt;/strong&gt;  — Writes the request (tool, input) to a file, then waits for a decision file. The terminal prompt stays live the whole time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop, SessionStart, UserPromptSubmit, Notification&lt;/strong&gt;  — Keep one background waiter per session alive.&lt;/p&gt;

&lt;p&gt;The decision goes back as the hook’s JSON output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"hookSpecificOutput"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"hookEventName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PermissionRequest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"behavior"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The waiter is how a message reaches an idle session. Those four hooks are registered with &lt;strong&gt;asyncRewake&lt;/strong&gt; : they run in the background, and when one exits with code 2, Claude Code wakes the session and hands it the hook’s stderr. So the waiter polls an inbox file; when a message lands, it prints the message and exits 2. The idle session wakes up and treats it as something the user said.&lt;/p&gt;

&lt;p&gt;Four hooks for one waiter looks redundant. It isn’t: a turn interrupted with Esc never fires &lt;strong&gt;Stop&lt;/strong&gt; , so without the others the waiter would be gone until the next full turn.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. A file queue
&lt;/h4&gt;

&lt;p&gt;All state is plain files under &lt;strong&gt;~/.claude/ccbridger/&lt;/strong&gt; , one directory per session: &lt;strong&gt;pending.json, decision.json, inbox, waiter.pid&lt;/strong&gt;. Writes are atomic renames; the inbox is taken with a rename so exactly one waiter gets each message. Polling is once a second. There is no daemon and nothing listens on the network.&lt;/p&gt;

&lt;p&gt;Which sessions exist, and whether each is idle, busy or waiting, comes from Claude Code’s own registry in &lt;strong&gt;~/.claude/sessions/&lt;/strong&gt;. What a session is doing comes from its transcript file. &lt;strong&gt;ccb ls&lt;/strong&gt; and &lt;strong&gt;ccb show&lt;/strong&gt; are just readers for those two things.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. The relay session
&lt;/h4&gt;

&lt;p&gt;When a request has waited 15 seconds without a local answer, the hook starts the relay in &lt;strong&gt;tmux&lt;/strong&gt; (simplified):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--model&lt;/span&gt; haiku &lt;span class="nt"&gt;--tools&lt;/span&gt; AskUserQuestion &lt;span class="nt"&gt;--remote-control&lt;/span&gt; &lt;span class="s2"&gt;"Approval - Prod deploy - infra"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="nt"&gt;--append-system-prompt&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;relay instructions&amp;gt;"&lt;/span&gt; &lt;span class="nt"&gt;--settings&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;two hooks&amp;gt;'&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;the request&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things about that line matter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;It runs on my Claude login&lt;/em&gt;&lt;/strong&gt; , with the provider variables ( &lt;strong&gt;CLAUDE_CODE_USE_BEDROCK, …_VERTEX, …_FOUNDRY&lt;/strong&gt; ) stripped, so Remote Control is available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;It has exactly one tool&lt;/em&gt;&lt;/strong&gt; , &lt;strong&gt;AskUserQuestion&lt;/strong&gt;. It cannot read files or run commands. Its whole job is to turn the request into a native question in the Claude app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;The model never decides anything&lt;/em&gt;&lt;/strong&gt;. A &lt;strong&gt;PostToolUse&lt;/strong&gt; hook on the relay reads the answer I tapped and writes the decision file itself. Only the exact Approve label counts as approval; anything else — Reject, free text, a typo — is a rejection.&lt;/p&gt;

&lt;p&gt;There is one relay per bridged session and it stays open, so the conversation on the phone becomes a readable history: what was asked, what I answered, in order. Its title follows the topic of the latest request.&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%2F7whruhgroc0tdcroaj1m.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%2F7whruhgroc0tdcroaj1m.png" alt="In the Claude mobile app: a blocked permission request from a Bedrock session arrives as a native Approve/Reject prompt, and a two-way conversation with a Vertex session where an AskUserQuestion is answered by option number." width="800" height="566"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;In the Claude mobile app: a blocked permission request from a Bedrock session arrives as a native Approve/Reject prompt, and a two-way conversation with a Vertex session where an AskUserQuestion is answered by option number.&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Questions, and why answers are mapped by position
&lt;/h4&gt;

&lt;p&gt;When the bridged session calls &lt;strong&gt;AskUserQuestion&lt;/strong&gt; , the relay asks the same question with the same options. Early on I noticed the small relay model sometimes shortened the option text. For a question like “upgrade prod now?” that is not a detail I wanted to leave to chance.&lt;/p&gt;

&lt;p&gt;So the relay hook doesn’t forward the label I tapped. It finds the position of my choice among the options the relay showed and maps it onto the option at the same position in the original question. The wording on the phone can drift; the answer cannot. The original text is always in the message right above the prompt.&lt;/p&gt;
&lt;h4&gt;
  
  
  A two-way conversation
&lt;/h4&gt;

&lt;p&gt;Approvals were the starting point, but once the relay existed the next step was obvious. The relay has a &lt;strong&gt;UserPromptSubmit&lt;/strong&gt; hook: anything I type into that conversation that isn’t one of ccBridger’s own messages is put into the session’s inbox instead of being answered by the relay model. The waiter delivers it. When the session finishes its turn, its Stop hook pastes the final message back into the relay.&lt;/p&gt;

&lt;p&gt;So from the phone I can write “ &lt;strong&gt;&lt;em&gt;Is the upgrade done? Are all the filters working?&lt;/em&gt;&lt;/strong&gt; ” and get the real session’s answer in the same thread. If the session is busy, I’m told so, along with what it is doing right now and that my message is queued.&lt;/p&gt;
&lt;h4&gt;
  
  
  Teams: one cloud account, many people
&lt;/h4&gt;

&lt;p&gt;API access is often shared across a team — one AWS account, one GCP project, one Azure resource. The bridge is not shared, and that falls out of the design rather than being added to it:&lt;/p&gt;

&lt;p&gt;Every developer &lt;strong&gt;installs&lt;/strong&gt;  &lt;strong&gt;ccBridger on their own machine&lt;/strong&gt;. State lives under their own &lt;strong&gt;~/.claude&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The relay runs on that person’s own Claude login, so prompts land only in their Claude app. Nobody can see or answer anyone else’s.&lt;/p&gt;

&lt;p&gt;There is nothing central to run, patch or secure.&lt;/p&gt;

&lt;p&gt;For anything beyond the app, each person can set a &lt;strong&gt;notify_cmd&lt;/strong&gt; in their config — a shell command that runs once per request with the title and body in its environment. That covers Slack, ntfy or a pager, per user. People without a Claude subscription can turn the relay off and answer with &lt;strong&gt;ccb ok&lt;/strong&gt; over SSH.&lt;/p&gt;
&lt;h4&gt;
  
  
  This isn’t just my problem — the open requests it answers
&lt;/h4&gt;

&lt;p&gt;I didn’t invent this gap. It’s one of the most-upvoted things on the Claude Code issue tracker. ccBridger doesn’t fix Claude Code — it’s a parallel sidecar, not a product change — but it covers the day-to-day need in these threads. Honestly, and partially:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/claude-code/issues/28795" rel="noopener noreferrer"&gt;&lt;strong&gt;#28795 — Bedrock + AWS SSO in Remote Control&lt;/strong&gt;&lt;/a&gt;. The ask: enterprise users on &lt;strong&gt;CLAUDE_CODE_USE_BEDROCK=1&lt;/strong&gt; + AWS SSO want &lt;strong&gt;Remote Control&lt;/strong&gt; without a separate Claude subscription. Signal: 131 👍, 13 comments — the flagship request&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;→ ccBridger&lt;/strong&gt; : &lt;strong&gt;Partially&lt;/strong&gt; delivers the everyday capability (approve/answer/message a Bedrock session from the phone) through hooks + a relay — different mechanism, not native decoupled auth.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/claude-code/issues/90606" rel="noopener noreferrer"&gt;&lt;strong&gt;#90606 — Permission dialog hangs the host terminal&lt;/strong&gt;&lt;/a&gt;. The ask: a permission prompt with no one at the keyboard blocks the whole session indefinitely. Signal: lower volume, but exactly on point.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;→ ccBridger:&lt;/strong&gt;  &lt;strong&gt;Solves&lt;/strong&gt; it. This is the flagship feature — after 15 seconds with no local answer, the prompt is on your phone, and the terminal stays live the whole time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/claude-code/issues/35637" rel="noopener noreferrer"&gt;&lt;strong&gt;#35637 — Permission prompts don’t render on mobile&lt;/strong&gt;&lt;/a&gt;. The ask: Remote Control shows the session but the actual Approve/Reject prompt never appears on the phone. Signal: 30 👍, 23 comments.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;→ ccBridger: Solves&lt;/strong&gt; the symptom. API-provider prompts arrive as a native Approve/Reject card — only the exact Approve label counts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/claude-code/issues/28508" rel="noopener noreferrer"&gt;&lt;strong&gt;#28508 — Mobile AskUserQuestion answer doesn’t return to the CLI&lt;/strong&gt;&lt;/a&gt;. The ask: you tap an answer on the phone and the terminal never receives it. Signal: 27 👍, 19 comments.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;→ ccBridger: Solves&lt;/strong&gt; the symptom. The answer is mapped back by position, so the wording can drift on the phone but the choice can’t.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/claude-code/issues/95744" rel="noopener noreferrer"&gt;&lt;strong&gt;#95744 — Non-Anthropic provider + Remote Control in one session.&lt;/strong&gt;&lt;/a&gt; The ask: one session that is both on an API provider and remote-controllable. Signal: newer, growing.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;→ ccBridger:&lt;/strong&gt; This is the whole architectural thesis — ccBridger splits those two jobs into two processes instead of forcing one session to do both.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two honest caveats that belong right here, not buried: &lt;strong&gt;&lt;em&gt;ccBridger doesn’t remove the subscription requirement &lt;/em&gt;&lt;/strong&gt; — it moves it to a second Claude login on the same machine (the relay). And it’s &lt;strong&gt;macOS/Linux + tmux&lt;/strong&gt; , not Windows, and ZDR/compliance setups are out of scope. If you can’t run a Claude login at all, you can turn the relay off and answer with &lt;strong&gt;ccb ok&lt;/strong&gt; over SSH, or wire a &lt;strong&gt;notify_cmd&lt;/strong&gt; to Slack/ntfy/a pager.&lt;/p&gt;
&lt;h4&gt;
  
  
  Getting started
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/fxerkan/ccbridger.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;ccbridger
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/ccbridger"&lt;/span&gt; ~/.local/bin/ccbridger
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/ccbridger"&lt;/span&gt; ~/.local/bin/ccb &lt;span class="c"&gt;# short alias&lt;/span&gt;
ccb selftest

ccb &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; &lt;span class="c"&gt;# every Bedrock / Vertex / Foundry session&lt;/span&gt;
&lt;span class="c"&gt;# or: ccb install ~/code/project-a ~/code/project-b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;--global&lt;/strong&gt; stays out of the way of sessions that already run on a Claude login. Restart the sessions you want bridged, and that’s it. From any terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ccb ls # sessions, status, what is pending
ccb show infra # what that session has been doing
ccb ok infra # approve · ccb no infra "not on prod"
ccb answer infra 1 # answer a question by option number
ccb send infra "run the tests again and summarise the failures"
ccb open infra # open the phone conversation without waiting for a request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  What I learned building it
&lt;/h4&gt;

&lt;p&gt;A few things only showed up once it met real sessions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A hook is not killed when you answer locally.&lt;/strong&gt; The PermissionRequest hook kept waiting after I pressed Enter in the terminal, so requests looked pending forever. It now watches the session’s status and leaves when the session stops waiting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pressing Enter in the wrong place approves things.&lt;/strong&gt; The relay is driven through tmux. Sending Enter while a question is on screen selects the first option — Approve. The code checks what is on screen before it sends a key, and a second hook process for the same request is not allowed to ask twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Text pasted too early disappears.&lt;/strong&gt; A follow-up request pasted into the relay while it was still finishing the previous one was silently lost. It now waits until the relay is idle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My own test sessions reached my phone.&lt;/strong&gt; They ran on my login, so their prompts showed up in my app and I answered one without knowing what it was. For a while I blamed Claude Code for “ignoring” an answer that I had, in fact, overridden myself.&lt;/p&gt;

&lt;h4&gt;
  
  
  Honest limits
&lt;/h4&gt;

&lt;p&gt;A session started with -- &lt;strong&gt;dangerously-skip-permissions&lt;/strong&gt; never asks, so there is nothing to approve. Watching and messaging still work.&lt;/p&gt;

&lt;p&gt;Only the last message of a turn comes back to the phone, not every step.&lt;/p&gt;

&lt;p&gt;Answers to questions reach the session as text; in the terminal that shows up as a denied tool call followed by the session carrying on with your answer.&lt;/p&gt;

&lt;p&gt;It depends on things Claude Code doesn’t promise to keep stable: the session registry, the hook behaviour, a few strings on the terminal screen. A future release can break it. The built-in self-test covers the queue logic, not the live integration.&lt;/p&gt;

&lt;p&gt;Developed and tested on macOS, with live sessions on AWS Bedrock. Vertex AI and Foundry go through the same hooks and are detected the same way, but I haven’t run live sessions on them yet. Linux should work; I haven’t tried it either.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why this matters
&lt;/h4&gt;

&lt;p&gt;Permission prompts are the part of agent tooling that keeps a human in the loop. The moment they become an obstacle, people remove them. I would rather make saying yes or no cost ten seconds from wherever I am than choose between an agent that waits all afternoon and one that never asks.&lt;/p&gt;

&lt;p&gt;The whole thing is one Python file, MIT-licensed, with no dependencies. Read it, run the self-test, and watch the network: there is nothing to see there.&lt;/p&gt;

&lt;h4&gt;
  
  
  Links
&lt;/h4&gt;

&lt;p&gt;⭐ GitHub: &lt;a href="https://github.com/fxerkan/ccbridger" rel="noopener noreferrer"&gt;github.com/fxerkan/ccbridger&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📦 Install: &lt;strong&gt;git clone · ln -s · ccb install — global&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If this was useful, a ⭐ on GitHub helps other people who are stuck at the same prompt find it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Developed by &lt;a href="https://fxerkan.com" rel="noopener noreferrer"&gt;FXerkan&lt;/a&gt; —  &lt;strong&gt;&lt;em&gt;Code more, worry less.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>azureaifoundry</category>
      <category>amazonbedrock</category>
      <category>googlevertexai</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>You Just Shared Your API Key With an AI. You Didn’t Even Notice.</title>
      <dc:creator>Erkan Ciftci</dc:creator>
      <pubDate>Fri, 18 Sep 2026 05:37:56 +0000</pubDate>
      <link>https://dev.to/fxerkan/you-just-shared-your-api-key-with-an-ai-you-didnt-even-notice-56gp</link>
      <guid>https://dev.to/fxerkan/you-just-shared-your-api-key-with-an-ai-you-didnt-even-notice-56gp</guid>
      <description>&lt;h2&gt;
  
  
  Why the AI-coding era needs a local-only AI secret manager — and how I ended up writing one in a single Python file
&lt;/h2&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%2Fj0lqyjb3e2gc71tehbha.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%2Fj0lqyjb3e2gc71tehbha.png" alt="concealer — your secrets stay encrypted on your machine; your assistant only ever sees the masked version" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Coding assistants read your repository to help you. The moment an API key lands in a &lt;code&gt;.env&lt;/code&gt; file, it can end up in a chat transcript, a log, or a bug report. &lt;code&gt;concealer&lt;/code&gt; is a local-only, open-source AI Secret Manager that keeps secrets encrypted and on your machine — while still letting your agents &lt;em&gt;use&lt;/em&gt; those secrets without ever &lt;em&gt;seeing&lt;/em&gt; them.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;&lt;a href="https://github.com/fxerkan/concealer" rel="noopener noreferrer"&gt;github.com/fxerkan/concealer&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The problem nobody warned us about
&lt;/h2&gt;

&lt;p&gt;For a decade, the &lt;code&gt;.env&lt;/code&gt; file was a reasonable compromise. Plaintext, sure — but it sat on your machine, git ignored it, and you were the only one reading it.&lt;/p&gt;

&lt;p&gt;Then our editors grew a brain.&lt;/p&gt;

&lt;p&gt;An AI coding assistant has to read your files to be useful; that's the whole job. It scans the repo, suggests fixes, runs commands, pipes the output back into a model. And somewhere in that repo sits a file that starts like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;OPENAI_API_KEY&lt;/span&gt;=&lt;span class="n"&gt;sk&lt;/span&gt;-&lt;span class="n"&gt;live&lt;/span&gt;-...
&lt;span class="n"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;=&lt;span class="n"&gt;wJalr&lt;/span&gt;...
&lt;span class="n"&gt;DATABASE_URL&lt;/span&gt;=&lt;span class="n"&gt;postgres&lt;/span&gt;://&lt;span class="n"&gt;user&lt;/span&gt;:&lt;span class="n"&gt;hunter2&lt;/span&gt;@&lt;span class="n"&gt;db&lt;/span&gt;:&lt;span class="m"&gt;5432&lt;/span&gt;/&lt;span class="n"&gt;prod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That key is now one &lt;code&gt;cat .env&lt;/code&gt; away from a context window. You can guess the rest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it gets &lt;strong&gt;echoed into a chat transcript&lt;/strong&gt; you later paste into an issue,&lt;/li&gt;
&lt;li&gt;it gets &lt;strong&gt;caught in a log&lt;/strong&gt; the agent streams back,&lt;/li&gt;
&lt;li&gt;it &lt;strong&gt;sits in the model's context&lt;/strong&gt; for the rest of a long session,&lt;/li&gt;
&lt;li&gt;it gets &lt;strong&gt;committed by accident&lt;/strong&gt; when the agent "helpfully" stages every file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these are real leak paths, and none of them existed when &lt;code&gt;.env&lt;/code&gt; was invented. The threat model moved. Our tooling stayed put.&lt;/p&gt;

&lt;h2&gt;
  
  
  An analogy before the tech
&lt;/h2&gt;

&lt;p&gt;If you don't write code for a living, picture this instead. You've hired a wonderfully capable house assistant. They tidy up, remind you about bills, find any document in seconds. One small detail: for years you've been writing all your passwords on a sticky note on the fridge. The assistant doesn't need to be malicious — they do their job by &lt;em&gt;reading your house&lt;/em&gt;, and that note is part of the house.&lt;/p&gt;

&lt;p&gt;What concealer does is, at its core, exactly this simple: it takes the note off the fridge and puts it in a locked drawer. The assistant can still ask what's in the drawer — "is the electricity account on file?" — and hears "yes, it's on file." They can say "pay this bill" and the bill gets paid, but the card number never leaves the drawer. And every time anyone opens the drawer, it goes into a ledger.&lt;/p&gt;

&lt;p&gt;The rest of this post is the technical version of that drawer. Even if you never touch a terminal, the idea carries: &lt;strong&gt;let the assistant do the work, without ever showing it the secrets.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the obvious answers don't fit
&lt;/h2&gt;

&lt;p&gt;The reflex answer is "just use a secret manager." Look at what that actually means in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloud vaults&lt;/strong&gt; (Vault, Doppler, cloud KMS): an account, a network round-trip, a service that has to stay up. Heavy for a solo dev or a laptop project — and now your secrets live on someone else's server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password managers&lt;/strong&gt; (1Password and friends): great for humans, clumsy for scripts and agents. And tied to an account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OS keychains&lt;/strong&gt;: tied to the machine. Wipe your laptop or switch to a new one, and your vault doesn't come along.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.env&lt;/code&gt; files&lt;/strong&gt;: plaintext. The exact thing we're trying to get away from.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I wanted was narrower, and I think more honest about how we actually code now:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Local-only. No cloud, no account, no telemetry. Portable — copy the files, type one password, decrypt anywhere. And designed with agents in mind, so an assistant can use a secret without the value ever entering its context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nothing on the shelf did all four. So I sat down and wrote it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet concealer
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;concealer&lt;/code&gt; is a &lt;strong&gt;single Python 3 script&lt;/strong&gt; (standard library only, no pip dependencies) wrapping two battle-tested tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/getsops/sops" rel="noopener noreferrer"&gt;SOPS&lt;/a&gt;&lt;/strong&gt; — born at Mozilla; encrypts a YAML/JSON file without wrecking its structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/FiloSottile/age" rel="noopener noreferrer"&gt;age&lt;/a&gt;&lt;/strong&gt; — Filippo Valsorda's modern, unflashy, correct encryption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On that foundation it adds what you'd actually want from a secret &lt;em&gt;manager&lt;/em&gt;: typed secrets, scoping by project and environment, a web console, a tamper-evident audit log, and — the part I care about most — &lt;strong&gt;an MCP server so AI agents can use secrets safely&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;All the encryption is deliberately left to SOPS and age; concealer doesn't invent its own cipher. The only crypto it does itself is verifying your password with the standard library's &lt;code&gt;scrypt&lt;/code&gt; and chaining the audit log with HMAC. Boring, I know. In security, boring is a compliment.&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%2Fkd0shr7v8bh8qfwkap6p.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%2Fkd0shr7v8bh8qfwkap6p.png" alt="concealer — five ways into one encrypted vault" width="800" height="529"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The whole system on one page: five interfaces funnel into a single-file core that shells out to SOPS + age. Neither the encrypted vault nor the keys ever leave your machine.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The feature I actually built this for: agents that never see the secret
&lt;/h2&gt;

&lt;p&gt;This is the part that separates concealer from just another vault — the part that makes it an &lt;em&gt;AI-era&lt;/em&gt; tool.&lt;/p&gt;

&lt;p&gt;concealer ships with an &lt;strong&gt;MCP server&lt;/strong&gt; (Model Context Protocol — the standard way agents talk to tools). An agent can do two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;List secret names.&lt;/strong&gt; "What database credentials exist for this project?" It gets back &lt;em&gt;names only&lt;/em&gt;. Never values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run a command with a secret injected.&lt;/strong&gt; The value is decrypted, handed to a &lt;strong&gt;child process's environment&lt;/strong&gt;, the command runs — and the secret values are &lt;strong&gt;redacted from the output&lt;/strong&gt; before the agent gets to read a single line.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The plaintext never enters the agent's context. Not on the way in, not on the way out.&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%2F8rxv0z9w9ct5ykyl6onp.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%2F8rxv0z9w9ct5ykyl6onp.png" alt="How an agent uses a secret without ever seeing it" width="800" height="465"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The agent asks for names, then asks concealer to run a command with a named secret. The value lives only inside the child process; the output comes back redacted; the whole exchange lands in the audit log — names and actions only, never values.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And since "an agent that can read secrets" is a risk all by itself, concealer stays on the cautious side and guards against bulk theft too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Registration is mandatory.&lt;/strong&gt; Only a &lt;em&gt;registered agent token&lt;/em&gt; can call the MCP tools. A human/CLI token, or no token at all, gets access denied.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Least privilege, by contract.&lt;/strong&gt; The agent has to name the exact secrets a command needs, one by one — there is no "give me everything."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limits against exfiltration.&lt;/strong&gt; Two per-agent caps: how many rows a single call may return, and how many distinct secret names an agent may reveal within a rolling time window. Names it has already seen re-list freely; new ones count against the quota. Bulk enumeration gets throttled and logged.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of a valet key: the valet can park your car but can't open the trunk. The agent never holds the key — concealer turns the lock, and the agent only sees the result.&lt;/p&gt;

&lt;p&gt;Here it is live — Claude Code using a Home Assistant token through concealer MCP; the value never enters the agent's context:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/R5zNqqA4g66yUawcah/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img width="480" src="https://i.giphy.com/media/R5zNqqA4g66yUawcah/giphy.gif" height="346"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Claude Code injecting a Home Assistant token via concealer MCP — the value never appears - demo-ha-token&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Five ways in, one encrypted vault
&lt;/h2&gt;

&lt;p&gt;Same vault, whichever way you like to work.&lt;/p&gt;
&lt;h3&gt;
  
  
  CLI — everything from the terminal
&lt;/h3&gt;

&lt;p&gt;Set, read, run-with, and deploy secrets from the terminal, scoped by tenant / project / environment / repo. &lt;code&gt;cer run&lt;/code&gt; injects values into a child process and scrubs them from its output — the value never touches your terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cer &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; OPENAI_API_KEY &lt;span class="nt"&gt;--project&lt;/span&gt; web &lt;span class="nt"&gt;--env&lt;/span&gt; prod &lt;span class="s1"&gt;'sk-DUMMY-123'&lt;/span&gt;
cer run &lt;span class="nt"&gt;--project&lt;/span&gt; web &lt;span class="nt"&gt;--env&lt;/span&gt; prod npm run deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Ff8kej03zb0ilyu2k5d3y.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%2Ff8kej03zb0ilyu2k5d3y.png" alt="concealer CLI — set, get, run and list secrets" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Web UI — a local console
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;concealer web&lt;/code&gt; and open &lt;code&gt;http://127.0.0.1:8787&lt;/code&gt;. Forms that adapt to the secret type, searchable filters, per-secret deploy templates, clipboard copy that clears itself, dark / light / matrix themes, full TR/EN localization. localhost only — this is &lt;em&gt;your&lt;/em&gt; console, unlocked with your master password.&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%2Fudm1nsj1ejhsn6k9ou07.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%2Fudm1nsj1ejhsn6k9ou07.png" alt="concealer Web UI — searchable, scoped secrets" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The main vault view: secrets filtered by project and environment, values masked until you reveal them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Secrets are &lt;strong&gt;typed&lt;/strong&gt;, not just key/value blobs: database credentials, cloud keys, web logins, custom multi-field records — each with the right fields and the right masking.&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%2Fywg20ycdymmejqhni9ov.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%2Fywg20ycdymmejqhni9ov.png" alt="Typed secrets — the right fields for each kind" width="800" height="888"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A database record with structured fields. Masking is record-aware: a value that looks like &lt;code&gt;user:pass@host&lt;/code&gt; gets masked even in a "plain" field.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A secret manager should also help you find your risk. That's what the &lt;strong&gt;risk dashboard&lt;/strong&gt; in the Web UI is for: values reused across secrets, overly broad scopes, even a scan of your shell history for keys you forgot there.&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%2F7oe8erl1iacjvvq7i5hk.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%2F7oe8erl1iacjvvq7i5hk.png" alt="Risk dashboard — reuse, exposure, and shell-history scanning" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And every action shows up in a &lt;strong&gt;tamper-evident audit viewer&lt;/strong&gt;:&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%2F5q3g0u86ap4ba3pyb7lv.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%2F5q3g0u86ap4ba3pyb7lv.png" alt="Audit log — every read, write, copy and inject, HMAC-chained" width="799" height="515"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Each line is appended to an HMAC-SHA256-chained log with a monotonic sequence number and a tail anchor — deletions and reordering stand out. Names and actions only, never values.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  TUI — without leaving the terminal
&lt;/h3&gt;

&lt;p&gt;If you'd rather stay in the terminal, &lt;code&gt;concealer tui&lt;/code&gt; opens a full-screen browser: arrow keys, instant filtering, type-aware editing, add / delete / reveal — no browser tab in sight.&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%2F09wx9gnfjgsr0yxefefp.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%2F09wx9gnfjgsr0yxefefp.png" alt="concealer TUI — terminal secret browser" width="799" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP — for AI agents
&lt;/h3&gt;

&lt;p&gt;Covered above: register an agent, hand it a revocable token, and it can use secrets it never sees. &lt;code&gt;concealer agent register &amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/yxzZ43zFeWBw4OEc1n/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img width="480" src="https://i.giphy.com/media/yxzZ43zFeWBw4OEc1n/giphy.gif" height="352"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;An agent listing concealer secrets over MCP — names only, never values - (mcp-secret-list)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Chrome Extension — copy from your toolbar
&lt;/h3&gt;

&lt;p&gt;Sometimes you just need to paste a value into a web form. The Chrome extension opens your vault and copies values straight from the toolbar. Multi-field records expand for per-field copy and reveal; the popup locks itself when idle.&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%2F3qwp6uh3r0cdab23ocx1.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%2F3qwp6uh3r0cdab23ocx1.png" alt="concealer Chrome extension — copy secrets from the toolbar" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The security model, briefly
&lt;/h2&gt;

&lt;p&gt;Without overselling it — concealer's security comes down to a few deliberate choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The key is encrypted at rest.&lt;/strong&gt; On a hardened vault, the age private key is never written to disk in plaintext. It sits master-password-encrypted (&lt;code&gt;age-key.txt.age&lt;/code&gt;), gets decrypted in memory, and is handed to SOPS through an environment variable — not a temp file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unlock tokens live client-side only.&lt;/strong&gt; The token value exists only in your environment (&lt;code&gt;CONCEALER_TOKEN&lt;/code&gt;); disk holds just its scrypt hash and a token-wrapped copy of the key. Tokens can be revoked and can expire. Humans get a short-lived one via &lt;code&gt;unlock&lt;/code&gt;; agents get a long-lived but revocable one via &lt;code&gt;agent register&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recovery codes as a second factor.&lt;/strong&gt; &lt;code&gt;init&lt;/code&gt; prints eight one-time codes, shown once. Rotating the master password &lt;em&gt;requires&lt;/em&gt; one of them — so a stolen password alone can't take over the vault.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tamper-evident audit.&lt;/strong&gt; The HMAC-chained log plus a tail anchor makes truncation and reordering detectable. (The honest ceiling: an attacker with both filesystem root &lt;em&gt;and&lt;/em&gt; the audit key can rewrite the chain — the docs say so, in plain words.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No security model is magic. But everything here is written down, readable, and the actual cryptography is left to tools that have earned their trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Portable by design
&lt;/h2&gt;

&lt;p&gt;Your vault is just a handful of files. Copy &lt;code&gt;keys/&lt;/code&gt; and &lt;code&gt;secrets.enc.yaml&lt;/code&gt; to a new machine, type your master password, carry on. No Keychain migration, no TPM binding, no "recover your account" flow. That portability is a choice, not an accident: your secrets belong to you, on hardware you control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open source — and you can check it does nothing sneaky
&lt;/h2&gt;

&lt;p&gt;concealer is one readable Python script, MIT-licensed. No black box, no account, no telemetry. Run it behind a firewall and confirm for yourself that it never phones home — because it doesn't. That's the whole point of a &lt;em&gt;local-only&lt;/em&gt; tool: you don't have to trust me. Read the code, watch the network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started in a minute
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# install (pulls in sops, age and expect)&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;fxerkan/tap/concealer     &lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
&lt;span class="c"&gt;# or: pipx install concealer  ·  scoop install concealer (Windows)&lt;/span&gt;

&lt;span class="c"&gt;# create the vault — prints recovery codes + a CLI token, once&lt;/span&gt;
concealer init

&lt;span class="c"&gt;# unlock your shell session (~8h token)&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;cer unlock&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# store &amp;amp; use a secret — the value never touches your terminal&lt;/span&gt;
cer &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; OPENAI_API_KEY &lt;span class="nt"&gt;--project&lt;/span&gt; web &lt;span class="nt"&gt;--env&lt;/span&gt; prod &lt;span class="s1"&gt;'sk-DUMMY-123'&lt;/span&gt;
cer run &lt;span class="nt"&gt;--project&lt;/span&gt; web &lt;span class="nt"&gt;--env&lt;/span&gt; prod npm run deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Encrypted, organized, portable — and safe to hand to an agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;p&gt;We opened our codebases to models because they genuinely make us faster. I still think that was the right call. But "the assistant can read everything" and "my secrets sit in plaintext right next to my code" are two sentences that should never have been allowed to be true at the same time.&lt;/p&gt;

&lt;p&gt;concealer is my attempt at making them coexist safely: &lt;strong&gt;keep the secrets encrypted, keep them local, keep them out of every transcript — and still let the agent do its job.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stop pasting keys into chat windows. concealer will take it from there.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ GitHub: &lt;a href="https://github.com/fxerkan/concealer" rel="noopener noreferrer"&gt;github.com/fxerkan/concealer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📖 Docs: &lt;a href="https://concealer.fxerkan.com/" rel="noopener noreferrer"&gt;concealer.fxerkan.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🧩 Chrome Web Store: &lt;a href="https://chromewebstore.google.com/detail/concealer/hecffnhjbhldmdpcnpkpcffmodnemdcj" rel="noopener noreferrer"&gt;concealer extension&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 Install: &lt;code&gt;brew install fxerkan/tap/concealer&lt;/code&gt; · &lt;code&gt;pipx install concealer&lt;/code&gt; · &lt;code&gt;scoop install concealer&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;If this post was useful, a ⭐ on GitHub genuinely helps others find the project — the name collides with a makeup product, so discovery is half the battle. Questions or feedback: &lt;code&gt;concealer@fxerkan.com&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

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