gchq/CyberChef · error · OperationError

${err}

Error message

${err}

What it means

Thrown by JWT Verify's outer catch when jwt.verify() throws directly - the common path in modern jsonwebtoken. The thrown Error (JsonWebTokenError, NotBeforeError, TokenExpiredError, or a key/algorithm error) is wrapped as OperationError(err) and stringified. Note: because the inner JsonWebTokenError path (error 450) throws inside the same try, that OperationError is re-caught here and double-wrapped.

Source

Thrown at src/core/operations/JWTVerify.mjs:57

     * @param {string} input
     * @param {Object[]} args
     * @returns {string}
     */
    run(input, args) {
        const [key] = args;
        const algos = JWT_ALGORITHMS;
        algos[algos.indexOf("None")] = "none";

        try {
            const verified = jwt.verify(input, key, { algorithms: algos });

            if (Object.prototype.hasOwnProperty.call(verified, "name") && verified.name === "JsonWebTokenError") {
                throw new OperationError(verified.message);
            }

            return verified;
        } catch (err) {
            throw new OperationError(err);
        }
    }

}

export default JWTVerify;

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Check the token's exp/nbf claims against a synchronised clock before verifying.
  2. Use the correct secret/public key matching the signing algorithm.
  3. Distinguish error types by inspecting err.name before wrapping (TokenExpiredError vs JsonWebTokenError).
  4. Avoid the double-wrap by rethrowing OperationError unchanged in the outer catch.

Example fix

// before: outer catch re-wraps every error indiscriminately
catch (err) { throw new OperationError(err); }
// after: rethrow OperationError as-is, wrap others with their name
if (err instanceof OperationError) throw err;
throw new OperationError(`${err.name}: ${err.message}`);
Defensive patterns

Strategy: try-catch

Validate before calling

function checkClaims(token) {
  const parts = token.split('.');
  const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
  const now = Math.floor(Date.now() / 1000);
  if (payload.exp && now >= payload.exp) throw new Error('Token expired');
  if (payload.nbf && now < payload.nbf) throw new Error('Token not yet valid');
  return payload;
}

Type guard

function isNonExpiredToken(token, skewSec = 0) {
  try {
    const p = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString());
    const now = Math.floor(Date.now() / 1000) + skewSec;
    return (!p.exp || now < p.exp) && (!p.nbf || now >= p.nbf);
  } catch { return false; }
}

Try / catch

try {
  return chef.JWTVerify(token, { key });
} catch (e) {
  const msg = String(e.message || e);
  if (/expired/i.test(msg)) throw new Error('Token expired - refresh it');
  if (/not yet valid|nbf/i.test(msg)) throw new Error('Token not yet valid - check clock skew');
  throw new Error(`JWT verify failed: ${msg}`);
}

Prevention

When it happens

Trigger: Expired token (TokenExpiredError), not-yet-valid token (NotBeforeError), invalid signature that throws, malformed key/PEM, or any verify failure the library raises by throwing. Also the double-wrap case where error 450's thrown OperationError lands here.

Common situations: Testing with an expired token. Clock skew causing NotBefore/TokenExpired errors. Wrong key type. The double-wrap making the surfaced message nested/less readable.

Related errors


AI-assisted analysis of gchq/CyberChef@4290ea7539 (2026-08-13). Data as JSON: /api/errors/f05d374de1beda9e. Report an issue: GitHub.