Repository navigation
👷 ci: lint activate.sh prompt with semgrep - #3483
Merged
Merged
Conversation
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
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
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
enabled auto-merge (squash)
October 28, 2026 02:30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bash and zsh prompt code in
activate.shis 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 readVIRTUAL_ENV_PROMPTunescaped, or setsPS1anywhere 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_PROMPTassignment that mentionsVIRTUAL_ENV_PROMPT, including the single-quoted'${VIRTUAL_ENV_PROMPT-}'form the template used before. The second requires everyPS1orPROMPTassignment, other than the restore indeactivate, to follow anif/elsewhose bash branch loops over the prompt and whose zsh branch hands it over throughpsvar[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 fromexport,declareand friends and drops the keyword (Bash_to_generic.ml), so a bareexport PS1leaves nothing to match. fish, csh and nushell have no Semgrep parser.The hooks come from
semgrep/pre-commit, sopre-commit rungives contributors the check locally. pre-commit.ci refuses the 346 MiB environment, so it skips them and acheck.yamljob 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'sdefault_language_version.Refs #3464