<?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: Clef.sh</title>
    <description>The latest articles on DEV Community by Clef.sh (@clef_sh).</description>
    <link>https://dev.to/clef_sh</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3843731%2F70f9b1ce-5d19-462a-819a-3866b187358b.png</url>
      <title>DEV Community: Clef.sh</title>
      <link>https://dev.to/clef_sh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/clef_sh"/>
    <language>en</language>
    <item>
      <title>AWS CDK + Clef: Shift secrets policy and governance left</title>
      <dc:creator>Clef.sh</dc:creator>
      <pubDate>Wed, 29 Apr 2026 16:06:30 +0000</pubDate>
      <link>https://dev.to/clef_sh/aws-cdk-clef-secrets-that-work-the-way-you-might-expect-123c</link>
      <guid>https://dev.to/clef_sh/aws-cdk-clef-secrets-that-work-the-way-you-might-expect-123c</guid>
      <description>&lt;p&gt;A ~10-minute, copy-paste tutorial that takes you from an empty directory to a fully working &lt;a href="https://clef.sh" rel="noopener noreferrer"&gt;Clef&lt;/a&gt; setup, then deploys the production secrets to AWS Secrets Manager via the &lt;a href="https://www.npmjs.com/package/@clef-sh/cdk" rel="noopener noreferrer"&gt;&lt;code&gt;@clef-sh/cdk&lt;/code&gt;&lt;/a&gt; constructs.&lt;/p&gt;

