Skip to content

fix: trust archive permissions instead of file(1) heuristics in ziextract#771

Merged
pschmitt merged 1 commit into
zdharma-continuum:mainfrom
alberti42:fix-executable-permissions
Mar 14, 2026
Merged

fix: trust archive permissions instead of file(1) heuristics in ziextract#771
pschmitt merged 1 commit into
zdharma-continuum:mainfrom
alberti42:fix-executable-permissions

Conversation

@alberti42

Copy link
Copy Markdown
Member

Problem

This PR fixes a bug in ziextract, the function responsible for unpacking archives
when installing plugins via from'gh-r' or any tarball download.

The original developer added a well-intentioned feature: after extracting an archive,
zinit would automatically detect which files are executables and set the +x permission
on them, so the user would not have to do it manually. To detect executables, the code
used the standard Unix utility file(1), which can inspect file contents and report
what kind of file each one is.

The problem is that file(1) does not actually tell you whether a file should be
executable. It tells you what kind of content a file contains. In practice, file(1)
labels any file containing common Python syntax — including documentation files, Vim
scripts, and test data — as "Python script text executable". Zinit's code then sees
the word "executable" in that output and dutifully applies chmod +x to all of them,
including files that have no business being executable at all.

Reproduction — install Neovim from its GitHub nightly release:

zinit lucid as'null' from'gh-r' for @neovim/neovim

The archive contains exactly 9 files with the execute bit set (bin/nvim,
lib/nvim/parser/*.so, share/nvim/runtime/scripts/less.sh). After installation,
fd -t=x reveals that many additional files have been marked executable:

bin/nvim
lib/nvim/parser/c.so
lib/nvim/parser/lua.so
lib/nvim/parser/markdown.so
lib/nvim/parser/markdown_inline.so
lib/nvim/parser/query.so
lib/nvim/parser/vim.so
lib/nvim/parser/vimdoc.so
share/nvim/runtime/autoload/python3complete.vim
share/nvim/runtime/autoload/pythoncomplete.vim
share/nvim/runtime/doc/if_pyth.txt
share/nvim/runtime/doc/remote_plugin.txt
share/nvim/runtime/indent/testdir/bash.in
share/nvim/runtime/indent/testdir/bash.ok
share/nvim/runtime/indent/testdir/bitbake.in
share/nvim/runtime/indent/testdir/bitbake.ok
share/nvim/runtime/scripts/less.sh

Root Cause

The file(1) magic database emits "Python script text executable" for any file
whose content matches common Python syntax patterns
import, class, def,
try/except — regardless of shebangs or filesystem permissions. A Vim documentation
file (if_pyth.txt) that merely documents the Python interface is enough to trigger it:

$ file share/nvim/runtime/doc/if_pyth.txt
share/nvim/runtime/doc/if_pyth.txt: Python script text executable, ASCII text

The word "executable" in file(1) output means "this is a file written in a
language that can be run"
— not "this file should have the execute permission bit".
Zinit's filter was asking the wrong question. To make matters worse, file(1) applies
this label even to Python files with no shebang line — files that the kernel would
refuse to execute directly anyway, since there is no interpreter specified.

Fix

Replace the file(1)-based detection with a single glob that collects only files
whose execute bit is already set in the archive:

execs=( **/*~(._zinit(|/*)|.git(|/*)|.svn(|/*)|.hg(|/*)|._backup(|/*))(DN-*.) )
# Glob qualifier legend:
#   (DN-.)  — D=include dotfiles, N=null glob, -=no symlinks, .=regular files
#   (DN-*.) — same plus *=has execute bit set

This is the authoritative signal: if the package author wanted a file executable, they
set +x when creating the archive. Any file that lacks +x in the archive — library
module, documentation, data file — should stay non-executable regardless of its
content. If a plugin or app is packaged with wrong permissions — rare but possible —
the best course of action is for the user to correct them via atclone/atpull,
rather than having zinit apply heuristics that will assign wrong permissions far more
often than they fix anything.

Discussion: shebang-based detection

An alternative considered during this fix is to additionally mark executable any file
that has a shebang (#!) but was not +x in the archive — to compensate for packages
that forget to set permissions. Eventually, this was deliberately left out for two reasons:

  1. A shebang does not reliably indicate that a file should be +x. The
    #!/hint/zsh pattern, common in the zinit ecosystem, is used purely as an editor
    syntax hint and is explicitly not meant to make a file executable.
  2. If a plugin or app is packaged with wrong permissions — rare but possible — the
    best course of action is for the user to correct them via atclone/atpull,
    rather than having zinit apply heuristics that will assign wrong permissions far
    more often than they fix anything.

Feedback on this decision is welcome.

@alberti42
alberti42 force-pushed the fix-executable-permissions branch from 5ca3039 to 12f2487 Compare March 13, 2026 08:54
Comment thread tests/ices.zunit
@pschmitt
pschmitt merged commit 1334994 into zdharma-continuum:main Mar 14, 2026
@alberti42
alberti42 deleted the fix-executable-permissions branch March 15, 2026 14:34
github-actions Bot pushed a commit that referenced this pull request Jul 1, 2026
# [3.15.0](v3.14.0...v3.15.0) (2026-07-01)

### Bug Fixes

* .zi-check-for-git-changes runs git fetch before checking for changes ([1107460](1107460))
* add markdownlint exceptions to linting ([507b4e5](507b4e5))
* avoid apostrophe that breaks typeset -f round-trip of :zinit-tmp-subst-autoload ([#783](#783)) ([d2d4237](d2d4237))
* correct SSH URL format for private repos ([#721](#721)) ([991e4a8](991e4a8))
* corrected print-out message in self-update routine ([bb37f3b](bb37f3b))
* impl gh-r version detection using GitHub API ([#731](#731)) ([f1e503d](f1e503d)), closes [#r](https://github.com/zdharma-continuum/zinit/issues/r)
* prevent "permission denied" errors from empty hook handlers ([#729](#729)) ([2167408](2167408))
* prevent double extraction when exclamation mark modifier is used in extract'!' ([4fd38b3](4fd38b3)), closes [#r](https://github.com/zdharma-continuum/zinit/issues/r) [#r](https://github.com/zdharma-continuum/zinit/issues/r)
* remove `local -A OPTS` that shadows caller's parallel flag ([#774](#774)) ([e88ed77](e88ed77))
* resolve a bug where the `-q` option for quiet mode was never dispatched ([6cf2f29](6cf2f29))
* resolve a second issue where `git -C <path>` had to be used instead of `git --work-tree <path>` ([2e83176](2e83176))
* resolves a bug with github action trying to push updated docs back to external forks ([#763](#763)) ([34a1811](34a1811))
* silenced `zinit self-update` when in quiet mode (`-q`) ([8d154b0](8d154b0))
* trust archive permissions instead of `file(1)` heuristics in `ziextract` ([#771](#771)) ([1334994](1334994))
* update `git-chglog` to `git-cliff` in gh-r tests ([18fc2cc](18fc2cc)), closes [#r](https://github.com/zdharma-continuum/zinit/issues/r)
* Update various tools in gh-r tests ([#759](#759)) ([01eb65f](01eb65f)), closes [#r](https://github.com/zdharma-continuum/zinit/issues/r)

### Features

* validate ice arguments ([#778](#778)) ([424b241](424b241))
* **zsh:** allow configuring settings like HOME_DIR/BIN_DIR via zstyle ([8f89f88](8f89f88))
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 3.15.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants