import "server-only"; import { createCipheriv, createDecipheriv, createHash, randomBytes, } from "node:crypto"; const ENCRYPTED_SECRET_PREFIX = "enc:v1"; function getEncryptionKey() { const secret = String( process.env.OPENCLAW_TASK_SECRET || process.env.AUTH_SECRET || process.env.NEXTAUTH_SECRET || "" ).trim(); if (!secret) { throw new Error( "OPENCLAW_TASK_SECRET or AUTH_SECRET is required for durable tasks" ); } return createHash("sha256").update(secret, "utf8").digest(); } export function encryptOpenClawTaskSecret(value: string) { const normalized = value.trim(); if (!normalized || normalized.startsWith(`${ENCRYPTED_SECRET_PREFIX}:`)) { return normalized; } const iv = randomBytes(12); const cipher = createCipheriv("aes-256-gcm", getEncryptionKey(), iv); const encrypted = Buffer.concat([ cipher.update(normalized, "utf8"), cipher.final(), ]); const authTag = cipher.getAuthTag(); return [ ENCRYPTED_SECRET_PREFIX, iv.toString("base64url"), authTag.toString("base64url"), encrypted.toString("base64url"), ].join(":"); } export function decryptOpenClawTaskSecret(value: string) { const normalized = value.trim(); if (!normalized.startsWith(`${ENCRYPTED_SECRET_PREFIX}:`)) { // Backward compatibility for tasks persisted before encryption was added. return normalized; } const [, , ivValue, authTagValue, encryptedValue] = normalized.split(":"); if (!ivValue || !authTagValue || !encryptedValue) { throw new Error("Invalid encrypted OpenClaw task secret"); } const decipher = createDecipheriv( "aes-256-gcm", getEncryptionKey(), Buffer.from(ivValue, "base64url") ); decipher.setAuthTag(Buffer.from(authTagValue, "base64url")); return Buffer.concat([ decipher.update(Buffer.from(encryptedValue, "base64url")), decipher.final(), ]).toString("utf8"); }