&lt;p&gt;By the end, you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;clef.yaml&lt;/code&gt; with two namespaces (&lt;code&gt;database&lt;/code&gt;, &lt;code&gt;payments&lt;/code&gt;) across two environments (&lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;production&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;All four matrix cells populated with demo secrets, encrypted with &lt;a href="https://github.com/getsops/sops" rel="noopener noreferrer"&gt;SOPS&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A service identity (&lt;code&gt;app&lt;/code&gt;) whose &lt;code&gt;production&lt;/code&gt; envelope is protected by AWS KMS&lt;/li&gt;
&lt;li&gt;A CloudFormation stack with three &lt;code&gt;ClefSecret&lt;/code&gt;s, each holding a Clef-managed value in AWS Secrets Manager — readable by your app via the standard ASM SDK, with no Clef agent at runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Steps 1–3 work fully offline. Steps 4–5 deploy real AWS resources and require working AWS credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Node.js 20+&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS account + credentials&lt;/strong&gt; for steps 4–5 only. Standard SDK resolution applies (&lt;code&gt;AWS_PROFILE&lt;/code&gt;, env vars, SSO, etc.). The KMS key, an unwrap Lambda, and three Secrets Manager secrets will be created in the account/region your credentials resolve to. All resources are tagged &lt;code&gt;clef-quick-start&lt;/code&gt; so you can find and remove them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git&lt;/strong&gt; — Clef is git-native, and the tutorial commits the initial state after &lt;code&gt;clef init&lt;/code&gt; so you can see exactly what each subsequent step adds. Cloning this repo (per the setup step below) gives you a git working tree already.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shell&lt;/strong&gt; — commands below are written for a POSIX shell (macOS/Linux Terminal, WSL, or Git Bash on Windows). PowerShell works too; the only block that needs a different syntax is the variable derivation in step 4, where a PowerShell variant is shown alongside.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/clef-sh/quick-start.git
&lt;span class="nb"&gt;cd &lt;/span&gt;quick-start
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;npm install&lt;/code&gt; puts the &lt;code&gt;clef&lt;/code&gt; CLI under &lt;code&gt;node_modules/.bin&lt;/code&gt;. The tutorial uses &lt;code&gt;npx clef&lt;/code&gt;, but you can also install it globally with &lt;code&gt;npm i -g @clef-sh/cli&lt;/code&gt; if you'd rather drop the prefix.&lt;/p&gt;

&lt;p&gt;Verify the install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx clef doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see green checks for &lt;code&gt;node&lt;/code&gt;, &lt;code&gt;sops&lt;/code&gt;, and &lt;code&gt;git&lt;/code&gt; (if present).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Initialise Clef
&lt;/h2&gt;

&lt;p&gt;Create the manifest and the encrypted matrix in one shot, using the &lt;a href="https://github.com/FiloSottile/age" rel="noopener noreferrer"&gt;age&lt;/a&gt; backend (no AWS account needed yet):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx clef init &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespaces&lt;/span&gt; database,payments &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--environments&lt;/span&gt; dev,production &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--backend&lt;/span&gt; age &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--non-interactive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What just happened:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;clef.yaml&lt;/code&gt; now declares your namespaces, environments, and the age recipient that owns this repo.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.clef/config.yaml&lt;/code&gt; records the local age private key location (stored in your OS keychain by default).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;secrets/database/{dev,production}.enc.yaml&lt;/code&gt; and &lt;code&gt;secrets/payments/{dev,production}.enc.yaml&lt;/code&gt; were created — each one is a valid SOPS file with no keys yet.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.clefignore&lt;/code&gt; and &lt;code&gt;.gitattributes&lt;/code&gt; were written so the SOPS merge driver picks up &lt;code&gt;*.enc.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Take a look:&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;cat &lt;/span&gt;clef.yaml
tree secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit the initial state. Clef is git-native and the matrix files are SOPS-encrypted, so they're safe to commit even once you start adding values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add clef.yaml .clef .clefignore .gitattributes secrets
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initialise Clef"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2 — Populate the matrix
&lt;/h2&gt;

&lt;p&gt;Set your first secret with an inline value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx clef &lt;span class="nb"&gt;set &lt;/span&gt;database/dev DB_HOST localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now set one with hidden input — Clef prompts and never echoes the value to the terminal or writes it to disk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx clef &lt;span class="nb"&gt;set &lt;/span&gt;database/dev DB_PASSWORD
&lt;span class="c"&gt;# Value: ********&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm it landed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx clef get database/dev DB_PASSWORD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To populate the rest of the matrix in one go, paste this block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx clef &lt;span class="nb"&gt;set &lt;/span&gt;database/dev        DB_USER       dev_user
npx clef &lt;span class="nb"&gt;set &lt;/span&gt;database/production DB_HOST       db.prod.internal
npx clef &lt;span class="nb"&gt;set &lt;/span&gt;database/production DB_USER       app
npx clef &lt;span class="nb"&gt;set &lt;/span&gt;database/production DB_PASSWORD   &lt;span class="nt"&gt;--random&lt;/span&gt;
npx clef &lt;span class="nb"&gt;set &lt;/span&gt;payments/dev        STRIPE_KEY    sk_test_demo
npx clef &lt;span class="nb"&gt;set &lt;/span&gt;payments/dev        WEBHOOK_URL   https://dev.example.com/webhooks/stripe
npx clef &lt;span class="nb"&gt;set &lt;/span&gt;payments/production STRIPE_KEY    &lt;span class="nt"&gt;--random&lt;/span&gt;
npx clef &lt;span class="nb"&gt;set &lt;/span&gt;payments/production WEBHOOK_URL   https://example.com/webhooks/stripe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--random&lt;/code&gt; generates a cryptographically random placeholder and marks the key as &lt;strong&gt;pending&lt;/strong&gt; — Clef tracks placeholders so you can find them later with &lt;code&gt;clef lint&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Explore the matrix
&lt;/h2&gt;

&lt;p&gt;Compare environments side by side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx clef diff database dev production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validate the whole repo — schema compliance, matrix completeness, SOPS integrity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx clef lint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the local web UI to browse the matrix visually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx clef ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This binds to &lt;code&gt;127.0.0.1:7777&lt;/code&gt; only. From the UI you can edit secrets with masked values, diff environments, and run lint with one click. Press &lt;code&gt;Ctrl-C&lt;/code&gt; to stop the server when you're done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — Provision KMS and migrate &lt;code&gt;production&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Steps 4 and 5 use AWS. Make sure your credentials are set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sts get-caller-identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@clef-sh/cdk&lt;/code&gt; &lt;code&gt;ClefSecret&lt;/code&gt; and &lt;code&gt;ClefParameter&lt;/code&gt; constructs require &lt;strong&gt;KMS-envelope identities&lt;/strong&gt; — they need a customer KMS key to wrap each pack's data encryption key. The &lt;code&gt;infra/&lt;/code&gt; directory ships two CDK stacks for this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;QuickStartKms&lt;/code&gt;&lt;/strong&gt; — provisions the KMS key plus the alias &lt;code&gt;alias/clef-quick-start&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;QuickStartApp&lt;/code&gt;&lt;/strong&gt; — deploys three &lt;code&gt;ClefSecret&lt;/code&gt;s into AWS Secrets Manager. We deploy this in step 5.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The KMS stack uses a fixed alias, so we can compute its ARN up front:&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="nv"&gt;ACCOUNT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws sts get-caller-identity &lt;span class="nt"&gt;--query&lt;/span&gt; Account &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws configure get region&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;KMS_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:kms:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ACCOUNT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:alias/clef-quick-start"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PowerShell equivalent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ACCOUNT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;get-caller-identity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Account&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;AWS_REGION&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="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;configure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$KMS_ARN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:kms:&lt;/span&gt;&lt;span class="nv"&gt;${REGION}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;${ACCOUNT}&lt;/span&gt;&lt;span class="s2"&gt;:alias/clef-quick-start"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the &lt;code&gt;app&lt;/code&gt; service identity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx clef service create app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--runtime&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespaces&lt;/span&gt; database,payments &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--kms-env&lt;/span&gt; &lt;span class="nv"&gt;production&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aws:&lt;span class="nv"&gt;$KMS_ARN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scoped to both namespaces, with KMS-envelope encryption on &lt;code&gt;production&lt;/code&gt;. &lt;code&gt;dev&lt;/code&gt; stays on age — fine, since the CDK stack only deploys production secrets. &lt;code&gt;--runtime&lt;/code&gt; keeps &lt;code&gt;clef&lt;/code&gt; from re-encrypting your matrix files with a new shared age key; the CDK pack-helper handles encryption itself at synth time.&lt;/p&gt;

&lt;p&gt;Bootstrap CDK (if needed) and deploy the KMS stack:&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;cd &lt;/span&gt;infra
npx cdk bootstrap
npx cdk deploy QuickStartKms &lt;span class="nt"&gt;--outputs-file&lt;/span&gt; ./kms-outputs.json
&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now re-encrypt the &lt;code&gt;production&lt;/code&gt; matrix cells with KMS (dev stays on age):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx clef migrate-backend &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--aws-kms-arn&lt;/span&gt; &lt;span class="nv"&gt;$KMS_ARN&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--environment&lt;/span&gt; production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;clef migrate-backend&lt;/code&gt; decrypts each &lt;code&gt;*/production.enc.yaml&lt;/code&gt; cell with your age key and re-encrypts it under the new KMS key, in place. Verify with &lt;code&gt;clef lint&lt;/code&gt; — the production cells should now show the new backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — Deploy secrets to AWS Secrets Manager
&lt;/h2&gt;

&lt;p&gt;Take a look at &lt;code&gt;infra/lib/app-stack.ts&lt;/code&gt; to see the &lt;code&gt;ClefSecret&lt;/code&gt; calls. Each construct synthesises one Secrets Manager secret, with the value computed at deploy time from the encrypted Clef matrix.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;shape&lt;/code&gt; property is a template with &lt;code&gt;{{name}}&lt;/code&gt; placeholders, and &lt;code&gt;refs&lt;/code&gt; binds each placeholder to a &lt;code&gt;(namespace, key)&lt;/code&gt; pair in the matrix. Namespace and key stay as separate fields rather than collapsing into a single token, which keeps &lt;code&gt;DB_USER&lt;/code&gt; from &lt;code&gt;database&lt;/code&gt; distinct from any future &lt;code&gt;DB_USER&lt;/code&gt; in another namespace this identity spans. &lt;code&gt;shape&lt;/code&gt; also accepts an object literal — see &lt;code&gt;PaymentsConfig&lt;/code&gt; in &lt;code&gt;app-stack.ts&lt;/code&gt; for a JSON-shaped secret. The unwrap happens inside a synth-time pack step plus a CloudFormation Custom Resource at deploy — see &lt;a href="https://github.com/clef-sh/clef/tree/main/packages/cdk#how-clefsecret-stays-secure" rel="noopener noreferrer"&gt;How &lt;code&gt;ClefSecret&lt;/code&gt; stays secure&lt;/a&gt; for the per-deploy KMS grant model.&lt;/p&gt;

&lt;p&gt;Deploy:&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;cd &lt;/span&gt;infra
npx cdk deploy QuickStartApp &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
cd&lt;/span&gt; ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-c app=true&lt;/code&gt; flag opts the app stack into synth — see the comment in &lt;code&gt;infra/bin/infra.ts&lt;/code&gt; for why we gate it.&lt;/p&gt;

&lt;p&gt;Once the stack settles, list the new secrets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager list-secrets &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="nv"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tag-value,Values&lt;span class="o"&gt;=&lt;/span&gt;clef-quick-start &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'SecretList[].Name'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read one back the way your application would:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager get-secret-value &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; clef-quick-start/database-url &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; SecretString &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That value was never typed in plaintext at deploy time — it was reconstructed from &lt;code&gt;secrets/database/production.enc.yaml&lt;/code&gt; inside the synth pack step, wrapped under your KMS key, and unwrapped exactly once by the per-deploy grant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 — Connect Clef Cloud (optional)
&lt;/h2&gt;

&lt;p&gt;So far everything is local: rotation due-dates, schema rules, lint warnings — they live in &lt;code&gt;.clef/policy.yaml&lt;/code&gt; and only fire when &lt;em&gt;you&lt;/em&gt; run &lt;code&gt;clef lint&lt;/code&gt; or &lt;code&gt;clef policy check&lt;/code&gt;. To enforce them across a team, you push the repo and let the Clef Cloud bot watch it on every PR.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;clef init&lt;/code&gt; already scaffolded two files for this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.clef/policy.yaml&lt;/code&gt;&lt;/strong&gt; — declares rotation cadence per namespace, schema requirements, allowed backends, and any custom policy rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.github/workflows/clef-compliance.yml&lt;/code&gt;&lt;/strong&gt; — a GitHub Actions workflow that runs &lt;code&gt;clef policy check&lt;/code&gt; on each PR, writes &lt;code&gt;compliance.json&lt;/code&gt;, and uploads it as the workflow artifact the bot reads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install the GitHub App and link this repo to a Clef Cloud workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx clef cloud init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This authenticates you via GitHub OAuth (device flow), installs the &lt;strong&gt;Clef&lt;/strong&gt; GitHub App on the repository, and registers the workspace. The CLI is non-destructive — &lt;code&gt;.clef/policy.yaml&lt;/code&gt; is left alone if it already exists.&lt;/p&gt;

&lt;p&gt;Once installed, the bot will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;post a status check on every PR summarising rotation overdue counts, schema violations, and pending placeholders,&lt;/li&gt;
&lt;li&gt;block merges that violate &lt;code&gt;.clef/policy.yaml&lt;/code&gt; (configurable per rule), and&lt;/li&gt;
&lt;li&gt;populate the Cloud dashboard with the compliance history of the repo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The dashboard won't show data until you actually push and let CI run.&lt;/strong&gt; Compliance is computed from the &lt;code&gt;compliance.json&lt;/code&gt; artifact produced by &lt;code&gt;.github/workflows/clef-compliance.yml&lt;/code&gt;, so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add .clef/policy.yaml .github/workflows/clef-compliance.yml
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Enable Clef Cloud"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a PR (even a no-op one) to trigger the workflow, then check the dashboard. The bot's status check will appear on the PR and the dashboard tile for this repo will fill in once the workflow finishes.&lt;/p&gt;

&lt;p&gt;This is how governance and policy enforcement come into play: &lt;code&gt;policy.yaml&lt;/code&gt; is the spec, the workflow is the enforcement point, and the bot is the cross-team visibility layer. Local devs get the same checks via &lt;code&gt;clef lint&lt;/code&gt; / &lt;code&gt;clef policy check&lt;/code&gt;, so violations surface long before review.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you just built
&lt;/h2&gt;

&lt;p&gt;Step back for a second — in the last ~10 minutes you stood up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A central, version-controlled source of truth for secrets.&lt;/strong&gt; Every value lives in &lt;code&gt;secrets/&amp;lt;ns&amp;gt;/&amp;lt;env&amp;gt;.enc.yaml&lt;/code&gt;, encrypted with SOPS, diffable in git, reviewable in PRs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-environment encryption with a clean handoff to AWS.&lt;/strong&gt; &lt;code&gt;dev&lt;/code&gt; rides on age for friction-free local work; &lt;code&gt;production&lt;/code&gt; is sealed with your own KMS key. The CDK constructs deliver those values into AWS Secrets Manager so applications keep using the standard &lt;code&gt;aws-sdk&lt;/code&gt; — no Clef binary, no agent, no sidecar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotation and schema tracking with a path to enforcement.&lt;/strong&gt; &lt;code&gt;clef lint&lt;/code&gt; already flags pending placeholders and policy violations locally; once Clef Cloud is connected, the same checks run on every PR and the dashboard tracks rotation health across repositories.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you swap the demo &lt;code&gt;--random&lt;/code&gt; placeholders for real values, point a real service at the secrets via the AWS SDK, and add a schema for each namespace, the same pattern scales straight from this tutorial to a production setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning up
&lt;/h2&gt;

&lt;p&gt;This repo is meant to be thrown away. To remove the AWS resources you just deployed:&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;cd &lt;/span&gt;infra
npx cdk destroy QuickStartApp QuickStartKms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;rm -rf&lt;/code&gt; the directory or re-clone if you want to run through the tutorial again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@clef-sh/cdk&lt;/code&gt; reference&lt;/strong&gt; — &lt;a href="https://github.com/clef-sh/clef/tree/main/packages/cdk" rel="noopener noreferrer"&gt;github.com/clef-sh/clef/tree/main/packages/cdk&lt;/a&gt; covers the other constructs (&lt;code&gt;ClefArtifactBucket&lt;/code&gt; for S3 delivery, &lt;code&gt;ClefParameter&lt;/code&gt; for SSM Parameter Store) and synth-time validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schemas&lt;/strong&gt; — define required keys and value patterns per namespace; &lt;code&gt;clef lint&lt;/code&gt; will then enforce them. See &lt;a href="https://docs.clef.sh/guide/schemas" rel="noopener noreferrer"&gt;docs.clef.sh/guide/schemas&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI&lt;/strong&gt; — for GitHub Actions, OIDC into KMS so CI never holds long-lived credentials. See &lt;a href="https://docs.clef.sh/guide/ci" rel="noopener noreferrer"&gt;docs.clef.sh/guide/ci&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you hit anything that didn't work, please &lt;a href="https://github.com/clef-sh/quick-start/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; — the goal is for this tutorial to run cleanly for everyone.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>The Windows Bug That Hung Our CI Forever (And What Node's socket.end() Won't Tell You)</title>
      <dc:creator>Clef.sh</dc:creator>
      <pubDate>Wed, 15 Apr 2026 18:45:59 +0000</pubDate>
      <link>https://dev.to/clef_sh/the-windows-bug-that-hung-our-ci-forever-and-what-nodes-socketend-wont-tell-you-1oco</link>
      <guid>https://dev.to/clef_sh/the-windows-bug-that-hung-our-ci-forever-and-what-nodes-socketend-wont-tell-you-1oco</guid>
      <description>&lt;p&gt;Building &lt;a href="https://clef.sh" rel="noopener noreferrer"&gt;Clef&lt;/a&gt; has a non-negotiable constraint: decrypted secrets never touch disk. Encryption and decryption go through the &lt;code&gt;sops&lt;/code&gt; binary, and we pipe everything through stdin/stdout. On Linux and macOS, that's a one-liner — pass &lt;code&gt;/dev/stdin&lt;/code&gt; as SOPS's input file and stream the content in.&lt;/p&gt;

&lt;p&gt;Then we enabled Windows CI, and every test hung until GitHub Actions killed the job at the six-hour timeout.&lt;/p&gt;

&lt;p&gt;Here's what happened, and why the fix is three characters different from what you'd probably write first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint and the Windows problem
&lt;/h2&gt;

&lt;p&gt;SOPS is a Go binary. It expects a file path as input. On Unix we exploit the fact that &lt;code&gt;/dev/stdin&lt;/code&gt; is a real path that resolves to the current process's standard input — SOPS opens it, reads to EOF, and we're done.&lt;/p&gt;

&lt;p&gt;Windows has no &lt;code&gt;/dev/stdin&lt;/code&gt;. There's no equivalent path you can hand to a subprocess. So we need another way to give SOPS a "file" that's really a stream.&lt;/p&gt;

&lt;p&gt;The Windows answer is &lt;strong&gt;named pipes&lt;/strong&gt;: paths of the form &lt;code&gt;\\.\pipe\name&lt;/code&gt; that Go's &lt;code&gt;os.Open&lt;/code&gt; / &lt;code&gt;CreateFile&lt;/code&gt; can open exactly like a regular file. The first naive version looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// &amp;lt;-- seems reasonable&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;pipe&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;clef-sops-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sops&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-e&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pipeName&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;socket.end(content)&lt;/code&gt; is the standard Node idiom: write the payload, half-close the stream, let the reader see EOF.&lt;/p&gt;

&lt;p&gt;On Unix this works perfectly. On Windows, SOPS would open the pipe, read our content, and then &lt;strong&gt;wait forever&lt;/strong&gt;. The Go side never saw EOF, so its &lt;code&gt;io.ReadAll&lt;/code&gt; never returned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;socket.end()&lt;/code&gt; lies on Windows pipes
&lt;/h2&gt;

&lt;p&gt;This is the part that cost me half a day.&lt;/p&gt;

&lt;p&gt;Node's &lt;code&gt;socket.end()&lt;/code&gt; is supposed to flush pending writes and then signal EOF to the peer via a half-close. Under the hood, that half-close is implemented by libuv's &lt;code&gt;uv_shutdown&lt;/code&gt;, which on Unix sockets calls &lt;code&gt;shutdown(fd, SHUT_WR)&lt;/code&gt;. The peer's &lt;code&gt;read()&lt;/code&gt; returns 0 bytes. Clean EOF.&lt;/p&gt;

&lt;p&gt;On Windows named pipes, &lt;strong&gt;&lt;code&gt;uv_shutdown&lt;/code&gt; is effectively a no-op&lt;/strong&gt;. There's no half-close primitive for Windows pipes — the only way to signal "I'm done writing" is to close the handle entirely. But libuv keeps the handle open because, from its perspective, you only asked to shut down the write side.&lt;/p&gt;

&lt;p&gt;So from the Go side: the pipe is still open, no bytes are arriving, but there's no EOF either. &lt;code&gt;ReadAll&lt;/code&gt; does the only thing it can — it blocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Close the handle instead of half-closing it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things matter here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;socket.write()&lt;/code&gt; instead of &lt;code&gt;socket.end()&lt;/code&gt;&lt;/strong&gt; — we're doing the flush ourselves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The callback&lt;/strong&gt; — don't destroy until the write is flushed to the kernel, or you'll truncate the content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;socket.destroy()&lt;/code&gt; instead of &lt;code&gt;socket.end()&lt;/code&gt;&lt;/strong&gt; — destroy closes the handle. Go's &lt;code&gt;CreateFile&lt;/code&gt; handle to the pipe now returns &lt;code&gt;ERROR_BROKEN_PIPE&lt;/code&gt; on the next read, which Go's stdlib maps to &lt;code&gt;io.EOF&lt;/code&gt;. The read loop terminates. SOPS proceeds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The final code lives in &lt;a href="https://github.com/clef-sh/clef/blob/main/packages/core/src/sops/client.ts" rel="noopener noreferrer"&gt;&lt;code&gt;packages/core/src/sops/client.ts&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// On Windows, socket.end() does not reliably signal EOF to named pipe&lt;/span&gt;
  &lt;span class="c1"&gt;// clients because libuv's uv_shutdown is a no-op for pipes. Write the&lt;/span&gt;
  &lt;span class="c1"&gt;// content and then force-destroy the socket so the pipe handle is closed,&lt;/span&gt;
  &lt;span class="c1"&gt;// which the Go client (sops) sees as ERROR_BROKEN_PIPE → io.EOF.&lt;/span&gt;
  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;A few things I walked away with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;socket.end()&lt;/code&gt; is not a portable EOF signal.&lt;/strong&gt; It works on TCP sockets and Unix domain sockets because the underlying primitives support half-close. On Windows named pipes, there is no half-close — only handle close.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-runtime pipes are a minefield.&lt;/strong&gt; Node and Go both abstract over platform-specific pipe semantics, but the abstractions don't line up exactly. The bug only shows up when both runtimes are on the Windows side of the abstraction at the same time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Silent hang in CI" is a strong signal.&lt;/strong&gt; If the failure mode is "no output, no error, just timeout," the problem is almost always an EOF / blocking-read mismatch somewhere. No exception means neither side &lt;em&gt;thinks&lt;/em&gt; anything is wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're curious about the larger design — why we're piping to SOPS in the first place, and how the in-memory-only constraint shapes the rest of the architecture — the &lt;a href="https://github.com/clef-sh/clef/blob/main/whitepaper.md" rel="noopener noreferrer"&gt;whitepaper&lt;/a&gt; has the longer version.&lt;/p&gt;

</description>
      <category>node</category>
      <category>testing</category>
      <category>go</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>The Tradeoff Every Secrets Manager Forces on You (And Why It's the Server's Fault)</title>
      <dc:creator>Clef.sh</dc:creator>
      <pubDate>Wed, 15 Apr 2026 18:36:42 +0000</pubDate>
      <link>https://dev.to/clef_sh/the-tradeoff-every-secrets-manager-forces-on-you-and-why-its-the-servers-fault-5h0a</link>
      <guid>https://dev.to/clef_sh/the-tradeoff-every-secrets-manager-forces-on-you-and-why-its-the-servers-fault-5h0a</guid>
      <description>&lt;p&gt;Pick any secrets manager and you're picking one of two tradeoffs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosted (Vault, Infisical):&lt;/strong&gt; you keep custody of your plaintext, but you also operate a stateful cluster — HA storage, unseal keys, PostgreSQL, Redis, backups, upgrades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SaaS (Doppler, AWS Secrets Manager, 1Password):&lt;/strong&gt; someone else operates the cluster, but your data sits in their system under their control. Ciphertext, key material, plaintext in some cases.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ops or custody. Pick one.&lt;/p&gt;

&lt;p&gt;This post is about the thing that forces the choice: &lt;strong&gt;the central server itself&lt;/strong&gt;. I work on &lt;a href="https://clef.sh" rel="noopener noreferrer"&gt;Clef&lt;/a&gt;, a git-native secrets tool, so I have a horse in this race. I'll try to be upfront about where the tradeoff actually moves and where it just changes shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the server creates the dilemma
&lt;/h2&gt;

&lt;p&gt;A central secrets server has to authenticate callers. That means something, somewhere, needs to hold a credential to talk to it — the classic "secret zero" problem. That credential ends up in one of three places: baked into container images, pasted into CI, or stored in &lt;em&gt;another&lt;/em&gt; secrets manager. Each is a custody problem in miniature.&lt;/p&gt;

&lt;p&gt;Cloud secret managers and Vault both have good answers — OIDC federation, IAM auth, AppRole, Kubernetes auth. The answers work. But they exist because the server model demands them. Every answer is configuration you have to get right, and every piece of configuration is a surface the server can be compromised through.&lt;/p&gt;

&lt;p&gt;And once a server is holding plaintext (or the keys to plaintext) for many tenants or many services, it becomes the highest-value target in your infrastructure. A breach is not one secret — it's all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What git-native changes
&lt;/h2&gt;

&lt;p&gt;Clef's bet is that you don't need a server at all. The architecture has two pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;At rest:&lt;/strong&gt; SOPS-encrypted files in your git repo. Keys visible, values encrypted. Code review, diffs, and drift detection work on the encrypted files directly because the key names are plaintext.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At runtime:&lt;/strong&gt; a lightweight agent (or Lambda extension) pulls a signed, pre-encrypted artifact from S3 or your VCS and decrypts it in memory using the workload's IAM role.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No secrets server. No bootstrap token. The workload's existing IAM identity &lt;em&gt;is&lt;/em&gt; the authentication. KMS is the key management. Clef is just the envelope and the delivery path.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/clef-sh/clef/blob/main/whitepaper.md" rel="noopener noreferrer"&gt;whitepaper&lt;/a&gt; walks through this in detail, including the hardened topology (three separate KMS keys: one for source encryption, one for artifact wrapping, one for signing) and how just-in-time decryption lets IAM revocation become an instant kill switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tradeoffs I won't hand-wave
&lt;/h2&gt;

&lt;p&gt;Moving off a server doesn't make tradeoffs disappear. It moves them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your repo becomes an access control layer.&lt;/strong&gt; Read access to the repo reveals namespace names, environment topology, and recipient fingerprints — a reconnaissance map, even without decryption keys. VCS providers weren't designed for that role. If that's unacceptable for your threat model, a private secrets repo with restricted access is the right call, and you lose some of the "secrets and code versioned together" benefit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git's limits are real.&lt;/strong&gt; Repo size grows. Branch-heavy workflows multiply encrypted variants. VCS outages affect CI even if they don't affect running agents (hosted-artifact mode keeps runtime on a separate availability plane).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clef is not the right answer for everyone.&lt;/strong&gt; If you're a small team on a PaaS with five secrets, use Doppler. If you're deep in AWS and want managed rotation, AWS Secrets Manager is genuinely good. If you need Vault's dynamic credential engines today, use Vault. The whitepaper's Section 1.1 says this in more words.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we think we do get right
&lt;/h2&gt;

&lt;p&gt;For teams that already live in git, want review workflows on secret changes, and want zero static credentials in production, removing the server is a real architectural win — not a marketing one. No cluster to operate. No vendor holding your plaintext. No bootstrap token hiding in CI. The custody-vs-ops dilemma doesn't get traded; it gets dissolved, because the thing creating it is gone.&lt;/p&gt;

&lt;p&gt;We're not claiming magic. We're claiming the server was the problem, and a different architecture — one built on primitives every team already has (git, IAM, KMS) — doesn't need one.&lt;/p&gt;

&lt;p&gt;If you want the long version with the full threat model, the artifact signing design, and the KMS-native envelope flow, the &lt;a href="https://github.com/clef-sh/clef/blob/main/whitepaper.md" rel="noopener noreferrer"&gt;whitepaper&lt;/a&gt; is the honest read. It calls out the places Clef is weaker as clearly as the places it's stronger. That's the only way this kind of decision should get made.&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>architecture</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Was Tired of Paying $0.40/Secret/Month, So I spent a month building a CLI tool to manage it.</title>
      <dc:creator>Clef.sh</dc:creator>
      <pubDate>Thu, 02 Apr 2026 03:08:53 +0000</pubDate>
      <link>https://dev.to/clef_sh/i-was-tired-of-paying-040secretmonth-so-i-spent-a-month-building-a-cli-tool-to-manage-it-44g3</link>
      <guid>https://dev.to/clef_sh/i-was-tired-of-paying-040secretmonth-so-i-spent-a-month-building-a-cli-tool-to-manage-it-44g3</guid>
      <description>&lt;p&gt;Last month I looked at my AWS bill for a side project — a static site on S3 and a Lambda function that maybe 3 people use, including me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The most expensive line item was secrets management.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AWS Secrets Manager charges $0.40 per secret per month, plus $0.05 per 10,000 API calls. I had 12 secrets across two environments. That's $4.80/month just to &lt;em&gt;store&lt;/em&gt; a Stripe key and a database URL — for a project that costs $1.20/month in compute.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Alternatives All Suck
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SSM Parameter Store&lt;/strong&gt; — free tier exists, but no encryption at rest for SecureString without KMS ($1/month/key), no rotation, no structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardcoded env vars&lt;/strong&gt; — works until you need to rotate something, or onboard a teammate, or remember what staging is actually pointing at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.env&lt;/code&gt; files in a private repo&lt;/strong&gt; — congratulations, your secrets are now in plaintext in git history forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HashiCorp Vault&lt;/strong&gt; — I need a &lt;em&gt;server&lt;/em&gt; to store 12 key-value pairs?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Actually Wanted
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Secrets stored alongside my code (one source of truth)&lt;/li&gt;
&lt;li&gt;Encrypted at rest (not plaintext in git)&lt;/li&gt;
&lt;li&gt;History of every change (who changed what, when)&lt;/li&gt;
&lt;li&gt;Works across environments (dev, staging, production)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Free&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  So I Built Clef
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/clef-sh/clef" rel="noopener noreferrer"&gt;Clef&lt;/a&gt; is a CLI that manages encrypted secrets in your git repo using &lt;a href="https://github.com/getsops/sops" rel="noopener noreferrer"&gt;Mozilla SOPS&lt;/a&gt; and &lt;a href="https://age-encryption.org" rel="noopener noreferrer"&gt;age encryption&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Setup takes about 2 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; @clef-sh/cli
clef init &lt;span class="nt"&gt;--namespaces&lt;/span&gt; api &lt;span class="nt"&gt;--non-interactive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates an age key (modern, simple encryption — no GPG), creates a &lt;code&gt;clef.yaml&lt;/code&gt; manifest, and scaffolds encrypted files for each namespace × environment.&lt;/p&gt;

&lt;p&gt;Setting a secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clef &lt;span class="nb"&gt;set &lt;/span&gt;api/production STRIPE_KEY sk_live_abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value is encrypted immediately. The file in git looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;STRIPE_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ENC[AES256_GCM,data:7a3b9c...,type:str]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key names visible (great for diffs and code review), values encrypted. Getting it back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clef get api/production STRIPE_KEY
&lt;span class="c"&gt;# sk_live_abc123&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Injecting into a process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clef &lt;span class="nb"&gt;exec &lt;/span&gt;api/production &lt;span class="nt"&gt;--&lt;/span&gt; node server.js
&lt;span class="c"&gt;# STRIPE_KEY is now in process.env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What It Replaced
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;AWS Secrets Manager&lt;/th&gt;
&lt;th&gt;Clef&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0.40/secret/month + API calls&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AWS managed&lt;/td&gt;
&lt;td&gt;Your git repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;History&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CloudTrail (extra cost)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git log&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Access control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;IAM policies&lt;/td&gt;
&lt;td&gt;Age keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AWS account required&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Offline access&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vendor lock-in&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (it's just encrypted YAML)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Tradeoffs (Being Honest)
&lt;/h2&gt;

&lt;p&gt;Clef isn't a Vault replacement. It's for a different use case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No runtime secret injection&lt;/strong&gt; — you decrypt at build time or via &lt;code&gt;clef exec&lt;/code&gt;. There's no API your app calls at runtime (though there is an &lt;a href="https://github.com/clef-sh/clef/tree/main/packages/agent" rel="noopener noreferrer"&gt;agent sidecar&lt;/a&gt; if you want that).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No automatic rotation&lt;/strong&gt; — you rotate by running &lt;code&gt;clef set&lt;/code&gt; with a new value and pushing. No Lambda rotation functions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust model is simpler&lt;/strong&gt; — if someone has the age key, they can decrypt everything they're a recipient on. There's no per-secret ACL. For a solo dev or small team, this is fine. For a 200-person org, use KMS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who This Is For
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Side projects where secrets management costs more than the project itself&lt;/li&gt;
&lt;li&gt;Small teams (2-5 people) tired of sharing &lt;code&gt;.env&lt;/code&gt; files over Slack&lt;/li&gt;
&lt;li&gt;Anyone who wants secrets versioned in git with real encryption, not base64 encoding&lt;/li&gt;
&lt;li&gt;Developers who don't want to run infrastructure just to store 10 key-value pairs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who This Is Not For
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Large orgs that need centralized access control and audit&lt;/li&gt;
&lt;li&gt;Teams that need runtime secret rotation without redeployment&lt;/li&gt;
&lt;li&gt;Anyone already happy with their current setup (seriously, don't switch for the sake of it)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The repo is at &lt;a href="https://github.com/clef-sh/clef" rel="noopener noreferrer"&gt;github.com/clef-sh/clef&lt;/a&gt;. It's MIT licensed. Stars appreciated but not required.&lt;/p&gt;

&lt;p&gt;If you've ever looked at your AWS bill and wondered why storing a database password costs more than running the database, give it a try.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>aws</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Gap Between Encrypting Secrets and Proving You Handled Them Right</title>
      <dc:creator>Clef.sh</dc:creator>
      <pubDate>Wed, 01 Apr 2026 15:12:20 +0000</pubDate>
      <link>https://dev.to/clef_sh/the-gap-between-encrypting-secrets-and-proving-you-handled-them-right-c0</link>
      <guid>https://dev.to/clef_sh/the-gap-between-encrypting-secrets-and-proving-you-handled-them-right-c0</guid>
      <description>&lt;p&gt;There's a moment in every secrets pipeline that nobody talks about.&lt;/p&gt;

&lt;p&gt;You encrypt your secrets at rest. You store them in git as ciphertext. You manage KMS keys with IAM policies. You rotate credentials. You might even use SOPS or sealed-secrets or Vault. Your secrets management story sounds solid in an architecture review.&lt;/p&gt;

&lt;p&gt;But at some point, something has to decrypt those secrets and do something with them. And that something runs on a CI runner.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Plaintext Moment
&lt;/h2&gt;

&lt;p&gt;Think about what happens during a typical deployment. Your CI pipeline checks out the repo, decrypts SOPS-encrypted files, merges the right values for the target service and environment, re-encrypts them into a deployment artifact, and pushes it somewhere your runtime can consume it.&lt;/p&gt;

&lt;p&gt;For a brief window, plaintext secrets exist in memory on a general-purpose compute environment. The same environment that runs your test suite, your linters, your build tools, and whatever transitive dependencies those tools pulled in this week.&lt;/p&gt;

&lt;p&gt;The ciphertext in your git repo is inert. Your KMS keys in isolation are useless. But the moment ciphertext meets decryption capability in the same execution context — that's when plaintext is born. And that moment happens on your CI runner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters Less Than You Think (And More Than You Think)
&lt;/h2&gt;

&lt;p&gt;If you self-host your CI runners, you probably trust them. You control the machine, the network, the software. Telling you "your CI runner might be compromised" is arguing against your own operational confidence, and it's a hard sell.&lt;/p&gt;

&lt;p&gt;But here's the thing: trusting your CI runner and being able to &lt;em&gt;prove&lt;/em&gt; what your CI runner did are two different things.&lt;/p&gt;

&lt;p&gt;When an auditor asks "what code processed these secrets during the last deployment," the honest answer from most pipelines is "we trust that our CI ran the right code." That's an assertion, not evidence. The CI logs say what happened — but the CI wrote those logs. If the runner were compromised, the logs would say whatever the attacker wanted them to say.&lt;/p&gt;

&lt;p&gt;This isn't a security gap in the traditional sense. It's a provenance gap. You might be doing everything right, but you can't prove it cryptographically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Cryptographic Provenance Looks Like
&lt;/h2&gt;

&lt;p&gt;Imagine if the "pack" operation — the moment where ciphertext meets KMS decryption and plaintext is born — ran inside hardware-isolated memory. The host operating system can't read it. The hypervisor can't read it. Root access on the machine can't read it.&lt;/p&gt;

&lt;p&gt;Now imagine the hardware produces a signed attestation document that says "this exact binary, with this exact image hash, ran this operation." And your KMS key policy says "only decrypt when the request comes with a valid attestation document bearing this specific image hash."&lt;/p&gt;

&lt;p&gt;You now have a chain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The binary is source-available — anyone can read the code&lt;/li&gt;
&lt;li&gt;The build is reproducible — anyone can verify the binary matches the source&lt;/li&gt;
&lt;li&gt;The image hash is deterministic — same source produces same hash&lt;/li&gt;
&lt;li&gt;The KMS policy only allows decryption by that exact image&lt;/li&gt;
&lt;li&gt;The attestation document is signed by the hardware, not by software you control&lt;/li&gt;
&lt;li&gt;The result is a signed artifact with a receipt that includes the attestation, the source commit, and the KMS keys used&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This isn't "we trust the CI runner." This is "the hardware proved what code ran, and the KMS proved only that code could decrypt." No one in the chain needs to be trusted — the proof is anchored in hardware attestation and KMS policy evaluation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Audit Answer Changes
&lt;/h2&gt;

&lt;p&gt;Without this: "We followed our procedures and our logs show the pipeline ran correctly."&lt;/p&gt;

&lt;p&gt;With this: "Here is the attestation receipt. It proves this specific binary, running inside hardware-isolated memory, processed secrets from this git commit, using these KMS keys, and produced this artifact. The binary is source-available and reproducibly built — here's the build spec if you want to verify the image hash yourself."&lt;/p&gt;

&lt;p&gt;The first answer requires trusting the organization. The second requires trusting math.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Actually Needs This
&lt;/h2&gt;

&lt;p&gt;Not everyone. If you're a startup with five engineers and you trust your CI, you probably don't need hardware-attested pack operations. Your threat model doesn't justify the operational complexity.&lt;/p&gt;

&lt;p&gt;But if you're in a regulated industry — finance, healthcare, government, or any organization where auditors ask pointed questions about secret handling — the provenance gap is real. And it gets wider as your secrets pipeline gets more complex: more services, more environments, more KMS keys, more people with access to CI infrastructure.&lt;/p&gt;

&lt;p&gt;The interesting thing is that this isn't about replacing your secrets manager. You keep Vault, or SOPS, or whatever you use. The attestation layer sits on top of your existing pipeline, specifically around the moment where secrets are decrypted and repackaged. It's a narrow, focused intervention at the one point in the pipeline where plaintext exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Punchline
&lt;/h2&gt;

&lt;p&gt;Encryption protects secrets at rest. KMS policies protect secrets at the API boundary. Network isolation protects secrets in transit. But none of these protect the &lt;em&gt;moment of use&lt;/em&gt; — the instant when ciphertext is decrypted and plaintext lives in memory.&lt;/p&gt;

&lt;p&gt;Hardware enclaves protect that moment. Attestation proves what happened during that moment. Reproducible builds let anyone verify that the attested code matches published source.&lt;/p&gt;

&lt;p&gt;The result isn't a security product. It's a provenance product. The answer to "how do you know your pipeline didn't leak these credentials" stops being an assertion and becomes a proof.&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>cryptography</category>
      <category>cloud</category>
    </item>
    <item>
      <title>We scored ourselves against HashiCorp's 12-point secrets management framework. We have some work to do.</title>
      <dc:creator>Clef.sh</dc:creator>
      <pubDate>Tue, 31 Mar 2026 14:22:48 +0000</pubDate>
      <link>https://dev.to/clef_sh/we-scored-ourselves-against-hashicorps-12-point-secrets-management-framework-we-have-some-work-to-34a0</link>
      <guid>https://dev.to/clef_sh/we-scored-ourselves-against-hashicorps-12-point-secrets-management-framework-we-have-some-work-to-34a0</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://dev.to/clef_sh/hashicorp-says-your-secrets-manager-needs-12-things-heres-how-we-stack-up-638" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojmjcaoe71mkl0s7n5hh.png" height="350" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://dev.to/clef_sh/hashicorp-says-your-secrets-manager-needs-12-things-heres-how-we-stack-up-638" rel="noopener noreferrer" class="c-link"&gt;
            HashiCorp Says Your Secrets Manager Needs 12 Things. Here's How We Stack Up. 🎹 - DEV Community
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            HashiCorp recently published a whitepaper called "12 Things a Modern Secrets Management Solution Must...
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png" width="300" height="299"&gt;
          dev.to
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
    </item>
    <item>
      <title>HashiCorp Says Your Secrets Manager Needs 12 Things. Here's How We Stack Up. 🎹</title>
      <dc:creator>Clef.sh</dc:creator>
      <pubDate>Tue, 31 Mar 2026 13:50:31 +0000</pubDate>
      <link>https://dev.to/clef_sh/hashicorp-says-your-secrets-manager-needs-12-things-heres-how-we-stack-up-638</link>
      <guid>https://dev.to/clef_sh/hashicorp-says-your-secrets-manager-needs-12-things-heres-how-we-stack-up-638</guid>
      <description>&lt;p&gt;HashiCorp recently published a whitepaper called &lt;a href="https://www.hashicorp.com/en/on-demand/12-things-a-modern-secrets-management-solution-must-do" rel="noopener noreferrer"&gt;&lt;em&gt;"12 Things a Modern Secrets Management Solution Must Do."&lt;/em&gt;&lt;/a&gt; It's a solid framework — genuinely useful for evaluating any secrets tool.&lt;/p&gt;

&lt;p&gt;So we ran &lt;a href="https://clef.sh" rel="noopener noreferrer"&gt;Clef&lt;/a&gt; through it. Honestly.&lt;/p&gt;

&lt;p&gt;We're not going to pretend we check every box the same way Vault does. We're a git-native secrets manager built on SOPS — no servers, no tokens, no vendor custody. Different architecture, different tradeoffs. Here's where we're strong, where we're different, and where we'll tell you to use something else.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Scorecard 📋
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Secure Secrets Storage 🔒
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vault:&lt;/strong&gt; Centralized encrypted KV store. Secrets encrypted before hitting persistent storage. Dashboard + CLI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clef:&lt;/strong&gt; Encrypted files in git. SOPS encrypts values using age or cloud KMS. Decrypted values exist only in memory — plaintext never touches disk. The repo &lt;em&gt;is&lt;/em&gt; the store.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Verdict:&lt;/strong&gt; Both nail this. Different storage model, same outcome — secrets encrypted at rest, protected from raw storage access.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Centralized Management 🏢
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vault:&lt;/strong&gt; Enterprise dashboard. SecOps manages everything from one place without bugging developers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clef:&lt;/strong&gt; The git repo is the single source of truth. &lt;code&gt;clef lint&lt;/code&gt; validates the matrix. &lt;code&gt;clef report&lt;/code&gt; publishes structured JSON for your observability stack.&lt;/p&gt;

&lt;p&gt;🟡 &lt;strong&gt;Verdict:&lt;/strong&gt; Clef is developer-first CLI today — no enterprise dashboard. If you need SecOps visibility without git fluency, that's not us &lt;em&gt;yet&lt;/em&gt;. Clef Pro is coming for exactly this. Right now, we're focused on teams where the developers &lt;em&gt;are&lt;/em&gt; the security team.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Secret Scanning 🔍
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vault:&lt;/strong&gt; HCP Vault Radar scans across your entire IT estate — code, wikis, Slack, ticketing, cloud storage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clef:&lt;/strong&gt; &lt;code&gt;clef scan&lt;/code&gt; does pattern matching + entropy analysis on the git repo. Pre-commit hooks and CI integration catch leaks before merge.&lt;/p&gt;

&lt;p&gt;🟡 &lt;strong&gt;Verdict:&lt;/strong&gt; Clef covers the most common leak vector (code), not the whole org. For everything else, pair with GitGuardian or Trufflehog. We're not going to pretend a git hook replaces organization-wide scanning.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Strong Encryption 🛡️
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vault:&lt;/strong&gt; Encryption as a Service (EaaS). AES-256-GCM via transit engine. Encrypt arbitrary application data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clef:&lt;/strong&gt; SOPS + age (X25519/ChaCha20-Poly1305) or cloud KMS (AES-256-GCM). All crypto delegated — zero custom cryptography.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Verdict:&lt;/strong&gt; Both strong. Clef encrypts secrets, not arbitrary payloads — we're not an EaaS. But the encryption itself? Rock solid, industry-standard, no homebrew.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Secrets Versioning 📜
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vault:&lt;/strong&gt; KV v2 version history. UI shows who changed what. API rollback to previous versions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clef:&lt;/strong&gt; Git &lt;em&gt;is&lt;/em&gt; the versioning. Full commit history, PR context, code review, blame, branching. Rollback = &lt;code&gt;git revert&lt;/code&gt; → PR → merge → CI repacks.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Verdict:&lt;/strong&gt; This is Clef's home turf. Git versioning is arguably &lt;em&gt;stronger&lt;/em&gt; — you get author, reviewer, CI status, and the complete code context around the change. And every developer already knows how to use it.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Advanced Access Control 🚪
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vault:&lt;/strong&gt; Identity-based policy engine. RBAC, MFA, fine-grained per-path permissions. Dozens of auth methods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clef:&lt;/strong&gt; Cryptographic scoping at the namespace level. Service identities are SOPS recipients only on their namespaces — enforcement is at the encryption layer. Per-environment keys. Git branch protection + CODEOWNERS guard the manifest.&lt;/p&gt;

&lt;p&gt;🟡 &lt;strong&gt;Verdict:&lt;/strong&gt; Clef's access control is &lt;em&gt;cryptographic&lt;/em&gt; — a service literally cannot decrypt secrets outside its scope. That's powerful. But there's no RBAC, no MFA, no per-key permissions. If you model namespaces at the right granularity (one per dependency), it works. If you need per-secret policies or MFA-gated access, Vault is purpose-built for that.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Backup and Recovery 💾
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vault:&lt;/strong&gt; Raft snapshots, cross-cluster replication, encrypted backups, OIDC integration for recovery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clef:&lt;/strong&gt; Every &lt;code&gt;git clone&lt;/code&gt; is a full backup. Break-glass age key in a physical safe decrypts everything if KMS goes sideways. AWS KMS enforces 7-30 day deletion waiting period.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Verdict:&lt;/strong&gt; Git's distributed nature means backup is inherent — no strategy to design, no snapshots to schedule. Your data is replicated across every developer's checkout.&lt;/p&gt;




&lt;h3&gt;
  
  
  8. Automated Secrets Rotation 🔄
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vault:&lt;/strong&gt; Dynamic secrets with TTL leasing. Vault changes the actual credential at the source — database passwords, cloud IAM, certificates. Auto-revoke on expiry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clef:&lt;/strong&gt; Two different things here. &lt;em&gt;Encryption key rotation&lt;/em&gt; — automatic, every &lt;code&gt;clef pack&lt;/code&gt; generates fresh ephemeral keys. &lt;em&gt;Credential rotation&lt;/em&gt; (the actual password at the source) — manual via &lt;code&gt;clef set&lt;/code&gt; → PR → merge → repack. Broker-backed dynamic credentials can automate this but require deploying a handler.&lt;/p&gt;

&lt;p&gt;🟡 &lt;strong&gt;Verdict:&lt;/strong&gt; We're being honest — Clef doesn't auto-rotate your database password out of the box. Encryption keys rotate automatically on every build, which is great. But if you need push-button credential rotation for 30 backends today, Vault's secrets engines are production-proven.&lt;/p&gt;




&lt;h3&gt;
  
  
  9. Dynamic Secrets ⚡
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vault:&lt;/strong&gt; First-class. Dozens of production-hardened engines — AWS, databases, PKI, SSH, LDAP. Years of battle-testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clef:&lt;/strong&gt; Broker SDK + registry. Templates for STS, RDS IAM, OAuth, SQL users. The architecture supports it. The ecosystem is young.&lt;/p&gt;

&lt;p&gt;🔴 &lt;strong&gt;Verdict:&lt;/strong&gt; We'll say it plainly — &lt;em&gt;if you need dynamic credentials today with minimal engineering, use Vault.&lt;/em&gt; Our broker architecture is extensible, but you're building, not consuming. Vault's breadth here is an order of magnitude larger.&lt;/p&gt;




&lt;h3&gt;
  
  
  10. Integrations, APIs, and Secrets Sync 🔌
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vault:&lt;/strong&gt; RESTful API for everything. One-way KV sync to external destinations. Massive plugin ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clef:&lt;/strong&gt; Agent HTTP API on &lt;code&gt;127.0.0.1:7779&lt;/code&gt;. Any language that can &lt;code&gt;GET&lt;/code&gt; can read secrets. No SDK, no vendor client library. No secrets sync — the packed artifact &lt;em&gt;is&lt;/em&gt; the distribution.&lt;/p&gt;

&lt;p&gt;🟡 &lt;strong&gt;Verdict:&lt;/strong&gt; Clef's interface is dead simple — one HTTP endpoint, universal. But Vault's pre-built integrations (K8s sidecar injector, native Lambda integration, etc.) mean less glue code. Simplicity vs. breadth. Pick your tradeoff.&lt;/p&gt;




&lt;h3&gt;
  
  
  11. Automated Key Management 🔑
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vault:&lt;/strong&gt; Unified API to manage key lifecycles across AWS KMS, Azure Key Vault, GCP Cloud KMS. Create, rotate, disable, delete — one interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clef:&lt;/strong&gt; Delegated model. Clef &lt;em&gt;uses&lt;/em&gt; KMS keys but doesn't manage their lifecycle. Key creation, rotation, and deletion happen through your cloud provider's console or IaC (Terraform, Pulumi). Ephemeral key pairs per &lt;code&gt;clef pack&lt;/code&gt; eliminate long-lived runtime keys.&lt;/p&gt;

&lt;p&gt;🟡 &lt;strong&gt;Verdict:&lt;/strong&gt; Clef doesn't provide a key management abstraction — your cloud provider does, and you manage it with IaC like everything else. For teams already running Terraform, this adds zero operational surface. If you want one API for keys across three clouds, Vault has that.&lt;/p&gt;




&lt;h3&gt;
  
  
  12. Robust Auditing 📊
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vault:&lt;/strong&gt; Built-in audit device. One dashboard, one query: "who accessed what, when."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clef:&lt;/strong&gt; Distributed across existing infra — CloudTrail for KMS events, git log for authorship, CI logs for packing, OTLP telemetry for agent health. JIT mode maps every secret read to a distinct CloudTrail entry.&lt;/p&gt;

&lt;p&gt;🟡 &lt;strong&gt;Verdict:&lt;/strong&gt; All the audit data exists — it's just in four places, not one. Correlating across them is work. Clef Pro will unify this. Today, teams that already have observability tooling (Datadog, Grafana, etc.) can wire it up, but there's no single-pane view out of the box.&lt;/p&gt;




&lt;h2&gt;
  
  
  The TL;DR 🎯
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Clef ✅&lt;/th&gt;
&lt;th&gt;Even 🟡&lt;/th&gt;
&lt;th&gt;Vault ✅&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Strong&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Storage, Encryption, Versioning, Backup&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Different tradeoff&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Management, Scanning, Access Control, Rotation, Integrations, Key Mgmt, Auditing&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vault wins today&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Dynamic Secrets&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Honest Pitch 🤝
&lt;/h2&gt;

&lt;p&gt;Clef is built for teams that want secrets versioned alongside code, reviewed in PRs, and delivered without a central server to babysit.&lt;/p&gt;

&lt;p&gt;If your team lives in git, manages infra through IaC, and values operational simplicity over integration breadth — Clef removes an entire category of infrastructure from your stack. No cluster. No unsealing. No token bootstrapping. No vendor custody.&lt;/p&gt;

&lt;p&gt;If you need a policy engine, an enterprise dashboard, or turnkey dynamic credentials for dozens of backends &lt;em&gt;today&lt;/em&gt; — Vault is purpose-built for that, and we'd rather you know upfront than find out after adoption.&lt;/p&gt;

&lt;p&gt;We think honesty builds more trust than a checkbox grid. 🎹&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://clef.sh" rel="noopener noreferrer"&gt;Clef&lt;/a&gt; is open source. Star us on &lt;a href="https://github.com/clef-sh/clef" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, read the &lt;a href="https://github.com/clef-sh/clef/blob/main/whitepaper.md" rel="noopener noreferrer"&gt;whitepaper&lt;/a&gt;, or just &lt;code&gt;npx clef init&lt;/code&gt; and see for yourself.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I thought running a HashiCorp Vault cluster just to securely pass API keys to production was a bit much.

So I built Clef: a tokenless, zero-server secrets manager using Git, SOPS, and KMS envelope encryption. Better security, smaller footprint.</title>
      <dc:creator>Clef.sh</dc:creator>
      <pubDate>Thu, 26 Mar 2026 12:41:38 +0000</pubDate>
      <link>https://dev.to/clef_sh/i-thought-running-a-hashicorp-vault-cluster-just-to-securely-pass-api-keys-to-production-was-a-bit-gd7</link>
      <guid>https://dev.to/clef_sh/i-thought-running-a-hashicorp-vault-cluster-just-to-securely-pass-api-keys-to-production-was-a-bit-gd7</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://dev.to/clef_sh/i-built-a-tokenless-secrets-manager-that-runs-entirely-on-git-and-kms-no-vault-required-41bi" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhs3bhhs5jerrs4lk2c4.png" height="350" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://dev.to/clef_sh/i-built-a-tokenless-secrets-manager-that-runs-entirely-on-git-and-kms-no-vault-required-41bi" rel="noopener noreferrer" class="c-link"&gt;
            I built a tokenless secrets manager that runs entirely on Git and KMS (No Vault required) - DEV Community
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            If you've ever had to manage secrets for a production application, you know the pain of the "Secret...
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j7kvp660rqzt99zui8e.png" width="300" height="299"&gt;
          dev.to
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>showdev</category>
      <category>devops</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I built a tokenless secrets manager that runs entirely on Git and KMS (No Vault required)</title>
      <dc:creator>Clef.sh</dc:creator>
      <pubDate>Wed, 25 Mar 2026 20:48:53 +0000</pubDate>
      <link>https://dev.to/clef_sh/i-built-a-tokenless-secrets-manager-that-runs-entirely-on-git-and-kms-no-vault-required-41bi</link>
      <guid>https://dev.to/clef_sh/i-built-a-tokenless-secrets-manager-that-runs-entirely-on-git-and-kms-no-vault-required-41bi</guid>
      <description>&lt;p&gt;If you've ever had to manage secrets for a production application, you know the pain of the "Secret Zero" problem: &lt;em&gt;How do you securely deliver a secret to a workload without giving it a static &lt;code&gt;.env&lt;/code&gt; file or password first?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Today, the industry standard way to solve this is to use HashiCorp Vault or Infisical tied to your cloud's machine identity (like AWS IAM Auth or Kubernetes Service Accounts). &lt;/p&gt;

&lt;p&gt;That works beautifully, &lt;strong&gt;but the infrastructure cost is massive.&lt;/strong&gt; You have to run an HA cluster, manage unseal keys, configure storage backends, and maintain a dedicated secrets server just to securely pass an API key. &lt;/p&gt;

&lt;p&gt;The alternative is raw Mozilla SOPS + Git, which gives an amazing developer experience but leaves you writing messy custom KMS-decryption bash scripts in your CI pipelines to get those secrets into production.&lt;/p&gt;

&lt;p&gt;I wanted the developer experience of Git, but the enterprise security of a tokenless, zero-trust architecture—without running a server. So, I built &lt;strong&gt;Clef&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Clef?
&lt;/h3&gt;

&lt;p&gt;Clef (&lt;a href="https://clef.sh" rel="noopener noreferrer"&gt;https://clef.sh&lt;/a&gt;) is an open-source secrets manager. It bridges the gap by treating your Git repository as the authoritative state and your cloud's native IAM as the authentication layer.&lt;/p&gt;

&lt;p&gt;Here is how the architecture works from development to production:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Local Development (Secrets as Code)
&lt;/h3&gt;

&lt;p&gt;Secrets are encrypted locally using SOPS and Age encryption. You define your service identities and namespaces in a simple &lt;code&gt;clef.yaml&lt;/code&gt; manifest. No plaintext is ever written to disk.&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;# Initialize a new project&lt;/span&gt;
clef init

&lt;span class="c"&gt;# Set a secret in development&lt;/span&gt;
clef &lt;span class="nb"&gt;set &lt;/span&gt;database/development DB_URL &lt;span class="s2"&gt;"postgres://localhost:5432/myapp"&lt;/span&gt;

&lt;span class="c"&gt;# Inject secrets directly into your local Node process&lt;/span&gt;
clef &lt;span class="nb"&gt;exec &lt;/span&gt;database/development &lt;span class="nt"&gt;--&lt;/span&gt; node server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. CI/CD (The Pack Phase)
&lt;/h3&gt;

&lt;p&gt;When you merge your code, your CI pipeline runs &lt;code&gt;clef pack&lt;/code&gt;. It decrypts only the SOPS files scoped to that specific service, merges them, and creates a lightweight JSON artifact. &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Solving "Secret Zero" (KMS Envelope Encryption)
&lt;/h3&gt;

&lt;p&gt;This is where the magic happens. To deliver that JSON artifact securely without static credentials, the CI pipeline generates an &lt;strong&gt;ephemeral, one-time-use Age key pair&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;It encrypts the artifact with the ephemeral key, and then &lt;em&gt;wraps&lt;/em&gt; the ephemeral private key using your cloud KMS (AWS or GCP). &lt;/p&gt;

&lt;h3&gt;
  
  
  4. Production Runtime (The Agent)
&lt;/h3&gt;

&lt;p&gt;A lightweight Clef Agent sidecar runs next to your production app. It doesn't need a password or a &lt;code&gt;.env&lt;/code&gt; file. Using the machine's native cloud IAM role (like an AWS EC2 or ECS Task Role), the agent simply asks KMS to unwrap the ephemeral key. &lt;/p&gt;

&lt;p&gt;It then decrypts the artifact and serves the secrets to your app via a localhost API (&lt;code&gt;127.0.0.1:7779&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Your app code just fetches from localhost&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secrets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://127.0.0.1:7779/v1/secrets&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bearer &amp;lt;local-token&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Honest Trade-off
&lt;/h3&gt;

&lt;p&gt;Security is always a trade-off. By eliminating the central server, &lt;strong&gt;your Git repository effectively becomes your access control list.&lt;/strong&gt; You are trading infrastructure complexity for strict GitOps process discipline. If an insider can merge a PR that adds their personal public key to your &lt;code&gt;clef.yaml&lt;/code&gt; manifest, they gain access to your secrets. To use Clef safely, you &lt;em&gt;must&lt;/em&gt; enforce strict branch protection, require CODEOWNERS reviews on your security manifests, and run isolated CI pipelines. &lt;/p&gt;

&lt;p&gt;Properly securing Git + CI is easier than securing Git + CI + Vault, but you have to treat your repo with the seriousness of a vault.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it out
&lt;/h3&gt;

&lt;p&gt;Clef is fully open-source under the MIT license. &lt;/p&gt;

&lt;p&gt;I wrote a detailed whitepaper breaking down the architecture, the KMS envelope math, and the threat model. I’d love for the DEV community to tear it apart, try out the CLI, and let me know what you think!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repo:&lt;/strong&gt; &lt;a href="https://github.com/clef-sh/clef" rel="noopener noreferrer"&gt;https://github.com/clef-sh/clef&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs &amp;amp; Whitepaper:&lt;/strong&gt; &lt;a href="https://clef.sh" rel="noopener noreferrer"&gt;https://clef.sh&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me know in the comments: For those of you running Vault today, is the operational overhead worth having a centralized policy engine, or would you trade it for a Git-native ACL like this?&lt;/p&gt;




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