decolua/9router · error · Error

Failed to decrypt Zed access token: ${message}

Error message

Failed to decrypt Zed access token: ${message}

What it means

decryptZedAccessToken tries RSA privateDecrypt with OAEP-SHA256 padding first, then falls back to PKCS#1 v1.5 padding. If both decryptions fail (wrong key, wrong input encoding, corrupted ciphertext), it rethrows as "Failed to decrypt Zed access token: <underlying message>". The underlying message (from the OAEP attempt) names the real crypto failure.

Source

Thrown at open-sse/shared/zedAuth.js:154

  const encrypted = Buffer.from(String(encryptedAccessToken), "base64url");
  try {
    return crypto
      .privateDecrypt(
        { key: privateKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256" },
        encrypted,
      )
      .toString("utf8");
  } catch (oaepError) {
    try {
      return crypto
        .privateDecrypt(
          { key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING },
          encrypted,
        )
        .toString("utf8");
    } catch {
      const message = oaepError instanceof Error ? oaepError.message : String(oaepError);
      throw new Error(`Failed to decrypt Zed access token: ${message}`);
    }
  }
}

export function buildZedUserAuthHeader(credentials) {
  const psd = credentials?.providerSpecificData || {};
  const userId = psd.userId || credentials?.userId;
  const accessToken = credentials?.accessToken || credentials?.apiKey;
  if (!userId || !accessToken) {
    throw new Error("Zed credential is missing userId or accessToken");
  }
  return `${userId} ${accessToken}`;
}

function getSystemId(credentials) {
  return String(
    credentials?.providerSpecificData?.systemId || credentials?.systemId || "",
  );

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Re-run the Zed sign-in flow so userId, encrypted token, and privateKeyVerifier are regenerated together and stored as one set.
  2. Verify the encrypted token is pure base64url (no '=', no '%3D' escapes, no whitespace) before decrypting.
  3. Confirm the privateKeyVerifier belongs to the same sign-in session as the token — never mix values from different attempts.
  4. Read the embedded underlying message: 'bad decrypt'/length errors usually mean key/padding mismatch; base64 errors mean encoding corruption.

Example fix

// before
decryptZedAccessToken(cred.tokenFromOldSession, cred.newKeyVerifier); // mismatched pair
// after
// store token+verifier atomically from the same sign-in
decryptZedAccessToken(cred.encryptedAccessToken, cred.privateKeyVerifier);
Defensive patterns

Strategy: try-catch

Validate before calling

function isBase64Url(s) {
  return typeof s === "string" && /^[A-Za-z0-9_-]+$/.test(s) && s.length % 4 !== 1;
}
// pre-check: isBase64Url(token) && verifier exists && token+verifier from same session

Type guard

const isDecryptablePair = (cred) => Boolean(cred?.encryptedAccessToken && cred?.privateKeyVerifier) && isBase64Url(cred.encryptedAccessToken);

Try / catch

let token;
try {
  token = decryptZedAccessToken(encryptedAccessToken, privateKeyVerifier);
} catch (e) {
  // log e.message (contains the underlying crypto reason), then re-run Zed sign-in to mint a fresh keypair+token
}

Prevention

When it happens

Trigger: Calling decryptZedAccessToken with an encrypted token that doesn't match the stored private key verifier — mismatched keypair, non-base64url token string, truncated/garbled ciphertext, or a token encrypted with different padding/keys by a newer Zed client.

Common situations: User re-ran Zed sign-in so a NEW keypair/token pair exists while the dashboard still stores the old privateKeyVerifier; credential row copied between machines; token value URL-encoded (contains %3D) or padded with '=' instead of base64url.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/7c83d0f2946f257d. Report an issue: GitHub.