Skip to content

add deploy badge#18

Merged
ctate merged 3 commits intomainfrom
ctate/readme-badges
Nov 24, 2025
Merged

add deploy badge#18
ctate merged 3 commits intomainfrom
ctate/readme-badges

Conversation

@ctate
Copy link
Collaborator

@ctate ctate commented Nov 24, 2025

No description provided.

@vercel
Copy link
Contributor

vercel bot commented Nov 24, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
workflow-builder Ready Ready Preview Comment Nov 24, 2025 9:20pm

* Accepts any string and hashes it to a 32-byte key
*/
function getEncryptionKey(): Buffer {
const keyHex = process.env[ENCRYPTION_KEY_ENV];
Copy link
Contributor

@vercel vercel bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from hex-string-based key derivation to SHA256-hashing introduces a breaking change that could cause data loss if existing encrypted credentials are migrated to this new version.

View Details
📝 Patch Details
diff --git a/lib/db/integrations.ts b/lib/db/integrations.ts
index 7a186fe..313a63e 100644
--- a/lib/db/integrations.ts
+++ b/lib/db/integrations.ts
@@ -18,8 +18,9 @@ const ENCRYPTION_KEY_ENV = "INTEGRATION_ENCRYPTION_KEY";
 /**
  * Get or generate encryption key from environment
  * Accepts any string and hashes it to a 32-byte key
+ * For backward compatibility, also supports 64-character hex strings (legacy format)
  */
-function getEncryptionKey(): Buffer {
+function getEncryptionKey(useLegacy: boolean = false): Buffer {
   const keyString = process.env[ENCRYPTION_KEY_ENV];
 
   if (!keyString) {
@@ -28,7 +29,17 @@ function getEncryptionKey(): Buffer {
     );
   }
 
-  // Hash the input string to get a consistent 32-byte key
+  if (useLegacy) {
+    // Legacy format: treat as 64-character hex string
+    if (keyString.length !== 64) {
+      throw new Error(
+        `${ENCRYPTION_KEY_ENV} must be a 64-character hex string (32 bytes) when using legacy decryption`
+      );
+    }
+    return Buffer.from(keyString, "hex");
+  }
+
+  // New format: hash the input string to get a consistent 32-byte key
   return createHash("sha256").update(keyString).digest();
 }
 
@@ -52,9 +63,9 @@ export function encrypt(plaintext: string): string {
 
 /**
  * Decrypt encrypted data
+ * Attempts to decrypt with the new key format first, then falls back to legacy format
  */
 export function decrypt(ciphertext: string): string {
-  const key = getEncryptionKey();
   const parts = ciphertext.split(":");
 
   if (parts.length !== 3) {
@@ -65,13 +76,32 @@ export function decrypt(ciphertext: string): string {
   const authTag = Buffer.from(parts[1], "hex");
   const encrypted = parts[2];
 
-  const decipher = createDecipheriv(ALGORITHM, key, iv);
-  decipher.setAuthTag(authTag);
+  // Try new key format first
+  try {
+    const key = getEncryptionKey(false);
+    const decipher = createDecipheriv(ALGORITHM, key, iv);
+    decipher.setAuthTag(authTag);
 
-  let decrypted = decipher.update(encrypted, "hex", "utf8");
-  decrypted += decipher.final("utf8");
+    let decrypted = decipher.update(encrypted, "hex", "utf8");
+    decrypted += decipher.final("utf8");
 
-  return decrypted;
+    return decrypted;
+  } catch (error) {
+    // If new format fails, try legacy format
+    try {
+      const key = getEncryptionKey(true);
+      const decipher = createDecipheriv(ALGORITHM, key, iv);
+      decipher.setAuthTag(authTag);
+
+      let decrypted = decipher.update(encrypted, "hex", "utf8");
+      decrypted += decipher.final("utf8");
+
+      return decrypted;
+    } catch (legacyError) {
+      // Both formats failed, throw the original error
+      throw error;
+    }
+  }
 }
 
 /**

Analysis

Breaking change in encryption key derivation causes silent data loss on upgrade

What fails: The getEncryptionKey() function was changed from treating the INTEGRATION_ENCRYPTION_KEY environment variable as a 64-character hex string to treating it as a plain string to hash with SHA256. This breaks decryption of existing encrypted credentials when upgrading between versions.

How to reproduce:

  1. With the old code, set INTEGRATION_ENCRYPTION_KEY=abcd1234567890abcd1234567890abcd1234567890abcd1234567890abcd1234 (64-char hex)
  2. Create and encrypt integration credentials, storing them in the database
  3. Update to the new version (without changing the env var)
  4. Attempt to retrieve the credentials by calling getIntegrations() or getIntegration()

Result: The encrypted credentials are decrypted with the new SHA256-based key, which produces a completely different key than the old hex-based derivation. The authentication tag mismatch causes decryption to fail with "Unsupported state or unable to authenticate data". The error is silently caught in decryptConfig() (lines 87-94), returning an empty object {} instead of the actual config.

Expected behavior: Existing encrypted credentials should remain readable after upgrading. New credentials can use the improved key derivation, but old data should not become inaccessible.

Root cause:

  • Old: Buffer.from(keyHex, 'hex') interprets the env var as hex-encoded binary (32 bytes directly)
  • New: createHash("sha256").update(keyString).digest() interprets the env var as a UTF-8 string to hash

For the same env var value, these produce entirely different 32-byte keys, rendering all old encrypted data unreadable.

Fix implemented: Modified decrypt() to support both key formats by attempting decryption with the new format first, then falling back to the legacy format if it fails. This allows seamless reading of both old encrypted data and new data, while all new encryptions use the improved format.

@ctate ctate merged commit 2170f6f into main Nov 24, 2025
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant