Metadata-Version: 2.4
Name: extuno
Version: 0.1.7
Summary: Assess a Python package for supply-chain risk before pip installs it
Author: extuno.com Tolga SEZER
Maintainer: extuno.com Tolga SEZER
License: MIT
Project-URL: Homepage, https://extuno.com/pip-scanner
Project-URL: Documentation, https://extuno.com/pip-scanner
Project-URL: Source, https://github.com/Extuno/extuno-cli
Project-URL: Issues, https://github.com/Extuno/extuno-cli/issues
Project-URL: Changelog, https://github.com/Extuno/extuno-cli/blob/main/CHANGELOG.md
Keywords: security,supply-chain,pip,malware,sandbox,sbom,dependency-confusion,typosquatting,ci,devsecops
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: System :: Software Distribution
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# extuno

Assess a Python package for supply-chain risk **before** pip installs it.

`pip install` is the moment a package first gets to run code on your machine: a source
distribution's `setup.py` executes during installation, before anything is ever imported. `extuno`
puts a check in front of that moment. It resolves the exact release you are about to install, asks
the Extuno platform what is known about that release, and only hands control to pip when the verdict
is acceptable.

```console
$ extuno pip install six
1 package(s) to assess, 5 credit(s) available
[1/1] six
  - six 1.17.0 ........................ sha256 ff70335d468e7eb6
  - checking Extuno ................... not analysed yet
  - scan queued ....................... 1 credit 0738cabf
  - downloading package ............... in the isolated environment
  - dynamic analysis .................. running in the sandbox
  - analysis complete
  verdict CLEAN  risk 0/100

OK 1 package(s) assessed, none blocked, 1 credit(s) used
OK verdicts acceptable, installing
Successfully installed six-1.17.0
```

When a release is already known, the answer is instant and costs nothing:

```console
$ extuno pip install requests
  - checking Extuno ................... already analysed, no credit used
  verdict CLEAN  risk 0/100
```

And when it is not safe, pip is never reached:

```console
$ extuno pip install <a package from the malicious catalog>
  - checking Extuno ................... listed in the malicious database
  verdict MALICIOUS
    malicious-package - Listed in the Datadog malicious-software-packages dataset.

BLOCKED 1 of 1 package(s) blocked
```

## Nothing hostile runs on your machine

This is the property the whole design is built around.

- The package is **never downloaded, unpacked, imported or executed locally**. `extuno` reads the
  index's JSON metadata to learn the version and digest, nothing more.
- The artifact itself is fetched and analysed by the Extuno platform, inside a network-segmented,
  ephemeral virtual machine that is destroyed after every run.
- `requirements.txt` is parsed by this tool directly, as text. pip's own resolver is not used for
  the assessment, because resolving a source distribution can execute its build backend, which is
  exactly what we are trying to get ahead of.
- pip is invoked with an argument list and no shell, only after every requirement passes. Anything
  the assessment cannot cover (a direct URL, a local path, an editable, an index override) stops the
  install rather than passing through unchecked; `--allow-unassessed` overrides that deliberately.
- A nested `-r` include cannot escape the directory of the first requirements file, and a line the
  parser cannot classify is reported by location, never by echoing its text into your build log.
- Package names, findings and server messages are stripped of control characters before printing, so
  a hostile name cannot drive your terminal.
- The tool has **zero runtime dependencies**. A supply-chain security tool should not add supply
  chain of its own.

## Install

```console
pip install extuno
```

Python 3.9 or newer. No compiled parts, no third-party packages.

## Authenticate

Create an API key in the Extuno console, then:

```console
extuno login --key extk_...
```

The key is stored in `~/.config/extuno/config.json` with owner-only permissions. In CI, set
`EXTUNO_API_KEY` instead and skip `login` entirely.

## Commands

| Command | What it does |
| --- | --- |
| `extuno pip install ...` | Assess, then hand off to pip when the verdict allows it |
| `extuno scan PKG...` | Assess without installing |
| `extuno check -r requirements.txt` | CI gate: assess, exit non-zero when blocked |
| `extuno credits` | Remaining scan credits |
| `extuno whoami` | Endpoint, key fragment, balance |
| `extuno login` / `extuno logout` | Manage the stored key |

