# pip-install attestor

Source: https://www.testifysec.com/docs/cilock/attestors/pip-install

The cilock pip-install attestor captures the post-build Python package environment via pip, statically analyzes installed code and PyPI PEP 740 provenance, and signs it into in-toto evidence.

Captures the Python package environment that exists **after** a build step by shelling out to `pip`, then statically analyses installed code, `setup.py`, `pyproject.toml`, and pickle files, and queries PyPI for PEP 740 provenance bundles.

 

| Name | `pip-install` |
| --- | --- |
| Predicate type | `https://aflock.ai/attestations/pip-install/v0.1` |
| Lifecycle | `postproduct` |
| Default binary? | No |
| Category | dependency-resolve (primary) |
| Recommended trace | full — benefits from full eBPF trace |
| Auto-attaches when | - preargv\_prefix: pip install<br>- preargv\_prefix: pip3 install<br>- postexec\_observed\_argv\_prefix: pip install<br>- postexec\_observed\_argv\_prefix: pip3 install |

The facts in this box are generated from the CI/lock binary's own catalog (`cilock tools list`). Do not hand-edit — run `npm run gen:catalog`.

 

## What it captures

 

The top-level predicate has eight JSON fields:

 

- `pipVersion` — output of `pip --version`, or `"unknown"` if the binary is missing.
 - `pythonVersion` — output of `python3 --version`.
 - `packages` — array of `PackageInfo` (see below), one per row of `pip list --format=json`.
 - `setupPyAnalysis` — array of `SetupPyAnalysis` records found by walking the working directory and `/tmp/pip-{download,install,build}`.
 - `totalInstalled` — `len(packages)`.
 - `installedFileAnalysis` — single `InstalledFileAnalysis` from walking `site.getsitepackages()[0]`.
 - `pyprojectAnalysis` — array of build-backend records parsed from any `pyproject.toml` under `/tmp/pip-download` or `/tmp/pip-build`.
 - `pep740Verification` — array of `PEP740Status`, one per installed package (excluding `pip`, `setuptools`, `wheel`), each fetched from `pypi.org`.

 

Each `PackageInfo` carries `name`, `version`, `installType`, `location`, `requires`, `requiredBy`, `homePage`, `author`, `license`, `hasSetupPy`, `hasCmdClass`, and `installer`. The descriptive fields (`location`, `requires`, `requiredBy`, `homePage`, `author`, `license`) are parsed line-by-line from `pip show <name>`. The provenance fields come from the installed package's on-disk layout, not from `pip show`: `installer` is read verbatim from `<dist-info>/INSTALLER` (e.g. `pip`); `installType` is `editable` (an `.egg-link`/`.egg-info` marker), `wheel`, or `sdist` (resolved from `<dist-info>/direct_url.json`, falling back to whether a build-time `setup.py` was executed); `hasSetupPy`/`hasCmdClass` are set when a `setup.py` correlated to this package (by canonical name + project directory) was found in the build cache and, for `hasCmdClass`, referenced `cmdclass`.

 

`InstalledFileAnalysis` records `suspiciousImports` (`sys.meta_path`, `atexit.register`, `codecs.register` references inside any `__init__.py`), `subprocessInInit`, `networkInInit`, every `.pkl`/`.pickle`/`.pt` file under site-packages (`pickleFiles`), a `pickleAnalysis` disassembly (capped at the first 50 files) produced by an embedded `python3 -c "import pickletools..."` script that flags `REDUCE`, `GLOBAL`, `STACK_GLOBAL`, `INST` opcodes, all `.pth` files, and a `totalPyFiles` count.

 

Each installed package is also emitted as an in-toto subject `pip://<name>@<version>` digested over the literal bytes `<name>==<version>` (SHA-256). That digest is **not** a hash of the wheel — it is only an identifier.

 

## When to use

 

