code-guardian-mcp 0.2.0__tar.gz → 0.3.3__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.
- {code_guardian_mcp-0.2.0/code_guardian_mcp.egg-info → code_guardian_mcp-0.3.3}/PKG-INFO +221 -37
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/README.md +218 -34
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/__init__.py +1 -1
- code_guardian_mcp-0.3.3/code_guardian/baseline.py +135 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/catalogue.py +38 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/cli.py +71 -6
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/finding.py +19 -13
- code_guardian_mcp-0.3.3/code_guardian/history.py +158 -0
- code_guardian_mcp-0.3.3/code_guardian/leftovers.py +266 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/mcp_server.py +80 -20
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/orphans.py +7 -4
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/outputs.py +52 -10
- code_guardian_mcp-0.3.3/code_guardian/report.py +661 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/scanners.py +181 -5
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/score.py +66 -17
- code_guardian_mcp-0.3.3/code_guardian/verify.py +247 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/walk.py +3 -2
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3/code_guardian_mcp.egg-info}/PKG-INFO +221 -37
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian_mcp.egg-info/SOURCES.txt +11 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/pyproject.toml +3 -3
- code_guardian_mcp-0.3.3/tests/test_baseline.py +155 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_complete.py +4 -1
- code_guardian_mcp-0.3.3/tests/test_coverage.py +142 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_fixtures.py +6 -3
- code_guardian_mcp-0.3.3/tests/test_history.py +139 -0
- code_guardian_mcp-0.3.3/tests/test_leftovers.py +160 -0
- code_guardian_mcp-0.3.3/tests/test_locations_redact.py +91 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_mcp.py +1 -1
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_registry_markers.py +8 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_selftests.py +1 -1
- code_guardian_mcp-0.3.3/tests/test_status.py +139 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_tool_annotations.py +2 -1
- code_guardian_mcp-0.3.3/tests/test_verify.py +201 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_works.py +2 -1
- code_guardian_mcp-0.2.0/code_guardian/report.py +0 -383
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/LICENSE +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/__main__.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/complete.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/data/known_packages.txt.gz +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/explain.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/goal.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/manifests.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/supply.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian/works.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian_mcp.egg-info/dependency_links.txt +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian_mcp.egg-info/entry_points.txt +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/code_guardian_mcp.egg-info/top_level.txt +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/setup.cfg +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_calibration.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_explain.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_outputs.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_readme_sync.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_safe_gaps.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_safety.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_scanners.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_scoring.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_self_scan.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_spdx_license.py +0 -0
- {code_guardian_mcp-0.2.0 → code_guardian_mcp-0.3.3}/tests/test_supply.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-guardian-mcp
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Is this codebase safe to run, will it work, and is it complete? Deterministic Safe, Works and Complete scores with an A-F grade, from static analysis only
|
|
3
|
+
Version: 0.3.3
|
|
4
|
+
Summary: Is this codebase safe to run, will it work, and is it complete? Deterministic Safe, Works and Complete scores with an A-F grade, from static analysis only, plus a verify loop for AI-assisted changes: baseline, repair plan, before/after diff. The code is never run.
|
|
5
5
|
Author: LuminariSoftwares
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/LuminariSoftwares/code-guardian
|
|
8
8
|
Project-URL: Issues, https://github.com/LuminariSoftwares/code-guardian/issues
|
|
9
9
|
Project-URL: Changelog, https://github.com/LuminariSoftwares/code-guardian/blob/main/CHANGELOG.md
|
|
10
|
-
Keywords: security,static-analysis,dead-code,supply-chain,secrets,mcp,local-llm
|
|
10
|
+
Keywords: security,static-analysis,dead-code,supply-chain,secrets,mcp,local-llm,ai-code-review,baseline
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Environment :: Console
|
|
@@ -37,14 +37,28 @@ from deterministic checks with fixed weights: same repo, same version, same outp
|
|
|
37
37
|
- **Orphan code**: functions, classes and modules nothing reachable from the project's entry points uses, judged
|
|
38
38
|
against a small goal contract.
|
|
39
39
|
- An optional **plain-English explanation** from a local model, which never adds, removes or rescores a finding.
|
|
40
|
+
- A **verify loop** for AI-assisted changes: run it before and after every change to learn what the change fixed,
|
|
41
|
+
what it broke, what to fix next, and whether the scan itself can be trusted (see [The verify loop](#the-verify-loop)).
|
|
40
42
|
|
|
41
43
|
It parses the code (`ast` for Python; `json`, `tomllib` and `configparser` for manifests; regular expressions for
|
|
42
44
|
secrets) and never executes, imports or installs it. A scan makes
|
|
43
|
-
no network calls. Python 3.11+, standard library only. Status: **0.
|
|
45
|
+
no network calls. Python 3.11+, standard library only. Status: **0.3.3 is the current release.** The fourth guardian,
|
|
44
46
|
next to [stack-guardian](https://github.com/LuminariSoftwares/stack-guardian),
|
|
45
47
|
[context-guardian](https://github.com/LuminariSoftwares/context-guardian) and
|
|
46
48
|
[tool-guardian](https://github.com/LuminariSoftwares/tool-guardian).
|
|
47
49
|
|
|
50
|
+
## What it analyses
|
|
51
|
+
|
|
52
|
+
**Python is analysed in depth**; every Safe, Works and Complete check runs on it. **Other languages** (JavaScript,
|
|
53
|
+
TypeScript, Go, Rust, shell and the rest) **get the secret scan and the repo-hygiene checks only**: key shapes,
|
|
54
|
+
manifest and install-hook reading (`package.json`, `setup.py`, `pyproject.toml`, ...) and the Complete checks
|
|
55
|
+
(licence, README, CI, `.env.example`). Every report prints a `coverage:` line saying which files were analysed, e.g.
|
|
56
|
+
`coverage: 40 Python analysed; 112 .ts, 9 .go secret-scan only`. When there is no Python source at all, Works is
|
|
57
|
+
**not scored** (`n/a (Python only)`, `null` in JSON), Safe is labelled **partial**, Overall comes from the scored
|
|
58
|
+
dimensions only, and the status is **PARTIAL**. A repo whose source is mostly another language is also PARTIAL with
|
|
59
|
+
the same scores (rule in [Scan status](#scan-status)). PARTIAL means "do not read this grade as covering the whole
|
|
60
|
+
repo"; it never fails `--fail-on` on its own.
|
|
61
|
+
|
|
48
62
|
## Install
|
|
49
63
|
|
|
50
64
|
```
|
|
@@ -78,8 +92,10 @@ code-guardian scan risky_repo
|
|
|
78
92
|
Real output:
|
|
79
93
|
|
|
80
94
|
```
|
|
81
|
-
code-guardian 0.
|
|
95
|
+
code-guardian 0.3.3 scan of risky
|
|
82
96
|
files: 7 Python, 3 manifests, 2 other (0 skipped, 0 excluded by the goal file)
|
|
97
|
+
status: COMPLETE
|
|
98
|
+
coverage: 7 Python analysed; 1 .js secret-scan only
|
|
83
99
|
|
|
84
100
|
Safe 0/100 is it safe to run? -237
|
|
85
101
|
Works 85/100 will it work? -15
|
|
@@ -87,11 +103,11 @@ files: 7 Python, 3 manifests, 2 other (0 skipped, 0 excluded by the goal file)
|
|
|
87
103
|
Overall 33/100 grade F (a Safe check is FAIL (exec.shell, obfuscation, secret.key, tls.off): the grade is at most D)
|
|
88
104
|
|
|
89
105
|
biggest deductions:
|
|
90
|
-
-60 Safe secret.key FAIL x3
|
|
91
|
-
-40 Safe obfuscation FAIL x1
|
|
92
|
-
-30 Safe exec.shell FAIL x1
|
|
93
|
-
-25 Safe tls.off FAIL x1
|
|
94
|
-
-23 Safe install.hook WARN x3
|
|
106
|
+
-60 Safe secret.key FAIL x3 at deploy/id_rsa:1, risky/settings.py:1, risky/settings.py:2
|
|
107
|
+
-40 Safe obfuscation FAIL x1 at risky/loader.py:7
|
|
108
|
+
-30 Safe exec.shell FAIL x1 at risky/runner.py:7
|
|
109
|
+
-25 Safe tls.off FAIL x1 at risky/network.py:11
|
|
110
|
+
-23 Safe install.hook WARN x3 at package.json:5, setup.py:5, setup.py:14
|
|
95
111
|
|
|
96
112
|
findings (6 FAIL, 19 WARN, 7 INFO; egress listed below):
|
|
97
113
|
Safe:
|
|
@@ -134,19 +150,17 @@ egress inventory (4):
|
|
|
134
150
|
|
|
135
151
|
hygiene (inferred (library public API) entry points: 1): 0 dead, 0 test-only, 0 uncertain
|
|
136
152
|
|
|
137
|
-
notes:
|
|
138
|
-
- code-guardian analyses Python only: 1 file(s) in other languages (.js 1) got the secret scan only
|
|
139
|
-
|
|
140
153
|
weights used (each check (code + level) deducts its weight once, plus a quarter of the weight per extra occurrence (rounded up), at most 2x the weight; INFO is never scored):
|
|
141
154
|
Safe deps.unpinned INFO 0 · deser.unsafe WARN 10 · exec.dynamic FAIL 30 · exec.dynamic WARN 10 ·
|
|
142
155
|
exec.shell FAIL 30 · exec.shell WARN 10 · fs.outside WARN 3 · install.hook WARN 15 ·
|
|
143
156
|
net.bind_all WARN 5 · net.egress INFO 0 · net.egress WARN 2 · obfuscation FAIL 40 ·
|
|
144
|
-
obfuscation WARN 10 ·
|
|
145
|
-
|
|
146
|
-
tls.off FAIL 25 · tls.off WARN 10
|
|
147
|
-
Works orphan.dead WARN 3 · works.
|
|
148
|
-
works.
|
|
149
|
-
works.
|
|
157
|
+
obfuscation WARN 10 · privacy.pii_log WARN 5 · privacy.pii_prompt WARN 5 · secret.history FAIL 30 ·
|
|
158
|
+
secret.key FAIL 40 · secret.key INFO 0 · supply.lookalike WARN 15 · supply.unknown_package FAIL 40 ·
|
|
159
|
+
supply.unknown_package INFO 0 · supply.unknown_package WARN 10 · tls.off FAIL 25 · tls.off WARN 10
|
|
160
|
+
Works orphan.dead WARN 3 · works.commented_code INFO 0 · works.dead_flag INFO 0 ·
|
|
161
|
+
works.entry_missing FAIL 30 · works.import_unresolved WARN 10 · works.relative_missing FAIL 25 ·
|
|
162
|
+
works.syntax FAIL 30 · works.syntax INFO 0 · works.test_no_assert WARN 5 ·
|
|
163
|
+
works.tests_missing WARN 15 · works.todo_stub WARN 3
|
|
150
164
|
Complete complete.ci_missing WARN 10 · complete.env_example WARN 10 · complete.license_missing WARN 20 ·
|
|
151
165
|
complete.license_undeclared WARN 5 · complete.lockfile WARN 10 · complete.metadata WARN 10 ·
|
|
152
166
|
complete.readme_missing WARN 20 · complete.readme_sections WARN 5 · complete.readme_short WARN 10 ·
|
|
@@ -160,13 +174,61 @@ Exit code 1: there are FAIL findings. `--json` prints the same report as JSON (s
|
|
|
160
174
|
|
|
161
175
|
| exit | meaning |
|
|
162
176
|
|---|---|
|
|
163
|
-
| 0 | nothing at or above `--fail-on` (default `FAIL`) |
|
|
177
|
+
| 0 | nothing at or above `--fail-on` (default `FAIL`), and the scan is COMPLETE (or PARTIAL for coverage only) |
|
|
164
178
|
| 1 | findings at or above it |
|
|
165
179
|
| 2 | usage error, a missing PATH, or a bad goal file |
|
|
180
|
+
| 3 | incomplete scan: the status is PARTIAL or INVALID (see [Scan status](#scan-status)) and nothing is at or above `--fail-on`; a PARTIAL that is only about coverage (other languages) never gives 3 |
|
|
166
181
|
|
|
167
182
|
`scan` gates on every scored finding (Safe, Works and Complete); `code-guardian orphans PATH` gates on the hygiene
|
|
168
183
|
findings.
|
|
169
184
|
|
|
185
|
+
## The verify loop
|
|
186
|
+
|
|
187
|
+
code-guardian answers four questions about a change, so a developer or a coding agent can run it before and after
|
|
188
|
+
every edit:
|
|
189
|
+
|
|
190
|
+
| question | command | MCP tool |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| Can I trust this scan? | every report's `status`: COMPLETE, PARTIAL (grade capped at B) or INVALID (no grade); exit 3 | `scan` |
|
|
193
|
+
| What should be fixed next? | `code-guardian plan PATH` (each check row's **estimated gain if fixed**, best first) | `plan` |
|
|
194
|
+
| What did the change fix, and what did it break? | `code-guardian diff BEFORE.json AFTER.json` (exit 1 when anything regressed) | `diff` |
|
|
195
|
+
| Is this finding new? | `code-guardian baseline PATH`, then `scan --baseline FILE` (gate on new findings only) | `scan` with `baseline` |
|
|
196
|
+
|
|
197
|
+
An agent's loop, from the shell:
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
code-guardian scan . --json > before.json # 1. scan: is the scan COMPLETE? what is the grade?
|
|
201
|
+
code-guardian plan . --top 5 # 2. plan: the five fixes worth the most points, with locations
|
|
202
|
+
# 3. fix the top item (the plan never edits code)
|
|
203
|
+
code-guardian scan . --json > after.json # 4. scan again
|
|
204
|
+
code-guardian diff before.json after.json # 5. diff: verdict improved / regressed / mixed / unchanged
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
verdict: improved (1 fixed, 0 new, 31 unchanged)
|
|
209
|
+
|
|
210
|
+
before after delta
|
|
211
|
+
Safe 0 0 +0
|
|
212
|
+
Works 85 85 +0
|
|
213
|
+
Complete 35 55 +20
|
|
214
|
+
Overall 33 37 +4
|
|
215
|
+
grade F F
|
|
216
|
+
status COMPLETE -> COMPLETE
|
|
217
|
+
|
|
218
|
+
fixed (1):
|
|
219
|
+
WARN complete.license_missing . no licence file: nobody may legally reuse this code
|
|
220
|
+
|
|
221
|
+
new (0):
|
|
222
|
+
none
|
|
223
|
+
|
|
224
|
+
matched by fingerprint (code, level, file and the evidence line; line numbers ignored); INFO findings never decide the verdict
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The same loop over MCP is `scan` → `plan` → edit → `scan` → `diff(before, after)`, where `before` and `after` are the
|
|
228
|
+
two `scan` replies (JSON text) or paths to `scan --json` files. Findings are matched by fingerprint (code, level,
|
|
229
|
+
file and the evidence line, never the line number), so moving code does not count as a change. Everything in the
|
|
230
|
+
loop is deterministic, offline and read-only.
|
|
231
|
+
|
|
170
232
|
## How the scores work
|
|
171
233
|
|
|
172
234
|
Every check is a small pure function with its own tests. Every finding has the guardian family's shape (`code`,
|
|
@@ -179,30 +241,69 @@ Complete.
|
|
|
179
241
|
one minor issue cannot zero a score.
|
|
180
242
|
- **Dimension score** = max(0, 100 − its deductions). Safe is 100 minus the v0.1 threat score; its weights are the
|
|
181
243
|
v0.1 weights, unchanged.
|
|
182
|
-
- **Overall** = 0.5 × Safe + 0.3 × Works + 0.2 × Complete, rounded half up.
|
|
244
|
+
- **Overall** = 0.5 × Safe + 0.3 × Works + 0.2 × Complete, rounded half up. A dimension that is not scored (Works
|
|
245
|
+
when there is no Python source) is left out and the others keep their relative weights: (5 × Safe + 2 × Complete)
|
|
246
|
+
/ 7. The Overall line names the dimension left out.
|
|
183
247
|
- **Grade**: **A** ≥ 90 · **B** ≥ 75 · **C** ≥ 60 · **D** ≥ 40 · **F** below 40.
|
|
184
248
|
- **FAIL cap**: any Safe finding at FAIL caps the grade at **D**, whatever the average. One leaked key never hides
|
|
185
249
|
behind a good README.
|
|
186
250
|
- INFO findings are never scored. Every report prints the weight table it used.
|
|
251
|
+
- **Every deduction cites its evidence.** Each check row lists every distinct `file:line` it was counted at
|
|
252
|
+
(sorted, at most 20, plus `more`: how many were left out); the text report shows up to three per row.
|
|
253
|
+
- **Nothing secret is printed.** Every output (JSON, text, Markdown, SARIF, MCP) goes through one `redact()`: API-key
|
|
254
|
+
shapes and email addresses are masked as `<redacted key>` / `<redacted email>` wherever they appear (a URL literal,
|
|
255
|
+
a path), and the evidence of `secret.*` and `privacy.*` findings is also stripped of any long token-like value.
|
|
187
256
|
|
|
188
257
|
The weight tables live in `code_guardian/score.py` (`TABLES`); `tests/test_readme_sync.py` fails if the tables in
|
|
189
258
|
this README drift from them.
|
|
190
259
|
|
|
260
|
+
### Scan status
|
|
261
|
+
|
|
262
|
+
Can I trust this scan? Every report carries a `status`, the reasons for it, and the outcome of every scanner,
|
|
263
|
+
check and the file walk (`ran`, `skipped` with a reason, or `crashed` with the error):
|
|
264
|
+
|
|
265
|
+
| status | when | effect |
|
|
266
|
+
|---|---|---|
|
|
267
|
+
| `COMPLETE` | everything ran | none |
|
|
268
|
+
| `PARTIAL` | a check crashed or was skipped, a Python file did not parse, or a Python file or manifest was too large (over 2 MB) or unreadable | the grade is capped at **B** and the report says why; exit 3 unless a finding already gives exit 1 |
|
|
269
|
+
| `PARTIAL` (coverage) | there is no Python source, or the source is mostly in other languages (rule below) | the grade is capped at **B** and the `coverage:` line says what was not analysed; **the exit code is unchanged** (0 or 1 from the findings, never 3) |
|
|
270
|
+
| `INVALID` | PATH is unreadable, or there are no Python files and no manifests to judge | no grade (`scores.grade` is `null`); exit 3 |
|
|
271
|
+
|
|
272
|
+
**Coverage rule.** Source files are counted by extension (`.js`, `.mjs`, `.ts`, `.tsx`, `.go`, `.rs`, `.java`,
|
|
273
|
+
`.rb`, `.php`, `.sh`, `.c`, `.cs` and other common languages; data, docs and config files are not source). With
|
|
274
|
+
**no Python source**, Works is not scored, Safe is partial (it can only see secrets, manifests and install hooks),
|
|
275
|
+
Overall is the weighted mean of Safe and Complete, and the scan is PARTIAL. With Python present, the scan is PARTIAL
|
|
276
|
+
when there are **at least 5 other-language source files and more than twice as many as Python files** (Python is
|
|
277
|
+
under a third of the source); the scores are computed as usual. A Python project with a few build, docs or shell
|
|
278
|
+
helpers stays COMPLETE: in [calibration](calibration.md) the most JS-heavy repo has 37 `.js`/`.mjs` files to 33
|
|
279
|
+
Python files and is COMPLETE. `tests/test_coverage.py` pins both sides of the line.
|
|
280
|
+
|
|
281
|
+
Binary files, symlinks and large non-source files (images, design files) are only ever secret-scanned; skipping them
|
|
282
|
+
is listed in the notes but does not make a scan PARTIAL.
|
|
283
|
+
|
|
191
284
|
### JSON shape (`--json`, and the MCP `scan` tool)
|
|
192
285
|
|
|
193
286
|
```
|
|
194
287
|
{
|
|
195
|
-
"tool", "version", "path", "goal_file",
|
|
288
|
+
"tool", "version", "schema_version": 2, "path", "goal_file",
|
|
289
|
+
"status": "COMPLETE" | "PARTIAL" | "INVALID", "status_reasons": [...],
|
|
290
|
+
"outcomes": [{"check", "outcome": "ran" | "skipped" | "crashed", "reason"}],
|
|
196
291
|
"files": {"python", "manifests", "other", "skipped", "ignored", "excluded", "unparsed"},
|
|
197
292
|
"other_languages": {".js": 1, ...},
|
|
293
|
+
"coverage": {"analysed": "python", "python", "other_source": {".js": 1, ...}, "other_source_files",
|
|
294
|
+
"works_scored", "safe_partial", "partial", "reason"},
|
|
198
295
|
"anchor": "pyproject.toml", (the file repo-level findings are attached to in SARIF)
|
|
199
296
|
"scores": {"safe", "works", "complete", "overall", "grade", "grade_uncapped", "capped", "cap_reason"},
|
|
200
|
-
|
|
201
|
-
|
|
297
|
+
(a dimension that is not scored is null)
|
|
298
|
+
"dimensions": {"safe" | "works" | "complete": {"score", "scored", "partial", "deducted",
|
|
299
|
+
"checks": [{"code", "level", "count", "weight", "deduction", "first",
|
|
300
|
+
"locations": [every distinct file:line, sorted, at most 20], "more": N}]}},
|
|
202
301
|
"top": [the five biggest deductions, each a check row plus "dimension"],
|
|
203
302
|
"weights": {"safe" | "works" | "complete": {"code LEVEL": weight}},
|
|
204
303
|
"weights_rule": "...",
|
|
205
|
-
"findings": [{"code", "severity", "level", "node", "summary", "evidence", "try", "confidence", "fix"
|
|
304
|
+
"findings": [{"code", "severity", "level", "node", "summary", "evidence", "try", "confidence", "fix",
|
|
305
|
+
"fingerprint", "new" (only with --baseline)}],
|
|
306
|
+
"baseline": {"file", "version", "new", "known"} (only with --baseline)
|
|
206
307
|
"egress": [{"node", "level", "kind", "target"}],
|
|
207
308
|
"hygiene": {"source", "goal", "entry_points", "keep", "findings", "counts", "notes"},
|
|
208
309
|
"notes": [...],
|
|
@@ -210,7 +311,12 @@ this README drift from them.
|
|
|
210
311
|
}
|
|
211
312
|
```
|
|
212
313
|
|
|
213
|
-
The MCP `scan` reply adds `exit_code`. Nothing in the report depends on the clock.
|
|
314
|
+
The MCP `scan` reply adds `exit_code`. Nothing in the report depends on the clock. `schema_version` 2 only adds
|
|
315
|
+
keys to the v0.2 shape (which had no `schema_version`); every v0.2 key keeps its meaning. `scores.grade` (and
|
|
316
|
+
`grade_uncapped`) is `null` for an INVALID scan; `capped` and `cap_reason` also cover the PARTIAL cap. With no Python
|
|
317
|
+
source, `scores.works` is `null` and `dimensions.works` is `{"score": null, "scored": false, ...}`;
|
|
318
|
+
`dimensions.safe.partial` is `true`. SARIF carries `status`, `status_reasons` and `coverage` in the run properties;
|
|
319
|
+
the text and Markdown reports print the status and the coverage line above the scores.
|
|
214
320
|
|
|
215
321
|
## Safe checks
|
|
216
322
|
|
|
@@ -218,6 +324,7 @@ The MCP `scan` reply adds `exit_code`. Nothing in the report depends on the cloc
|
|
|
218
324
|
|---|---|---|---|
|
|
219
325
|
| `secret.key` | API-key shapes (the privacy workflow's built-in list: `sk-` (which covers Anthropic's `sk-ant-`), `sk-or-`, `tvly-`, `ghp_`, `github_pat_`, `nvapi-`, `gsk_`, `AIza`; plus AWS access key IDs `AKIA`/`ASIA` + 16, Stripe `sk_live_`/`rk_live_`, Slack `xox[abprs]-`, Hugging Face `hf_` + 30 letters), a 40-character AWS secret key assigned to a name containing `aws` and `secret`, private-key blocks, `password = "<literal>"` (also `api_key`, `secret`, `access_token`…), and any `*_KEY` / `*_TOKEN` name holding a long, high-entropy literal. One finding per line. Values are never printed; values that call themselves fake (`mock`, `dummy`, `example`…) are skipped | FAIL | 40 |
|
|
220
326
|
| `secret.key` | a Stripe test-mode key (`sk_test_`) | INFO | 0 |
|
|
327
|
+
| `secret.history` | **opt-in, `scan --history`**: a key shape (the `secret.key` API-key shapes, AWS secret keys, private-key blocks) that a commit added and the file no longer has; it may still be valid. Names the short sha and the path, never the value | FAIL | 30 |
|
|
221
328
|
| `exec.dynamic` | `eval`, `exec`, `compile` on non-literal input; `__import__` with a variable | WARN | 10 |
|
|
222
329
|
| `exec.dynamic` | …when `eval` or `exec` runs text from `input()`, `sys.argv`, request data, `os.environ` or a network read (the `exec.shell` taint rule, one function deep; `exec(compile(...))` counts at the `exec`): "runs code typed by the user" | FAIL | 30 |
|
|
223
330
|
| `exec.shell` | `os.system`, `os.popen`, `subprocess.*(..., shell=True)` | WARN | 10 |
|
|
@@ -232,6 +339,8 @@ The MCP `scan` reply adds `exit_code`. Nothing in the report depends on the cloc
|
|
|
232
339
|
| `obfuscation` | `exec`/`eval` of `base64`/`zlib`/`marshal`/hex-decoded data | FAIL | 40 |
|
|
233
340
|
| `obfuscation` | base64 or hex blobs of 200+ characters, high-entropy string literals | WARN | 10 |
|
|
234
341
|
| `fs.outside` | writes, deletes and moves aimed at absolute paths, `~`, or `..` escapes | WARN | 3 |
|
|
342
|
+
| `privacy.pii_log` | a personal-data name (`email`, `phone`, `ssn`, `dob`/`birth`, a person's `address`, `passport`, `credit_card`/`card_number`, `iban`, `ip_address`; whole words, so `emails_sent` and `server_address` do not count) flows into `logging.*`, a logger method or `print` (the `exec.shell` taint rule, one function deep) | WARN | 5 |
|
|
343
|
+
| `privacy.pii_prompt` | the same names flow into an LLM prompt: a `messages=` or `prompt=` argument, or a `.chat(`, `.complete(` or `.generate(` call | WARN | 5 |
|
|
235
344
|
| `deps.unpinned` | dependencies with no pin or upper bound (`pyproject.toml`, `setup.py`, `setup.cfg`, `requirements*.txt`, `package.json`) | INFO | 0 |
|
|
236
345
|
|
|
237
346
|
`net.egress` targets that stay in the inventory at INFO: https to a named host, loopback, the local network
|
|
@@ -241,6 +350,21 @@ The MCP `scan` reply adds `exit_code`. Nothing in the report depends on the cloc
|
|
|
241
350
|
The walk skips `.git`, `venv`/`.venv`, `node_modules`, `__pycache__`, `dist`, `build` and the tool caches, anything
|
|
242
351
|
the root `.gitignore` matches (simple globs; `!` negation is ignored), binary files, files over 2 MB and symlinks.
|
|
243
352
|
|
|
353
|
+
### Secrets in the git history (opt-in)
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
code-guardian scan . --history [--history-depth 500]
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
A key deleted from a file is still in every clone's history. `--history` runs one read-only git command (an argument
|
|
360
|
+
list, never a shell): `git log -p --no-color --no-ext-diff --no-textconv --relative --format=%H --max-count=N -- .`
|
|
361
|
+
with the scanned repo's fsmonitor and signature-checking config switched off, so the repo cannot make the scan run a
|
|
362
|
+
program. It checks only the lines commits **added**, with the `secret.key` key-shape rules, reads at most the last N
|
|
363
|
+
commits (default 500) and at most 64 MB of log, and never modifies the repository. A key still present in the current
|
|
364
|
+
file is left to `secret.key`; the oldest commit that added a value is the one reported. Without git, or when PATH is
|
|
365
|
+
not a repository, the history check is skipped with a note (and the scan is PARTIAL, since what was asked did not
|
|
366
|
+
run). The scan itself stays offline.
|
|
367
|
+
|
|
244
368
|
### Invented and lookalike packages (Safe)
|
|
245
369
|
|
|
246
370
|
AI coding assistants sometimes suggest a package that does not exist. Anyone can register that name on PyPI and
|
|
@@ -280,6 +404,9 @@ Will it work? Read from the parse and the manifests, never by importing or runni
|
|
|
280
404
|
| `works.test_no_assert` | test functions with no `assert`, no `pytest.raises`/`warns`, no `self.assert*` and no call to a local helper that asserts; once per file, with the count | WARN | 5 |
|
|
281
405
|
| `works.entry_missing` | a `[project.scripts]` (or `setup.cfg` console script) target `module:function` whose module or function is not in the tree | FAIL | 30 |
|
|
282
406
|
| `orphan.dead` | dead code from the hygiene section (below), folded in at a low weight, only when entry points exist | WARN | 3 |
|
|
407
|
+
| `works.todo_stub` | a function whose body is only `pass`, `...` or `raise NotImplementedError` (a docstring allowed) **and** an entry point reaches it; not abstract methods, `Protocol` classes, `@overload` or a method a subclass in the repo overrides | WARN | 3 |
|
|
408
|
+
| `works.commented_code` | 5+ consecutive comment lines that parse as Python (found with `tokenize` + `ast.parse`); once per file, with the count of blocks | INFO | 0 |
|
|
409
|
+
| `works.dead_flag` | a module-level ALL_CAPS boolean (`DEBUG = False`) that no file reads, imports or names | INFO | 0 |
|
|
283
410
|
|
|
284
411
|
How `works.import_unresolved` decides:
|
|
285
412
|
|
|
@@ -332,6 +459,51 @@ that merely holds `os.environ` is not followed.
|
|
|
332
459
|
declared but unused. A repo with no Python source and no manifest has nothing for Works or Complete to check: it
|
|
333
460
|
scores 100 there, and the report says so.
|
|
334
461
|
|
|
462
|
+
## Baseline: gate on new findings only
|
|
463
|
+
|
|
464
|
+
An existing project rarely starts clean. Write a baseline once, commit it, and the gate then fails only on findings
|
|
465
|
+
that are new since:
|
|
466
|
+
|
|
467
|
+
```
|
|
468
|
+
code-guardian baseline . # writes ./code-guardian.baseline.json; commit it
|
|
469
|
+
code-guardian scan . --baseline code-guardian.baseline.json --fail-on WARN
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
- Each finding gets a `fingerprint`: sha256 of `code | level | path | evidence line`, where the evidence line is the
|
|
473
|
+
source line the finding points at, stripped, whitespace collapsed and redacted. **Line numbers are left out**, so
|
|
474
|
+
moving code up or down does not make a finding new; editing the line does. The baseline file holds the sorted
|
|
475
|
+
fingerprints and the version that wrote them, nothing else.
|
|
476
|
+
- With `--baseline`, every finding carries `new: true|false`, the report says `N new, M known`, and `--fail-on`
|
|
477
|
+
counts **new findings only**. A finding fixed since the baseline simply disappears; `scan` never rewrites the
|
|
478
|
+
baseline file (run `code-guardian baseline` again to accept the current state).
|
|
479
|
+
- **A baseline never hides the real grade.** The scores, the grade and every finding are computed and shown exactly
|
|
480
|
+
as without one; only the exit code changes.
|
|
481
|
+
- Same line twice counts twice: a third copy of a known line is new.
|
|
482
|
+
- SARIF results carry the fingerprint as `partialFingerprints["codeGuardian/v1"]` and `new` in their properties.
|
|
483
|
+
|
|
484
|
+
## Diff and repair plan
|
|
485
|
+
|
|
486
|
+
```
|
|
487
|
+
code-guardian scan . --json > before.json
|
|
488
|
+
code-guardian plan . # what to fix next: Markdown (--json for JSON), --top N rows (default 10)
|
|
489
|
+
# ...edit...
|
|
490
|
+
code-guardian scan . --json > after.json
|
|
491
|
+
code-guardian diff before.json after.json # what the change fixed and broke; exit 1 when anything regressed
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**`diff BEFORE.json AFTER.json`** compares two `scan --json` reports (0.3 or newer) by fingerprint: `fixed`, `new`
|
|
495
|
+
and `unchanged` findings, per-dimension score deltas, and a one-line verdict: **improved**, **regressed**, **mixed**
|
|
496
|
+
or **unchanged**. A new WARN/FAIL finding, or a status that got worse (COMPLETE → PARTIAL → INVALID), is a
|
|
497
|
+
regression; INFO findings are listed but never decide the verdict. Exit 1 when anything regressed (regressed or
|
|
498
|
+
mixed), 2 when a report cannot be read.
|
|
499
|
+
|
|
500
|
+
**`plan PATH`** is the repair plan. For each check row it re-runs the scoring on the finding list without that row:
|
|
501
|
+
the exact Overall points the row is worth, labelled **estimated gain if fixed** (the plan never touches the code, so
|
|
502
|
+
fixing more or less than the whole row changes the real gain). Rows are ranked by that gain, ties by the fewest
|
|
503
|
+
locations, and each comes with the catalogue's "how to fix it" and every location. When a dimension is floored at
|
|
504
|
+
0 (more than 100 points deducted), no single fix in it moves Overall yet: its rows show +0 and the plan says how
|
|
505
|
+
many points must go first.
|
|
506
|
+
|
|
335
507
|
## Outputs, GitHub Action and pre-commit
|
|
336
508
|
|
|
337
509
|
| flag | prints |
|
|
@@ -345,8 +517,9 @@ Every format is deterministic: no timestamps, no absolute paths. `--fail-on` wor
|
|
|
345
517
|
`scan` tool keeps returning JSON.
|
|
346
518
|
|
|
347
519
|
**GitHub Action** (`action.yml` in this repo, a composite action). It installs code-guardian from the action's own
|
|
348
|
-
checkout, writes `code-guardian.sarif`, adds the Markdown report to the job summary
|
|
349
|
-
scanning, and then fails the job when a finding is at or above
|
|
520
|
+
checkout, writes `code-guardian.sarif`, adds the Markdown report to the job summary (it opens with the status and
|
|
521
|
+
the coverage line), uploads the SARIF to code scanning, and then fails the job when a finding is at or above
|
|
522
|
+
`fail-on`:
|
|
350
523
|
|
|
351
524
|
```yaml
|
|
352
525
|
name: code-guardian
|
|
@@ -359,15 +532,15 @@ jobs:
|
|
|
359
532
|
runs-on: ubuntu-latest
|
|
360
533
|
steps:
|
|
361
534
|
- uses: actions/checkout@v4
|
|
362
|
-
- uses: LuminariSoftwares/code-guardian@v0.
|
|
535
|
+
- uses: LuminariSoftwares/code-guardian@v0.3.3
|
|
363
536
|
with:
|
|
364
537
|
path: .
|
|
365
538
|
fail-on: FAIL # or WARN
|
|
366
539
|
online: "false" # "true" asks PyPI about unknown dependency names
|
|
367
540
|
```
|
|
368
541
|
|
|
369
|
-
Inputs: `path`, `fail-on`, `online`, `
|
|
370
|
-
`python-version` (default `3.11`). Outputs: `overall`, `grade`, `exit-code`. Inputs reach the shell only through
|
|
542
|
+
Inputs: `path`, `fail-on`, `online`, `baseline` (a committed baseline file: gate on new findings only),
|
|
543
|
+
`sarif-file` (default `code-guardian.sarif`), `upload-sarif` (default `true`), `python-version` (default `3.11`). Outputs: `overall`, `grade`, `status` (`COMPLETE`, `PARTIAL` or `INVALID`), `exit-code`. Inputs reach the shell only through
|
|
371
544
|
environment variables, never by text substitution.
|
|
372
545
|
|
|
373
546
|
**pre-commit** (`.pre-commit-hooks.yaml`). It scans the whole repository, not just the staged files:
|
|
@@ -375,10 +548,12 @@ environment variables, never by text substitution.
|
|
|
375
548
|
```yaml
|
|
376
549
|
repos:
|
|
377
550
|
- repo: https://github.com/LuminariSoftwares/code-guardian
|
|
378
|
-
rev: v0.
|
|
551
|
+
rev: v0.3.3
|
|
379
552
|
hooks:
|
|
380
553
|
- id: code-guardian
|
|
381
554
|
# args: [--fail-on, WARN]
|
|
555
|
+
# or, with a committed baseline, fail only on new findings:
|
|
556
|
+
# - id: code-guardian-new
|
|
382
557
|
```
|
|
383
558
|
|
|
384
559
|
## Check catalogue
|
|
@@ -406,7 +581,10 @@ above. Two rules apply across Safe:
|
|
|
406
581
|
| `net.bind_all` | Safe | A server binds 0.0.0.0 or ::, so other machines can reach it. | Bind to 127.0.0.1 unless other machines must connect. | Containers usually must bind 0.0.0.0; a test that asserts the bind is refused still matches. |
|
|
407
582
|
| `net.egress` | Safe | The egress inventory: every URL and IP literal and every network call site. WARN for a public IP, or plain http/ws/ftp to a public name or to a host set at runtime. | Use https, and confirm each destination is expected. | Local-network and documentation hosts stay INFO; a URL in a help string is still inventoried. |
|
|
408
583
|
| `obfuscation` | Safe | exec/eval of base64, zlib, marshal or hex-decoded data (FAIL); long encoded blobs or high-entropy literals (WARN). | Decode the payload offline and read what it does before running anything. | Embedded icons, test vectors and hashes can look like blobs. |
|
|
584
|
+
| `secret.history` | Safe | With scan --history: a key shape (the secret.key API-key shapes, AWS secret keys, private-key blocks) was added by a commit and is no longer in that file. Anyone with a clone can still read it, and it may still be valid. The finding names the short sha and the path, never the value. | Rotate the key now; deleting it does not remove it from history. Then consider git filter-repo. | Keys that were revoked long ago, and test keys committed on purpose; a key still in the file is secret.key. |
|
|
409
585
|
| `secret.key` | Safe | An API-key shape (OpenAI-style sk-, GitHub, Google, AWS, Stripe live, Slack, Hugging Face and others), an AWS secret key, a private-key block, a password-like literal or a high-entropy literal in a name ending _KEY or _TOKEN is written into a file (a Stripe test key is INFO). | Move it to an environment variable or a secret store, and rotate it: it is in the history. | Test fixtures and documentation samples; values that call themselves fake (mock, dummy, example) are skipped. |
|
|
586
|
+
| `privacy.pii_log` | Safe | A name for personal data (email, phone, ssn, dob/birth, a person's address, passport, credit card or card number, iban, ip address) flows into logging.*, a logger method or print, directly or through a variable in the same function. | Log an id or a masked value instead; logs are copied and kept far longer than the data. | Names that only look personal (a count of emails is named emails_n, not email); taint is one function deep. |
|
|
587
|
+
| `privacy.pii_prompt` | Safe | The same personal-data names flow into an LLM prompt: a messages= or prompt= argument, or a .chat(, .complete( or .generate( call. | Send an id or a redacted value; check the provider's data terms before sending personal data. | A .complete( or .generate( method that is not a model call; a prompt built in another function is missed. |
|
|
410
588
|
| `supply.lookalike` | Safe | The name is one or two edits away from a known package without being it: the typosquatting pattern. | Check you meant the well-known package. | Real packages with names close to a popular one. |
|
|
411
589
|
| `supply.unknown_package` | Safe | Offline: the name is not in the bundled list of known packages (INFO). Online: PyPI has no such project (FAIL), or it is new or has a single release (WARN). | Check the name is the one you meant; remove names an assistant invented; run with --online. | Offline misses are common for less-known real packages, which is why they are INFO. Existing is not safe. |
|
|
412
590
|
| `tls.off` | Safe | verify=False, ssl._create_unverified_context, CERT_NONE or check_hostname = False. | Remove it; point at a CA bundle if the certificate is private. | Rare: code that deliberately probes broken servers. |
|
|
@@ -416,6 +594,9 @@ above. Two rules apply across Safe:
|
|
|
416
594
|
| `works.entry_missing` | Works | A [project.scripts] (or setup.cfg console script) target module:function is not in the tree. | Point the script at an existing module:function. | Modules generated at build time. |
|
|
417
595
|
| `works.import_unresolved` | Works | An absolute import that is not in the standard library, not in the repo and not a declared dependency. | Declare the distribution that provides it, or guard an optional import with try/except ImportError. | Packages pulled in only by another dependency (they work by accident); import names the mapping table misses. |
|
|
418
596
|
| `works.relative_missing` | Works | from .x import y points at a module (or a package name) the repo does not have. | Fix the module path or add the missing module. | Modules generated at build time. |
|
|
597
|
+
| `works.todo_stub` | Works | A function whose body is only pass, ..., or raise NotImplementedError (a docstring allowed), and the entry points reach it: it will be called and do nothing. Abstract methods, Protocol classes, @overload and methods a subclass overrides do not count. | Implement it, or delete it and its callers; mark it @abc.abstractmethod if subclasses must implement it. | Hook methods meant to be overridden by users of a library; no-op callbacks kept on purpose. |
|
|
598
|
+
| `works.commented_code` | Works | A run of 5 or more consecutive comment lines that parse as Python (once per file, with the count of blocks). | Delete it: version control keeps the old code. | Code samples in comments that explain an API; INFO, never scored. |
|
|
599
|
+
| `works.dead_flag` | Works | A module-level ALL_CAPS boolean constant (DEBUG = False) that no file reads, imports or names. | Delete it, or wire it to the code it was meant to switch. | Flags read only by another tool or by string name; INFO, never scored. |
|
|
419
600
|
| `works.syntax` | Works | ast.parse rejects the file, so it cannot be imported or run (INFO when the project needs a newer Python than the one scanning). | Fix the syntax error; port Python 2 code to Python 3. | Files kept on purpose for another interpreter, or templates with a .py name. |
|
|
420
601
|
| `works.test_no_assert` | Works | Test functions with no assert, no pytest.raises/warns, no self.assert* and no call to an asserting helper. | Assert on the result; use pytest.raises for expected errors. | Smoke tests that only check nothing raises; helpers defined in another file. |
|
|
421
602
|
| `works.tests_missing` | Works | There are no test files at all. | Add a tests/ folder with at least one test per entry point. | Tests kept in another repository. |
|
|
@@ -489,12 +670,14 @@ Guarantees, each covered by a test against a stub model on loopback:
|
|
|
489
670
|
|
|
490
671
|
## MCP server
|
|
491
672
|
|
|
492
|
-
`code-guardian-mcp` (or `code-guardian mcp`) speaks MCP over stdio with
|
|
673
|
+
`code-guardian-mcp` (or `code-guardian mcp`) speaks MCP over stdio with five read-only tools, each with a `title`
|
|
493
674
|
and `readOnlyHint: true`, `destructiveHint: false`, `idempotentHint: true`:
|
|
494
675
|
|
|
495
676
|
| tool | does | `openWorldHint` |
|
|
496
677
|
|---|---|---|
|
|
497
|
-
| `scan` | the full report for `path` (+ optional `goal`, `fail_on`; the reply carries `exit_code`) | false |
|
|
678
|
+
| `scan` | the full report for `path` (+ optional `goal`, `fail_on`, `baseline`; the reply carries `exit_code`) | false |
|
|
679
|
+
| `diff` | `before` and `after` (each a scan report as JSON text, or a path to a `scan --json` file): fixed, new, unchanged, score deltas, verdict and `exit_code` | false |
|
|
680
|
+
| `plan` | the repair plan for `path` (+ optional `top`, default 10, and `goal`) | false |
|
|
498
681
|
| `orphans` | the hygiene section for `path` | false |
|
|
499
682
|
| `explain` | `scan` plus the model explanation | true |
|
|
500
683
|
|
|
@@ -524,7 +707,8 @@ The `env` block is only needed for `explain`.
|
|
|
524
707
|
|
|
525
708
|
- **Only Python is analysed.** Manifests (`package.json`, `setup.py`, `setup.cfg`, `pyproject.toml`,
|
|
526
709
|
`requirements*.txt`) are read for install hooks and pins; every other language gets the secret scan only, and the
|
|
527
|
-
|
|
710
|
+
`coverage:` line says which files (see [What it analyses](#what-it-analyses)). An `eval` of request input in a
|
|
711
|
+
`.js` file is not seen.
|
|
528
712
|
- **Static analysis cannot see runtime behaviour.** A finding means the code is there, not that it runs; code that
|
|
529
713
|
builds its behaviour at runtime (downloaded plugins, `exec` of fetched text) is flagged where the parse can see it
|
|
530
714
|
and invisible where it cannot. A grade A is "nothing the scanners recognise", not "proven safe".
|
|
@@ -540,11 +724,11 @@ The `env` block is only needed for `explain`.
|
|
|
540
724
|
|
|
541
725
|
```
|
|
542
726
|
python -m code_guardian.scanners --selftest
|
|
543
|
-
scanners selftest:
|
|
727
|
+
scanners selftest: 27 checks, 27 passed, 0 failed
|
|
544
728
|
```
|
|
545
729
|
|
|
546
730
|
Each of `finding`, `walk`, `scanners`, `manifests`, `score`, `goal`, `orphans`, `works`, `complete`, `supply`,
|
|
547
|
-
`explain`, `report`, `cli` and `
|
|
731
|
+
`explain`, `report`, `cli`, `mcp_server`, `baseline`, `verify`, `history` and `leftovers` has one; `tests/test_selftests.py` runs them all.
|
|
548
732
|
|
|
549
733
|
## License
|
|
550
734
|
|