Watch at EaseStories. Answers. Perspective.

Technology · PRACTICAL GUIDE

zipfile.Path.is_symlink() always False? Check the runtime and stored member type

Twenty local controls separate a Python 3.12 predicate stub, Unix-marked ZIP metadata, CRC integrity and what the standard extractor actually creates.

Original explanatory illustration comparing an existing ZIP alias, is_file=True, is_symlink=False and stored Unix link-type bits.
Original explanatory illustration: actual CPython 3.12.14 results; a False predicate coexists with stored Unix link metadata. Watch at Ease

On CPython 3.12.14, a synthetic ZIP member marked as a Unix symbolic link existed and returned True from zipfile.Path.is_file(), yet is_symlink() returned False. Inspecting its stored mode identified the link. The False result did not prove that the archive contained only regular files.

Check the runtime and the archive’s metadata convention before changing the archive. Our 20 asserted controls used seven members in a 751-byte synthetic ZIP on Linux. They also showed that a successful CRC test and a successful extraction answer different questions from stored member-type inspection.

1. Test the implementation, not just method availability

Print sys.version and confirm that the object is zipfile.Path, rather than pathlib.Path referring to an already extracted file. On our actual 3.12.14 interpreter, inspection of zipfile.Path.is_symlink showed an implementation that simply returns False. The method was callable, so an attribute-existence test would not have caught the limitation.

Python’s versioned reference records the behavior change in 3.13: earlier is_symlink returned False unconditionally. We read that documentation but did not run 3.13. Probe your own deployed version with a known benign link-marked fixture; do not infer behavior from the latest documentation or a method name alone.

2. Inspect each member under an explicit metadata convention

Iterate the open archive’s infolist() and retain each ZipInfo. Record filename, create_system and the mode expression info.external_attr >>16. For the Unix-host convention tested here, require create_system==3 before interpreting that mode with stat.S_ISLNK, stat.S_ISREG or stat.S_ISDIR. These classify the stored type bits, not the current filesystem.

PKWARE defines external attributes as host-dependent. Our deliberately narrow classifier labels non-Unix hosts and modes without a recognized type as unknown. Unknown is not regular and not a clean bill of health. Other producers, including other host conventions, need their own verified handling rather than a forced negative answer.

3. Keep names, target text and type bits separate

In the fixture, alias and dangling were both link-marked even though dangling named an absent target. Reading those members returned target.txt and absent.txt as their stored payload text. An ordinary member named ordinary.link remained a regular file by its explicit Unix type bits. A filename suffix therefore did not establish link type.

The folder/ member had both a directory-style name and directory mode. ZipInfo.is_dir uses the name’s trailing slash; it is not the same test as examining every stored Unix type bit. Our permissions-only member had no recognized type and remained unknown. Permission bits alone did not justify labeling it a regular file.

4. Measure the actual extraction outcome separately

testzip() returned None for the entire fixture, meaning its tested members passed that CRC/header check. The link metadata was still present. Integrity checking had not answered whether a member was a symbolic link or whether another extractor would restore one.

We extracted only the benign alias member with Python’s standard ZipFile.extract into a fresh temporary directory. On this 3.12.14/Linux run, the output was a regular file containing the text target.txt, not an operating-system symlink. No external extractor was run. Do not generalize this output to other programs or rewrite metadata just to make a predicate return the answer you expected.

5. Set a completion test for the handoff you intend

Our 20 controls checked method availability, exists/is_file/is_symlink, the seven-member type matrix, dangling and suffix controls, unknown metadata, CRC success, stored target text, an absent archive entry, and the extracted file’s type and exact contents. The saved report includes the runtime, predicate source, script hash and archive hash.

For your own workflow, keep an unchanged archive and a small known fixture. Verify the member inventory and classify only the conventions your policy actually supports; investigate or reject unknown types according to that policy. Then check the intended extractor’s resulting object types and contents. This is a member-type diagnosis, not a complete untrusted-archive extraction validator or a claim of cross-platform compatibility.

Original explanatory illustration of the seven-member ZIP type matrix and distinct CRC and extraction outcomes.
Original explanatory illustration: type, integrity and extraction are different results; unknown metadata is not a regular-file classification. Watch at Ease