[object Object]

← back to Secrets Manager

secrets: backup-before-overwrite guard in writeEnvFile (timestamped .bak on real change, prune last 5) — born from the ZENDESK secret incident

34dcb5ddb64d0b0ec46625853ff41945d3fa8ede · 2026-08-11 07:20:34 -0700 · steve

Files touched

Diff

commit 34dcb5ddb64d0b0ec46625853ff41945d3fa8ede
Author: steve <steve@designerwallcoverings.com>
Date:   Tue Aug 11 07:20:34 2026 -0700

    secrets: backup-before-overwrite guard in writeEnvFile (timestamped .bak on real change, prune last 5) — born from the ZENDESK secret incident
---
 cli.js | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/cli.js b/cli.js
index 394588b..6d41835 100755
--- a/cli.js
+++ b/cli.js
@@ -124,6 +124,22 @@ function writeEnvFile(p, kv, preserveComments = true) {
   } else {
     for (const [k, v] of Object.entries(kv)) body += `${k}=${envEscape(v)}\n`;
   }
+  // Backup-before-overwrite guard (2026-08-11): never destroy a prior secret value in
+  // place. If the file exists and its content is ACTUALLY changing, snapshot it to a
+  // timestamped .bak first (0600), then prune to the last 5 for this file. Born from the
+  // ZENDESK_CHAT_CLIENT_SECRET incident — an `add` overwrote a value with no recovery path.
+  if (fs.existsSync(p)) {
+    const prev = fs.readFileSync(p, 'utf8');
+    if (prev !== body) {
+      const bak = `${p}.bak.${Date.now()}`;
+      try {
+        fs.copyFileSync(p, bak); fs.chmodSync(bak, 0o600);
+        const dir = path.dirname(p), base = path.basename(p) + '.bak.';
+        const olds = fs.readdirSync(dir).filter(f => f.startsWith(base)).sort();
+        for (const o of olds.slice(0, -5)) { try { fs.unlinkSync(path.join(dir, o)); } catch {} }
+      } catch {}
+    }
+  }
   fs.writeFileSync(p, body);
   try { fs.chmodSync(p, 0o600); } catch {}
 }

← 1465ee8 auto-data-snapshot: 2026-08-11T07:04:50 (1 data files) — reg  ·  back to Secrets Manager  ·  auto-data-snapshot: 2026-08-12T09:56:27 (1 data files) — reg cc73261 →