github-security-report 0.4.1__tar.gz → 0.5.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.5.0}/PKG-INFO +18 -9
- {github_security_report-0.4.1 → github_security_report-0.5.0}/README.md +16 -7
- {github_security_report-0.4.1 → github_security_report-0.5.0}/pyproject.toml +2 -2
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/_version.py +2 -2
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/categories.py +17 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/cli.py +9 -1
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/client.py +26 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/collect.py +21 -5
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/posture.py +22 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/render/html.py +10 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/render/markdown.py +11 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/render/slack.py +8 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/render/terminal.py +50 -11
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/report.py +3 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/templates/report.html.j2 +7 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/.gitignore +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/LICENSE +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/LICENSES/Apache-2.0.txt +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/scripts/README.md +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/__init__.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/classify.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/config.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/gitctx.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/models.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/py.typed +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/render/__init__.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/rulesets.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/runner.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/scope.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/severity.py +0 -0
- {github_security_report-0.4.1 → github_security_report-0.5.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.5.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
|
|
@@ -36,7 +36,7 @@ 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.
|
|
39
|
+
Requires-Dist: ruff==0.15.20; extra == 'dev'
|
|
40
40
|
Requires-Dist: syrupy==5.3.3; 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'
|
|
@@ -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
|
|
@@ -365,7 +374,7 @@ $XDG_CONFIG_HOME/github-security-report/config.json
|
|
|
365
374
|
An explicit `--config`, `--config-data`, or `--org` always takes precedence, and
|
|
366
375
|
the action itself never reads this path (it is supplied configuration directly).
|
|
367
376
|
Secrets stay out of the file: reference the token by environment-variable name
|
|
368
|
-
via `token_env` (e.g. `
|
|
377
|
+
via `token_env` (e.g. `LFRELENG_ACTIONS_REPORT_PAT`, exported in your shell or sourced
|
|
369
378
|
from a secrets file) — the channel ID is the only Slack value the file holds,
|
|
370
379
|
and the Slack **bot token** is consumed by the workflow, not the CLI.
|
|
371
380
|
|
|
@@ -380,7 +389,7 @@ and the Slack **bot token** is consumed by the workflow, not the CLI.
|
|
|
380
389
|
| `org` | No | — | Single organisation (shorthand for org mode) |
|
|
381
390
|
| `repo` | No | detected | `owner/name` for repo mode |
|
|
382
391
|
| `token` | No | `${{ github.token }}` | PAT (org mode) or `GITHUB_TOKEN` (repo mode) |
|
|
383
|
-
| `token_env` | No | `GITHUB_TOKEN` | Env var name the
|
|
392
|
+
| `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
393
|
| `output_dir` | No | — | Directory for Pages output (org mode) |
|
|
385
394
|
| `pages_url` | No | — | Published Pages URL (used in the Slack link) |
|
|
386
395
|
| `slack_channel` | No | — | Slack channel ID; overrides the config `slack.channel` (e.g. the `SLACK_CHANNEL_ID` variable) |
|
|
@@ -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
|
|
@@ -321,7 +330,7 @@ $XDG_CONFIG_HOME/github-security-report/config.json
|
|
|
321
330
|
An explicit `--config`, `--config-data`, or `--org` always takes precedence, and
|
|
322
331
|
the action itself never reads this path (it is supplied configuration directly).
|
|
323
332
|
Secrets stay out of the file: reference the token by environment-variable name
|
|
324
|
-
via `token_env` (e.g. `
|
|
333
|
+
via `token_env` (e.g. `LFRELENG_ACTIONS_REPORT_PAT`, exported in your shell or sourced
|
|
325
334
|
from a secrets file) — the channel ID is the only Slack value the file holds,
|
|
326
335
|
and the Slack **bot token** is consumed by the workflow, not the CLI.
|
|
327
336
|
|
|
@@ -336,7 +345,7 @@ and the Slack **bot token** is consumed by the workflow, not the CLI.
|
|
|
336
345
|
| `org` | No | — | Single organisation (shorthand for org mode) |
|
|
337
346
|
| `repo` | No | detected | `owner/name` for repo mode |
|
|
338
347
|
| `token` | No | `${{ github.token }}` | PAT (org mode) or `GITHUB_TOKEN` (repo mode) |
|
|
339
|
-
| `token_env` | No | `GITHUB_TOKEN` | Env var name the
|
|
348
|
+
| `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
349
|
| `output_dir` | No | — | Directory for Pages output (org mode) |
|
|
341
350
|
| `pages_url` | No | — | Published Pages URL (used in the Slack link) |
|
|
342
351
|
| `slack_channel` | No | — | Slack channel ID; overrides the config `slack.channel` (e.g. the `SLACK_CHANNEL_ID` variable) |
|
|
@@ -65,7 +65,7 @@ dev = [
|
|
|
65
65
|
"respx==0.23.1",
|
|
66
66
|
"syrupy==5.3.3",
|
|
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
|
]
|
|
@@ -97,7 +97,7 @@ dev = [
|
|
|
97
97
|
"respx==0.23.1",
|
|
98
98
|
"syrupy==5.3.3",
|
|
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.5.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.5.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 5, 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)
|
|
@@ -210,6 +211,22 @@ _CATEGORIES: dict[CategoryKey, CategoryMeta] = {
|
|
|
210
211
|
"cannot change after the fact."
|
|
211
212
|
),
|
|
212
213
|
),
|
|
214
|
+
CategoryKey.PRIVATE_VULNERABILITY_REPORTING: CategoryMeta(
|
|
215
|
+
key=CategoryKey.PRIVATE_VULNERABILITY_REPORTING,
|
|
216
|
+
title="Private Vulnerability Reporting",
|
|
217
|
+
pass_label="Enabled",
|
|
218
|
+
fail_label="Not enabled",
|
|
219
|
+
url=(
|
|
220
|
+
"https://docs.github.com/en/code-security/security-advisories/"
|
|
221
|
+
"working-with-repository-security-advisories/"
|
|
222
|
+
"configuring-private-vulnerability-reporting-for-a-repository"
|
|
223
|
+
),
|
|
224
|
+
description=(
|
|
225
|
+
"Repositories with private vulnerability reporting disabled. Enable "
|
|
226
|
+
"it so security researchers can privately report vulnerabilities "
|
|
227
|
+
"instead of disclosing them publicly."
|
|
228
|
+
),
|
|
229
|
+
),
|
|
213
230
|
}
|
|
214
231
|
|
|
215
232
|
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/cli.py
RENAMED
|
@@ -48,7 +48,10 @@ def _version_callback(value: bool) -> None:
|
|
|
48
48
|
if value:
|
|
49
49
|
# Match the dependamerge style: a label emoji plus a Rich-highlighted
|
|
50
50
|
# version number (Rich colourises the numeric version automatically).
|
|
51
|
-
|
|
51
|
+
# A single space follows the emoji: terminals that honour the VS16
|
|
52
|
+
# emoji-presentation width (e.g. Ghostty) render it two cells wide, so
|
|
53
|
+
# the extra pad the old double space added now reads as a gap.
|
|
54
|
+
Console().print(f"🏷️ github-security-report version {__version__}")
|
|
52
55
|
raise typer.Exit()
|
|
53
56
|
|
|
54
57
|
|
|
@@ -133,6 +136,11 @@ def _org_to_dict(org: OrgReport) -> dict:
|
|
|
133
136
|
"mutable_releases": (
|
|
134
137
|
_table_to_dict(org.mutable_releases) if org.mutable_releases else None
|
|
135
138
|
),
|
|
139
|
+
"private_vulnerability_reporting": (
|
|
140
|
+
_table_to_dict(org.private_vulnerability_reporting)
|
|
141
|
+
if org.private_vulnerability_reporting
|
|
142
|
+
else None
|
|
143
|
+
),
|
|
136
144
|
}
|
|
137
145
|
|
|
138
146
|
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/client.py
RENAMED
|
@@ -756,6 +756,32 @@ class GitHubClient:
|
|
|
756
756
|
await resp.aclose() # release the connection once the body is read
|
|
757
757
|
return bool(data.get("enabled"))
|
|
758
758
|
|
|
759
|
+
async def private_vulnerability_reporting(
|
|
760
|
+
self, org: str, repo: str
|
|
761
|
+
) -> bool | None:
|
|
762
|
+
"""Whether private vulnerability reporting is enabled (None = unknown).
|
|
763
|
+
|
|
764
|
+
``GET .../private-vulnerability-reporting`` returns ``{enabled}`` (200);
|
|
765
|
+
any other status (e.g. 422) is treated as indeterminate. A classic PAT
|
|
766
|
+
needs the ``repo`` scope (``public_repo`` suffices for public repos);
|
|
767
|
+
fine-grained tokens and Apps need only ``Metadata: read``. Org mode
|
|
768
|
+
already grants ``repo``, so it works with the same token used for the
|
|
769
|
+
other read probes -- but GitHub exposes no org-wide or GraphQL
|
|
770
|
+
equivalent, so it is one REST call per repository, gathered alongside
|
|
771
|
+
the automated-security-fixes probe.
|
|
772
|
+
"""
|
|
773
|
+
resp = await self._request(
|
|
774
|
+
"GET",
|
|
775
|
+
f"{self._api_url}/repos/{org}/{repo}/private-vulnerability-reporting",
|
|
776
|
+
)
|
|
777
|
+
status = resp.status_code
|
|
778
|
+
if status != 200:
|
|
779
|
+
await resp.aclose() # unread body would leak a pooled connection
|
|
780
|
+
return None
|
|
781
|
+
data = resp.json()
|
|
782
|
+
await resp.aclose() # release the connection once the body is read
|
|
783
|
+
return bool(data.get("enabled"))
|
|
784
|
+
|
|
759
785
|
async def repo_graph_batch(
|
|
760
786
|
self, org: str, names: list[str]
|
|
761
787
|
) -> dict[str, RepoGraphData]:
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/collect.py
RENAMED
|
@@ -72,6 +72,12 @@ class ClientProtocol(Protocol):
|
|
|
72
72
|
"""Whether Dependabot automated security fixes are enabled."""
|
|
73
73
|
raise NotImplementedError
|
|
74
74
|
|
|
75
|
+
async def private_vulnerability_reporting(
|
|
76
|
+
self, org: str, repo: str
|
|
77
|
+
) -> bool | None:
|
|
78
|
+
"""Whether private vulnerability reporting is enabled for a repository."""
|
|
79
|
+
raise NotImplementedError
|
|
80
|
+
|
|
75
81
|
async def repo_graph_batch(
|
|
76
82
|
self, org: str, names: list[str]
|
|
77
83
|
) -> dict[str, RepoGraphData]:
|
|
@@ -199,11 +205,18 @@ async def _posture_for_repo(
|
|
|
199
205
|
"""Build one repo's Dependabot posture and release/tag freshness.
|
|
200
206
|
|
|
201
207
|
``dependabot_alerts`` and the release/tag/``dependabot.yml`` data come from
|
|
202
|
-
the batched GraphQL prefetch (:func:`_collect_graph`);
|
|
203
|
-
|
|
204
|
-
GraphQL equivalent.
|
|
208
|
+
the batched GraphQL prefetch (:func:`_collect_graph`); the security-updates
|
|
209
|
+
and private-vulnerability-reporting flags remain per-repo REST calls, since
|
|
210
|
+
GitHub exposes no GraphQL equivalent. They are independent, so they are
|
|
211
|
+
gathered together and the two reads overlap (bounded by the client
|
|
212
|
+
semaphore); private vulnerability reporting is always probed, like every
|
|
213
|
+
other signal, with the per-category toggle governing only whether the
|
|
214
|
+
resulting table renders.
|
|
205
215
|
"""
|
|
206
|
-
security_updates = await
|
|
216
|
+
security_updates, pvr = await asyncio.gather(
|
|
217
|
+
client.automated_security_fixes(org, repo.name),
|
|
218
|
+
client.private_vulnerability_reporting(org, repo.name),
|
|
219
|
+
)
|
|
207
220
|
config_text = graph.dependabot_config
|
|
208
221
|
has_config = config_text is not None
|
|
209
222
|
cooldown_missing = (
|
|
@@ -215,6 +228,7 @@ async def _posture_for_repo(
|
|
|
215
228
|
repo=repo,
|
|
216
229
|
dependabot_alerts=dependabot_alerts,
|
|
217
230
|
security_updates=security_updates,
|
|
231
|
+
private_vulnerability_reporting=pvr,
|
|
218
232
|
cooldown_missing=cooldown_missing,
|
|
219
233
|
has_dependabot_config=has_config,
|
|
220
234
|
latest_release_at=graph.latest_release_at,
|
|
@@ -356,7 +370,8 @@ async def collect_org(
|
|
|
356
370
|
# Extra reporting categories (outside the four-state model): Dependabot
|
|
357
371
|
# configuration posture and release/tag freshness. The Dependabot alerts
|
|
358
372
|
# enablement flag and the release/tag data are reused from the batched
|
|
359
|
-
# GraphQL prefetch;
|
|
373
|
+
# GraphQL prefetch; the security-updates and private-vulnerability-reporting
|
|
374
|
+
# flags are still per-repo calls.
|
|
360
375
|
when = report.generated_at
|
|
361
376
|
dependabot_on = {f.repo.name: f.dependabot_enabled for f in facts}
|
|
362
377
|
postures: list[RepoPosture] = []
|
|
@@ -384,6 +399,7 @@ async def collect_org(
|
|
|
384
399
|
exclude=org_cfg.releases_exclude,
|
|
385
400
|
)
|
|
386
401
|
report.mutable_releases = posture.build_mutable_releases_table(postures)
|
|
402
|
+
report.private_vulnerability_reporting = posture.build_pvr_table(postures)
|
|
387
403
|
# The Dependabot alerts enablement sub-table carries the repositories with
|
|
388
404
|
# Dependabot alerts disabled, so drop them from the Dependabot signal
|
|
389
405
|
# section's nag list to avoid listing the same repositories twice under the
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/posture.py
RENAMED
|
@@ -48,6 +48,8 @@ class RepoPosture:
|
|
|
48
48
|
# Dependabot repo-level feature flags (None = indeterminate).
|
|
49
49
|
dependabot_alerts: bool | None = None
|
|
50
50
|
security_updates: bool | None = None
|
|
51
|
+
# GitHub "private vulnerability reporting" enablement (None = indeterminate).
|
|
52
|
+
private_vulnerability_reporting: bool | None = None
|
|
51
53
|
# Ecosystems declared in .github/dependabot.yml that set no cooldown.
|
|
52
54
|
cooldown_missing: tuple[str, ...] = ()
|
|
53
55
|
# True when .github/dependabot.yml exists and declares version updates.
|
|
@@ -240,6 +242,25 @@ def build_dependabot_tables(postures: list[RepoPosture]) -> list[TableSection]:
|
|
|
240
242
|
]
|
|
241
243
|
|
|
242
244
|
|
|
245
|
+
def build_pvr_table(postures: list[RepoPosture]) -> TableSection:
|
|
246
|
+
"""Repositories where private vulnerability reporting is not enabled.
|
|
247
|
+
|
|
248
|
+
Like the Dependabot enablement tables this is a single-boolean feature
|
|
249
|
+
check, so it reuses :func:`_build_feature_table`: offenders are repositories
|
|
250
|
+
where the feature is confirmed off; an indeterminate (``None``) reading
|
|
251
|
+
counts towards neither side of the standardised summary footer. The table is
|
|
252
|
+
always built (the flag is probed for every repository); the per-category
|
|
253
|
+
render toggle governs whether it is shown.
|
|
254
|
+
"""
|
|
255
|
+
return _build_feature_table(
|
|
256
|
+
postures,
|
|
257
|
+
category_key=CategoryKey.PRIVATE_VULNERABILITY_REPORTING,
|
|
258
|
+
columns=("Repository",),
|
|
259
|
+
enabled_of=lambda p: p.private_vulnerability_reporting,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
243
264
|
def build_releases_table(
|
|
244
265
|
postures: list[RepoPosture],
|
|
245
266
|
*,
|
|
@@ -390,4 +411,5 @@ __all__ = [
|
|
|
390
411
|
"build_alerts_table",
|
|
391
412
|
"build_security_updates_table",
|
|
392
413
|
"build_cooldown_table",
|
|
414
|
+
"build_pvr_table",
|
|
393
415
|
]
|
|
@@ -239,6 +239,16 @@ def render_org_html(
|
|
|
239
239
|
and visible(org.mutable_releases.category.key)
|
|
240
240
|
else None
|
|
241
241
|
),
|
|
242
|
+
private_vulnerability_reporting=(
|
|
243
|
+
_table_context(
|
|
244
|
+
org.private_vulnerability_reporting,
|
|
245
|
+
excluded=excluded,
|
|
246
|
+
top_n=top_n,
|
|
247
|
+
)
|
|
248
|
+
if org.private_vulnerability_reporting
|
|
249
|
+
and visible(org.private_vulnerability_reporting.category.key)
|
|
250
|
+
else None
|
|
251
|
+
),
|
|
242
252
|
datatables_version=DATATABLES_VERSION,
|
|
243
253
|
datatables_css_sri=DATATABLES_CSS_SRI,
|
|
244
254
|
datatables_js_sri=DATATABLES_JS_SRI,
|
|
@@ -281,6 +281,17 @@ def render_org(
|
|
|
281
281
|
org.mutable_releases, level=2, excluded=excluded, top_n=top_n
|
|
282
282
|
)
|
|
283
283
|
)
|
|
284
|
+
if org.private_vulnerability_reporting is not None and visible(
|
|
285
|
+
org.private_vulnerability_reporting.category.key
|
|
286
|
+
):
|
|
287
|
+
parts.append(
|
|
288
|
+
render_table_section(
|
|
289
|
+
org.private_vulnerability_reporting,
|
|
290
|
+
level=2,
|
|
291
|
+
excluded=excluded,
|
|
292
|
+
top_n=top_n,
|
|
293
|
+
)
|
|
294
|
+
)
|
|
284
295
|
return "\n".join(parts).rstrip() + "\n"
|
|
285
296
|
|
|
286
297
|
|
|
@@ -239,6 +239,14 @@ def render_org_blocks(
|
|
|
239
239
|
block = _table_block(org.mutable_releases, top_n, excluded=excluded)
|
|
240
240
|
if block is not None:
|
|
241
241
|
blocks.append(block)
|
|
242
|
+
if org.private_vulnerability_reporting is not None and visible(
|
|
243
|
+
org.private_vulnerability_reporting.category.key
|
|
244
|
+
):
|
|
245
|
+
block = _table_block(
|
|
246
|
+
org.private_vulnerability_reporting, top_n, excluded=excluded
|
|
247
|
+
)
|
|
248
|
+
if block is not None:
|
|
249
|
+
blocks.append(block)
|
|
242
250
|
if pages_url:
|
|
243
251
|
blocks.append(
|
|
244
252
|
{
|
|
@@ -11,6 +11,7 @@ sections 10-11.
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
from collections.abc import Callable, Sequence
|
|
14
|
+
from dataclasses import replace
|
|
14
15
|
|
|
15
16
|
from rich.console import Console
|
|
16
17
|
from rich.table import Table
|
|
@@ -87,20 +88,28 @@ def _truncated_names(names: Sequence[str], top_n: int | None) -> str:
|
|
|
87
88
|
|
|
88
89
|
|
|
89
90
|
def _render_summary(
|
|
90
|
-
console: Console,
|
|
91
|
+
console: Console,
|
|
92
|
+
lines: Sequence[SummaryLine],
|
|
93
|
+
*,
|
|
94
|
+
top_n: int | None,
|
|
95
|
+
name_labels: dict[str, str] | None = None,
|
|
91
96
|
) -> None:
|
|
92
97
|
"""Print the standardised footer: count lines, then any name lists.
|
|
93
98
|
|
|
94
99
|
Counts come first (failures and not-enabled at the top, the healthy pass
|
|
95
|
-
line lower down), then the repository-name breakdowns for the
|
|
96
|
-
|
|
97
|
-
name lists honour the same offender limit as the tables.
|
|
100
|
+
line lower down), then the repository-name breakdowns for the kinds in
|
|
101
|
+
``name_labels`` -- numbers and names are never mixed on one line, and the
|
|
102
|
+
name lists honour the same offender limit as the tables. ``name_labels``
|
|
103
|
+
defaults to the disabled/excluded kinds; a boolean feature table passes an
|
|
104
|
+
extended map so its offenders list inline under the fail line too.
|
|
98
105
|
"""
|
|
106
|
+
if name_labels is None:
|
|
107
|
+
name_labels = _NAME_LIST_LABEL
|
|
99
108
|
for line in lines:
|
|
100
109
|
style = _SUMMARY_STYLE[line.kind]
|
|
101
110
|
console.print(f" [{style}]{SUMMARY_EMOJI[line.kind]} {line.text}[/{style}]")
|
|
102
111
|
for line in lines:
|
|
103
|
-
label =
|
|
112
|
+
label = name_labels.get(line.kind)
|
|
104
113
|
if label and line.names:
|
|
105
114
|
style = _SUMMARY_STYLE[line.kind]
|
|
106
115
|
console.print(
|
|
@@ -155,13 +164,20 @@ def render_table_section(
|
|
|
155
164
|
) -> None:
|
|
156
165
|
"""Render a generic posture/freshness table to the terminal.
|
|
157
166
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
167
|
+
A section with a single column carries only repository names -- a boolean
|
|
168
|
+
feature check (enabled/not enabled) with no qualitative data -- so it is
|
|
169
|
+
rendered like a signal section: no table, just the standardised footer with
|
|
170
|
+
the offenders listed inline under the fail line (e.g. ``Not enabled:``).
|
|
171
|
+
Tables are reserved for sections whose extra columns carry qualitative data
|
|
172
|
+
that cannot be expressed as a count (release/tag ages, ecosystems, release
|
|
173
|
+
tags). The explanatory description is deliberately omitted either way: the
|
|
174
|
+
terminal is a brevity-first surface, so the guidance text is reserved for
|
|
175
|
+
the Markdown and HTML (GitHub Pages) outputs.
|
|
161
176
|
"""
|
|
177
|
+
inline = len(section.columns) == 1
|
|
162
178
|
rows, hidden = truncate(section.rows, top_n)
|
|
163
179
|
console.print(f"[bold]{section.title}[/bold]")
|
|
164
|
-
if rows:
|
|
180
|
+
if not inline and rows:
|
|
165
181
|
table = Table(title_justify="left", title_style="bold")
|
|
166
182
|
for i, col in enumerate(section.columns):
|
|
167
183
|
table.add_column(col, overflow="fold", justify="left" if i == 0 else "right")
|
|
@@ -170,9 +186,23 @@ def render_table_section(
|
|
|
170
186
|
console.print(table)
|
|
171
187
|
if hidden:
|
|
172
188
|
console.print(f" [dim]\u2026 and {hidden} more[/dim]")
|
|
173
|
-
|
|
189
|
+
counts = section.summary_counts(excluded)
|
|
190
|
+
name_labels = _NAME_LIST_LABEL
|
|
191
|
+
if inline:
|
|
192
|
+
# Surface the offenders inline under the fail line, labelled with the
|
|
193
|
+
# category's fail wording (e.g. "Not enabled"), instead of a one-column
|
|
194
|
+
# table. The name list honours top_n like every other breakdown.
|
|
195
|
+
fail_label = section.category.fail_label or "Failing"
|
|
196
|
+
counts = [
|
|
197
|
+
replace(c, names=tuple(r.repo.name for r in section.rows))
|
|
198
|
+
if c.kind == "fail"
|
|
199
|
+
else c
|
|
200
|
+
for c in counts
|
|
201
|
+
]
|
|
202
|
+
name_labels = {**_NAME_LIST_LABEL, "fail": fail_label}
|
|
203
|
+
lines = build_summary(counts)
|
|
174
204
|
if lines:
|
|
175
|
-
_render_summary(console, lines, top_n=top_n)
|
|
205
|
+
_render_summary(console, lines, top_n=top_n, name_labels=name_labels)
|
|
176
206
|
elif not rows:
|
|
177
207
|
console.print(" [dim]No data[/dim]")
|
|
178
208
|
console.print()
|
|
@@ -214,6 +244,15 @@ def render_org(
|
|
|
214
244
|
render_table_section(
|
|
215
245
|
org.mutable_releases, console, excluded=org.excluded_repos, top_n=top_n
|
|
216
246
|
)
|
|
247
|
+
if org.private_vulnerability_reporting is not None and visible(
|
|
248
|
+
org.private_vulnerability_reporting.category.key
|
|
249
|
+
):
|
|
250
|
+
render_table_section(
|
|
251
|
+
org.private_vulnerability_reporting,
|
|
252
|
+
console,
|
|
253
|
+
excluded=org.excluded_repos,
|
|
254
|
+
top_n=top_n,
|
|
255
|
+
)
|
|
217
256
|
|
|
218
257
|
|
|
219
258
|
def render_orgs(
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/report.py
RENAMED
|
@@ -252,6 +252,9 @@ class OrgReport:
|
|
|
252
252
|
# The Mutable Releases table: repositories whose "Latest" or last-published
|
|
253
253
|
# release is not immutable. None in repo mode / when not collected.
|
|
254
254
|
mutable_releases: TableSection | None = None
|
|
255
|
+
# The Private Vulnerability Reporting table: repositories where the feature
|
|
256
|
+
# is not enabled. None in repo mode / when not collected.
|
|
257
|
+
private_vulnerability_reporting: TableSection | None = None
|
|
255
258
|
|
|
256
259
|
|
|
257
260
|
@dataclass
|
|
@@ -125,6 +125,13 @@
|
|
|
125
125
|
</section>
|
|
126
126
|
{% endif %}
|
|
127
127
|
|
|
128
|
+
{% if private_vulnerability_reporting %}
|
|
129
|
+
<section>
|
|
130
|
+
<h2>{{ private_vulnerability_reporting.title }}</h2>
|
|
131
|
+
{{ render_table(private_vulnerability_reporting) }}
|
|
132
|
+
</section>
|
|
133
|
+
{% endif %}
|
|
134
|
+
|
|
128
135
|
<footer>
|
|
129
136
|
Generated by
|
|
130
137
|
<a href="https://github.com/lfreleng-actions/github-security-report-action">github-security-report</a>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/__init__.py
RENAMED
|
File without changes
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/classify.py
RENAMED
|
File without changes
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/config.py
RENAMED
|
File without changes
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/gitctx.py
RENAMED
|
File without changes
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/models.py
RENAMED
|
File without changes
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/rulesets.py
RENAMED
|
File without changes
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/runner.py
RENAMED
|
File without changes
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/scope.py
RENAMED
|
File without changes
|
{github_security_report-0.4.1 → github_security_report-0.5.0}/src/github_security_report/severity.py
RENAMED
|
File without changes
|
|
File without changes
|