Skip to content

👷 ci: lint activate.sh prompt with semgrep - #3483

Merged
gaborbernat merged 6 commits into
pypa:mainfrom
gaborbernat:ci/semgrep-activation-rules
Oct 28, 2026
Merged

gaborbernat merged 6 commits into
pypa:mainfrom
gaborbernat:ci/semgrep-activation-rules

Conversation

@gaborbernat

@gaborbernat gaborbernat commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

The bash and zsh prompt code in activate.sh is where most activation injection advisories came from, and CodeQL has no shell extractor, so no static check read it (item 5 of #3464). 🛡️ With this change pre-commit and CI fail when an edit lets the prompt read VIRTUAL_ENV_PROMPT unescaped, or sets PS1 anywhere but after the block that escapes it. The activation tests stay the behavioural guard; these rules guard the shape of the code those tests exercise.

Two Semgrep rules parse the template with Semgrep's Bash front end. The first flags a PS1, PROMPT (zsh's alias) or _VIRTUAL_PROMPT assignment that mentions VIRTUAL_ENV_PROMPT, including the single-quoted '${VIRTUAL_ENV_PROMPT-}' form the template used before. The second requires every PS1 or PROMPT assignment, other than the restore in deactivate, to follow an if/else whose bash branch loops over the prompt and whose zsh branch hands it over through psvar[9]. What the loop does to each character stays with the prompt tests, so the escaping can change without touching the rules.

No rule checks for export PS1: the Bash front end keeps only the assignments from export, declare and friends and drops the keyword (Bash_to_generic.ml), so a bare export PS1 leaves nothing to match. fish, csh and nushell have no Semgrep parser.

The hooks come from semgrep/pre-commit, so pre-commit run gives contributors the check locally. pre-commit.ci refuses the 346 MiB environment, so it skips them and a check.yaml job runs the same hooks, gated by 🚦 All checks pass. Semgrep 1.182.1 crashes on Python 3.15; the hooks run on 3.14, the repo's default_language_version.

Refs #3464

CodeQL has no shell extractor, so nothing guards the structure of the
bash and zsh prompt code in activate.sh. Two Semgrep rules now run as a
pre-commit hook against that template. The first flags a PS1, PROMPT or
_VIRTUAL_PROMPT assignment that reads VIRTUAL_ENV_PROMPT. The second
flags a PS1 or PROMPT assignment that does not follow the if/else that
escapes the value for bash and stores it in psvar[9] for zsh. A second
hook runs semgrep --test on an annotated fixture with a passing case and
one mutation per regression.

No rule covers export PS1: the Bash front end drops export, declare,
typeset, local, readonly and unset statements from the AST it matches
on, so no structural pattern can see them.

Refs pypa#3464
@gaborbernat gaborbernat added security Fixes a vulnerability or hardens the supply chain, CI or release skip news No user-facing changelog entry required labels Oct 28, 2026
pre-commit.ci refused to build the semgrep hook environment: "exceeds
tier max size 250MiB: 346MiB". Skip the hooks there and run them from a
check.yaml job instead, through pre-commit so the semgrep version stays
pinned in one place. The all-checks-pass gate waits on the new job.

Refs pypa#3464
@gaborbernat gaborbernat added the github_actions Pull requests that update GitHub Actions code label Oct 28, 2026
The ordering rule spelled out every case arm of the escaping loop, so any
edit to activate.sh had to touch the rule as well, and the prompt tests
already catch a broken arm. It now asks only that PS1 follows an if/else
whose bash branch loops over the prompt and whose zsh branch sets psvar.
The fixture shrinks to one passing shape and one case per rule, the hook
is pinned by commit like the others, and the job notes why it reads.

Refs pypa#3464
…on-rules

# Conflicts:
#	.github/workflows/check.yaml
@gaborbernat
gaborbernat enabled auto-merge (squash) October 28, 2026 02:30
@gaborbernat
gaborbernat merged commit fc01ea4 into pypa:main Oct 28, 2026
78 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

github_actions Pull requests that update GitHub Actions code security Fixes a vulnerability or hardens the supply chain, CI or release skip news No user-facing changelog entry required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant