gchq/CyberChef · error · OperationError
Malformed LZNT1 stream: Invalid shift!
Error message
Malformed LZNT1 stream: Invalid shift!
What it means
Inside an LZNT1 compressed block, flag bits introduce 2-byte back-reference pointers. The decoder computes a source offset into the already-decompressed buffer and copies bytes from there. If the computed shift is below 0 or beyond the current decompressed length, the back-reference points outside valid data and the stream is malformed.
Source
Thrown at src/core/lib/LZNT1.mjs:73
while (coffset < blockEnd) {
let header = compressed[coffset++];
for (let i = 0; i < 8 && coffset < blockEnd; i++) {
if ((header & 1) === 0) {
decompressed.push(compressed[coffset++]);
} else {
const pointer = Utils.byteArrayToInt(compressed.slice(coffset, coffset + 2), "little");
coffset += 2;
const displacement = getDisplacement(decompressed.length - doffset - 1);
const symbolOffset = (pointer >> (12 - displacement)) + 1;
const symbolLength = (pointer & (0xFFF >> displacement)) + 2;
const shiftOffset = decompressed.length - symbolOffset;
for (let shiftDelta = 0; shiftDelta < symbolLength + 1; shiftDelta++) {
const shift = shiftOffset + shiftDelta;
if (shift < 0 || decompressed.length <= shift) {
throw new OperationError("Malformed LZNT1 stream: Invalid shift!");
}
decompressed.push(decompressed[shift]);
}
}
header >>= 1;
}
}
} else {
decompressed.push(...compressed.slice(coffset, coffset + size + 1));
coffset += size + 1;
}
}
return decompressed;
}
View on GitHub (pinned to 4290ea7539)
Solutions
- Re-acquire the compressed bytes from the authoritative source to rule out corruption.
- Confirm the algorithm is actually LZNT1 (used by NTFS) and not a sibling LZ format.
- Compare the byte sequence against a known-good reference decoder output to localize the bad pointer.
- If you only need best-effort recovery, copy bytes up to the failing offset and accept partial output (wrap in try/catch and keep the partial `decompressed` array).
Example fix
// before
const out = LZNT1.decompress(bytes); // throws mid-stream, loses all output
// after - capture partial output for forensic/recovery use
let partial;
try {
partial = LZNT1.decompress(bytes);
} catch (e) {
if (/Invalid shift/.test(e.message)) {
// re-run a tolerant decoder that stops at the bad block
partial = tolerantLZNT1(bytes);
} else throw e;
} Defensive patterns
Strategy: try-catch
Try / catch
let partial;
try {
partial = LZNT1.decompress(bytes);
} catch (e) {
if (!(e instanceof OperationError) || !/Invalid shift/.test(e.message)) throw e;
partial = tolerantLZNT1Until(bytes, failOffset); // custom partial decoder
}
return partial; Prevention
- Treat an 'Invalid shift' as data corruption, not a code bug.
- Re-acquire the source bytes from the authoritative copy.
- Confirm the stream is genuinely LZNT1 (NTFS) and not a related LZ variant.
- Validate a checksum of the compressed attribute before decompressing.
When it happens
Trigger: Calling LZNT1.decompress on a stream whose compressed back-reference pointer resolves to an offset not yet written (negative shift or past end). Indicates the pointer/displacement bits do not correspond to a legitimate LZ77-style reference.
Common situations: Corrupted compressed attribute (bit-flip in storage); data tampering; feeding a different LZ variant (e.g. XPRESS/LZXPRESS) into the LZNT1 decoder; partial overwrite of the compressed buffer; wrong endianness when slicing the input.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Malformed LZNT1 stream: Block too small! Has the stream been
- Invalid block type while parsing DEFLATE stream at pos ${str
- Invalid ciphertext length: ${cipherText.length} bytes. Must
- Please provide an input.
- Please provide an input.
AI-assisted analysis of gchq/CyberChef@4290ea7539 (2026-08-13).
Data as JSON: /api/errors/530f7dba52153e43.
Report an issue: GitHub.