github-security-report 0.12.0__tar.gz → 0.13.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.12.0 → github_security_report-0.13.0}/PKG-INFO +121 -10
- {github_security_report-0.12.0 → github_security_report-0.13.0}/README.md +118 -7
- {github_security_report-0.12.0 → github_security_report-0.13.0}/pyproject.toml +4 -4
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/_version.py +2 -2
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/categories.py +27 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/cli/__init__.py +13 -6
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/cli/app.py +97 -130
- github_security_report-0.13.0/src/github_security_report/cli/boundary.py +172 -0
- github_security_report-0.13.0/src/github_security_report/cli/modes.py +287 -0
- github_security_report-0.13.0/src/github_security_report/cli/options.py +93 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/cli/outputs.py +13 -9
- github_security_report-0.13.0/src/github_security_report/cli/publish.py +147 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/cli/serialise.py +10 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/client/transport.py +186 -105
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/collect/extras.py +7 -1
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/config/__init__.py +12 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/config/loader.py +3 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/config/models.py +4 -0
- github_security_report-0.13.0/src/github_security_report/config/order.py +175 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/config/schema.py +35 -1
- github_security_report-0.13.0/src/github_security_report/layout.py +272 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/ordering.py +4 -0
- github_security_report-0.13.0/src/github_security_report/pulls/__init__.py +148 -0
- github_security_report-0.13.0/src/github_security_report/pulls/columns.py +53 -0
- github_security_report-0.13.0/src/github_security_report/pulls/counting.py +140 -0
- github_security_report-0.13.0/src/github_security_report/pulls/presentation.py +133 -0
- github_security_report-0.13.0/src/github_security_report/pulls/table.py +218 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/render/html.py +25 -28
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/render/markdown.py +9 -13
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/render/slack.py +10 -12
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/render/terminal.py +12 -12
- github_security_report-0.13.0/src/github_security_report/report/__init__.py +103 -0
- github_security_report-0.13.0/src/github_security_report/report/aggregate.py +135 -0
- github_security_report-0.13.0/src/github_security_report/report/display.py +181 -0
- github_security_report-0.13.0/src/github_security_report/report/signals.py +95 -0
- github_security_report-0.13.0/src/github_security_report/report/tables.py +148 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/templates/report.html.j2 +0 -42
- github_security_report-0.12.0/src/github_security_report/cli/modes.py +0 -418
- github_security_report-0.12.0/src/github_security_report/pulls.py +0 -499
- github_security_report-0.12.0/src/github_security_report/report.py +0 -531
- {github_security_report-0.12.0 → github_security_report-0.13.0}/.gitignore +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/LICENSE +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/LICENSES/Apache-2.0.txt +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/scripts/README.md +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/__init__.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/authors.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/classify.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/cli/__main__.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/client/__init__.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/client/endpoints.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/client/org_reads.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/client/parsers.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/client/queries.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/client/reads.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/client/writes.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/collect/__init__.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/collect/context.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/collect/org.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/collect/protocols.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/collect/repo.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/gating.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/gitctx.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/issues.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/models.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/posture/__init__.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/posture/enablement.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/posture/facts.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/posture/releases.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/py.typed +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/remediate.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/render/__init__.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/render/slack_limits.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/rulesets.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/runner.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/scope.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/severity.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/summary.py +0 -0
- {github_security_report-0.12.0 → github_security_report-0.13.0}/src/github_security_report/templates/index.html.j2 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: github-security-report
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.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
|
|
@@ -31,12 +31,12 @@ Requires-Dist: pyyaml==6.0.3
|
|
|
31
31
|
Requires-Dist: rich==15.0.0
|
|
32
32
|
Requires-Dist: typer==0.27.1
|
|
33
33
|
Provides-Extra: dev
|
|
34
|
-
Requires-Dist: mypy==2.3.
|
|
34
|
+
Requires-Dist: mypy==2.3.1; 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.16.
|
|
39
|
+
Requires-Dist: ruff==0.16.3; extra == 'dev'
|
|
40
40
|
Requires-Dist: syrupy==5.5.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'
|
|
@@ -95,8 +95,8 @@ no alerts, no analyses on a sample of repositories — gets a single
|
|
|
95
95
|
`⏩ Skipping feature: organisation support missing` line for that section
|
|
96
96
|
instead of a nag list. See the
|
|
97
97
|
[organisation scan setup guide](docs/org-scan-setup.md) for the required
|
|
98
|
-
workflows, and disable the check with `report.gating: false`
|
|
99
|
-
probe everything regardless.
|
|
98
|
+
workflows, and disable the check with `report.gating: false` (or `--no-gating`)
|
|
99
|
+
if you want to probe everything regardless.
|
|
100
100
|
|
|
101
101
|
Further sections report **configuration posture** and **freshness** as plain
|
|
102
102
|
tables (org mode):
|
|
@@ -223,7 +223,8 @@ organisations. Grant these scopes:
|
|
|
223
223
|
scope: "org"
|
|
224
224
|
config: "${{ secrets.GSR_CONFIG || vars.GSR_CONFIG }}"
|
|
225
225
|
token: "${{ secrets.LFRELENG_ACTIONS_REPORT_PAT }}"
|
|
226
|
-
#
|
|
226
|
+
# Exports the token under this name, and overrides the per-org
|
|
227
|
+
# "token_env" in the config (below) so the two cannot drift apart.
|
|
227
228
|
token_env: "LFRELENG_ACTIONS_REPORT_PAT"
|
|
228
229
|
output_dir: "site"
|
|
229
230
|
pages_url: "https://lfreleng-actions.github.io/github-security-report-action/"
|
|
@@ -246,6 +247,15 @@ digest only on the configured `report_day` (default Tuesday).
|
|
|
246
247
|
# requires: permissions: { security-events: read }
|
|
247
248
|
```
|
|
248
249
|
|
|
250
|
+
Repo mode renders one repository to the terminal and the job summary. It
|
|
251
|
+
honours `top_n`, `top_n_report`, `top_n_cli`, `hide`, and the `report` block of
|
|
252
|
+
a supplied `config` (including every `report.categories.*` toggle). The inputs
|
|
253
|
+
that only shape an organisation-wide run — `output_dir`, `pages_url`,
|
|
254
|
+
`slack_channel`, `force_notify`, `top_n_slack`, and the Releases/Tagging levers
|
|
255
|
+
— are **rejected** with exit code 2 rather than accepted and discarded, so a
|
|
256
|
+
misconfigured workflow says so instead of quietly producing a report that
|
|
257
|
+
ignores them.
|
|
258
|
+
|
|
249
259
|
## Configuration
|
|
250
260
|
|
|
251
261
|
Configuration is JSON, supplied as a plain `vars.` entry or base64-encoded in a
|
|
@@ -264,7 +274,8 @@ environment-variable name, never embedded.
|
|
|
264
274
|
"include_archived": false,
|
|
265
275
|
"include_test": false,
|
|
266
276
|
"repo_min_age_days": 28,
|
|
267
|
-
"release_max_age_days": 60
|
|
277
|
+
"release_max_age_days": 60,
|
|
278
|
+
"order": { "style": "auto" }
|
|
268
279
|
},
|
|
269
280
|
"organizations": [
|
|
270
281
|
{
|
|
@@ -300,7 +311,12 @@ The Releases / Tagging section has two independent freshness levers:
|
|
|
300
311
|
repositories drop out of the table. CLI: `--release-max-age-days`.
|
|
301
312
|
|
|
302
313
|
The per-org `releases_exclude` (CLI `--releases-exclude`, repeatable) drops
|
|
303
|
-
named repositories from the section entirely.
|
|
314
|
+
named repositories from the section entirely. The flag *replaces* the
|
|
315
|
+
configured list rather than adding to it, and cannot say which organisation it
|
|
316
|
+
means, so it is refused when the run covers more than one — set
|
|
317
|
+
`releases_exclude` per organisation in the configuration for that case. The two
|
|
318
|
+
age levers above are scalar policy and do apply to every configured
|
|
319
|
+
organisation, as `--top-n` does.
|
|
304
320
|
|
|
305
321
|
> The former `release_min_age_days` key was a misleading name for
|
|
306
322
|
> `repo_min_age_days` (it gates *repository* age, not *release* age). It is
|
|
@@ -311,6 +327,10 @@ The per-org `exclude` list removes repositories from analysis entirely; they are
|
|
|
311
327
|
reported as **excluded** (distinct from "not enabled"), so an intentional
|
|
312
328
|
exclusion is visible rather than silently dropped.
|
|
313
329
|
|
|
330
|
+
Archived and test repositories are excluded from analysis by default. Opt them
|
|
331
|
+
back in with `report.include_archived` / `report.include_test` in the config, or
|
|
332
|
+
for a single run with `--include-archived` / `--include-test`.
|
|
333
|
+
|
|
314
334
|
### Per-category render toggles
|
|
315
335
|
|
|
316
336
|
Every reporting category can be switched on or off, globally and per output
|
|
@@ -487,6 +507,92 @@ a column list — Scorecard cascades through the worst severity rung any offende
|
|
|
487
507
|
actually carries, so a lone Critical is never buried by a weaker repository with
|
|
488
508
|
a lower score.
|
|
489
509
|
|
|
510
|
+
### Output order
|
|
511
|
+
|
|
512
|
+
`sort` above orders the rows *within* a table. `report.order` orders the
|
|
513
|
+
**sections themselves** — which category a reader meets first.
|
|
514
|
+
|
|
515
|
+
By default the report sorts its sections into three bands:
|
|
516
|
+
|
|
517
|
+
| Band | Contains | Why |
|
|
518
|
+
| ---- | -------- | --- |
|
|
519
|
+
| **Priority** | Secret Scanning, Dependabot: Security Alerts, CodeQL, Mutable Releases | Findings worth acting on today |
|
|
520
|
+
| **Middle** | Everything else | Neither urgent nor constant |
|
|
521
|
+
| **BAU** | OpenSSF Scorecard, GitHub Issues, Pull Requests, Assigned to Me | Carry data every run, so none of it is news |
|
|
522
|
+
|
|
523
|
+
The bands are not fixed slots. **A band member with nothing to report moves
|
|
524
|
+
into the middle**: a clean priority category is noise at the top of a page, and
|
|
525
|
+
a clean BAU category has stopped being background. Demoted priority categories
|
|
526
|
+
sit at the top of the middle band and demoted BAU ones at the bottom, so
|
|
527
|
+
whatever survives in each band still bounds the middle from its own side.
|
|
528
|
+
|
|
529
|
+
"Nothing to report" means no rows and no named repositories — no offenders, and
|
|
530
|
+
nobody nagged for having the tool disabled. The bare counts every category
|
|
531
|
+
prints regardless (`All Clean`, an unknown tally) do not hold a band position. A
|
|
532
|
+
section skipped by [feature gating](#organisation-feature-gating) renders a
|
|
533
|
+
notice rather than results, so it demotes too.
|
|
534
|
+
|
|
535
|
+
The order is resolved **once**, when the report is assembled, and every surface
|
|
536
|
+
draws it — the terminal, the Slack digest, the Markdown artifact and the Pages
|
|
537
|
+
HTML cannot disagree. `report.json` publishes the resolved sequence as
|
|
538
|
+
`section_order`, listing each nested posture table after the signal it renders
|
|
539
|
+
beneath, so a machine consumer can reproduce the same layout.
|
|
540
|
+
|
|
541
|
+
Four styles are available via `report.order.style`:
|
|
542
|
+
|
|
543
|
+
| Style | Reads | Behaviour |
|
|
544
|
+
| ----- | ----- | --------- |
|
|
545
|
+
| `auto` (default) | — | The bands above, with the built-in membership. `automatic` is accepted as the same thing |
|
|
546
|
+
| `dual` | `priority`, `bau` | The same algorithm over your own band lists. Omit either to keep the built-in one |
|
|
547
|
+
| `single` | `sequence` | A strict hierarchy, applied verbatim with **no** demotion. Categories the list omits keep their assembly order behind it |
|
|
548
|
+
| `fixed` | — | No reordering at all — the order this tool produced before `report.order` existed |
|
|
549
|
+
|
|
550
|
+
Two bands of your own:
|
|
551
|
+
|
|
552
|
+
```json
|
|
553
|
+
{
|
|
554
|
+
"report": {
|
|
555
|
+
"order": {
|
|
556
|
+
"style": "dual",
|
|
557
|
+
"priority": ["secret_scanning", "codeql"],
|
|
558
|
+
"bau": ["github_issues", "pull_requests"]
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
"organizations": [{ "name": "lfreleng-actions" }]
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Or one strict hierarchy, honoured whatever the data says:
|
|
566
|
+
|
|
567
|
+
```json
|
|
568
|
+
{
|
|
569
|
+
"report": {
|
|
570
|
+
"order": {
|
|
571
|
+
"style": "single",
|
|
572
|
+
"sequence": ["secret_scanning", "dependabot_alerts", "codeql"]
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
"organizations": [{ "name": "lfreleng-actions" }]
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
A list key paired with a style that does not read it is an **error** rather than
|
|
580
|
+
a silent no-op: writing a `priority` band and leaving the style at `auto` asks
|
|
581
|
+
for a custom band and would otherwise quietly get the built-in one. Listing a
|
|
582
|
+
category twice, or in both bands, is rejected for the same reason — a category
|
|
583
|
+
holds exactly one position.
|
|
584
|
+
|
|
585
|
+
An organisation inherits the global `order` block and may override it. Setting
|
|
586
|
+
`style` back to `"auto"` restores the built-in bands rather than keeping the
|
|
587
|
+
ones a global `dual` block supplied, since the built-in bands are what `auto`
|
|
588
|
+
means.
|
|
589
|
+
|
|
590
|
+
The three Dependabot posture tables (alerts enabled, security updates enabled,
|
|
591
|
+
cooldown) are not independently placeable. They render beneath the Dependabot
|
|
592
|
+
Alerts signal on every surface and travel with it, since three near-identical
|
|
593
|
+
headings adrift in the report would not say which signal they qualified.
|
|
594
|
+
Naming one in an ordering list is rejected rather than accepted and ignored.
|
|
595
|
+
|
|
490
596
|
### GitHub Issues
|
|
491
597
|
|
|
492
598
|
The `github_issues` category counts each repository's **open issues**, split by
|
|
@@ -942,7 +1048,8 @@ code-scanning alerts from the tool, analyses on a sample of repositories, or
|
|
|
942
1048
|
**skipped** — not probed per repository, not classified — and its section
|
|
943
1049
|
shows a single `⏩ Skipping feature: organisation support missing` line
|
|
944
1050
|
linking the setup guide, on every output surface. Set `report.gating` to
|
|
945
|
-
`false` (globally or per organisation)
|
|
1051
|
+
`false` (globally or per organisation), or pass `--no-gating` for a single run,
|
|
1052
|
+
to always probe everything:
|
|
946
1053
|
|
|
947
1054
|
```json
|
|
948
1055
|
{
|
|
@@ -951,6 +1058,10 @@ linking the setup guide, on every output surface. Set `report.gating` to
|
|
|
951
1058
|
}
|
|
952
1059
|
```
|
|
953
1060
|
|
|
1061
|
+
Because a skipped section is the most likely prompt for "why is zizmor missing
|
|
1062
|
+
from my report?", `--no-gating` exists so that question can be answered without
|
|
1063
|
+
writing a configuration file to set one boolean.
|
|
1064
|
+
|
|
954
1065
|
Gating decides **collection**; the per-category render toggles above decide
|
|
955
1066
|
**presentation**. A skipped section still renders (as the one-line notice)
|
|
956
1067
|
unless its category is also disabled.
|
|
@@ -1021,7 +1132,7 @@ and the Slack **bot token** is consumed by the workflow, not the CLI.
|
|
|
1021
1132
|
| `org` | No | — | Single organisation (shorthand for org mode) |
|
|
1022
1133
|
| `repo` | No | detected | `owner/name` for repo mode |
|
|
1023
1134
|
| `token` | No | `${{ github.token }}` | PAT (org mode) or `GITHUB_TOKEN` (repo mode) |
|
|
1024
|
-
| `token_env` | No | `GITHUB_TOKEN` | Env var name the token is exported under.
|
|
1135
|
+
| `token_env` | No | `GITHUB_TOKEN` | Env var name the token is exported under. When set it **overrides** the per-org `token_env` in your config, so the two no longer have to be kept in step; leave it unset to let each organisation use its own configured variable. |
|
|
1025
1136
|
| `output_dir` | No | — | Directory for Pages output (org mode) |
|
|
1026
1137
|
| `pages_url` | No | — | Published Pages URL (used in the Slack link) |
|
|
1027
1138
|
| `slack_channel` | No | — | Slack channel ID; overrides the config `slack.channel` (e.g. the `SLACK_CHANNEL_ID` variable) |
|
|
@@ -51,8 +51,8 @@ no alerts, no analyses on a sample of repositories — gets a single
|
|
|
51
51
|
`⏩ Skipping feature: organisation support missing` line for that section
|
|
52
52
|
instead of a nag list. See the
|
|
53
53
|
[organisation scan setup guide](docs/org-scan-setup.md) for the required
|
|
54
|
-
workflows, and disable the check with `report.gating: false`
|
|
55
|
-
probe everything regardless.
|
|
54
|
+
workflows, and disable the check with `report.gating: false` (or `--no-gating`)
|
|
55
|
+
if you want to probe everything regardless.
|
|
56
56
|
|
|
57
57
|
Further sections report **configuration posture** and **freshness** as plain
|
|
58
58
|
tables (org mode):
|
|
@@ -179,7 +179,8 @@ organisations. Grant these scopes:
|
|
|
179
179
|
scope: "org"
|
|
180
180
|
config: "${{ secrets.GSR_CONFIG || vars.GSR_CONFIG }}"
|
|
181
181
|
token: "${{ secrets.LFRELENG_ACTIONS_REPORT_PAT }}"
|
|
182
|
-
#
|
|
182
|
+
# Exports the token under this name, and overrides the per-org
|
|
183
|
+
# "token_env" in the config (below) so the two cannot drift apart.
|
|
183
184
|
token_env: "LFRELENG_ACTIONS_REPORT_PAT"
|
|
184
185
|
output_dir: "site"
|
|
185
186
|
pages_url: "https://lfreleng-actions.github.io/github-security-report-action/"
|
|
@@ -202,6 +203,15 @@ digest only on the configured `report_day` (default Tuesday).
|
|
|
202
203
|
# requires: permissions: { security-events: read }
|
|
203
204
|
```
|
|
204
205
|
|
|
206
|
+
Repo mode renders one repository to the terminal and the job summary. It
|
|
207
|
+
honours `top_n`, `top_n_report`, `top_n_cli`, `hide`, and the `report` block of
|
|
208
|
+
a supplied `config` (including every `report.categories.*` toggle). The inputs
|
|
209
|
+
that only shape an organisation-wide run — `output_dir`, `pages_url`,
|
|
210
|
+
`slack_channel`, `force_notify`, `top_n_slack`, and the Releases/Tagging levers
|
|
211
|
+
— are **rejected** with exit code 2 rather than accepted and discarded, so a
|
|
212
|
+
misconfigured workflow says so instead of quietly producing a report that
|
|
213
|
+
ignores them.
|
|
214
|
+
|
|
205
215
|
## Configuration
|
|
206
216
|
|
|
207
217
|
Configuration is JSON, supplied as a plain `vars.` entry or base64-encoded in a
|
|
@@ -220,7 +230,8 @@ environment-variable name, never embedded.
|
|
|
220
230
|
"include_archived": false,
|
|
221
231
|
"include_test": false,
|
|
222
232
|
"repo_min_age_days": 28,
|
|
223
|
-
"release_max_age_days": 60
|
|
233
|
+
"release_max_age_days": 60,
|
|
234
|
+
"order": { "style": "auto" }
|
|
224
235
|
},
|
|
225
236
|
"organizations": [
|
|
226
237
|
{
|
|
@@ -256,7 +267,12 @@ The Releases / Tagging section has two independent freshness levers:
|
|
|
256
267
|
repositories drop out of the table. CLI: `--release-max-age-days`.
|
|
257
268
|
|
|
258
269
|
The per-org `releases_exclude` (CLI `--releases-exclude`, repeatable) drops
|
|
259
|
-
named repositories from the section entirely.
|
|
270
|
+
named repositories from the section entirely. The flag *replaces* the
|
|
271
|
+
configured list rather than adding to it, and cannot say which organisation it
|
|
272
|
+
means, so it is refused when the run covers more than one — set
|
|
273
|
+
`releases_exclude` per organisation in the configuration for that case. The two
|
|
274
|
+
age levers above are scalar policy and do apply to every configured
|
|
275
|
+
organisation, as `--top-n` does.
|
|
260
276
|
|
|
261
277
|
> The former `release_min_age_days` key was a misleading name for
|
|
262
278
|
> `repo_min_age_days` (it gates *repository* age, not *release* age). It is
|
|
@@ -267,6 +283,10 @@ The per-org `exclude` list removes repositories from analysis entirely; they are
|
|
|
267
283
|
reported as **excluded** (distinct from "not enabled"), so an intentional
|
|
268
284
|
exclusion is visible rather than silently dropped.
|
|
269
285
|
|
|
286
|
+
Archived and test repositories are excluded from analysis by default. Opt them
|
|
287
|
+
back in with `report.include_archived` / `report.include_test` in the config, or
|
|
288
|
+
for a single run with `--include-archived` / `--include-test`.
|
|
289
|
+
|
|
270
290
|
### Per-category render toggles
|
|
271
291
|
|
|
272
292
|
Every reporting category can be switched on or off, globally and per output
|
|
@@ -443,6 +463,92 @@ a column list — Scorecard cascades through the worst severity rung any offende
|
|
|
443
463
|
actually carries, so a lone Critical is never buried by a weaker repository with
|
|
444
464
|
a lower score.
|
|
445
465
|
|
|
466
|
+
### Output order
|
|
467
|
+
|
|
468
|
+
`sort` above orders the rows *within* a table. `report.order` orders the
|
|
469
|
+
**sections themselves** — which category a reader meets first.
|
|
470
|
+
|
|
471
|
+
By default the report sorts its sections into three bands:
|
|
472
|
+
|
|
473
|
+
| Band | Contains | Why |
|
|
474
|
+
| ---- | -------- | --- |
|
|
475
|
+
| **Priority** | Secret Scanning, Dependabot: Security Alerts, CodeQL, Mutable Releases | Findings worth acting on today |
|
|
476
|
+
| **Middle** | Everything else | Neither urgent nor constant |
|
|
477
|
+
| **BAU** | OpenSSF Scorecard, GitHub Issues, Pull Requests, Assigned to Me | Carry data every run, so none of it is news |
|
|
478
|
+
|
|
479
|
+
The bands are not fixed slots. **A band member with nothing to report moves
|
|
480
|
+
into the middle**: a clean priority category is noise at the top of a page, and
|
|
481
|
+
a clean BAU category has stopped being background. Demoted priority categories
|
|
482
|
+
sit at the top of the middle band and demoted BAU ones at the bottom, so
|
|
483
|
+
whatever survives in each band still bounds the middle from its own side.
|
|
484
|
+
|
|
485
|
+
"Nothing to report" means no rows and no named repositories — no offenders, and
|
|
486
|
+
nobody nagged for having the tool disabled. The bare counts every category
|
|
487
|
+
prints regardless (`All Clean`, an unknown tally) do not hold a band position. A
|
|
488
|
+
section skipped by [feature gating](#organisation-feature-gating) renders a
|
|
489
|
+
notice rather than results, so it demotes too.
|
|
490
|
+
|
|
491
|
+
The order is resolved **once**, when the report is assembled, and every surface
|
|
492
|
+
draws it — the terminal, the Slack digest, the Markdown artifact and the Pages
|
|
493
|
+
HTML cannot disagree. `report.json` publishes the resolved sequence as
|
|
494
|
+
`section_order`, listing each nested posture table after the signal it renders
|
|
495
|
+
beneath, so a machine consumer can reproduce the same layout.
|
|
496
|
+
|
|
497
|
+
Four styles are available via `report.order.style`:
|
|
498
|
+
|
|
499
|
+
| Style | Reads | Behaviour |
|
|
500
|
+
| ----- | ----- | --------- |
|
|
501
|
+
| `auto` (default) | — | The bands above, with the built-in membership. `automatic` is accepted as the same thing |
|
|
502
|
+
| `dual` | `priority`, `bau` | The same algorithm over your own band lists. Omit either to keep the built-in one |
|
|
503
|
+
| `single` | `sequence` | A strict hierarchy, applied verbatim with **no** demotion. Categories the list omits keep their assembly order behind it |
|
|
504
|
+
| `fixed` | — | No reordering at all — the order this tool produced before `report.order` existed |
|
|
505
|
+
|
|
506
|
+
Two bands of your own:
|
|
507
|
+
|
|
508
|
+
```json
|
|
509
|
+
{
|
|
510
|
+
"report": {
|
|
511
|
+
"order": {
|
|
512
|
+
"style": "dual",
|
|
513
|
+
"priority": ["secret_scanning", "codeql"],
|
|
514
|
+
"bau": ["github_issues", "pull_requests"]
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
"organizations": [{ "name": "lfreleng-actions" }]
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Or one strict hierarchy, honoured whatever the data says:
|
|
522
|
+
|
|
523
|
+
```json
|
|
524
|
+
{
|
|
525
|
+
"report": {
|
|
526
|
+
"order": {
|
|
527
|
+
"style": "single",
|
|
528
|
+
"sequence": ["secret_scanning", "dependabot_alerts", "codeql"]
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
"organizations": [{ "name": "lfreleng-actions" }]
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
A list key paired with a style that does not read it is an **error** rather than
|
|
536
|
+
a silent no-op: writing a `priority` band and leaving the style at `auto` asks
|
|
537
|
+
for a custom band and would otherwise quietly get the built-in one. Listing a
|
|
538
|
+
category twice, or in both bands, is rejected for the same reason — a category
|
|
539
|
+
holds exactly one position.
|
|
540
|
+
|
|
541
|
+
An organisation inherits the global `order` block and may override it. Setting
|
|
542
|
+
`style` back to `"auto"` restores the built-in bands rather than keeping the
|
|
543
|
+
ones a global `dual` block supplied, since the built-in bands are what `auto`
|
|
544
|
+
means.
|
|
545
|
+
|
|
546
|
+
The three Dependabot posture tables (alerts enabled, security updates enabled,
|
|
547
|
+
cooldown) are not independently placeable. They render beneath the Dependabot
|
|
548
|
+
Alerts signal on every surface and travel with it, since three near-identical
|
|
549
|
+
headings adrift in the report would not say which signal they qualified.
|
|
550
|
+
Naming one in an ordering list is rejected rather than accepted and ignored.
|
|
551
|
+
|
|
446
552
|
### GitHub Issues
|
|
447
553
|
|
|
448
554
|
The `github_issues` category counts each repository's **open issues**, split by
|
|
@@ -898,7 +1004,8 @@ code-scanning alerts from the tool, analyses on a sample of repositories, or
|
|
|
898
1004
|
**skipped** — not probed per repository, not classified — and its section
|
|
899
1005
|
shows a single `⏩ Skipping feature: organisation support missing` line
|
|
900
1006
|
linking the setup guide, on every output surface. Set `report.gating` to
|
|
901
|
-
`false` (globally or per organisation)
|
|
1007
|
+
`false` (globally or per organisation), or pass `--no-gating` for a single run,
|
|
1008
|
+
to always probe everything:
|
|
902
1009
|
|
|
903
1010
|
```json
|
|
904
1011
|
{
|
|
@@ -907,6 +1014,10 @@ linking the setup guide, on every output surface. Set `report.gating` to
|
|
|
907
1014
|
}
|
|
908
1015
|
```
|
|
909
1016
|
|
|
1017
|
+
Because a skipped section is the most likely prompt for "why is zizmor missing
|
|
1018
|
+
from my report?", `--no-gating` exists so that question can be answered without
|
|
1019
|
+
writing a configuration file to set one boolean.
|
|
1020
|
+
|
|
910
1021
|
Gating decides **collection**; the per-category render toggles above decide
|
|
911
1022
|
**presentation**. A skipped section still renders (as the one-line notice)
|
|
912
1023
|
unless its category is also disabled.
|
|
@@ -977,7 +1088,7 @@ and the Slack **bot token** is consumed by the workflow, not the CLI.
|
|
|
977
1088
|
| `org` | No | — | Single organisation (shorthand for org mode) |
|
|
978
1089
|
| `repo` | No | detected | `owner/name` for repo mode |
|
|
979
1090
|
| `token` | No | `${{ github.token }}` | PAT (org mode) or `GITHUB_TOKEN` (repo mode) |
|
|
980
|
-
| `token_env` | No | `GITHUB_TOKEN` | Env var name the token is exported under.
|
|
1091
|
+
| `token_env` | No | `GITHUB_TOKEN` | Env var name the token is exported under. When set it **overrides** the per-org `token_env` in your config, so the two no longer have to be kept in step; leave it unset to let each organisation use its own configured variable. |
|
|
981
1092
|
| `output_dir` | No | — | Directory for Pages output (org mode) |
|
|
982
1093
|
| `pages_url` | No | — | Published Pages URL (used in the Slack link) |
|
|
983
1094
|
| `slack_channel` | No | — | Slack channel ID; overrides the config `slack.channel` (e.g. the `SLACK_CHANNEL_ID` variable) |
|
|
@@ -64,8 +64,8 @@ dev = [
|
|
|
64
64
|
"pytest-cov==7.1.0",
|
|
65
65
|
"respx==0.23.1",
|
|
66
66
|
"syrupy==5.5.3",
|
|
67
|
-
"mypy==2.3.
|
|
68
|
-
"ruff==0.16.
|
|
67
|
+
"mypy==2.3.1",
|
|
68
|
+
"ruff==0.16.3",
|
|
69
69
|
"types-jsonschema==4.26.0.20260518",
|
|
70
70
|
"types-PyYAML==6.0.12.20250915",
|
|
71
71
|
]
|
|
@@ -96,8 +96,8 @@ dev = [
|
|
|
96
96
|
"pytest-cov==7.1.0",
|
|
97
97
|
"respx==0.23.1",
|
|
98
98
|
"syrupy==5.5.3",
|
|
99
|
-
"mypy==2.3.
|
|
100
|
-
"ruff==0.16.
|
|
99
|
+
"mypy==2.3.1",
|
|
100
|
+
"ruff==0.16.3",
|
|
101
101
|
"types-jsonschema==4.26.0.20260518",
|
|
102
102
|
"types-PyYAML==6.0.12.20250915",
|
|
103
103
|
]
|
|
@@ -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.13.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 13, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -337,6 +337,33 @@ def category_meta(key: CategoryKey) -> CategoryMeta:
|
|
|
337
337
|
return _CATEGORIES[key]
|
|
338
338
|
|
|
339
339
|
|
|
340
|
+
# Categories rendered as sub-tables beneath another category rather than as
|
|
341
|
+
# sections of their own. The three Dependabot posture tables qualify their
|
|
342
|
+
# parent signal -- "Alerts Enabled" means nothing adrift from "Dependabot:
|
|
343
|
+
# Security Alerts" -- so they travel with it and cannot be positioned
|
|
344
|
+
# independently. Named here rather than in the layout module so the config
|
|
345
|
+
# schema can refuse to accept one in an ordering list, which would otherwise be
|
|
346
|
+
# a setting that validates and then does nothing.
|
|
347
|
+
NESTED_CATEGORIES: frozenset[CategoryKey] = frozenset(
|
|
348
|
+
{
|
|
349
|
+
CategoryKey.DEPENDABOT_ALERTS_ENABLED,
|
|
350
|
+
CategoryKey.DEPENDABOT_UPDATES_ENABLED,
|
|
351
|
+
CategoryKey.DEPENDABOT_COOLDOWN,
|
|
352
|
+
}
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def orderable_categories() -> tuple[CategoryMeta, ...]:
|
|
357
|
+
"""Categories an ordering list may name, in registry order.
|
|
358
|
+
|
|
359
|
+
Every category except the nested ones, which have no position of their own
|
|
360
|
+
to configure.
|
|
361
|
+
"""
|
|
362
|
+
return tuple(
|
|
363
|
+
meta for meta in _CATEGORIES.values() if meta.key not in NESTED_CATEGORIES
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
340
367
|
def all_categories() -> tuple[CategoryMeta, ...]:
|
|
341
368
|
"""Every category's metadata, in registry (render) order."""
|
|
342
369
|
return tuple(_CATEGORIES.values())
|
|
@@ -11,6 +11,8 @@ The command surface is split across:
|
|
|
11
11
|
|
|
12
12
|
- :mod:`cli.app` -- the Typer application, options and validation
|
|
13
13
|
- :mod:`cli.modes` -- the org, repo and remediate run modes
|
|
14
|
+
- :mod:`cli.options` -- what a run takes from the command line
|
|
15
|
+
- :mod:`cli.publish` -- the Pages files, Slack digest and job summary
|
|
14
16
|
- :mod:`cli.outputs` -- offender limits, category visibility, file writing
|
|
15
17
|
- :mod:`cli.serialise` -- the machine-readable ``report.json`` shape
|
|
16
18
|
"""
|
|
@@ -25,8 +27,6 @@ from github_security_report.cli.app import (
|
|
|
25
27
|
report,
|
|
26
28
|
)
|
|
27
29
|
from github_security_report.cli.modes import (
|
|
28
|
-
OrgRunOptions,
|
|
29
|
-
ReleaseOverrides,
|
|
30
30
|
_abort_auth,
|
|
31
31
|
_abort_network,
|
|
32
32
|
_load_config,
|
|
@@ -34,6 +34,7 @@ from github_security_report.cli.modes import (
|
|
|
34
34
|
_run_remediate,
|
|
35
35
|
_run_repo,
|
|
36
36
|
)
|
|
37
|
+
from github_security_report.cli.options import OrgRunOptions, ReportOverrides
|
|
37
38
|
from github_security_report.cli.outputs import (
|
|
38
39
|
TopNLimits,
|
|
39
40
|
_safe_component,
|
|
@@ -42,12 +43,18 @@ from github_security_report.cli.outputs import (
|
|
|
42
43
|
)
|
|
43
44
|
from github_security_report.cli.serialise import _org_to_dict, _table_to_dict
|
|
44
45
|
|
|
45
|
-
# The package façade: every name
|
|
46
|
-
# ``github_security_report.cli
|
|
47
|
-
#
|
|
46
|
+
# The package façade: every name the module split moved is still importable
|
|
47
|
+
# from ``github_security_report.cli``, including the private helpers the tests
|
|
48
|
+
# reach for, so relocating a definition changes no caller's import path.
|
|
49
|
+
#
|
|
50
|
+
# ``ReleaseOverrides`` is the one deliberate exception. It gained the gating
|
|
51
|
+
# and inclusion overrides and became ``ReportOverrides``, so it is a rename
|
|
52
|
+
# rather than a move. No alias is kept: the old name now describes only a third
|
|
53
|
+
# of what the class carries, and a façade entry that points somewhere
|
|
54
|
+
# misleading is worse than an import error that says exactly what happened.
|
|
48
55
|
__all__ = [
|
|
49
56
|
"OrgRunOptions",
|
|
50
|
-
"
|
|
57
|
+
"ReportOverrides",
|
|
51
58
|
"TopNLimits",
|
|
52
59
|
"_abort_auth",
|
|
53
60
|
"_abort_network",
|