After a Python build or install step, to record what actually ended up on disk and to surface a few classes of supply-chain red flag in the same predicate. Pair with [`lockfiles`](https://www.testifysec.com/docs/cilock/attestors/lockfiles) (pre-material pin) and [`sbom`](https://www.testifysec.com/docs/cilock/attestors/sbom) (resolved tree) for full coverage; this attestor is the only one that observes the post-install state of `site-packages`.

 

## Flags

 

None. The package exposes a `WithTargetPackage` `Option` internally, but it is not wired to any CLI flag.

 

## Output shape

 

```json
{
  "pipVersion": "pip 24.0 from /usr/lib/python3.12/site-packages/pip (python 3.12)",
  "pythonVersion": "Python 3.12.3",
  "totalInstalled": 1,
  "packages": [
    {
      "name": "requests",
      "version": "2.32.3",
      "location": "/usr/lib/python3.12/site-packages",
      "requires": ["charset-normalizer", "idna", "urllib3", "certifi"],
      "homePage": "https://requests.readthedocs.io",
      "author": "Kenneth Reitz",
      "license": "Apache-2.0"
    }
  ],
  "pep740Verification": [
    {
      "package": "requests",
      "version": "2.32.3",
      "hasAttestation": true,
      "attestationUrl": "https://pypi.org/integrity/requests/2.32.3/requests-2.32.3-py3-none-any.whl/provenance",
      "publisherKind": "GitHub",
      "repository": "psf/requests",
      "workflow": "publish.yml"
    }
  ]
}
```

 

## Gotchas

 

- Requires `pip` and `python3` on `PATH`. If `pip list` fails the attestor logs and returns `nil` — you get an attestation with empty `packages` and zero `totalInstalled`, not a failure.
 - Does **not** run `pip install` itself, does **not** read a `requirements.txt` or `pip freeze` output, and does **not** scan declared products. It snapshots whatever is installed in the active Python environment at the time `Attest` runs, so virtualenv activation must happen before the step.
 - `setupPyAnalysis` and `pyprojectAnalysis` only look under the working directory and three hard-coded `/tmp/pip-*` paths; if pip's build cache lives elsewhere (e.g. inside a container with `TMPDIR` override) nothing is found.
 - The pickle scanner shells out to `python3 -c '...pickletools...'` per file and is capped at 50 pickles per attestation. `IsSafe` is a heuristic — absence of `REDUCE`/`GLOBAL`/`INST` does not prove safety.
 - PEP 740 lookup hits `pypi.org/pypi/<pkg>/<ver>/json` and `pypi.org/integrity/.../provenance` over a 5 s HTTP client per package, serially. For an environment with hundreds of packages this dominates wall time, and offline runs silently return `hasAttestation: false` for every package.
 - Bundled but not default: even though `pip-install` registers correctly and appears in `cilock attestors list`, it is absent from `options.DefaultAttestors`, so you must pass `-a pip-install` explicitly or it will never run.

 

## CLI example

 

Real `pip install` against PyPI inside a virtualenv. The attestor reads `pip list --format=json`, walks site-packages, and checks PEP 740 attestations for every installed package.

 

```bash
python3 -m venv .venv && source .venv/bin/activate

cilock run --step pip-install-real \
  --signer-file-key-path key.pem --outfile attestation.json \
  --attestations pip-install,environment,git \
  -- pip install --quiet requests httpx
```

 

Validated against real PyPI: 10 packages, 9 had valid PEP 740 attestations from GitHub-issued Sigstore certs. See the full real-data example at [https://github.com/aflock-ai/attestor-compliance-examples/tree/main/11-pip-install](https://github.com/aflock-ai/attestor-compliance-examples/tree/main/11-pip-install).

 

## See also

 

- [Catalog row](https://www.testifysec.com/docs/cilock/reference/attestor-catalog)
 - [`lockfiles`](https://www.testifysec.com/docs/cilock/attestors/lockfiles), [`sbom`](https://www.testifysec.com/docs/cilock/attestors/sbom)
 - Upstream: [witness/pip-install.md](https://github.com/in-toto/witness/blob/main/docs/attestors/pip-install.md)

 

---

**This page is generated from the CI/lock tool catalog.** Don't edit it here — the source is [`attestation/detection/docs/pip-install.doc.md`](https://github.com/aflock-ai/rookery/edit/main/attestation/detection/docs/pip-install.doc.md) in [aflock-ai/rookery](https://github.com/aflock-ai/rookery). The same catalog powers `cilock tools show pip-install` in the CLI, so a single edit keeps the terminal and the website in sync.

[Edit this page →](https://github.com/aflock-ai/rookery/edit/main/attestation/detection/docs/pip-install.doc.md) ·  [Add a new tool →](https://github.com/aflock-ai/rookery/new/main/attestation/detection/docs)

When you open the pull request: if you are an **AI agent**, add the `ai-agent` label. If you are a **human**, add the `human` label.

Reference generated from the product documentation. Match commands and support details to your installed release.
