Skip to main content
Version: v0.0.8

Interface: UndefinedVrFinding

One element that was emptied because its on-wire VR is not one of the 34 PS3.5 section 6.2 defines, so nothing this library did to its bytes counts as decoding a Value Field.

Why such an element exists at all

Under an Explicit VR Transfer Syntax the VR is two bytes the sender wrote, and this parser trusts them (Postel's Law on the read path). The routine way two arbitrary bytes end up in a VR field is an under-declared Value Length upstream: the reader finishes the short value, and the leftover bytes of the value that was actually encoded are read as the next Data Element header. Tag, VR and length are then all fragments of somebody's value, and the element that genuinely followed is consumed as this fabricated element's "value".

Measured on scripts/measure-sq-bound-grid.ts: a carrier under-declaring by 6 produces (4156,554C) with the VR bytes "E ", whose value holds the source (0010,0020) Patient ID in full. It reaches string carriers exactly as it reaches binary ones, because the carrier's own VR is not what decides it.

Why emptying, and why it is not a guess

PS3.5 2026c section 6.2 requires every VR not yet defined to use the long-form Data Element Structure - "with reserved bytes after the VR and a 32-bit unsigned integer VL" - so an unrecognized VR read short-form, which is what this parser does, is by the standard's own structure rule not a reading of a Value Field. There is nothing to prove about the content: the test is a membership check against the closed 34-VR set on a field the parser already recorded, so there is no scan, no per-offset loop, and no cost that follows an attacker-chosen value length.

PS3.15 2026c section E.1.1 obliges an implementation claiming the Basic Application Level Confidentiality Profile to "protect or retain all instances of the Attributes listed in [Table E.1-1]". Those instances cannot be reached inside bytes that were never a value, so the obligation falls on the carrier.

UN is not this. UN is one of the 34, so an ordinary unknown-VR element

  • the Implicit VR fallback for a tag this build's dictionary does not publish, and the CP-246 shape - never reaches here. That is the line the sibling SQ-with-no-items rule could not draw.

Why this finding names no tag, when every sibling finding does

Because the tag may be content, and nothing here can tell. The paragraph above is the whole argument: when an under-declare desynchronized the reader, the four tag bytes and the two VR bytes were read out of the middle of some element's Value Field, so reporting the "tag" would republish four bytes of the document. An unrecognized VR written honestly, at a correct length, raises this same code and has an ordinary tag - and the two are indistinguishable here, so the tag is withheld on both routes rather than on a guess. Measured on a synthetic ST carrier holding "MR BRAIN SMITHSON", the fabricated tag is 48544F53 - four bytes of the surname. EmbeddedAttributeFinding and UnauditableSequenceFinding may carry a tag because theirs came from a header the sender really wrote; this one may not, and the asymmetry is the finding rather than an inconsistency.

byteOffset locates the element instead - a position this parser counted. Nothing here renders a document byte: an offset the parser counted, a decoded length, and the structural contextPath. Safe to log.

Example

import { deidentify, parseDicom } from "@cosyte/dicom";
const { report } = deidentify(parseDicom(buf));
for (const u of report.undefinedVrElements) {
console.warn(`offset ${String(u.byteOffset)}: ${String(u.byteLength)} bytes dropped`);
}

Properties

byteLength

readonly byteLength: number

Byte length of the value field that was dropped.

An input-derived count, and on this finding specifically that is not a formality. For the sibling findings the length came from a header the sender wrote; here the two length bytes can themselves be value bytes, like the tag bytes. What is published is the number they decode to, never the bytes - the same footing as every {n} in the warning registry, which is documented there as "an input-derived count". A number is not a rendering, and the reach is at most a character or so, but do not describe this field as "structural, never a value" the way its siblings are described.


byteOffset

readonly byteOffset: number

Byte offset of the emptied element's header. This is how the element is identified, and there is deliberately no tag field - see the note above: a fabricated header's tag bytes are part of some element's value.


contextPath?

readonly optional contextPath?: readonly string[]

Tag/index chain when the carrier is inside a sequence item; omitted at the root.