`extuno pip install` accepts the pip arguments you already use:

```console
extuno pip install -r requirements.txt
extuno pip install -U 'flask==3.0.0' --user
extuno scan requests==2.32.3 urllib3==2.5.0
```

## Verdicts

| Verdict | Meaning | Blocks by default |
| --- | --- | --- |
| `malicious` | In the known-malicious catalog, or analysis proved attack behaviour: code that runs at install time, credentials read then transmitted, a decoded payload executed | yes |
| `suspicious` | A real attack technique that is dual-use on its own, so a human should look | yes |
| `review` | Findings worth knowing (a leaked-looking secret, a vulnerable pattern) that say nothing about intent | no |
| `clean` | Neither | no |
| `error` | The check could not be completed: the release is missing from the index, the scan failed or timed out, or the index now serves a different artifact than the one analysed | yes |
| `unknown` | Not assessable, for example when `--no-scan` is used | no |

Change the threshold with `--fail-on`:

```console
extuno check -r requirements.txt --fail-on malicious,suspicious,review,unknown
```

A verdict is deliberately not the same thing as a risk score. A widely used library that ships an
integration test suite, a throwaway certificate and a permissive TLS option scores high and is still
`review`, not `malicious`, because none of that is evidence of intent.

## Analysis stages

Every release gets static analysis. Sandbox execution and source review are applied by the platform
according to the ecosystem, and can be requested or skipped per run:

```console
extuno scan somepkg --dynamic --ai      # force sandbox execution and source review
extuno pip install somepkg --no-dynamic # static only, faster
```

## Continuous integration

`extuno check` is the gate. It never installs anything and exits non-zero when a package is blocked.

```yaml
- name: Supply-chain gate
  env:
    EXTUNO_API_KEY: ${{ secrets.EXTUNO_API_KEY }}
  run: |
    pip install extuno
    extuno check -r requirements.txt --sarif extuno.sarif --json report.json

- uses: github/codeql-action/upload-sarif@v3
  if: always()
  with:
    sarif_file: extuno.sarif
```

Or use the action, which installs the CLI for you:

```yaml
- uses: Extuno/extuno-cli@v0.1.7
  with:
    api-key: ${{ secrets.EXTUNO_API_KEY }}
    requirements: requirements.txt
```

pre-commit:

```yaml
repos:
  - repo: https://github.com/Extuno/extuno-cli
    rev: v0.1.7
    hooks:
      - id: extuno
```

The default hook runs with `--no-scan`, so it answers from what Extuno already knows and spends
nothing. Use `id: extuno-scan` when an unseen release should be analysed on the spot.

GitLab CI:

```yaml
supply-chain:
  image: python:3.12-slim
  script:
    - pip install extuno
    - extuno check -r requirements.txt --json report.json
  artifacts:
    when: always
    paths: [report.json]
```

Exit codes:

| Code | Meaning |
| --- | --- |
| 0 | Nothing blocked |
| 1 | At least one package blocked by policy |
| 2 | Usage, network or server error |
| 3 | Out of scan credits |
| 4 | A scan did not finish in time |

`--json` writes a machine-readable report; `--sarif` writes SARIF 2.1.0 for inline annotations on
GitHub, GitLab and Azure DevOps.

## Credits

Assessing a release the platform already knows is free and instant. Analysing a release for the
first time costs one scan credit; new accounts start with five. `extuno credits` shows the balance,
and a run stops with exit code 3 rather than half-finishing when the balance runs out.

Use `--no-scan` to run entirely on what is already known, spending nothing:

```console
extuno check -r requirements.txt --no-scan
```

## Private indexes

If you pass `--index-url` or `--extra-index-url`, the tool says so plainly: versions are resolved
against the public index, so the artifact pip installs from a private index may not be the artifact
that was analysed. That gap is the shape of a dependency-confusion attack, and it should be a
decision rather than a surprise.

## Links

- Platform: https://extuno.com
- Documentation: https://extuno.com/pip-scanner

MIT licensed.
