decolua/9router · error · Error

Missing Zed private key verifier; restart the login flow

Error message

Missing Zed private key verifier; restart the login flow

What it means

Zed native-app login stores an RSA private key wrapped in a special prefixed verifier string (PRIVATE_KEY_PREFIX + base64url). decodeZedPrivateKeyVerifier parses that verifier back into the PEM key material and throws when the input does not carry the expected prefix — meaning the value is not a valid private-key verifier from the current login flow.

Source

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

function normalizeBaseUrl(baseUrl, fallback) {
  return String(baseUrl || fallback).replace(/\/+$/, "");
}

function zedUrl(config, key, path, fallbackBase) {
  const base = normalizeBaseUrl(config?.[key], fallbackBase);
  return `${base}${path}`;
}

/** Encode a PEM private key as an opaque verifier (flows through the OAuth codeVerifier slot). */
export function encodeZedPrivateKeyVerifier(privateKeyPem) {
  return `${PRIVATE_KEY_PREFIX}${b64url(privateKeyPem)}`;
}

export function decodeZedPrivateKeyVerifier(verifier) {
  const value = String(verifier || "");
  if (!value.startsWith(PRIVATE_KEY_PREFIX)) {
    throw new Error("Missing Zed private key verifier; restart the login flow");
  }
  return fromB64url(value.slice(PRIVATE_KEY_PREFIX.length));
}

/** Generate a fresh RSA keypair + the zed.dev native_app_signin URL for it. */
export function createZedNativeAuthData(config = {}, options = {}) {
  const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
    modulusLength: 2048,
    publicKeyEncoding: { type: "pkcs1", format: "der" },
    privateKeyEncoding: { type: "pkcs1", format: "pem" },
  });

  const nativeAppPort = Number(
    options.nativeAppPort || config.defaultNativeAppPort || 58443,
  );
  const systemId = options.systemId || crypto.randomUUID();
  const publicKeyString = b64urlPadded(publicKey);
  const signInUrl = new URL(

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Restart the Zed login flow (createZedNativeAuthData) to generate a fresh keypair + verifier
  2. Verify the stored verifier is the full prefixed string, not a truncated copy or the callback URL
  3. Check for version mismatch — old verifiers from a previous prefix format are unrecoverable, re-auth
  4. Ensure storage did not strip the prefix (e.g. URL parsing of the stored value)

Example fix

// before
const pem = decodeZedPrivateKeyVerifier(account.callbackUrl); // wrong field
// after
const pem = decodeZedPrivateKeyVerifier(account.privateKeyVerifier);
if (!account.privateKeyVerifier?.startsWith(PRIVATE_KEY_PREFIX)) await restartZedLogin(account);
Defensive patterns

Strategy: validation

Validate before calling

function hasValidZedVerifier(v) {
  return typeof v === "string" && v.startsWith(PRIVATE_KEY_PREFIX) && v.length > PRIVATE_KEY_PREFIX.length;
}
if (!hasValidZedVerifier(account.zedVerifier)) await startZedLogin(account);

Type guard

function isZedPrivateKeyVerifier(v) { return typeof v === "string" && v.startsWith(PRIVATE_KEY_PREFIX); }

Try / catch

try {
  const pem = decodeZedPrivateKeyVerifier(verifier);
} catch (e) {
  if (/Missing Zed private key verifier/.test(e.message)) {
    const fresh = await createZedNativeAuthData(config);
    // restart login: present fresh.verificationUriComplete to the user
  } else throw e;
}

Prevention

When it happens

Trigger: privateKey() is called with a verifier string that is empty, truncated, JSON-escaped, or was generated by an older build using a different prefix format — i.e. the string does not start with PRIVATE_KEY_PREFIX.

Common situations: User pasted only the callback URL instead of the stored verifier; app restarted mid-login so the in-memory verifier was lost; schema/format change between versions; verifier stored in DB was corrupted or trimmed.

Related errors


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