github-security-report 0.4.1__tar.gz → 0.6.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.4.1 → github_security_report-0.6.0}/PKG-INFO +76 -14
- {github_security_report-0.4.1 → github_security_report-0.6.0}/README.md +72 -10
- {github_security_report-0.4.1 → github_security_report-0.6.0}/pyproject.toml +5 -5
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/_version.py +2 -2
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/categories.py +20 -2
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/cli.py +118 -2
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/client.py +149 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/collect.py +21 -5
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/posture.py +22 -0
- github_security_report-0.6.0/src/github_security_report/remediate.py +248 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/render/html.py +10 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/render/markdown.py +11 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/render/slack.py +8 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/render/terminal.py +125 -11
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/report.py +3 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/severity.py +21 -12
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/templates/report.html.j2 +7 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/.gitignore +0 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/LICENSE +0 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/LICENSES/Apache-2.0.txt +0 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/scripts/README.md +0 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/__init__.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/classify.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/config.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/gitctx.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/models.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/py.typed +0 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/render/__init__.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/rulesets.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/runner.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/scope.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.6.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.6.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,15 +29,15 @@ 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'
|
|
36
36
|
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
|
-
Requires-Dist: ruff==0.15.
|
|
40
|
-
Requires-Dist: syrupy==5.3.
|
|
39
|
+
Requires-Dist: ruff==0.15.20; extra == 'dev'
|
|
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
|
|
@@ -87,7 +87,7 @@ back to code-scanning findings. See [`docs/BRIEF.md`](docs/BRIEF.md) and
|
|
|
87
87
|
[`docs/phase0-findings.md`](docs/phase0-findings.md) for the full design and the
|
|
88
88
|
API research it is built on.
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
Further sections report **configuration posture** and **freshness** as plain
|
|
91
91
|
tables (org mode):
|
|
92
92
|
|
|
93
93
|
- **Dependabot** — three tables: repositories with vulnerability **alerts not
|
|
@@ -101,6 +101,12 @@ tables (org mode):
|
|
|
101
101
|
release or tag is older than `release_max_age_days` (default 60; `0` flags
|
|
102
102
|
every eligible repository), so a repository released or tagged within that
|
|
103
103
|
window counts as recently maintained and drops out of the table.
|
|
104
|
+
- **Private Vulnerability Reporting** — repositories where GitHub's private
|
|
105
|
+
vulnerability reporting feature is **not enabled**, so security researchers
|
|
106
|
+
cannot privately disclose vulnerabilities. Probed per repository (GitHub
|
|
107
|
+
exposes no org-wide or GraphQL equivalent) and, like every other category,
|
|
108
|
+
always collected; hide it with the `private_vulnerability_reporting` render
|
|
109
|
+
toggle.
|
|
104
110
|
|
|
105
111
|
## Operating modes
|
|
106
112
|
|
|
@@ -176,7 +182,7 @@ organisations. Grant these scopes:
|
|
|
176
182
|
|
|
177
183
|
> For organisations that enforce SSO, the PAT must be **SSO-authorised** for
|
|
178
184
|
> each target organisation, or the org-level endpoints return `403` (reported as
|
|
179
|
-
> unknown). Store the token as a secret (e.g. `
|
|
185
|
+
> unknown). Store the token as a secret (e.g. `LFRELENG_ACTIONS_REPORT_PAT`) and
|
|
180
186
|
> reference it by env-var name via `token_env`; never embed it in the config.
|
|
181
187
|
|
|
182
188
|
## Usage
|
|
@@ -190,7 +196,9 @@ organisations. Grant these scopes:
|
|
|
190
196
|
with:
|
|
191
197
|
scope: "org"
|
|
192
198
|
config: "${{ secrets.GSR_CONFIG || vars.GSR_CONFIG }}"
|
|
193
|
-
token: "${{ secrets.
|
|
199
|
+
token: "${{ secrets.LFRELENG_ACTIONS_REPORT_PAT }}"
|
|
200
|
+
# Must match the per-org "token_env" in your config (below).
|
|
201
|
+
token_env: "LFRELENG_ACTIONS_REPORT_PAT"
|
|
194
202
|
output_dir: "site"
|
|
195
203
|
pages_url: "https://lfreleng-actions.github.io/github-security-report-action/"
|
|
196
204
|
```
|
|
@@ -235,7 +243,7 @@ environment-variable name, never embedded.
|
|
|
235
243
|
"organizations": [
|
|
236
244
|
{
|
|
237
245
|
"name": "lfreleng-actions",
|
|
238
|
-
"token_env": "
|
|
246
|
+
"token_env": "LFRELENG_ACTIONS_REPORT_PAT",
|
|
239
247
|
"exclude": ["actions-template"],
|
|
240
248
|
"releases_exclude": ["internal-only-repo"]
|
|
241
249
|
}
|
|
@@ -305,7 +313,8 @@ out of the terminal and Slack while still publishing it to the Markdown and HTML
|
|
|
305
313
|
Pages output. The valid category keys are: `codeql`, `scorecard`, `zizmor`,
|
|
306
314
|
`dependabot_alerts`, `secret_scanning`, `dependabot_alerts_enabled`,
|
|
307
315
|
`dependabot_updates_enabled`, `dependabot_cooldown`, `releases`,
|
|
308
|
-
`mutable_releases`. Like the other `report`
|
|
316
|
+
`mutable_releases`, `private_vulnerability_reporting`. Like the other `report`
|
|
317
|
+
settings, `categories` can be set
|
|
309
318
|
globally and overridden per organisation (overrides merge key-by-key, so
|
|
310
319
|
flipping one output leaves the rest untouched). The machine-readable
|
|
311
320
|
`report.json` artifact always contains the complete dataset, regardless of these
|
|
@@ -326,8 +335,11 @@ The severity-ranked signals (CodeQL, Scorecard, Zizmor, Dependabot alerts) use a
|
|
|
326
335
|
repository is flagged as an offender only when it carries a finding **at or
|
|
327
336
|
above** the cutoff; findings below it fold into the clean count. Severities run
|
|
328
337
|
(lowest to highest) `informational`, `low`, `medium`, `high`, `critical` —
|
|
329
|
-
`informational` being the
|
|
330
|
-
|
|
338
|
+
`informational` being the sub-low rung for SARIF `none` findings and
|
|
339
|
+
unclassifiable alerts. Zizmor's SARIF `note` findings normalise to `low`
|
|
340
|
+
(zizmor emits its Low findings at `note`, and the organisation scan pipeline's
|
|
341
|
+
`--min-severity low` floor keeps informational findings out of the uploaded
|
|
342
|
+
SARIF), matching the ruleset-enforced PR gate that blocks on note-and-above.
|
|
331
343
|
|
|
332
344
|
The global default cutoff is `medium`, so `low` and `informational` findings
|
|
333
345
|
pass. Zizmor defaults to `low` (only `informational` passes). Override the
|
|
@@ -365,7 +377,7 @@ $XDG_CONFIG_HOME/github-security-report/config.json
|
|
|
365
377
|
An explicit `--config`, `--config-data`, or `--org` always takes precedence, and
|
|
366
378
|
the action itself never reads this path (it is supplied configuration directly).
|
|
367
379
|
Secrets stay out of the file: reference the token by environment-variable name
|
|
368
|
-
via `token_env` (e.g. `
|
|
380
|
+
via `token_env` (e.g. `LFRELENG_ACTIONS_REPORT_PAT`, exported in your shell or sourced
|
|
369
381
|
from a secrets file) — the channel ID is the only Slack value the file holds,
|
|
370
382
|
and the Slack **bot token** is consumed by the workflow, not the CLI.
|
|
371
383
|
|
|
@@ -380,7 +392,7 @@ and the Slack **bot token** is consumed by the workflow, not the CLI.
|
|
|
380
392
|
| `org` | No | — | Single organisation (shorthand for org mode) |
|
|
381
393
|
| `repo` | No | detected | `owner/name` for repo mode |
|
|
382
394
|
| `token` | No | `${{ github.token }}` | PAT (org mode) or `GITHUB_TOKEN` (repo mode) |
|
|
383
|
-
| `token_env` | No | `GITHUB_TOKEN` | Env var name the
|
|
395
|
+
| `token_env` | No | `GITHUB_TOKEN` | Env var name the token is exported under. In org mode it **must match** the per-org `token_env` in your config (e.g. `LFRELENG_ACTIONS_REPORT_PAT`), otherwise the tool looks up an unset variable and reports no token. |
|
|
384
396
|
| `output_dir` | No | — | Directory for Pages output (org mode) |
|
|
385
397
|
| `pages_url` | No | — | Published Pages URL (used in the Slack link) |
|
|
386
398
|
| `slack_channel` | No | — | Slack channel ID; overrides the config `slack.channel` (e.g. the `SLACK_CHANNEL_ID` variable) |
|
|
@@ -417,8 +429,57 @@ uvx github-security-report report
|
|
|
417
429
|
uvx github-security-report report --org lfreleng-actions
|
|
418
430
|
```
|
|
419
431
|
|
|
432
|
+
## Remediation
|
|
433
|
+
|
|
434
|
+
The `remediate` subcommand is the in-tool counterpart to the report: it runs the
|
|
435
|
+
same collection, then switches on each selected security feature wherever a
|
|
436
|
+
repository has it **confirmed off**. Only the offenders the report already
|
|
437
|
+
surfaces are acted on — repositories whose state could not be read are counted
|
|
438
|
+
as *unknown* and are never written to, so remediation never blind-writes.
|
|
439
|
+
|
|
440
|
+
It is **dry run by default** (these are privileged writes); pass `--apply` to
|
|
441
|
+
make changes. A single **write-capable** org-admin token (from `--token-env`,
|
|
442
|
+
default `GITHUB_TOKEN`) drives both the read and the writes across every
|
|
443
|
+
configured organisation, so it bypasses the per-org read-only `token_env` in the
|
|
444
|
+
config.
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
# An org-admin token is required: a classic PAT with the `repo` scope
|
|
448
|
+
# (administers repository security settings) plus `read:org` to enumerate repos.
|
|
449
|
+
source ~/.secrets.github.classic.god # exports $GITHUB_TOKEN
|
|
450
|
+
|
|
451
|
+
# Dry run (default): preview every change, touch nothing.
|
|
452
|
+
uvx github-security-report remediate --org lfreleng-actions
|
|
453
|
+
|
|
454
|
+
# Apply: enable every remediable feature that is off, across all configured orgs.
|
|
455
|
+
uvx github-security-report remediate \
|
|
456
|
+
--config ~/.config/github-security-report/config.json --apply
|
|
457
|
+
|
|
458
|
+
# Limit to specific categories (repeatable).
|
|
459
|
+
uvx github-security-report remediate --org lfreleng-actions \
|
|
460
|
+
--category codeql --category private_vulnerability_reporting --apply
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
The remediable categories are the simple on/off features with a documented
|
|
464
|
+
enablement endpoint:
|
|
465
|
+
|
|
466
|
+
| `--category` | Enables |
|
|
467
|
+
|---|---|
|
|
468
|
+
| `codeql` | CodeQL default setup (provisioned asynchronously) |
|
|
469
|
+
| `secret_scanning` | Secret scanning |
|
|
470
|
+
| `dependabot_alerts_enabled` | Dependabot vulnerability alerts |
|
|
471
|
+
| `dependabot_updates_enabled` | Dependabot security updates (plus alerts) |
|
|
472
|
+
| `private_vulnerability_reporting` | Private vulnerability reporting |
|
|
473
|
+
|
|
474
|
+
Qualitative findings (Scorecard, zizmor, open Dependabot alerts, cooldown,
|
|
475
|
+
release freshness/mutability) are reported but not auto-remediated. Remediation
|
|
476
|
+
is organisation-scoped (`--scope org`, the default and only supported scope).
|
|
477
|
+
|
|
420
478
|
## Bulk Remediation Scripts
|
|
421
479
|
|
|
480
|
+
The standalone scripts below predate the `remediate` subcommand and remain for
|
|
481
|
+
ad-hoc, single-feature runs. For most workflows, prefer `remediate` above.
|
|
482
|
+
|
|
422
483
|
The report ends with **nag lists** — repositories where a supported feature is
|
|
423
484
|
switched off. Where GitHub exposes the relevant toggle through its REST API,
|
|
424
485
|
the [`scripts/`](scripts/) directory ships standalone helpers that clear a whole
|
|
@@ -440,7 +501,8 @@ the current state of each repository, enables the feature where it is off, and
|
|
|
440
501
|
verifies the result.
|
|
441
502
|
|
|
442
503
|
```bash
|
|
443
|
-
# An org-admin token is required
|
|
504
|
+
# An org-admin token is required: a classic PAT with the `repo` scope
|
|
505
|
+
# (administers repository security settings) plus `read:org` to enumerate repos.
|
|
444
506
|
source ~/.secrets.github.classic.god # exports $GITHUB_TOKEN
|
|
445
507
|
|
|
446
508
|
# Dry run (default): preview every change, touch nothing.
|
|
@@ -43,7 +43,7 @@ back to code-scanning findings. See [`docs/BRIEF.md`](docs/BRIEF.md) and
|
|
|
43
43
|
[`docs/phase0-findings.md`](docs/phase0-findings.md) for the full design and the
|
|
44
44
|
API research it is built on.
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
Further sections report **configuration posture** and **freshness** as plain
|
|
47
47
|
tables (org mode):
|
|
48
48
|
|
|
49
49
|
- **Dependabot** — three tables: repositories with vulnerability **alerts not
|
|
@@ -57,6 +57,12 @@ tables (org mode):
|
|
|
57
57
|
release or tag is older than `release_max_age_days` (default 60; `0` flags
|
|
58
58
|
every eligible repository), so a repository released or tagged within that
|
|
59
59
|
window counts as recently maintained and drops out of the table.
|
|
60
|
+
- **Private Vulnerability Reporting** — repositories where GitHub's private
|
|
61
|
+
vulnerability reporting feature is **not enabled**, so security researchers
|
|
62
|
+
cannot privately disclose vulnerabilities. Probed per repository (GitHub
|
|
63
|
+
exposes no org-wide or GraphQL equivalent) and, like every other category,
|
|
64
|
+
always collected; hide it with the `private_vulnerability_reporting` render
|
|
65
|
+
toggle.
|
|
60
66
|
|
|
61
67
|
## Operating modes
|
|
62
68
|
|
|
@@ -132,7 +138,7 @@ organisations. Grant these scopes:
|
|
|
132
138
|
|
|
133
139
|
> For organisations that enforce SSO, the PAT must be **SSO-authorised** for
|
|
134
140
|
> each target organisation, or the org-level endpoints return `403` (reported as
|
|
135
|
-
> unknown). Store the token as a secret (e.g. `
|
|
141
|
+
> unknown). Store the token as a secret (e.g. `LFRELENG_ACTIONS_REPORT_PAT`) and
|
|
136
142
|
> reference it by env-var name via `token_env`; never embed it in the config.
|
|
137
143
|
|
|
138
144
|
## Usage
|
|
@@ -146,7 +152,9 @@ organisations. Grant these scopes:
|
|
|
146
152
|
with:
|
|
147
153
|
scope: "org"
|
|
148
154
|
config: "${{ secrets.GSR_CONFIG || vars.GSR_CONFIG }}"
|
|
149
|
-
token: "${{ secrets.
|
|
155
|
+
token: "${{ secrets.LFRELENG_ACTIONS_REPORT_PAT }}"
|
|
156
|
+
# Must match the per-org "token_env" in your config (below).
|
|
157
|
+
token_env: "LFRELENG_ACTIONS_REPORT_PAT"
|
|
150
158
|
output_dir: "site"
|
|
151
159
|
pages_url: "https://lfreleng-actions.github.io/github-security-report-action/"
|
|
152
160
|
```
|
|
@@ -191,7 +199,7 @@ environment-variable name, never embedded.
|
|
|
191
199
|
"organizations": [
|
|
192
200
|
{
|
|
193
201
|
"name": "lfreleng-actions",
|
|
194
|
-
"token_env": "
|
|
202
|
+
"token_env": "LFRELENG_ACTIONS_REPORT_PAT",
|
|
195
203
|
"exclude": ["actions-template"],
|
|
196
204
|
"releases_exclude": ["internal-only-repo"]
|
|
197
205
|
}
|
|
@@ -261,7 +269,8 @@ out of the terminal and Slack while still publishing it to the Markdown and HTML
|
|
|
261
269
|
Pages output. The valid category keys are: `codeql`, `scorecard`, `zizmor`,
|
|
262
270
|
`dependabot_alerts`, `secret_scanning`, `dependabot_alerts_enabled`,
|
|
263
271
|
`dependabot_updates_enabled`, `dependabot_cooldown`, `releases`,
|
|
264
|
-
`mutable_releases`. Like the other `report`
|
|
272
|
+
`mutable_releases`, `private_vulnerability_reporting`. Like the other `report`
|
|
273
|
+
settings, `categories` can be set
|
|
265
274
|
globally and overridden per organisation (overrides merge key-by-key, so
|
|
266
275
|
flipping one output leaves the rest untouched). The machine-readable
|
|
267
276
|
`report.json` artifact always contains the complete dataset, regardless of these
|
|
@@ -282,8 +291,11 @@ The severity-ranked signals (CodeQL, Scorecard, Zizmor, Dependabot alerts) use a
|
|
|
282
291
|
repository is flagged as an offender only when it carries a finding **at or
|
|
283
292
|
above** the cutoff; findings below it fold into the clean count. Severities run
|
|
284
293
|
(lowest to highest) `informational`, `low`, `medium`, `high`, `critical` —
|
|
285
|
-
`informational` being the
|
|
286
|
-
|
|
294
|
+
`informational` being the sub-low rung for SARIF `none` findings and
|
|
295
|
+
unclassifiable alerts. Zizmor's SARIF `note` findings normalise to `low`
|
|
296
|
+
(zizmor emits its Low findings at `note`, and the organisation scan pipeline's
|
|
297
|
+
`--min-severity low` floor keeps informational findings out of the uploaded
|
|
298
|
+
SARIF), matching the ruleset-enforced PR gate that blocks on note-and-above.
|
|
287
299
|
|
|
288
300
|
The global default cutoff is `medium`, so `low` and `informational` findings
|
|
289
301
|
pass. Zizmor defaults to `low` (only `informational` passes). Override the
|
|
@@ -321,7 +333,7 @@ $XDG_CONFIG_HOME/github-security-report/config.json
|
|
|
321
333
|
An explicit `--config`, `--config-data`, or `--org` always takes precedence, and
|
|
322
334
|
the action itself never reads this path (it is supplied configuration directly).
|
|
323
335
|
Secrets stay out of the file: reference the token by environment-variable name
|
|
324
|
-
via `token_env` (e.g. `
|
|
336
|
+
via `token_env` (e.g. `LFRELENG_ACTIONS_REPORT_PAT`, exported in your shell or sourced
|
|
325
337
|
from a secrets file) — the channel ID is the only Slack value the file holds,
|
|
326
338
|
and the Slack **bot token** is consumed by the workflow, not the CLI.
|
|
327
339
|
|
|
@@ -336,7 +348,7 @@ and the Slack **bot token** is consumed by the workflow, not the CLI.
|
|
|
336
348
|
| `org` | No | — | Single organisation (shorthand for org mode) |
|
|
337
349
|
| `repo` | No | detected | `owner/name` for repo mode |
|
|
338
350
|
| `token` | No | `${{ github.token }}` | PAT (org mode) or `GITHUB_TOKEN` (repo mode) |
|
|
339
|
-
| `token_env` | No | `GITHUB_TOKEN` | Env var name the
|
|
351
|
+
| `token_env` | No | `GITHUB_TOKEN` | Env var name the token is exported under. In org mode it **must match** the per-org `token_env` in your config (e.g. `LFRELENG_ACTIONS_REPORT_PAT`), otherwise the tool looks up an unset variable and reports no token. |
|
|
340
352
|
| `output_dir` | No | — | Directory for Pages output (org mode) |
|
|
341
353
|
| `pages_url` | No | — | Published Pages URL (used in the Slack link) |
|
|
342
354
|
| `slack_channel` | No | — | Slack channel ID; overrides the config `slack.channel` (e.g. the `SLACK_CHANNEL_ID` variable) |
|
|
@@ -373,8 +385,57 @@ uvx github-security-report report
|
|
|
373
385
|
uvx github-security-report report --org lfreleng-actions
|
|
374
386
|
```
|
|
375
387
|
|
|
388
|
+
## Remediation
|
|
389
|
+
|
|
390
|
+
The `remediate` subcommand is the in-tool counterpart to the report: it runs the
|
|
391
|
+
same collection, then switches on each selected security feature wherever a
|
|
392
|
+
repository has it **confirmed off**. Only the offenders the report already
|
|
393
|
+
surfaces are acted on — repositories whose state could not be read are counted
|
|
394
|
+
as *unknown* and are never written to, so remediation never blind-writes.
|
|
395
|
+
|
|
396
|
+
It is **dry run by default** (these are privileged writes); pass `--apply` to
|
|
397
|
+
make changes. A single **write-capable** org-admin token (from `--token-env`,
|
|
398
|
+
default `GITHUB_TOKEN`) drives both the read and the writes across every
|
|
399
|
+
configured organisation, so it bypasses the per-org read-only `token_env` in the
|
|
400
|
+
config.
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
# An org-admin token is required: a classic PAT with the `repo` scope
|
|
404
|
+
# (administers repository security settings) plus `read:org` to enumerate repos.
|
|
405
|
+
source ~/.secrets.github.classic.god # exports $GITHUB_TOKEN
|
|
406
|
+
|
|
407
|
+
# Dry run (default): preview every change, touch nothing.
|
|
408
|
+
uvx github-security-report remediate --org lfreleng-actions
|
|
409
|
+
|
|
410
|
+
# Apply: enable every remediable feature that is off, across all configured orgs.
|
|
411
|
+
uvx github-security-report remediate \
|
|
412
|
+
--config ~/.config/github-security-report/config.json --apply
|
|
413
|
+
|
|
414
|
+
# Limit to specific categories (repeatable).
|
|
415
|
+
uvx github-security-report remediate --org lfreleng-actions \
|
|
416
|
+
--category codeql --category private_vulnerability_reporting --apply
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
The remediable categories are the simple on/off features with a documented
|
|
420
|
+
enablement endpoint:
|
|
421
|
+
|
|
422
|
+
| `--category` | Enables |
|
|
423
|
+
|---|---|
|
|
424
|
+
| `codeql` | CodeQL default setup (provisioned asynchronously) |
|
|
425
|
+
| `secret_scanning` | Secret scanning |
|
|
426
|
+
| `dependabot_alerts_enabled` | Dependabot vulnerability alerts |
|
|
427
|
+
| `dependabot_updates_enabled` | Dependabot security updates (plus alerts) |
|
|
428
|
+
| `private_vulnerability_reporting` | Private vulnerability reporting |
|
|
429
|
+
|
|
430
|
+
Qualitative findings (Scorecard, zizmor, open Dependabot alerts, cooldown,
|
|
431
|
+
release freshness/mutability) are reported but not auto-remediated. Remediation
|
|
432
|
+
is organisation-scoped (`--scope org`, the default and only supported scope).
|
|
433
|
+
|
|
376
434
|
## Bulk Remediation Scripts
|
|
377
435
|
|
|
436
|
+
The standalone scripts below predate the `remediate` subcommand and remain for
|
|
437
|
+
ad-hoc, single-feature runs. For most workflows, prefer `remediate` above.
|
|
438
|
+
|
|
378
439
|
The report ends with **nag lists** — repositories where a supported feature is
|
|
379
440
|
switched off. Where GitHub exposes the relevant toggle through its REST API,
|
|
380
441
|
the [`scripts/`](scripts/) directory ships standalone helpers that clear a whole
|
|
@@ -396,7 +457,8 @@ the current state of each repository, enables the feature where it is off, and
|
|
|
396
457
|
verifies the result.
|
|
397
458
|
|
|
398
459
|
```bash
|
|
399
|
-
# An org-admin token is required
|
|
460
|
+
# An org-admin token is required: a classic PAT with the `repo` scope
|
|
461
|
+
# (administers repository security settings) plus `read:org` to enumerate repos.
|
|
400
462
|
source ~/.secrets.github.classic.god # exports $GITHUB_TOKEN
|
|
401
463
|
|
|
402
464
|
# 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,9 +63,9 @@ 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
|
-
"ruff==0.15.
|
|
68
|
+
"ruff==0.15.20",
|
|
69
69
|
"types-jsonschema==4.26.0.20260518",
|
|
70
70
|
"types-PyYAML==6.0.12.20250915",
|
|
71
71
|
]
|
|
@@ -95,9 +95,9 @@ 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
|
-
"ruff==0.15.
|
|
100
|
+
"ruff==0.15.20",
|
|
101
101
|
"types-jsonschema==4.26.0.20260518",
|
|
102
102
|
"types-PyYAML==6.0.12.20250915",
|
|
103
103
|
]
|
{github_security_report-0.4.1 → github_security_report-0.6.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.6.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 6, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -38,6 +38,7 @@ class CategoryKey(str, Enum):
|
|
|
38
38
|
DEPENDABOT_COOLDOWN = "dependabot_cooldown"
|
|
39
39
|
RELEASES = "releases"
|
|
40
40
|
MUTABLE_RELEASES = "mutable_releases"
|
|
41
|
+
PRIVATE_VULNERABILITY_REPORTING = "private_vulnerability_reporting"
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
@dataclass(frozen=True)
|
|
@@ -104,8 +105,9 @@ _CATEGORIES: dict[CategoryKey, CategoryMeta] = {
|
|
|
104
105
|
"Zizmor static analysis of GitHub Actions workflows, ranked "
|
|
105
106
|
"worst-first by severity."
|
|
106
107
|
),
|
|
107
|
-
#
|
|
108
|
-
#
|
|
108
|
+
# zizmor emits its Low findings at SARIF level "note", which
|
|
109
|
+
# normalises to LOW (see severity.py), so any zizmor finding fails --
|
|
110
|
+
# matching the ruleset-enforced PR gate that blocks on note-and-above.
|
|
109
111
|
fail_severity=Severity.LOW,
|
|
110
112
|
),
|
|
111
113
|
CategoryKey.DEPENDABOT_ALERTS: CategoryMeta(
|
|
@@ -210,6 +212,22 @@ _CATEGORIES: dict[CategoryKey, CategoryMeta] = {
|
|
|
210
212
|
"cannot change after the fact."
|
|
211
213
|
),
|
|
212
214
|
),
|
|
215
|
+
CategoryKey.PRIVATE_VULNERABILITY_REPORTING: CategoryMeta(
|
|
216
|
+
key=CategoryKey.PRIVATE_VULNERABILITY_REPORTING,
|
|
217
|
+
title="Private Vulnerability Reporting",
|
|
218
|
+
pass_label="Enabled",
|
|
219
|
+
fail_label="Not enabled",
|
|
220
|
+
url=(
|
|
221
|
+
"https://docs.github.com/en/code-security/security-advisories/"
|
|
222
|
+
"working-with-repository-security-advisories/"
|
|
223
|
+
"configuring-private-vulnerability-reporting-for-a-repository"
|
|
224
|
+
),
|
|
225
|
+
description=(
|
|
226
|
+
"Repositories with private vulnerability reporting disabled. Enable "
|
|
227
|
+
"it so security researchers can privately report vulnerabilities "
|
|
228
|
+
"instead of disclosing them publicly."
|
|
229
|
+
),
|
|
230
|
+
),
|
|
213
231
|
}
|
|
214
232
|
|
|
215
233
|
|
{github_security_report-0.4.1 → github_security_report-0.6.0}/src/github_security_report/cli.py
RENAMED
|
@@ -17,7 +17,7 @@ import logging
|
|
|
17
17
|
import os
|
|
18
18
|
import re
|
|
19
19
|
import sys
|
|
20
|
-
from collections.abc import Callable, Mapping
|
|
20
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
21
21
|
from dataclasses import replace
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
from typing import NoReturn
|
|
@@ -26,6 +26,7 @@ import typer
|
|
|
26
26
|
from rich.console import Console
|
|
27
27
|
|
|
28
28
|
from github_security_report import __version__, collect, config, gitctx, runner
|
|
29
|
+
from github_security_report import remediate as remediate_mod
|
|
29
30
|
from github_security_report.categories import CategoryKey
|
|
30
31
|
from github_security_report.client import GitHubClient, NetworkError
|
|
31
32
|
from github_security_report.config import Config, OrgConfig, ReportConfig
|
|
@@ -48,7 +49,10 @@ def _version_callback(value: bool) -> None:
|
|
|
48
49
|
if value:
|
|
49
50
|
# Match the dependamerge style: a label emoji plus a Rich-highlighted
|
|
50
51
|
# version number (Rich colourises the numeric version automatically).
|
|
51
|
-
|
|
52
|
+
# A single space follows the emoji: terminals that honour the VS16
|
|
53
|
+
# emoji-presentation width (e.g. Ghostty) render it two cells wide, so
|
|
54
|
+
# the extra pad the old double space added now reads as a gap.
|
|
55
|
+
Console().print(f"🏷️ github-security-report version {__version__}")
|
|
52
56
|
raise typer.Exit()
|
|
53
57
|
|
|
54
58
|
|
|
@@ -133,6 +137,11 @@ def _org_to_dict(org: OrgReport) -> dict:
|
|
|
133
137
|
"mutable_releases": (
|
|
134
138
|
_table_to_dict(org.mutable_releases) if org.mutable_releases else None
|
|
135
139
|
),
|
|
140
|
+
"private_vulnerability_reporting": (
|
|
141
|
+
_table_to_dict(org.private_vulnerability_reporting)
|
|
142
|
+
if org.private_vulnerability_reporting
|
|
143
|
+
else None
|
|
144
|
+
),
|
|
136
145
|
}
|
|
137
146
|
|
|
138
147
|
|
|
@@ -415,6 +424,43 @@ async def _run_repo(owner: str, repo_name: str, *, token_env: str, console: Cons
|
|
|
415
424
|
return 0
|
|
416
425
|
|
|
417
426
|
|
|
427
|
+
async def _run_remediate(
|
|
428
|
+
cfg: Config,
|
|
429
|
+
*,
|
|
430
|
+
console: Console,
|
|
431
|
+
token: str,
|
|
432
|
+
categories: Sequence[CategoryKey],
|
|
433
|
+
apply: bool,
|
|
434
|
+
) -> int:
|
|
435
|
+
"""Collect each org's posture and enable (or preview enabling) features.
|
|
436
|
+
|
|
437
|
+
A single write-capable token drives both the read (collection) and the
|
|
438
|
+
writes for every configured org, so the per-org read ``token_env`` in the
|
|
439
|
+
config is intentionally bypassed. Returns 1 when any enable failed, else 0.
|
|
440
|
+
"""
|
|
441
|
+
now = dt.datetime.now(dt.timezone.utc)
|
|
442
|
+
failures = 0
|
|
443
|
+
async with GitHubClient(token) as client:
|
|
444
|
+
for org_cfg in cfg.organizations:
|
|
445
|
+
report = await collect.collect_org(
|
|
446
|
+
client, org_cfg, org_cfg.report, generated_at=now
|
|
447
|
+
)
|
|
448
|
+
results = await remediate_mod.remediate_org(
|
|
449
|
+
client, report, categories=categories, apply=apply
|
|
450
|
+
)
|
|
451
|
+
# Honour the org's configured terminal offender limit, the same
|
|
452
|
+
# cap the report's CLI output uses, so large orgs stay readable.
|
|
453
|
+
term_render.render_remediation(
|
|
454
|
+
report.org,
|
|
455
|
+
results,
|
|
456
|
+
console,
|
|
457
|
+
apply=apply,
|
|
458
|
+
top_n=org_cfg.report.cli_top_n,
|
|
459
|
+
)
|
|
460
|
+
failures += sum(result.failures for result in results)
|
|
461
|
+
return 1 if failures else 0
|
|
462
|
+
|
|
463
|
+
|
|
418
464
|
def _repo_outputs(signals: list[RepoSignal], fail_threshold: str) -> dict[str, str]:
|
|
419
465
|
outputs = {s.signal.value + "_open": str(s.counts.total) for s in signals}
|
|
420
466
|
outputs["failed"] = "true" if runner.should_fail(signals, fail_threshold) else "false"
|
|
@@ -532,5 +578,75 @@ def report(
|
|
|
532
578
|
raise typer.Exit(code)
|
|
533
579
|
|
|
534
580
|
|
|
581
|
+
@app.command()
|
|
582
|
+
def remediate(
|
|
583
|
+
config_file: str | None = typer.Option(None, "--config", "-c", help="Path to a JSON config file."),
|
|
584
|
+
config_data: str | None = typer.Option(None, "--config-data", help="Raw or base64 JSON config (vars/secrets)."),
|
|
585
|
+
org: str | None = typer.Option(None, "--org", help="Single organisation (shorthand for org mode)."),
|
|
586
|
+
scope: str = typer.Option("org", "--scope", help="Only 'org' is supported; remediation is organisation-scoped."),
|
|
587
|
+
category: list[str] | None = typer.Option(None, "--category", help="Remediable category to act on (repeatable; default: all). One of: codeql, secret_scanning, dependabot_alerts_enabled, dependabot_updates_enabled, private_vulnerability_reporting."),
|
|
588
|
+
token_env: str = typer.Option("GITHUB_TOKEN", "--token-env", help="Env var holding a WRITE-capable org-admin PAT. Used for both reading posture and enabling features across every configured org."),
|
|
589
|
+
apply: bool = typer.Option(False, "--apply", help="Perform the writes. Without this flag remediate only previews (dry run)."),
|
|
590
|
+
no_color: bool = typer.Option(False, "--no-color", help="Disable coloured output."),
|
|
591
|
+
) -> None:
|
|
592
|
+
"""Enable security features on repositories that lack them.
|
|
593
|
+
|
|
594
|
+
Runs the same collection the report uses, then switches on each selected
|
|
595
|
+
remediable feature wherever a repository has it confirmed off. Dry run by
|
|
596
|
+
default: pass --apply to make changes. Requires a write-capable token
|
|
597
|
+
(org admin), distinct from the read-only reporting PAT.
|
|
598
|
+
"""
|
|
599
|
+
plain = no_color or bool(os.environ.get("CI")) or not sys.stdout.isatty()
|
|
600
|
+
console = Console(no_color=plain, highlight=False)
|
|
601
|
+
|
|
602
|
+
if scope != "org":
|
|
603
|
+
console.print("[red]remediate supports only --scope org[/red]")
|
|
604
|
+
raise typer.Exit(2)
|
|
605
|
+
|
|
606
|
+
keys, unknown = remediate_mod.parse_categories(category or [])
|
|
607
|
+
if unknown:
|
|
608
|
+
valid = ", ".join(key.value for key in remediate_mod.REMEDIABLE)
|
|
609
|
+
# markup=False: the user-supplied --category values are printed
|
|
610
|
+
# literally, so bracketed input cannot be interpreted as Rich markup.
|
|
611
|
+
console.print(
|
|
612
|
+
f"Unknown --category: {', '.join(unknown)}. Valid values: {valid}",
|
|
613
|
+
style="red",
|
|
614
|
+
markup=False,
|
|
615
|
+
)
|
|
616
|
+
raise typer.Exit(2)
|
|
617
|
+
categories = keys or list(remediate_mod.REMEDIABLE)
|
|
618
|
+
|
|
619
|
+
cfg = _load_config(config_file, config_data, org, token_env, console=console)
|
|
620
|
+
if cfg is None:
|
|
621
|
+
console.print(
|
|
622
|
+
"[red]No configuration: provide --config, --config-data or --org.[/red]"
|
|
623
|
+
)
|
|
624
|
+
raise typer.Exit(2)
|
|
625
|
+
|
|
626
|
+
token = os.environ.get(token_env, "").strip()
|
|
627
|
+
if not token:
|
|
628
|
+
# markup=False guards the user-supplied --token-env value.
|
|
629
|
+
console.print(
|
|
630
|
+
f"No token in ${token_env} (a write-capable org-admin PAT is required).",
|
|
631
|
+
style="red",
|
|
632
|
+
markup=False,
|
|
633
|
+
)
|
|
634
|
+
raise typer.Exit(2)
|
|
635
|
+
|
|
636
|
+
try:
|
|
637
|
+
code = asyncio.run(
|
|
638
|
+
_run_remediate(
|
|
639
|
+
cfg,
|
|
640
|
+
console=console,
|
|
641
|
+
token=token,
|
|
642
|
+
categories=categories,
|
|
643
|
+
apply=apply,
|
|
644
|
+
)
|
|
645
|
+
)
|
|
646
|
+
except NetworkError as exc:
|
|
647
|
+
_abort_network(console, exc)
|
|
648
|
+
raise typer.Exit(code)
|
|
649
|
+
|
|
650
|
+
|
|
535
651
|
if __name__ == "__main__": # pragma: no cover
|
|
536
652
|
app()
|