<?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: kh.vibecoding</title>
    <description>The latest articles on DEV Community by kh.vibecoding (@neostorm112boop).</description>
    <link>https://dev.to/neostorm112boop</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%2F4119779%2F8fb8693c-24f7-428d-8bc7-59daa4246451.png</url>
      <title>DEV Community: kh.vibecoding</title>
      <link>https://dev.to/neostorm112boop</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/neostorm112boop"/>
    <language>en</language>
    <item>
      <title>We open-sourced our vault's encryption code — and wrote honestly about what it doesn't guarantee</title>
      <dc:creator>kh.vibecoding</dc:creator>
      <pubDate>Thu, 10 Sep 2026 19:28:32 +0000</pubDate>
      <link>https://dev.to/neostorm112boop/we-open-sourced-our-vaults-encryption-code-and-wrote-honestly-about-what-it-doesnt-guarantee-29hb</link>
      <guid>https://dev.to/neostorm112boop/we-open-sourced-our-vaults-encryption-code-and-wrote-honestly-about-what-it-doesnt-guarantee-29hb</guid>
      <description>&lt;p&gt;We build proxykey, a credential proxy: real API keys sit encrypted on our side, and apps and AI agents talk to us using revocable virtual tokens instead. We recently &lt;a href="https://habr.com/ru/articles/1056070/" rel="noopener noreferrer"&gt;wrote about the scheme itself&lt;/a&gt;. Underneath that post and in DMs, one question kept coming up — the right question to ask any service like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Can you read my keys yourselves?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Short honest answer: technically, yes. Instead of hiding that behind marketing fog, we did two things: published the crypto module in full, and wrote a threat-model page where this point is first and in bold. This post is about why, and exactly what we opened up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why "zero-knowledge" is impossible here by design
&lt;/h3&gt;

&lt;p&gt;A proxy has to decrypt the key to put it into the request to the provider — that's the entire job. A password manager can be zero-knowledge: the server never needs your password. A credential proxy can't be: at the moment of the request, the plaintext key exists in the process's memory.&lt;/p&gt;

&lt;p&gt;That leads to an uncomfortable but unavoidable conclusion: a process with full access — and therefore the server operator — can in principle obtain the plaintext. This is a property of the architecture, not a bug. It's the same for every hosted solution in this category — secret managers, proxies, cloud vaults. The only difference is who says so out loud.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the encryption actually protects against
&lt;/h3&gt;

&lt;p&gt;Against a set of concrete, and much more likely, scenarios:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Protected?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Database dump leak&lt;/td&gt;
&lt;td&gt;Yes — secrets are encrypted, the master key isn't in the database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup theft&lt;/td&gt;
&lt;td&gt;Yes — same ciphertexts, no key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Log leak&lt;/td&gt;
&lt;td&gt;Yes — no keys or auth headers in logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database-only compromise&lt;/td&gt;
&lt;td&gt;Yes — without the KEK from the process environment, ciphertexts are useless&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Virtual token leak&lt;/td&gt;
&lt;td&gt;Yes — IP binding, rate limits, one-click revocation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Full server compromise / malicious operator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No — honestly, no&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The full version of this table is on the &lt;a href="https://proxykey.org/security/?utm_source=habr" rel="noopener noreferrer"&gt;security page&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What we open-sourced
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/neostorm112-boop/proxykey-crypto" rel="noopener noreferrer"&gt;proxykey-crypto&lt;/a&gt; repo is exactly the envelope-encryption module that encrypts secrets in our production system. 450 lines of TypeScript with tests, no dependencies besides &lt;code&gt;node:crypto&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every secret gets its own DEK; the plaintext is encrypted with &lt;strong&gt;AES-256-GCM&lt;/strong&gt; using AAD = the secret's id (a ciphertext can't be silently swapped into another record);&lt;/li&gt;
&lt;li&gt;the DEK is wrapped with a master key (KEK) that lives only in the process environment — it's never in the database;&lt;/li&gt;
&lt;li&gt;decryption happens in memory for the duration of a single request; the DEK and the plaintext copy are zeroed in &lt;code&gt;finally&lt;/code&gt; on every code path, including exceptions;&lt;/li&gt;
&lt;li&gt;virtual tokens in the database are stored only as SHA-256 hashes;&lt;/li&gt;
&lt;li&gt;18 tests: roundtrip, ciphertext/tag/wrapper tampering, wrong AAD, wrong key version.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What the open code does NOT guarantee
&lt;/h3&gt;

&lt;p&gt;Also stated plainly, because this is the other half of being honest:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Open code is not proof.&lt;/strong&gt; You can review the design, but a repository can't cryptographically prove that this exact code is what runs on the server. It's a transparency gesture, not an attestation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;fill(0)&lt;/code&gt; is best-effort.&lt;/strong&gt; V8 may have already copied buffers internally as part of its own operations, and a decrypted key stored in a JS string is immutable — you can't overwrite it, it lives until garbage collection. We narrowed the window; we didn't close it — and we're saying so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We validate the KEK's length, not its entropy.&lt;/strong&gt; There's no KDF, on purpose: the master key is required to &lt;em&gt;be&lt;/em&gt; a random key (&lt;code&gt;openssl rand -base64 32&lt;/code&gt;), not a password.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The repo's README has a "Design notes" section answering the standard reviewer questions — the bounds on the random nonce for GCM, why the DEK wrapper isn't bound to the AAD (to allow future KEK rotation), and so on. If you find something to pick apart beyond that, file an issue — that's the best possible contribution to everyone's security.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to reduce risk even against "the operator"
&lt;/h3&gt;

