gchq/CyberChef · error · Error

Unable to parse JPEG successfully

Error message

Unable to parse JPEG successfully

What it means

Thrown by extractJPEG after the marker-walking loop exits because the stream was exhausted without ever encountering the End-of-Image marker (0xFFD9, case 0xd9 which returns stream.carve()). It indicates a truncated or malformed JPEG that has no EOI. Like the other extractor throws this is a plain Error, not an OperationError.

Source

Thrown at src/core/lib/FileSignatures.mjs:2722

            case 0x00: // Byte stuffing
            case 0xd0: // Restart
            case 0xd1: // Restart
            case 0xd2: // Restart
            case 0xd3: // Restart
            case 0xd4: // Restart
            case 0xd5: // Restart
            case 0xd6: // Restart
            case 0xd7: // Restart
                stream.continueUntil(0xff);
                break;

            default:
                stream.continueUntil(0xff);
                break;
        }
    }

    throw new Error("Unable to parse JPEG successfully");
}


/**
 * GIF extractor.
 *
 * @param {Uint8Array} bytes
 * @param {Number} offset
 * @returns {Uint8Array}
 */
export function extractGIF(bytes, offset) {
    const stream = new Stream(bytes.slice(offset));

    // Move to application extension block.
    stream.continueUntil([0x21, 0xff]);

    // Move to Graphic Control Extension for frame #1.
    stream.continueUntil([0x21, 0xf9]);

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Ensure the byte stream contains both SOI (0xFFD8) and a trailing EOI (0xFFD9).
  2. Re-download or re-export the source image to obtain a complete JPEG.
  3. Check the caller-supplied offset is not past the real EOI.
Defensive patterns

Strategy: validation

Validate before calling

function hasJPEGEOI(bytes, offset = 0) {
  for (let i = bytes.length - 2; i >= offset; i--) {
    if (bytes[i] === 0xff && bytes[i + 1] === 0xd9) return true;
  }
  return false;
}
if (!looksLikeJPEG(bytes, offset) || !hasJPEGEOI(bytes, offset)) {
  throw new Error("JPEG is truncated (missing EOI marker)");
}
extractJPEG(bytes, offset);

Type guard

const hasCompleteJPEGFraming = (bytes, offset = 0) =>
  bytes[offset] === 0xff && bytes[offset + 1] === 0xd8 &&
  bytes[bytes.length - 2] === 0xff && bytes[bytes.length - 1] === 0xd9;

Try / catch

try {
  extractJPEG(bytes, offset);
} catch (err) {
  if (/Unable to parse JPEG successfully/.test(err.message)) {
    // truncated/missing EOI; re-acquire the file
  } else throw err;
}

Prevention

When it happens

Trigger: Feeding extractJPEG a JPEG whose EOI marker is missing (cut-off download, appended garbage, or a file terminated by a different marker); an offset that starts past the EOI; crafted data that loops until bytes run out.

Common situations: Incomplete file transfer; image intentionally missing EOI (some cameras/embedded tools); progressive JPEG whose scan data was truncated; data with embedded thumbnails where the outer stream ends early.

Understand the failure class

Related errors


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