You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Spurious nested application/zip match on the end of central directory magic field #3470
Parsing any ZIP archive produces a spurious nested application/zip match on the four-byte magic
field of the end of central directory record, along with a parser warning. The nested match is a
false positive: the four bytes PK\x05\x06 are a field of the record that was already matched, not
an embedded archive.
Parser parse_zip for MIME type application/zip raised an exception while parsing
Zip archive data (empty) in /tmp/tmp0o2w_3pd: Reached the end of stream while
expecting 2-byte unsigned integer for EndOfCentralDirectory.disk_number
No nested application/zip match on the magic field, and no parser warning.
Cause
PolyFileStruct.match in polyfile/structmatcher.py:42-44 writes every bytes field to a
temporary file and runs the full matcher over it, to find embedded files. The relaxed ZIP test in polyfile/zipmatcher.py:15-20 searches for PK\x05\x06 anywhere in a file, so a four-byte file
containing exactly that sequence matches as an empty ZIP. parse_zip then runs on those four bytes
and fails, because there is nothing after the signature to read.
The same pattern applies to any struct whose magic field happens to satisfy a matcher, so raising
the minimum size for the relaxed ZIP test, or skipping recursion into Constant fields, would both
address it.
Version
PolyFile 0.5.6, at ffdf476. This is unrelated to #3364; it reproduces on an ordinary archive that
starts at offset 0.
Constant is a bytes subclass.class Constant(ByteField) is at polyfile/structs.py:210-224
and ByteField(bytes, Field, ...) at :159. So EndOfCentralDirectory.magic
(polyfile/zipmatcher.py:124, the four bytes PK\x05\x06) hits the :42 branch unconditionally,
gets written to a temp file, and is re-matched. The relaxed ZIP test at zipmatcher.py:15-20 is a
bare search for that signature anywhere in a file, so a four-byte file containing exactly it
matches as an empty ZIP. parse_zip then fails on it, which is the warning.
Either fix named in the issue works: an isinstance(value, Constant) guard at :42, or a minimum
size for the relaxed test. The Constant guard is the more general of the two — the issue is right
that any struct whose magic field satisfies a matcher shows the same symptom, not just ZIP.
The three entry points that trigger it are parse_zip at polyfile/zipmatcher.py:194-224: fh.match(...) at :210, cd.match(...) at :223, eocd.match(...) at :224.
Sequencing
This blocks #3530.structmatcher.py:44 calls Matcher.match, which #3466 rewrites, so this
guard has to land first or the two changes disagree about how many submatches that line yields.
Assert no nested application/zip on the magic field and no parser warning on stderr. tests/test_zipmatcher.py is the home for it. Check whether any other bundled struct has a Constant field that a matcher accepts, and cover the general case rather than only ZIP.
Activity
ESultanik commentedon Sep 11, 2026
Anchor confirmation, and the precise mechanism
Verified against
masterat906d916. The cause in the issue body is right; here is the exactchain.
PolyFileStruct.matchatpolyfile/structmatcher.py:18-46recurses at:39-46:Constantis abytessubclass.class Constant(ByteField)is atpolyfile/structs.py:210-224and
ByteField(bytes, Field, ...)at:159. SoEndOfCentralDirectory.magic(
polyfile/zipmatcher.py:124, the four bytesPK\x05\x06) hits the:42branch unconditionally,gets written to a temp file, and is re-matched. The relaxed ZIP test at
zipmatcher.py:15-20is abare
searchfor that signature anywhere in a file, so a four-byte file containing exactly itmatches as an empty ZIP.
parse_zipthen fails on it, which is the warning.Either fix named in the issue works: an
isinstance(value, Constant)guard at:42, or a minimumsize for the relaxed test. The
Constantguard is the more general of the two — the issue is rightthat any struct whose magic field satisfies a matcher shows the same symptom, not just ZIP.
The three entry points that trigger it are
parse_zipatpolyfile/zipmatcher.py:194-224:fh.match(...)at:210,cd.match(...)at:223,eocd.match(...)at:224.Sequencing
This blocks #3530.
structmatcher.py:44callsMatcher.match, which #3466 rewrites, so thisguard has to land first or the two changes disagree about how many submatches that line yields.
Verification
The issue's reproducer is the acceptance test:
python -c "import zipfile; zipfile.ZipFile('a.zip','w').writestr('a.txt', b'hello\n')" polyfile --debug --format sbud a.zipAssert no nested
application/zipon the magic field and no parser warning on stderr.tests/test_zipmatcher.pyis the home for it. Check whether any other bundled struct has aConstantfield that a matcher accepts, and cover the general case rather than only ZIP.