&lt;p&gt;A practical takeaway for anyone using any hosted key vault, not just ours:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;create scoped keys, not master keys&lt;/strong&gt;: an OpenAI project key with a $20 budget instead of a key for the whole account — then even a full vault compromise costs you $20, not everything;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;set limits on the provider's side&lt;/strong&gt; — a second line of defense after the vault's own limits;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rotate the originals&lt;/strong&gt; — reissuing at the provider zeroes out the value of anything that may have leaked earlier;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;if you don't trust any of this — that's a rational position&lt;/strong&gt;: the design is reproducible, the crypto module is open, go build your own instance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Disclaimer
&lt;/h3&gt;

&lt;p&gt;We're the people behind proxykey (&lt;a href="https://proxykey.org" rel="noopener noreferrer"&gt;proxykey.org&lt;/a&gt;). The service is free. This post isn't "trust us, we're secure" — it's the opposite: here's the boundary up to which it's secure, here's the code, and here's what's left resting on trust. We think that's more honest, and more useful for anyone deciding where to keep their keys.&lt;/p&gt;

&lt;p&gt;I build ProxyKey at &lt;a href="https://hikmah-labs.dev/en/" rel="noopener noreferrer"&gt;Hikmah Labs&lt;/a&gt;. You can try the vault at &lt;a href="https://proxykey.org" rel="noopener noreferrer"&gt;proxykey.org&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>encryption</category>
      <category>opensource</category>
      <category>api</category>
    </item>
    <item>
      <title>Your API Key Will Leak. Make That Not Matter</title>
      <dc:creator>kh.vibecoding</dc:creator>
      <pubDate>Thu, 10 Sep 2026 19:15:38 +0000</pubDate>
      <link>https://dev.to/neostorm112boop/your-api-key-will-leak-make-that-not-matter-1n0g</link>
      <guid>https://dev.to/neostorm112boop/your-api-key-will-leak-make-that-not-matter-1n0g</guid>
      <description>&lt;p&gt;According to GitGuardian's State of Secrets Sprawl report, about 23.7 million secrets — API keys, tokens, passwords — leaked into public GitHub in 2024 alone. The overwhelming majority aren't the result of a hack; they're ordinary commits, logs, and client-side bundles. A fresh key that lands in a public repo gets tried by bots in under a minute.&lt;/p&gt;

&lt;p&gt;The conclusion we reached after the Nth incident: fighting to keep a key from leaking is a war you lose. The winning move is to make the leak useless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why keys always leak
&lt;/h2&gt;

&lt;p&gt;A single OpenAI key in an average project lives in five places at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;in &lt;code&gt;.env&lt;/code&gt; on developers' laptops;&lt;/li&gt;
&lt;li&gt;in CI secrets (and sometimes in CI logs — a &lt;code&gt;printenv&lt;/code&gt; in a debug step);&lt;/li&gt;
&lt;li&gt;in the production config;&lt;/li&gt;
&lt;li&gt;in "just for a minute" scripts (&lt;code&gt;test_gpt.py&lt;/code&gt; with the key hardcoded as a string);&lt;/li&gt;
&lt;li&gt;and, more recently, in the context of an AI agent that "writes the code itself."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every copy is an independent point of failure. The classic countermeasures all work, but each one has a ceiling:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;What it covers&lt;/th&gt;
&lt;th&gt;What it doesn't cover&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;.env&lt;/code&gt; + &lt;code&gt;.gitignore&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;an accidental commit&lt;/td&gt;
&lt;td&gt;logs, CI, agents, "just a quick script"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scanners (gitleaks, trufflehog)&lt;/td&gt;
&lt;td&gt;a leak into git before push&lt;/td&gt;
&lt;td&gt;every other channel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret managers (Vault, AWS Secrets Manager)&lt;/td&gt;
&lt;td&gt;storage and delivery&lt;/td&gt;
&lt;td&gt;the key still ends up in the app's runtime environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provider-scoped keys&lt;/td&gt;
&lt;td&gt;blast radius&lt;/td&gt;
&lt;td&gt;not every provider supports them; revoking one still breaks every consumer of that key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The common thread: in every scheme, the real key ends up in the consumer's hands at some point.&lt;/p&gt;

&lt;p&gt;Which means it leaks along with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scheme: credential proxy
&lt;/h2&gt;

