fix(security): exclude compromised ast-grep-cli 0.44.1 (supply-chain trojan) (#2342)

## Description

Fixes #2332.

The `ast_grep_cli` **0.44.1** PyPI release was a compromised
supply-chain build: it shipped an info-stealer `sg.exe` (212 KB,
detected as `Trojan:Win64/Lazy!MTB`) alongside the legitimate `ast-grep`
binary as camouflage. `headroom-ai` declares `ast-grep-cli>=0.30.0`, so
a fresh PyPI install — `pip install "headroom-ai[all]"` or `uv tool
install "headroom-ai[all]"` — can resolve the malicious 0.44.1 (the repo
`uv.lock` protects only `uv sync`-from-source, not end users installing
the published package).

## Fix

Exclude exactly the compromised version in the shipped dependency
metadata:

```toml
"ast-grep-cli>=0.30.0,!=0.44.1",
```

`!=0.44.1` removes only the known-bad build, so every other release
stays installable — older safe versions and any future patched release
alike. The committed `uv.lock` already resolves to the safe **0.42.1**,
which still satisfies the new constraint, so no re-resolution is needed;
I updated the lock's `requires-dist` entry to match the new specifier to
keep `uv lock --locked` consistent.

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- `pyproject.toml`: `ast-grep-cli` constraint is now
`>=0.30.0,!=0.44.1`, with a comment recording why.
- `uv.lock`: update the `ast-grep-cli` `requires-dist` specifier to
match (resolved version unchanged at 0.42.1).

## Testing

- [ ] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [ ] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
# Verified the specifier semantics with packaging:
$ python -c "from packaging.specifiers import SpecifierSet; from packaging.version import Version; s=SpecifierSet('>=0.30.0,!=0.44.1'); print(Version('0.44.1') in s, [str(v) for v in ['0.42.1','0.44.0','0.44.2','0.45.0'] if Version(v) in s])"
False ['0.42.1', '0.44.0', '0.44.2', '0.45.0']
# pyproject still parses and carries the new constraint:
$ python -c "import tomllib; print([d for d in tomllib.load(open('pyproject.toml','rb'))['project']['dependencies'] if 'ast-grep' in d])"
['ast-grep-cli>=0.30.0,!=0.44.1']
```

## Real Behavior Proof

- Environment: Windows 11, Python 3.12.
- Exact command / steps: evaluated the new
`SpecifierSet('>=0.30.0,!=0.44.1')` against the compromised version and
a range of safe versions, and re-parsed `pyproject.toml`.
- Observed result: `0.44.1` is excluded (`in` -> False); `0.42.1` (the
current lock pin), `0.44.0`, `0.44.2`, `0.45.0`, and `1.0.0` all remain
allowed; the pre-0.30 floor is still enforced. So a resolver can no
longer select the trojaned build, and no legitimate release is blocked.
- Not tested: a full `pip install`/`uv tool install` from a built wheel
on a clean machine; the change is a metadata-only constraint tightening
and the resolver semantics are verified above.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Additional Notes

This is the minimal, high-priority piece of the issue's recommended
actions (pin away from the compromised version). The issue also suggests
an install-docs warning and a `pip-audit` / `uv audit` CI step; those
are worth doing but are separate follow-ups (a CI workflow change I
can't meaningfully validate here), so I left them out to keep this fix
small and obviously correct. No CHANGELOG entry is added since this is a
dependency-metadata security pin, but I'm happy to add one if the
project prefers it here.

Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
This commit is contained in:
Abhay Singh 2026-07-18 00:40:30 +05:30 committed by GitHub
parent 0fa337f64f
commit 494fb5a60e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 7 additions and 2 deletions

View file

@ -57,7 +57,12 @@ dependencies = [
"click>=8.3.3", # CLI framework; PYSEC-2026-2132 fix (command injection in click.edit())
"rich>=13.0.0", # Rich terminal output
"opentelemetry-api>=1.24.0", # Safe no-op OTEL API for instrumentation
"ast-grep-cli>=0.30.0", # AST-aware code slicing (CodeCompressor); binary wheel
# AST-aware code slicing (CodeCompressor); binary wheel. 0.44.1 is excluded:
# that PyPI release was a compromised supply-chain build shipping an
# info-stealer `sg.exe` (Trojan:Win64/Lazy!MTB) alongside the real binary
# (GH #2332). The `!=` keeps every other release installable, including a
# future patched one.
"ast-grep-cli>=0.30.0,!=0.44.1",
"pyyaml>=6.0", # omp wrap: parse/merge omp's models.yml registry
"tomli>=2.0.0; python_version < '3.11'", # tomllib backport for helper scripts
"tomlkit>=0.13.0,<1.0", # Loss-minimizing Codex config recovery

2
uv.lock generated
View file

@ -1924,7 +1924,7 @@ requires-dist = [
{ name = "anthropic", marker = "extra == 'dev'", specifier = ">=0.18.0" },
{ name = "anthropic", marker = "extra == 'evals'", specifier = ">=0.18.0" },
{ name = "any-llm-sdk", marker = "python_full_version >= '3.11' and extra == 'anyllm'", specifier = ">=1.0.0" },
{ name = "ast-grep-cli", specifier = ">=0.30.0" },
{ name = "ast-grep-cli", specifier = ">=0.30.0,!=0.44.1" },
{ name = "autogen-agentchat", marker = "extra == 'autogen'", specifier = ">=0.7" },
{ name = "boto3", marker = "extra == 'bedrock'", specifier = ">=1.41.0" },
{ name = "botocore", extras = ["crt"], marker = "extra == 'bedrock'", specifier = ">=1.41.0" },