Faintprint

How to install

You need your access token: a name and a secret, which you receive when you buy. Then:

Requirements

Install

From the private package index, with the access token you were sent: a name and a secret. The token is yours alone; keep it in your password manager or your pipeline's secret store, and tell us at once if it leaks — you get a new one. Read both into the shell, the secret without it being shown or kept in your history:

read -r FAINTPRINT_USER && export FAINTPRINT_USER
read -rs FAINTPRINT_TOKEN && export FAINTPRINT_TOKEN

Install it once with pipx, a standard tool that puts a Python command where your terminal finds it, in any folder. pipx is needed once per machine: on a Mac with Homebrew, brew install pipx, then pipx ensurepath and a new terminal window; elsewhere, see pipx's install page.

PIP_INDEX_URL="https://$FAINTPRINT_USER:$FAINTPRINT_TOKEN@gitlab.com/api/v4/projects/faintprint%2Ffaintprint/packages/pypi/simple" pipx install --force "faintprint[connect]==0.5.0"

The index also passes on requests for the other libraries Faintprint needs to the public Python index, so this one address is all you give. The token goes to this one command only; nothing on your machine keeps it. To update, run the same line with the new version number from the changelog.

Without pipx, in a Python environment of its own in the current folder. Faintprint needs Python 3.11 or newer, and the python3 Apple ships with macOS is older: use one from Homebrew or python.org. pip shows the secret as **** in its output.

python3 -m venv .venv && .venv/bin/pip install --index-url "https://$FAINTPRINT_USER:$FAINTPRINT_TOKEN@gitlab.com/api/v4/projects/faintprint%2Ffaintprint/packages/pypi/simple" "faintprint[connect]==0.5.0"
source .venv/bin/activate

As a container image (built for Intel/AMD Linux; on an Apple-silicon Mac, Docker runs it under emulation), with the same token:

printf '%s' "$FAINTPRINT_TOKEN" | docker login registry.gitlab.com --username "$FAINTPRINT_USER" --password-stdin
docker run --rm -v "$PWD:/work" registry.gitlab.com/faintprint/faintprint:0.5.0 scan /work/mcp.json

The folder you run it from is /work inside the container, and the reports are written to scan-reports/ in it. On Linux, add --user "$(id -u):$(id -g)" after --rm so the reports belong to you.

Installed with pipx, the command is then faintprint, in any folder (in a .venv environment, after source .venv/bin/activate):

faintprint --version
faintprint scan --help

First run

What your AI tools on this machine are connected to:

faintprint scan

With nothing named, it reads the config files of your AI tools on this machine (read only) and scans the servers they list — the same as --discover. It prints every place it looked.

One server by its web address:

faintprint scan https://example.com/mcp

Write the address in full, with https://. A name without it is read as a file name; if it looks like an address, the command says so and shows the full form.

A saved tool list, or a config file of Claude Desktop, Claude Code, Cursor, Windsurf or VS Code:

faintprint scan path/to/mcp.json

Reports go to scan-reports/<date and time>/ unless you pass --out DIR: index.html for the run, one page and one JSON file per server, and the fetched tool list beside each report so the same report can be rebuilt later with no network. Typed in a terminal, the run's page opens in your browser when the scan ends; --no-open skips that. In a pipeline or a script it never opens.

Add --review to have the borderline passages read by the AI reviewer. The command prints the passages, the number of calls, the estimated and worst-case price, the model, and exactly what is sent; checks your key with a free token count that sends the key and one fixed word (a refused key is said there, and nothing is spent); then asks. In a pipeline, --yes answers in advance and the review runs only within --max-cost (default $1.00).

The build check

The exit code is the answer:

codemeaning
0pass: everything checked, nothing at or above the level
1fail: a server shows signs of an attack at or above the level
2could not run: wrong arguments, an unreadable file, or nothing to scan
3not finished: something was not checked (a review not run, a server not reachable, a local server without a tool list)

The level is "Suspicious or worse" unless you pass --fail-on minor | suspicious | likely-attack | never. --allow-unfinished lets an unfinished run pass, with a warning.

The full guide, including the one command that saves a local server's tool list, is the guide.

GitHub's Security tab

--sarif FILE also writes the findings in SARIF, the standard format code scanners hand their findings over in. Uploaded to GitHub, each sign of an attack becomes an alert in the repository's Security tab, at the line that holds the passage, and a borderline passage the AI review has not read becomes a note:

faintprint scan .vscode/mcp.json --out faintprint-reports --sarif faintprint-reports/faintprint.sarif

In a pipeline the step below writes it for you and uploads it with upload-sarif: true. Code scanning is free on public repositories; on a private one it needs GitHub Code Security. The guide has the details.

In a pipeline

A ready-made step installs Faintprint with your token, scans what you name, keeps the reports as the job's artefact (also when the job fails) and ends the job with the exit code above. Store the token's name and secret as pipeline secrets, FAINTPRINT_USER and FAINTPRINT_TOKEN; the step hands them to the install only. The AI review stays off unless you turn it on with your own ANTHROPIC_API_KEY, and then runs only within max-cost.

GitHub Actions:

      - uses: faintprint/scan-action@714a55b7270c66d97276c687557f4284b0aeb448 # v1.3.0
        with:
          scan: .vscode/mcp.json
          deploy-user: ${{ secrets.FAINTPRINT_USER }}
          deploy-token: ${{ secrets.FAINTPRINT_TOKEN }}

GitLab CI (on gitlab.com; the guide has the line for a GitLab you run yourself):

include:
  - component: $CI_SERVER_FQDN/faintprint/pipeline/scan@7f17722aff486b22446effa85649453ba179a6de # 1.3.0
    inputs:
      scan: .vscode/mcp.json

Both lines are pinned to a commit, so your check changes only when you change the line; faintprint/scan-action@v1 and scan@1.3.0 are the shorter forms. Every input — scan, tools, fail-on, review, max-cost, allow-unfinished, version, reports, and on GitHub upload-sarif and sarif-category — is in the guide.