&lt;p&gt;The idea isn't new (it's how, say, payment tokenizers work), but for API keys it's barely used. Break the link between the app and the key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app ──(vlt_pass)──▶ proxy ──(real key)──▶ provider
                       │
              status / IP / limits → log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Secret&lt;/strong&gt; — the real key. Entered once, encrypted, and never returned by any API, ever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pass&lt;/strong&gt; — a virtual token bound to a secret. Its own IP binding, its own rpm/rpd limits, its own lifetime, its own log. This is what applications, scripts, and agents actually get.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proxy&lt;/strong&gt; — validates the pass, decrypts the secret in memory for the duration of a single request, substitutes it, and streams the response back.&lt;/p&gt;

&lt;p&gt;Consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The zone where the original key can leak shrinks down to a single server.&lt;/li&gt;
&lt;li&gt;A leaked pass isn't an incident: wrong IP → 403, over the limit → 429, revocation is one click, the original is never touched.&lt;/li&gt;
&lt;li&gt;Side bonus: you can see who is calling each downstream service and how much, because every consumer has its own pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For your code, the switch is two lines: &lt;code&gt;api.openai.com&lt;/code&gt; → &lt;code&gt;&amp;lt;proxy&amp;gt;/p/openai&lt;/code&gt;, &lt;code&gt;sk-…&lt;/code&gt; → &lt;code&gt;vlt_…&lt;/code&gt;. The path, body, headers, and SSE streaming all pass through unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works under the hood (our implementation)
&lt;/h2&gt;

&lt;p&gt;We built this scheme as a service — ProxyKey (Node 22 + Fastify 5 + PostgreSQL + Redis). A few technical decisions that might be interesting independent of the product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Envelope encryption&lt;/strong&gt;: every secret gets its own DEK (AES-256-GCM, AAD = secret id), and the DEK is wrapped by a KEK from the environment. Decryption happens in memory for the duration of a single request; the plaintext is never cached or logged anywhere, and the DEK is zeroed out after use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tokens aren't stored&lt;/strong&gt;: the database only holds SHA-256 hashes of passes; the hot path validates against a Redis cache (TTL 300s) with a fallback to Postgres.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Degradation is asymmetric&lt;/strong&gt;: if Postgres goes down, we serve from cache (fail-closed for anything not cached); if Redis goes down, we validate against Postgres, but rate limits fail open — validation does not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSRF guard&lt;/strong&gt;: custom base URLs are resolved and checked against private/metadata IP ranges — otherwise a proxy that accepts user-supplied upstreams is a ready-made SSRF vector.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request logs&lt;/strong&gt;: Postgres partitioned by month, metadata only, no authorization headers or key values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram bots are a special pain&lt;/strong&gt;: the bot token lives in the URL path, and aiogram/grammY (popular Telegram bot libraries) validate its format before the first request. The proxy accepts &lt;code&gt;bot&amp;lt;digits&amp;gt;:vlt_…&lt;/code&gt; and ignores the digits — the format passes client-library validation, and the real token gets substituted server-side.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AI agents: the main new consumer of keys
&lt;/h2&gt;

&lt;p&gt;This is the actual reason we got into this in the first place. Agents (Claude Code, Cursor, and the like) spin up services and configure bots — they constantly need keys. But anything that ends up in a model's context has to be treated as published: context gets logged, traced, and can be extracted via prompt injection.&lt;/p&gt;

&lt;p&gt;The fix is to give the agent a tool, not a secret: an MCP server for the vault. The agent connects via a URL with an &lt;code&gt;mcp_…&lt;/code&gt; token and can issue, rotate, revoke passes, and read logs. "Read the real key" is simply not in the tool set — that's a property of the access protocol, not a promise we're trusting the agent to keep.&lt;/p&gt;

&lt;p&gt;Our favorite scenario is the "pending secret": an agent deploys a Telegram bot whose token doesn't exist yet. The agent creates a pending pass, wires it into the config, and hands a human a link; the human enters the real token in the panel, the pass activates, and the bot comes alive. The agent finished the job without ever seeing the secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest trade-offs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The proxy is a critical dependency.&lt;/strong&gt; If it's down, all your calls are down. Replicas and validation caching help, but you need to understand this going in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The proxy sees the traffic.&lt;/strong&gt; The question isn't whether it sees it, it's what it logs. In our implementation, only metadata; body previews are optional and opt-in per pass. If your threat model doesn't allow a third party in the loop, run this pattern yourself — it's reproducible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency.&lt;/strong&gt; One extra hop plus a cache lookup adds single-digit milliseconds against hundreds of milliseconds of LLM generation. For low-latency, non-LLM APIs, do your own math.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;p&gt;We're the people behind ProxyKey (proxykey.org). The service is free, no card required; the panel, the proxy, and the MCP server are described here honestly, limitations included. The credential-proxy pattern is reproducible without us — if you build your own, just don't skip the SSRF guard and the fail-closed validation path.&lt;/p&gt;




&lt;p&gt;Built at &lt;a href="https://hikmah-labs.dev/en/" rel="noopener noreferrer"&gt;Hikmah Labs&lt;/a&gt;, a small studio I run. ProxyKey itself lives at &lt;a href="https://proxykey.org" rel="noopener noreferrer"&gt;proxykey.org&lt;/a&gt;.&lt;/p&gt;

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