github-security-report 0.6.0__tar.gz → 0.7.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.
- {github_security_report-0.6.0 → github_security_report-0.7.0}/PKG-INFO +56 -18
- {github_security_report-0.6.0 → github_security_report-0.7.0}/README.md +55 -17
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/_version.py +2 -2
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/categories.py +16 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/classify.py +56 -19
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/cli.py +3 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/client.py +64 -30
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/collect.py +63 -8
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/config.py +16 -1
- github_security_report-0.7.0/src/github_security_report/gating.py +189 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/models.py +18 -2
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/render/html.py +20 -1
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/render/markdown.py +11 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/render/slack.py +13 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/render/terminal.py +13 -1
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/report.py +26 -3
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/rulesets.py +15 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/severity.py +7 -7
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/templates/report.html.j2 +4 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/.gitignore +0 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/LICENSE +0 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/LICENSES/Apache-2.0.txt +0 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/pyproject.toml +0 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/scripts/README.md +0 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/__init__.py +0 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/gitctx.py +0 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/posture.py +0 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/py.typed +0 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/remediate.py +0 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/render/__init__.py +0 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/runner.py +0 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/scope.py +0 -0
- {github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/templates/index.html.j2 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github-security-report
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Security and quality reporting across GitHub organisations
|
|
5
5
|
Project-URL: Homepage, https://github.com/lfreleng-actions/github-security-report-action
|
|
6
6
|
Project-URL: Repository, https://github.com/lfreleng-actions/github-security-report-action
|
|
@@ -56,8 +56,8 @@ Description-Content-Type: text/markdown
|
|
|
56
56
|
|
|
57
57
|
> Security and quality **reporting** (not scanning) across GitHub
|
|
58
58
|
> organisations. Aggregates existing signals — CodeQL, OpenSSF Scorecard,
|
|
59
|
-
> zizmor, Dependabot, and secret scanning — and ranks the
|
|
60
|
-
> remediation effort goes where it is needed.
|
|
59
|
+
> zizmor, aislop (AI slop), Dependabot, and secret scanning — and ranks the
|
|
60
|
+
> worst offenders so remediation effort goes where it is needed.
|
|
61
61
|
|
|
62
62
|
## 🗒️ Published reports
|
|
63
63
|
|
|
@@ -82,10 +82,21 @@ stay brevity-first; the explanatory per-category description and documentation
|
|
|
82
82
|
link are shown only on the richer Markdown and HTML (GitHub Pages) outputs.
|
|
83
83
|
|
|
84
84
|
The single GitHub code-scanning feed is partitioned by `tool.name` into CodeQL,
|
|
85
|
-
Scorecard, and
|
|
86
|
-
back to code-scanning findings. See [`docs/BRIEF.md`](docs/BRIEF.md)
|
|
87
|
-
[`docs/phase0-findings.md`](docs/phase0-findings.md) for the full design and
|
|
88
|
-
API research it is built on.
|
|
85
|
+
Scorecard, zizmor, and aislop; Scorecard prefers the external aggregate score
|
|
86
|
+
and falls back to code-scanning findings. See [`docs/BRIEF.md`](docs/BRIEF.md)
|
|
87
|
+
and [`docs/phase0-findings.md`](docs/phase0-findings.md) for the full design and
|
|
88
|
+
the API research it is built on.
|
|
89
|
+
|
|
90
|
+
The workflow-driven signals (OpenSSF Scorecard, zizmor, aislop) only produce
|
|
91
|
+
data when an organisation has deployed supporting workflows. The tool checks
|
|
92
|
+
for that support cheaply before collecting (**feature gating**): an
|
|
93
|
+
organisation with no evidence of a tool — no ruleset requiring its workflow,
|
|
94
|
+
no alerts, no analyses on a sample of repositories — gets a single
|
|
95
|
+
`⏩ Skipping feature: organisation support missing` line for that section
|
|
96
|
+
instead of a nag list. See the
|
|
97
|
+
[organisation scan setup guide](docs/org-scan-setup.md) for the required
|
|
98
|
+
workflows, and disable the check with `report.gating: false` if you want to
|
|
99
|
+
probe everything regardless.
|
|
89
100
|
|
|
90
101
|
Further sections report **configuration posture** and **freshness** as plain
|
|
91
102
|
tables (org mode):
|
|
@@ -153,7 +164,7 @@ organisation and **Repository access** set to *All repositories*, then grant:
|
|
|
153
164
|
| Metadata | Mandatory baseline; listing organisation repositories |
|
|
154
165
|
| Contents | `.github/dependabot.yml`, latest release, and tag dates |
|
|
155
166
|
| Dependabot alerts | Open Dependabot vulnerability alerts |
|
|
156
|
-
| Code scanning alerts | CodeQL / Scorecard / zizmor findings |
|
|
167
|
+
| Code scanning alerts | CodeQL / Scorecard / zizmor / aislop findings |
|
|
157
168
|
| Secret scanning alerts | Open secret-scanning alerts |
|
|
158
169
|
| Administration | Dependabot enablement + security-updates status, and effective branch rules |
|
|
159
170
|
|
|
@@ -311,7 +322,7 @@ both true.
|
|
|
311
322
|
The example above hides Zizmor on every surface, and keeps Releases / Tagging
|
|
312
323
|
out of the terminal and Slack while still publishing it to the Markdown and HTML
|
|
313
324
|
Pages output. The valid category keys are: `codeql`, `scorecard`, `zizmor`,
|
|
314
|
-
`dependabot_alerts`, `secret_scanning`, `dependabot_alerts_enabled`,
|
|
325
|
+
`aislop`, `dependabot_alerts`, `secret_scanning`, `dependabot_alerts_enabled`,
|
|
315
326
|
`dependabot_updates_enabled`, `dependabot_cooldown`, `releases`,
|
|
316
327
|
`mutable_releases`, `private_vulnerability_reporting`. Like the other `report`
|
|
317
328
|
settings, `categories` can be set
|
|
@@ -328,22 +339,49 @@ unless every org sharing that channel also disables it (this mirrors the
|
|
|
328
339
|
most-generous `top_n` rule applied to the same grouping). The terminal, Markdown
|
|
329
340
|
and HTML surfaces are per-org and are not affected by this union.
|
|
330
341
|
|
|
342
|
+
### Organisation feature gating
|
|
343
|
+
|
|
344
|
+
The workflow-driven signals (OpenSSF Scorecard, zizmor, aislop) need
|
|
345
|
+
organisation-deployed workflows before they produce any data (see the
|
|
346
|
+
[organisation scan setup guide](docs/org-scan-setup.md)). By default the tool
|
|
347
|
+
runs a cheap support check per organisation before collecting each of them:
|
|
348
|
+
evidence is an org ruleset requiring the tool's workflow, existing
|
|
349
|
+
code-scanning alerts from the tool, analyses on a sample of repositories, or
|
|
350
|
+
(for Scorecard) an external scorecard.dev score. A signal with no evidence is
|
|
351
|
+
**skipped** — not probed per repository, not classified — and its section
|
|
352
|
+
shows a single `⏩ Skipping feature: organisation support missing` line
|
|
353
|
+
linking the setup guide, on every output surface. Set `report.gating` to
|
|
354
|
+
`false` (globally or per organisation) to always probe everything:
|
|
355
|
+
|
|
356
|
+
```json
|
|
357
|
+
{
|
|
358
|
+
"report": { "gating": false },
|
|
359
|
+
"organizations": [{ "name": "lfreleng-actions" }]
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Gating decides **collection**; the per-category render toggles above decide
|
|
364
|
+
**presentation**. A skipped section still renders (as the one-line notice)
|
|
365
|
+
unless its category is also disabled.
|
|
366
|
+
|
|
331
367
|
### Pass/fail severity cutoff
|
|
332
368
|
|
|
333
|
-
The severity-ranked signals (CodeQL, Scorecard, Zizmor, Dependabot
|
|
334
|
-
`fail_severity` cutoff to decide when a repository counts as a
|
|
335
|
-
repository is flagged as an offender only when it carries a finding
|
|
336
|
-
above** the cutoff; findings below it fold into the clean count.
|
|
337
|
-
(lowest to highest) `informational`, `low`, `medium`, `high`,
|
|
338
|
-
`informational` being the sub-low rung for SARIF `none` findings
|
|
339
|
-
unclassifiable alerts. Zizmor's SARIF `note` findings normalise to `low`
|
|
369
|
+
The severity-ranked signals (CodeQL, Scorecard, Zizmor, aislop, Dependabot
|
|
370
|
+
alerts) use a `fail_severity` cutoff to decide when a repository counts as a
|
|
371
|
+
failure. A repository is flagged as an offender only when it carries a finding
|
|
372
|
+
**at or above** the cutoff; findings below it fold into the clean count.
|
|
373
|
+
Severities run (lowest to highest) `informational`, `low`, `medium`, `high`,
|
|
374
|
+
`critical` — `informational` being the sub-low rung for SARIF `none` findings
|
|
375
|
+
and unclassifiable alerts. Zizmor's SARIF `note` findings normalise to `low`
|
|
340
376
|
(zizmor emits its Low findings at `note`, and the organisation scan pipeline's
|
|
341
377
|
`--min-severity low` floor keeps informational findings out of the uploaded
|
|
342
378
|
SARIF), matching the ruleset-enforced PR gate that blocks on note-and-above.
|
|
379
|
+
aislop populates the same SARIF level axis and normalises identically.
|
|
343
380
|
|
|
344
381
|
The global default cutoff is `medium`, so `low` and `informational` findings
|
|
345
|
-
pass. Zizmor
|
|
346
|
-
cutoff per category under
|
|
382
|
+
pass. Zizmor and aislop default to `low` (only `informational` passes).
|
|
383
|
+
Override the cutoff per category under
|
|
384
|
+
`report.categories.<key>.fail_severity`:
|
|
347
385
|
|
|
348
386
|
```json
|
|
349
387
|
{
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
> Security and quality **reporting** (not scanning) across GitHub
|
|
14
14
|
> organisations. Aggregates existing signals — CodeQL, OpenSSF Scorecard,
|
|
15
|
-
> zizmor, Dependabot, and secret scanning — and ranks the
|
|
16
|
-
> remediation effort goes where it is needed.
|
|
15
|
+
> zizmor, aislop (AI slop), Dependabot, and secret scanning — and ranks the
|
|
16
|
+
> worst offenders so remediation effort goes where it is needed.
|
|
17
17
|
|
|
18
18
|
## 🗒️ Published reports
|
|
19
19
|
|
|
@@ -38,10 +38,21 @@ stay brevity-first; the explanatory per-category description and documentation
|
|
|
38
38
|
link are shown only on the richer Markdown and HTML (GitHub Pages) outputs.
|
|
39
39
|
|
|
40
40
|
The single GitHub code-scanning feed is partitioned by `tool.name` into CodeQL,
|
|
41
|
-
Scorecard, and
|
|
42
|
-
back to code-scanning findings. See [`docs/BRIEF.md`](docs/BRIEF.md)
|
|
43
|
-
[`docs/phase0-findings.md`](docs/phase0-findings.md) for the full design and
|
|
44
|
-
API research it is built on.
|
|
41
|
+
Scorecard, zizmor, and aislop; Scorecard prefers the external aggregate score
|
|
42
|
+
and falls back to code-scanning findings. See [`docs/BRIEF.md`](docs/BRIEF.md)
|
|
43
|
+
and [`docs/phase0-findings.md`](docs/phase0-findings.md) for the full design and
|
|
44
|
+
the API research it is built on.
|
|
45
|
+
|
|
46
|
+
The workflow-driven signals (OpenSSF Scorecard, zizmor, aislop) only produce
|
|
47
|
+
data when an organisation has deployed supporting workflows. The tool checks
|
|
48
|
+
for that support cheaply before collecting (**feature gating**): an
|
|
49
|
+
organisation with no evidence of a tool — no ruleset requiring its workflow,
|
|
50
|
+
no alerts, no analyses on a sample of repositories — gets a single
|
|
51
|
+
`⏩ Skipping feature: organisation support missing` line for that section
|
|
52
|
+
instead of a nag list. See the
|
|
53
|
+
[organisation scan setup guide](docs/org-scan-setup.md) for the required
|
|
54
|
+
workflows, and disable the check with `report.gating: false` if you want to
|
|
55
|
+
probe everything regardless.
|
|
45
56
|
|
|
46
57
|
Further sections report **configuration posture** and **freshness** as plain
|
|
47
58
|
tables (org mode):
|
|
@@ -109,7 +120,7 @@ organisation and **Repository access** set to *All repositories*, then grant:
|
|
|
109
120
|
| Metadata | Mandatory baseline; listing organisation repositories |
|
|
110
121
|
| Contents | `.github/dependabot.yml`, latest release, and tag dates |
|
|
111
122
|
| Dependabot alerts | Open Dependabot vulnerability alerts |
|
|
112
|
-
| Code scanning alerts | CodeQL / Scorecard / zizmor findings |
|
|
123
|
+
| Code scanning alerts | CodeQL / Scorecard / zizmor / aislop findings |
|
|
113
124
|
| Secret scanning alerts | Open secret-scanning alerts |
|
|
114
125
|
| Administration | Dependabot enablement + security-updates status, and effective branch rules |
|
|
115
126
|
|
|
@@ -267,7 +278,7 @@ both true.
|
|
|
267
278
|
The example above hides Zizmor on every surface, and keeps Releases / Tagging
|
|
268
279
|
out of the terminal and Slack while still publishing it to the Markdown and HTML
|
|
269
280
|
Pages output. The valid category keys are: `codeql`, `scorecard`, `zizmor`,
|
|
270
|
-
`dependabot_alerts`, `secret_scanning`, `dependabot_alerts_enabled`,
|
|
281
|
+
`aislop`, `dependabot_alerts`, `secret_scanning`, `dependabot_alerts_enabled`,
|
|
271
282
|
`dependabot_updates_enabled`, `dependabot_cooldown`, `releases`,
|
|
272
283
|
`mutable_releases`, `private_vulnerability_reporting`. Like the other `report`
|
|
273
284
|
settings, `categories` can be set
|
|
@@ -284,22 +295,49 @@ unless every org sharing that channel also disables it (this mirrors the
|
|
|
284
295
|
most-generous `top_n` rule applied to the same grouping). The terminal, Markdown
|
|
285
296
|
and HTML surfaces are per-org and are not affected by this union.
|
|
286
297
|
|
|
298
|
+
### Organisation feature gating
|
|
299
|
+
|
|
300
|
+
The workflow-driven signals (OpenSSF Scorecard, zizmor, aislop) need
|
|
301
|
+
organisation-deployed workflows before they produce any data (see the
|
|
302
|
+
[organisation scan setup guide](docs/org-scan-setup.md)). By default the tool
|
|
303
|
+
runs a cheap support check per organisation before collecting each of them:
|
|
304
|
+
evidence is an org ruleset requiring the tool's workflow, existing
|
|
305
|
+
code-scanning alerts from the tool, analyses on a sample of repositories, or
|
|
306
|
+
(for Scorecard) an external scorecard.dev score. A signal with no evidence is
|
|
307
|
+
**skipped** — not probed per repository, not classified — and its section
|
|
308
|
+
shows a single `⏩ Skipping feature: organisation support missing` line
|
|
309
|
+
linking the setup guide, on every output surface. Set `report.gating` to
|
|
310
|
+
`false` (globally or per organisation) to always probe everything:
|
|
311
|
+
|
|
312
|
+
```json
|
|
313
|
+
{
|
|
314
|
+
"report": { "gating": false },
|
|
315
|
+
"organizations": [{ "name": "lfreleng-actions" }]
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Gating decides **collection**; the per-category render toggles above decide
|
|
320
|
+
**presentation**. A skipped section still renders (as the one-line notice)
|
|
321
|
+
unless its category is also disabled.
|
|
322
|
+
|
|
287
323
|
### Pass/fail severity cutoff
|
|
288
324
|
|
|
289
|
-
The severity-ranked signals (CodeQL, Scorecard, Zizmor, Dependabot
|
|
290
|
-
`fail_severity` cutoff to decide when a repository counts as a
|
|
291
|
-
repository is flagged as an offender only when it carries a finding
|
|
292
|
-
above** the cutoff; findings below it fold into the clean count.
|
|
293
|
-
(lowest to highest) `informational`, `low`, `medium`, `high`,
|
|
294
|
-
`informational` being the sub-low rung for SARIF `none` findings
|
|
295
|
-
unclassifiable alerts. Zizmor's SARIF `note` findings normalise to `low`
|
|
325
|
+
The severity-ranked signals (CodeQL, Scorecard, Zizmor, aislop, Dependabot
|
|
326
|
+
alerts) use a `fail_severity` cutoff to decide when a repository counts as a
|
|
327
|
+
failure. A repository is flagged as an offender only when it carries a finding
|
|
328
|
+
**at or above** the cutoff; findings below it fold into the clean count.
|
|
329
|
+
Severities run (lowest to highest) `informational`, `low`, `medium`, `high`,
|
|
330
|
+
`critical` — `informational` being the sub-low rung for SARIF `none` findings
|
|
331
|
+
and unclassifiable alerts. Zizmor's SARIF `note` findings normalise to `low`
|
|
296
332
|
(zizmor emits its Low findings at `note`, and the organisation scan pipeline's
|
|
297
333
|
`--min-severity low` floor keeps informational findings out of the uploaded
|
|
298
334
|
SARIF), matching the ruleset-enforced PR gate that blocks on note-and-above.
|
|
335
|
+
aislop populates the same SARIF level axis and normalises identically.
|
|
299
336
|
|
|
300
337
|
The global default cutoff is `medium`, so `low` and `informational` findings
|
|
301
|
-
pass. Zizmor
|
|
302
|
-
cutoff per category under
|
|
338
|
+
pass. Zizmor and aislop default to `low` (only `informational` passes).
|
|
339
|
+
Override the cutoff per category under
|
|
340
|
+
`report.categories.<key>.fail_severity`:
|
|
303
341
|
|
|
304
342
|
```json
|
|
305
343
|
{
|
{github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/_version.py
RENAMED
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.7.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 7, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -31,6 +31,7 @@ class CategoryKey(str, Enum):
|
|
|
31
31
|
CODEQL = "codeql"
|
|
32
32
|
SCORECARD = "scorecard"
|
|
33
33
|
ZIZMOR = "zizmor"
|
|
34
|
+
AISLOP = "aislop"
|
|
34
35
|
DEPENDABOT_ALERTS = "dependabot_alerts"
|
|
35
36
|
SECRET_SCANNING = "secret_scanning"
|
|
36
37
|
DEPENDABOT_ALERTS_ENABLED = "dependabot_alerts_enabled"
|
|
@@ -110,6 +111,21 @@ _CATEGORIES: dict[CategoryKey, CategoryMeta] = {
|
|
|
110
111
|
# matching the ruleset-enforced PR gate that blocks on note-and-above.
|
|
111
112
|
fail_severity=Severity.LOW,
|
|
112
113
|
),
|
|
114
|
+
CategoryKey.AISLOP: CategoryMeta(
|
|
115
|
+
key=CategoryKey.AISLOP,
|
|
116
|
+
title="AI Slop Analysis",
|
|
117
|
+
pass_label="Clean",
|
|
118
|
+
fail_label=None,
|
|
119
|
+
url="https://github.com/scanaislop/aislop",
|
|
120
|
+
description=(
|
|
121
|
+
"aislop AI-slop / code-quality findings, ranked worst-first by "
|
|
122
|
+
"severity."
|
|
123
|
+
),
|
|
124
|
+
# aislop, like zizmor, populates only the SARIF level axis
|
|
125
|
+
# (error/warning/note); "note" normalises to LOW (see severity.py), so
|
|
126
|
+
# any aislop finding fails -- matching the ruleset-enforced PR gate.
|
|
127
|
+
fail_severity=Severity.LOW,
|
|
128
|
+
),
|
|
113
129
|
CategoryKey.DEPENDABOT_ALERTS: CategoryMeta(
|
|
114
130
|
key=CategoryKey.DEPENDABOT_ALERTS,
|
|
115
131
|
title="Dependabot: Security Alerts",
|
{github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/classify.py
RENAMED
|
@@ -6,8 +6,8 @@ Pure, transport-free logic encoding every Phase 0 finding
|
|
|
6
6
|
(``docs/phase0-findings.md``):
|
|
7
7
|
|
|
8
8
|
- the single code-scanning feed is partitioned by ``tool.name`` into CodeQL,
|
|
9
|
-
Scorecard and
|
|
10
|
-
- CodeQL/Scorecard/zizmor enablement is the presence of that tool in
|
|
9
|
+
Scorecard, zizmor and aislop -- counts are filtered per tool;
|
|
10
|
+
- CodeQL/Scorecard/zizmor/aislop enablement is the presence of that tool in
|
|
11
11
|
``code-scanning/analyses`` (not ``default-setup``); a 404 on code scanning
|
|
12
12
|
means it is disabled entirely;
|
|
13
13
|
- secret scanning 404 = disabled, 200 [] = enabled-clean;
|
|
@@ -18,11 +18,12 @@ Pure, transport-free logic encoding every Phase 0 finding
|
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
-
from collections.abc import Mapping
|
|
21
|
+
from collections.abc import Callable, Mapping, Set
|
|
22
22
|
from dataclasses import dataclass, field
|
|
23
23
|
|
|
24
24
|
from github_security_report import severity
|
|
25
25
|
from github_security_report.models import (
|
|
26
|
+
CODE_SCANNING_TOOLS,
|
|
26
27
|
Repo,
|
|
27
28
|
RepoSignal,
|
|
28
29
|
RepoState,
|
|
@@ -50,7 +51,7 @@ class RepoFacts:
|
|
|
50
51
|
"""Raw per-repository facts gathered by the client, pre-classification."""
|
|
51
52
|
|
|
52
53
|
repo: Repo
|
|
53
|
-
# Code scanning (covers CodeQL, Scorecard, zizmor).
|
|
54
|
+
# Code scanning (covers CodeQL, Scorecard, zizmor, aislop).
|
|
54
55
|
code_scanning_status: int = 200 # 200 ok, 404 disabled, 403 forbidden
|
|
55
56
|
code_scanning_tools: set[str] = field(default_factory=set) # analyses tool names
|
|
56
57
|
code_scanning_alerts: list[dict] = field(default_factory=list) # all tools
|
|
@@ -149,7 +150,10 @@ def classify_codeql(
|
|
|
149
150
|
facts: RepoFacts, fail_severities: FailSeverities | None = None
|
|
150
151
|
) -> RepoSignal:
|
|
151
152
|
return _code_scanning_tool_signal(
|
|
152
|
-
facts,
|
|
153
|
+
facts,
|
|
154
|
+
SignalType.CODEQL,
|
|
155
|
+
CODE_SCANNING_TOOLS[SignalType.CODEQL],
|
|
156
|
+
fail_severities,
|
|
153
157
|
)
|
|
154
158
|
|
|
155
159
|
|
|
@@ -157,7 +161,21 @@ def classify_zizmor(
|
|
|
157
161
|
facts: RepoFacts, fail_severities: FailSeverities | None = None
|
|
158
162
|
) -> RepoSignal:
|
|
159
163
|
return _code_scanning_tool_signal(
|
|
160
|
-
facts,
|
|
164
|
+
facts,
|
|
165
|
+
SignalType.ZIZMOR,
|
|
166
|
+
CODE_SCANNING_TOOLS[SignalType.ZIZMOR],
|
|
167
|
+
fail_severities,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def classify_aislop(
|
|
172
|
+
facts: RepoFacts, fail_severities: FailSeverities | None = None
|
|
173
|
+
) -> RepoSignal:
|
|
174
|
+
return _code_scanning_tool_signal(
|
|
175
|
+
facts,
|
|
176
|
+
SignalType.AISLOP,
|
|
177
|
+
CODE_SCANNING_TOOLS[SignalType.AISLOP],
|
|
178
|
+
fail_severities,
|
|
161
179
|
)
|
|
162
180
|
|
|
163
181
|
|
|
@@ -166,8 +184,13 @@ def classify_scorecard(
|
|
|
166
184
|
) -> RepoSignal:
|
|
167
185
|
"""Scorecard: prefer the external aggregate score, else code-scanning findings."""
|
|
168
186
|
repo = facts.repo
|
|
169
|
-
counts = count_code_scanning(
|
|
170
|
-
|
|
187
|
+
counts = count_code_scanning(
|
|
188
|
+
facts.code_scanning_alerts, CODE_SCANNING_TOOLS[SignalType.SCORECARD]
|
|
189
|
+
)
|
|
190
|
+
has_cs = (
|
|
191
|
+
CODE_SCANNING_TOOLS[SignalType.SCORECARD] in facts.code_scanning_tools
|
|
192
|
+
and facts.code_scanning_status == 200
|
|
193
|
+
)
|
|
171
194
|
cutoff = _cutoff(SignalType.SCORECARD, fail_severities)
|
|
172
195
|
|
|
173
196
|
if facts.scorecard_status == 200 and facts.scorecard_score is not None:
|
|
@@ -241,22 +264,36 @@ def classify_dependabot(
|
|
|
241
264
|
return RepoSignal(repo, SignalType.DEPENDABOT, state, counts=counts)
|
|
242
265
|
|
|
243
266
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
267
|
+
# One classifier per signal, keyed so orchestration can skip gated-out signals.
|
|
268
|
+
_Classifier = Callable[["RepoFacts", "FailSeverities | None"], RepoSignal]
|
|
269
|
+
|
|
270
|
+
_CLASSIFIERS: dict[SignalType, _Classifier] = {
|
|
271
|
+
SignalType.CODEQL: classify_codeql,
|
|
272
|
+
SignalType.SCORECARD: classify_scorecard,
|
|
273
|
+
SignalType.ZIZMOR: classify_zizmor,
|
|
274
|
+
SignalType.AISLOP: classify_aislop,
|
|
275
|
+
SignalType.DEPENDABOT: classify_dependabot,
|
|
276
|
+
SignalType.SECRET_SCANNING: classify_secret_scanning,
|
|
277
|
+
}
|
|
251
278
|
|
|
252
279
|
|
|
253
280
|
def classify_repo(
|
|
254
|
-
facts: RepoFacts,
|
|
281
|
+
facts: RepoFacts,
|
|
282
|
+
fail_severities: FailSeverities | None = None,
|
|
283
|
+
*,
|
|
284
|
+
skip: Set[SignalType] = frozenset(),
|
|
255
285
|
) -> list[RepoSignal]:
|
|
256
|
-
"""Classify a repository across all
|
|
286
|
+
"""Classify a repository across all signals (minus any in ``skip``).
|
|
257
287
|
|
|
258
288
|
``fail_severities`` optionally overrides the per-signal fail-severity cutoff
|
|
259
289
|
(otherwise each signal uses its category default); it governs which findings
|
|
260
|
-
are severe enough to mark a repository as an offender.
|
|
290
|
+
are severe enough to mark a repository as an offender. ``skip`` names
|
|
291
|
+
signals to leave unclassified entirely -- used when organisation feature
|
|
292
|
+
gating (:mod:`gating`) found no support for a workflow-driven tool, so the
|
|
293
|
+
repository is neither nagged nor counted for that signal.
|
|
261
294
|
"""
|
|
262
|
-
return [
|
|
295
|
+
return [
|
|
296
|
+
classifier(facts, fail_severities)
|
|
297
|
+
for signal, classifier in _CLASSIFIERS.items()
|
|
298
|
+
if signal not in skip
|
|
299
|
+
]
|
{github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/cli.py
RENAMED
|
@@ -128,6 +128,9 @@ def _org_to_dict(org: OrgReport) -> dict:
|
|
|
128
128
|
"clean_count": s.clean_count,
|
|
129
129
|
"nag": [r.full_name for r in s.nag_repos],
|
|
130
130
|
"unknown_count": s.unknown_count,
|
|
131
|
+
# True when organisation feature gating skipped this signal
|
|
132
|
+
# (no supporting workflows found), so nothing was collected.
|
|
133
|
+
"skipped": s.skipped,
|
|
131
134
|
}
|
|
132
135
|
for s in org.sections
|
|
133
136
|
],
|
{github_security_report-0.6.0 → github_security_report-0.7.0}/src/github_security_report/client.py
RENAMED
|
@@ -21,7 +21,12 @@ from typing import cast
|
|
|
21
21
|
|
|
22
22
|
import httpx
|
|
23
23
|
|
|
24
|
-
from github_security_report.models import
|
|
24
|
+
from github_security_report.models import (
|
|
25
|
+
CODE_SCANNING_TOOLS,
|
|
26
|
+
ReleaseRef,
|
|
27
|
+
Repo,
|
|
28
|
+
RepoGraphData,
|
|
29
|
+
)
|
|
25
30
|
|
|
26
31
|
log = logging.getLogger(__name__)
|
|
27
32
|
|
|
@@ -122,8 +127,9 @@ query($owner: String!, $name: String!) {
|
|
|
122
127
|
# The code-scanning-derived signal tools whose enablement we probe per repo.
|
|
123
128
|
# Each is checked via the analyses ``tool_name`` filter (a definitive presence
|
|
124
129
|
# test) rather than scanning the analysis history, which a busy repo could push
|
|
125
|
-
# a low-frequency tool out of.
|
|
126
|
-
|
|
130
|
+
# a low-frequency tool out of. Derived from the shared signal->tool mapping so
|
|
131
|
+
# adding a SARIF-uploading tool needs no client change.
|
|
132
|
+
_CODE_SCANNING_SIGNAL_TOOLS = tuple(CODE_SCANNING_TOOLS.values())
|
|
127
133
|
|
|
128
134
|
# Batched per-repo prefetch. One aliased query fetches, for many repositories
|
|
129
135
|
# at once: Dependabot-alerts enablement, the most-recent tag's commit date
|
|
@@ -558,48 +564,76 @@ class GitHubClient:
|
|
|
558
564
|
# ------------------------------------------------------------------ #
|
|
559
565
|
# Per-repo enabled-probes
|
|
560
566
|
# ------------------------------------------------------------------ #
|
|
561
|
-
async def
|
|
562
|
-
|
|
567
|
+
async def _analyses_tool_probe(
|
|
568
|
+
self, org: str, repo: str, tool: str
|
|
569
|
+
) -> tuple[int, bool]:
|
|
570
|
+
"""Probe a repo's code-scanning analyses for one tool (status, present).
|
|
563
571
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
how many analyses a busy repo has accumulated (the previous page-by-page
|
|
567
|
-
scan could miss a low-frequency tool past its page cap and wrongly nag
|
|
568
|
-
it). The first probe's status is authoritative for the endpoint (404 =
|
|
569
|
-
code scanning disabled, 403 = forbidden, 5xx/0 = indeterminate); a later
|
|
570
|
-
per-tool probe that fails is skipped (its tool goes undetected for this
|
|
571
|
-
run) rather than discarding the whole result.
|
|
572
|
+
A ``per_page=1`` + ``tool_name`` filtered read of the analyses endpoint:
|
|
573
|
+
a definitive, single-request presence test for a SARIF-uploading tool.
|
|
572
574
|
"""
|
|
573
575
|
url = f"{self._api_url}/repos/{org}/{repo}/code-scanning/analyses"
|
|
576
|
+
resp = await self._request(
|
|
577
|
+
"GET", url, params={"per_page": 1, "tool_name": tool}
|
|
578
|
+
)
|
|
579
|
+
status = resp.status_code
|
|
580
|
+
if status != 200:
|
|
581
|
+
await resp.aclose() # unread body would leak a pooled connection
|
|
582
|
+
return status, False
|
|
583
|
+
has_analyses = bool(resp.json())
|
|
584
|
+
await resp.aclose() # release the connection once the body is read
|
|
585
|
+
return 200, has_analyses
|
|
574
586
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
status = resp.status_code
|
|
580
|
-
if status != 200:
|
|
581
|
-
await resp.aclose() # unread body would leak a pooled connection
|
|
582
|
-
return status, False
|
|
583
|
-
has_analyses = bool(resp.json())
|
|
584
|
-
await resp.aclose() # release the connection once the body is read
|
|
585
|
-
return 200, has_analyses
|
|
587
|
+
async def code_scanning_tool_present(
|
|
588
|
+
self, org: str, repo: str, tool: str
|
|
589
|
+
) -> bool:
|
|
590
|
+
"""Whether ``tool`` has uploaded code-scanning analyses to this repo.
|
|
586
591
|
|
|
587
|
-
|
|
588
|
-
|
|
592
|
+
The lightweight organisation-gating probe: any non-200 (disabled,
|
|
593
|
+
forbidden, indeterminate) simply reports the tool as not present here,
|
|
594
|
+
so a gating sweep degrades to "no evidence" rather than erroring.
|
|
595
|
+
"""
|
|
596
|
+
status, present = await self._analyses_tool_probe(org, repo, tool)
|
|
597
|
+
return status == 200 and present
|
|
598
|
+
|
|
599
|
+
async def code_scanning_tools(
|
|
600
|
+
self, org: str, repo: str, tools: tuple[str, ...] | None = None
|
|
601
|
+
) -> tuple[int, set[str]]:
|
|
602
|
+
"""Return (status, enabled signal tool names) from code-scanning analyses.
|
|
603
|
+
|
|
604
|
+
Each tool in ``tools`` (default: every signal tool) is probed with the
|
|
605
|
+
analyses ``tool_name`` filter, a definitive presence test that does not
|
|
606
|
+
depend on how many analyses a busy repo has accumulated (the previous
|
|
607
|
+
page-by-page scan could miss a low-frequency tool past its page cap and
|
|
608
|
+
wrongly nag it). The first probe's status is authoritative for the
|
|
609
|
+
endpoint (404 = code scanning disabled, 403 = forbidden, 5xx/0 =
|
|
610
|
+
indeterminate); a later per-tool probe that fails is skipped (its tool
|
|
611
|
+
goes undetected for this run) rather than discarding the whole result.
|
|
612
|
+
Callers pass a subset when organisation gating has already ruled some
|
|
613
|
+
tools out, saving one request per repo per skipped tool.
|
|
614
|
+
"""
|
|
615
|
+
probe_tools = tools if tools is not None else _CODE_SCANNING_SIGNAL_TOOLS
|
|
616
|
+
if not probe_tools:
|
|
617
|
+
return 200, set()
|
|
618
|
+
|
|
619
|
+
first_tool, *rest = probe_tools
|
|
620
|
+
status, has = await self._analyses_tool_probe(org, repo, first_tool)
|
|
589
621
|
if status != 200:
|
|
590
622
|
# The first probe's status is authoritative for the endpoint
|
|
591
623
|
# (404 = disabled, 403 = forbidden, 5xx/0 = indeterminate).
|
|
592
624
|
return status, set()
|
|
593
|
-
|
|
625
|
+
tools_found: set[str] = {first_tool} if has else set()
|
|
594
626
|
# The endpoint is reachable; probe the remaining tools concurrently. A
|
|
595
627
|
# later probe that fails just leaves its tool undetected for this run.
|
|
596
|
-
results = await asyncio.gather(
|
|
597
|
-
|
|
628
|
+
results = await asyncio.gather(
|
|
629
|
+
*(self._analyses_tool_probe(org, repo, tool) for tool in rest)
|
|
630
|
+
)
|
|
631
|
+
tools_found.update(
|
|
598
632
|
tool
|
|
599
633
|
for tool, (st, hit) in zip(rest, results, strict=True)
|
|
600
634
|
if st == 200 and hit
|
|
601
635
|
)
|
|
602
|
-
return 200,
|
|
636
|
+
return 200, tools_found
|
|
603
637
|
|
|
604
638
|
async def secret_scanning_status(self, org: str, repo: str) -> int:
|
|
605
639
|
"""HTTP status of the secret-scanning alerts endpoint (404 = disabled)."""
|