Skip to content

tap-new: with_env drops PATH breaking non‑system Perl Git hooks; initial git init  runs outside secure env #20216

@nugged

Description

@nugged

(note: this is not priority request, more "notes")

brew tap-new runs most Git commands inside a minimal with_env sandbox that keeps only HOME (and sometimes TMPDIR). This strips the user’s PATH, so global Git hooks that rely on non-system interpreters (e.g., perlbrew or Homebrew-installed Perl) fail. Worse, the initial git init is executed outside the sandbox, so the repository is half-created before the failure occurs -- and any env-based protections the sandbox provides are bypassed, potentially opening a security gap.

env

Homebrew 4.5.8-157-g114d660-dirty
git version 2.50.0 from homebrew
custom githook which uses Git::Hook, installed not in system paths, but for Perl 5.42.0 from perlbrew
macOS 15.5

Longer story (detailed)

Symptoms

  • Global or template Git hooks do spawn (#!/usr/bin/env perl resolves to the system Perl) but then fail at runtime because required modules (e.g. Git::Hooks) are missing from @INC. As soon as the hook dies, brew tap-new aborts midway.
  • The process stops after git init, leaving an incomplete tap directory on disk. This might lead to future block for both directions of changes.

Expected vs Actual

Expected: All Git commands triggered by brew tap-new should inherit the same environment--either the full user PATH or a consistently sanitised version--so that global Git hooks run without incident.

Actual: The first git init inherits the full PATH, while subsequent commands (git add, git commit, git branch) run inside the pared-down with_env sandbox. Hooks load the system Perl successfully but then die with errors such as:

Can't locate Git/Hooks.pm in \@INC ...

The missing module lives in a path injected by Perlbrew, which is no longer present in PATH/PERL5LIB inside the sandbox.

Minimal reproduction

# 1. Install a non-system Perl and expose it via PATH
perlbrew install perl-5.42.0
perlbrew switch  perl-5.42.0
# Install cpanminus into this Perl and install Git::Hooks there
perlbrew install-cpanm
cpanm Git::Hooks # this installed in $HOME/... and never visible for system Perl

# 2. Create a temporary pre‑commit hook that uses that Perl
mkdir -p ~/.git-templates/hooks
cat > ~/.git-templates/hooks/pre-commit <<'EOF'
#!/usr/bin/env perl
use strict;
use warnings;
use Git::Hooks;     # deliberately missing from system Perl
print "This line never runs in macOS system Perl";
EOF
chmod +x ~/.git-templates/hooks/pre-commit

# 3. Point Git *for this test only* at that template dir and run tap‑new
#    (store any previous value and restore it afterwards so we don't pollute the tester's setup)
prev_template=$(git config --global --get init.templateDir || true)
git config --global init.templateDir ~/.git-templates
brew tap-new foo/bar   # fails during git commit

# 4. Clean‑up: restore original git templateDir (or unset if it was blank)
if [ -n "$prev_template" ]; then
  git config --global init.templateDir "$prev_template"
else
  git config --global --unset init.templateDir || true
fi

Root cause analysis

safe_system "git", "-c", "init.defaultBranch=#{branch}", "init"  # ← full ENV

Utils::UID.drop_euid do
  env = { HOME: Utils::UID.uid_home }.compact               # ← PATH is stripped
  with_env(env) do
    safe_system "git", *args, "add", "--all"
    safe_system "git", *args, "commit", "-m", "Create #{tap} tap"
  end
end

git init inherits the full environment, but git add/commit/branch run under a reduced one, so any hook requiring PATH fails.

Impact & security note

  • Breaks workflows that depend on Homebrew-installed language toolchains (Perlbrew, pyenv, asdf, etc.).
  • If with_env exists for security hardening, running the first Git command outside it defeats that goal.

Workaround

Quick fix (hard-coded): inject the necessary Perlbrew (or other toolchain) paths into env[:PATH] just before calling with_env, e.g.:

user_path = [
  "#{ENV["HOME"]}/perl5/perlbrew/bin",
  "#{ENV["HOMEBREW_PREFIX"]}/opt/perl/bin"
].join(":")

env[:PATH] = "#{user_path}:#{ENV.fetch("PATH")}"  # prepend safely

This is intentionally narrow and brittle--sufficient for a one-off tap creation but not a portable solution.

Not solved yet: I have not found a clean, cross-platform way to let Perlbrew (or pyenv/asdf) regenerate its PATH inside the sandbox without re-executing their shell init scripts. Ideas welcome. I mean:

#{ENV["HOME"]}/perl5/perlbrew/perls/perl-5.42.0/bin  # example perlbrew version bin

Proposed directions

  1. Wrap all Git commands, including git init, in the same environment.
  2. Instead of blanking PATH, merge a filtered version (e.g., keep segments owned by the invoking UID).
  3. Document the limitation if the current behaviour is intentional.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions