github-security-report 0.5.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.5.0 → github_security_report-0.7.0}/PKG-INFO +112 -21
- {github_security_report-0.5.0 → github_security_report-0.7.0}/README.md +109 -18
- {github_security_report-0.5.0 → github_security_report-0.7.0}/pyproject.toml +3 -3
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/_version.py +2 -2
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/categories.py +19 -2
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/classify.py +56 -19
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/cli.py +112 -1
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/client.py +187 -30
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/collect.py +63 -8
- {github_security_report-0.5.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.5.0 → github_security_report-0.7.0}/src/github_security_report/models.py +18 -2
- github_security_report-0.7.0/src/github_security_report/remediate.py +248 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/render/html.py +20 -1
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/render/markdown.py +11 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/render/slack.py +13 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/render/terminal.py +87 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/report.py +26 -3
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/rulesets.py +15 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/severity.py +22 -13
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/templates/report.html.j2 +4 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/.gitignore +0 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/LICENSE +0 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/LICENSES/Apache-2.0.txt +0 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/scripts/README.md +0 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/__init__.py +0 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/gitctx.py +0 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/posture.py +0 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/py.typed +0 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/render/__init__.py +0 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/runner.py +0 -0
- {github_security_report-0.5.0 → github_security_report-0.7.0}/src/github_security_report/scope.py +0 -0
- {github_security_report-0.5.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
|
|
@@ -29,7 +29,7 @@ Requires-Dist: jinja2==3.1.6
|
|
|
29
29
|
Requires-Dist: jsonschema==4.26.0
|
|
30
30
|
Requires-Dist: pyyaml==6.0.3
|
|
31
31
|
Requires-Dist: rich==15.0.0
|
|
32
|
-
Requires-Dist: typer==0.26.
|
|
32
|
+
Requires-Dist: typer==0.26.8
|
|
33
33
|
Provides-Extra: dev
|
|
34
34
|
Requires-Dist: mypy==2.1.0; extra == 'dev'
|
|
35
35
|
Requires-Dist: pytest-asyncio==1.4.0; extra == 'dev'
|
|
@@ -37,7 +37,7 @@ Requires-Dist: pytest-cov==7.1.0; extra == 'dev'
|
|
|
37
37
|
Requires-Dist: pytest==9.1.1; extra == 'dev'
|
|
38
38
|
Requires-Dist: respx==0.23.1; extra == 'dev'
|
|
39
39
|
Requires-Dist: ruff==0.15.20; extra == 'dev'
|
|
40
|
-
Requires-Dist: syrupy==5.3.
|
|
40
|
+
Requires-Dist: syrupy==5.3.4; extra == 'dev'
|
|
41
41
|
Requires-Dist: types-jsonschema==4.26.0.20260518; extra == 'dev'
|
|
42
42
|
Requires-Dist: types-pyyaml==6.0.12.20250915; extra == 'dev'
|
|
43
43
|
Description-Content-Type: text/markdown
|
|
@@ -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,19 +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
|
|
339
|
-
|
|
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`
|
|
376
|
+
(zizmor emits its Low findings at `note`, and the organisation scan pipeline's
|
|
377
|
+
`--min-severity low` floor keeps informational findings out of the uploaded
|
|
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.
|
|
340
380
|
|
|
341
381
|
The global default cutoff is `medium`, so `low` and `informational` findings
|
|
342
|
-
pass. Zizmor
|
|
343
|
-
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`:
|
|
344
385
|
|
|
345
386
|
```json
|
|
346
387
|
{
|
|
@@ -426,8 +467,57 @@ uvx github-security-report report
|
|
|
426
467
|
uvx github-security-report report --org lfreleng-actions
|
|
427
468
|
```
|
|
428
469
|
|
|
470
|
+
## Remediation
|
|
471
|
+
|
|
472
|
+
The `remediate` subcommand is the in-tool counterpart to the report: it runs the
|
|
473
|
+
same collection, then switches on each selected security feature wherever a
|
|
474
|
+
repository has it **confirmed off**. Only the offenders the report already
|
|
475
|
+
surfaces are acted on — repositories whose state could not be read are counted
|
|
476
|
+
as *unknown* and are never written to, so remediation never blind-writes.
|
|
477
|
+
|
|
478
|
+
It is **dry run by default** (these are privileged writes); pass `--apply` to
|
|
479
|
+
make changes. A single **write-capable** org-admin token (from `--token-env`,
|
|
480
|
+
default `GITHUB_TOKEN`) drives both the read and the writes across every
|
|
481
|
+
configured organisation, so it bypasses the per-org read-only `token_env` in the
|
|
482
|
+
config.
|
|
483
|
+
|
|
484
|
+
```bash
|
|
485
|
+
# An org-admin token is required: a classic PAT with the `repo` scope
|
|
486
|
+
# (administers repository security settings) plus `read:org` to enumerate repos.
|
|
487
|
+
source ~/.secrets.github.classic.god # exports $GITHUB_TOKEN
|
|
488
|
+
|
|
489
|
+
# Dry run (default): preview every change, touch nothing.
|
|
490
|
+
uvx github-security-report remediate --org lfreleng-actions
|
|
491
|
+
|
|
492
|
+
# Apply: enable every remediable feature that is off, across all configured orgs.
|
|
493
|
+
uvx github-security-report remediate \
|
|
494
|
+
--config ~/.config/github-security-report/config.json --apply
|
|
495
|
+
|
|
496
|
+
# Limit to specific categories (repeatable).
|
|
497
|
+
uvx github-security-report remediate --org lfreleng-actions \
|
|
498
|
+
--category codeql --category private_vulnerability_reporting --apply
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
The remediable categories are the simple on/off features with a documented
|
|
502
|
+
enablement endpoint:
|
|
503
|
+
|
|
504
|
+
| `--category` | Enables |
|
|
505
|
+
|---|---|
|
|
506
|
+
| `codeql` | CodeQL default setup (provisioned asynchronously) |
|
|
507
|
+
| `secret_scanning` | Secret scanning |
|
|
508
|
+
| `dependabot_alerts_enabled` | Dependabot vulnerability alerts |
|
|
509
|
+
| `dependabot_updates_enabled` | Dependabot security updates (plus alerts) |
|
|
510
|
+
| `private_vulnerability_reporting` | Private vulnerability reporting |
|
|
511
|
+
|
|
512
|
+
Qualitative findings (Scorecard, zizmor, open Dependabot alerts, cooldown,
|
|
513
|
+
release freshness/mutability) are reported but not auto-remediated. Remediation
|
|
514
|
+
is organisation-scoped (`--scope org`, the default and only supported scope).
|
|
515
|
+
|
|
429
516
|
## Bulk Remediation Scripts
|
|
430
517
|
|
|
518
|
+
The standalone scripts below predate the `remediate` subcommand and remain for
|
|
519
|
+
ad-hoc, single-feature runs. For most workflows, prefer `remediate` above.
|
|
520
|
+
|
|
431
521
|
The report ends with **nag lists** — repositories where a supported feature is
|
|
432
522
|
switched off. Where GitHub exposes the relevant toggle through its REST API,
|
|
433
523
|
the [`scripts/`](scripts/) directory ships standalone helpers that clear a whole
|
|
@@ -449,7 +539,8 @@ the current state of each repository, enables the feature where it is off, and
|
|
|
449
539
|
verifies the result.
|
|
450
540
|
|
|
451
541
|
```bash
|
|
452
|
-
# An org-admin token is required
|
|
542
|
+
# An org-admin token is required: a classic PAT with the `repo` scope
|
|
543
|
+
# (administers repository security settings) plus `read:org` to enumerate repos.
|
|
453
544
|
source ~/.secrets.github.classic.god # exports $GITHUB_TOKEN
|
|
454
545
|
|
|
455
546
|
# Dry run (default): preview every change, touch nothing.
|
|
@@ -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,19 +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
|
|
295
|
-
|
|
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`
|
|
332
|
+
(zizmor emits its Low findings at `note`, and the organisation scan pipeline's
|
|
333
|
+
`--min-severity low` floor keeps informational findings out of the uploaded
|
|
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.
|
|
296
336
|
|
|
297
337
|
The global default cutoff is `medium`, so `low` and `informational` findings
|
|
298
|
-
pass. Zizmor
|
|
299
|
-
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`:
|
|
300
341
|
|
|
301
342
|
```json
|
|
302
343
|
{
|
|
@@ -382,8 +423,57 @@ uvx github-security-report report
|
|
|
382
423
|
uvx github-security-report report --org lfreleng-actions
|
|
383
424
|
```
|
|
384
425
|
|
|
426
|
+
## Remediation
|
|
427
|
+
|
|
428
|
+
The `remediate` subcommand is the in-tool counterpart to the report: it runs the
|
|
429
|
+
same collection, then switches on each selected security feature wherever a
|
|
430
|
+
repository has it **confirmed off**. Only the offenders the report already
|
|
431
|
+
surfaces are acted on — repositories whose state could not be read are counted
|
|
432
|
+
as *unknown* and are never written to, so remediation never blind-writes.
|
|
433
|
+
|
|
434
|
+
It is **dry run by default** (these are privileged writes); pass `--apply` to
|
|
435
|
+
make changes. A single **write-capable** org-admin token (from `--token-env`,
|
|
436
|
+
default `GITHUB_TOKEN`) drives both the read and the writes across every
|
|
437
|
+
configured organisation, so it bypasses the per-org read-only `token_env` in the
|
|
438
|
+
config.
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
# An org-admin token is required: a classic PAT with the `repo` scope
|
|
442
|
+
# (administers repository security settings) plus `read:org` to enumerate repos.
|
|
443
|
+
source ~/.secrets.github.classic.god # exports $GITHUB_TOKEN
|
|
444
|
+
|
|
445
|
+
# Dry run (default): preview every change, touch nothing.
|
|
446
|
+
uvx github-security-report remediate --org lfreleng-actions
|
|
447
|
+
|
|
448
|
+
# Apply: enable every remediable feature that is off, across all configured orgs.
|
|
449
|
+
uvx github-security-report remediate \
|
|
450
|
+
--config ~/.config/github-security-report/config.json --apply
|
|
451
|
+
|
|
452
|
+
# Limit to specific categories (repeatable).
|
|
453
|
+
uvx github-security-report remediate --org lfreleng-actions \
|
|
454
|
+
--category codeql --category private_vulnerability_reporting --apply
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
The remediable categories are the simple on/off features with a documented
|
|
458
|
+
enablement endpoint:
|
|
459
|
+
|
|
460
|
+
| `--category` | Enables |
|
|
461
|
+
|---|---|
|
|
462
|
+
| `codeql` | CodeQL default setup (provisioned asynchronously) |
|
|
463
|
+
| `secret_scanning` | Secret scanning |
|
|
464
|
+
| `dependabot_alerts_enabled` | Dependabot vulnerability alerts |
|
|
465
|
+
| `dependabot_updates_enabled` | Dependabot security updates (plus alerts) |
|
|
466
|
+
| `private_vulnerability_reporting` | Private vulnerability reporting |
|
|
467
|
+
|
|
468
|
+
Qualitative findings (Scorecard, zizmor, open Dependabot alerts, cooldown,
|
|
469
|
+
release freshness/mutability) are reported but not auto-remediated. Remediation
|
|
470
|
+
is organisation-scoped (`--scope org`, the default and only supported scope).
|
|
471
|
+
|
|
385
472
|
## Bulk Remediation Scripts
|
|
386
473
|
|
|
474
|
+
The standalone scripts below predate the `remediate` subcommand and remain for
|
|
475
|
+
ad-hoc, single-feature runs. For most workflows, prefer `remediate` above.
|
|
476
|
+
|
|
387
477
|
The report ends with **nag lists** — repositories where a supported feature is
|
|
388
478
|
switched off. Where GitHub exposes the relevant toggle through its REST API,
|
|
389
479
|
the [`scripts/`](scripts/) directory ships standalone helpers that clear a whole
|
|
@@ -405,7 +495,8 @@ the current state of each repository, enables the feature where it is off, and
|
|
|
405
495
|
verifies the result.
|
|
406
496
|
|
|
407
497
|
```bash
|
|
408
|
-
# An org-admin token is required
|
|
498
|
+
# An org-admin token is required: a classic PAT with the `repo` scope
|
|
499
|
+
# (administers repository security settings) plus `read:org` to enumerate repos.
|
|
409
500
|
source ~/.secrets.github.classic.god # exports $GITHUB_TOKEN
|
|
410
501
|
|
|
411
502
|
# Dry run (default): preview every change, touch nothing.
|
|
@@ -45,7 +45,7 @@ keywords = [
|
|
|
45
45
|
]
|
|
46
46
|
dependencies = [
|
|
47
47
|
"httpx[http2]==0.28.1",
|
|
48
|
-
"typer==0.26.
|
|
48
|
+
"typer==0.26.8",
|
|
49
49
|
"rich==15.0.0",
|
|
50
50
|
"jinja2==3.1.6",
|
|
51
51
|
"jsonschema==4.26.0",
|
|
@@ -63,7 +63,7 @@ dev = [
|
|
|
63
63
|
"pytest-asyncio==1.4.0",
|
|
64
64
|
"pytest-cov==7.1.0",
|
|
65
65
|
"respx==0.23.1",
|
|
66
|
-
"syrupy==5.3.
|
|
66
|
+
"syrupy==5.3.4",
|
|
67
67
|
"mypy==2.1.0",
|
|
68
68
|
"ruff==0.15.20",
|
|
69
69
|
"types-jsonschema==4.26.0.20260518",
|
|
@@ -95,7 +95,7 @@ dev = [
|
|
|
95
95
|
"pytest-asyncio==1.4.0",
|
|
96
96
|
"pytest-cov==7.1.0",
|
|
97
97
|
"respx==0.23.1",
|
|
98
|
-
"syrupy==5.3.
|
|
98
|
+
"syrupy==5.3.4",
|
|
99
99
|
"mypy==2.1.0",
|
|
100
100
|
"ruff==0.15.20",
|
|
101
101
|
"types-jsonschema==4.26.0.20260518",
|
{github_security_report-0.5.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"
|
|
@@ -105,8 +106,24 @@ _CATEGORIES: dict[CategoryKey, CategoryMeta] = {
|
|
|
105
106
|
"Zizmor static analysis of GitHub Actions workflows, ranked "
|
|
106
107
|
"worst-first by severity."
|
|
107
108
|
),
|
|
108
|
-
#
|
|
109
|
-
#
|
|
109
|
+
# zizmor emits its Low findings at SARIF level "note", which
|
|
110
|
+
# normalises to LOW (see severity.py), so any zizmor finding fails --
|
|
111
|
+
# matching the ruleset-enforced PR gate that blocks on note-and-above.
|
|
112
|
+
fail_severity=Severity.LOW,
|
|
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.
|
|
110
127
|
fail_severity=Severity.LOW,
|
|
111
128
|
),
|
|
112
129
|
CategoryKey.DEPENDABOT_ALERTS: CategoryMeta(
|
{github_security_report-0.5.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
|
+
]
|