github-security-report 0.14.0__tar.gz → 0.14.2__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.14.0 → github_security_report-0.14.2}/PKG-INFO +75 -8
- {github_security_report-0.14.0 → github_security_report-0.14.2}/README.md +73 -6
- {github_security_report-0.14.0 → github_security_report-0.14.2}/pyproject.toml +10 -1
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/_version.py +2 -2
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/cli/app.py +8 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/cli/boundary.py +12 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/cli/options.py +10 -3
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/client/__init__.py +6 -2
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/client/alerts.py +2 -1
- github_security_report-0.14.2/src/github_security_report/client/batch_errors.py +247 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/client/copilot.py +5 -0
- github_security_report-0.14.2/src/github_security_report/client/errors.py +79 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/client/org_reads.py +15 -95
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/client/queries.py +14 -5
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/client/transport.py +1 -32
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/collect/__init__.py +6 -1
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/collect/context.py +0 -4
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/collect/org.py +51 -9
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/config/loader.py +1 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/config/models.py +13 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/config/schema.py +8 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/.gitignore +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/LICENSE +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/LICENSES/Apache-2.0.txt +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/scripts/README.md +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/__init__.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/authors.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/categories.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/classify.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/cli/__init__.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/cli/__main__.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/cli/modes.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/cli/outputs.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/cli/publish.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/cli/serialise.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/client/endpoints.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/client/parsers.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/client/reads.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/client/writes.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/collect/extras.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/collect/protocols.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/collect/repo.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/config/__init__.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/config/order.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/gating.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/gitctx.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/issues.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/layout.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/models.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/ordering.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/posture/__init__.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/posture/enablement.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/posture/facts.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/posture/releases.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/pulls/__init__.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/pulls/columns.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/pulls/counting.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/pulls/presentation.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/pulls/table.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/py.typed +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/remediate.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/render/__init__.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/render/html.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/render/markdown.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/render/slack.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/render/slack_limits.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/render/terminal.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/report/__init__.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/report/aggregate.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/report/display.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/report/signals.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/report/tables.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/rulesets.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/runner.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/scope.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/secret_patterns.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/severity.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/summary.py +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/templates/index.html.j2 +0 -0
- {github_security_report-0.14.0 → github_security_report-0.14.2}/src/github_security_report/templates/report.html.j2 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: github-security-report
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.2
|
|
4
4
|
Summary: Security and quality reporting across GitHub organisations
|
|
5
5
|
Project-URL: Homepage, https://github.com/lfreleng-actions/github-security-report-action
|
|
6
6
|
Project-URL: Repository, https://github.com/lfreleng-actions/github-security-report-action
|
|
@@ -29,7 +29,7 @@ Requires-Dist: jinja2==3.1.6
|
|
|
29
29
|
Requires-Dist: jsonschema==4.26.0
|
|
30
30
|
Requires-Dist: pyyaml==6.0.3
|
|
31
31
|
Requires-Dist: rich==15.0.0
|
|
32
|
-
Requires-Dist: typer==0.27.
|
|
32
|
+
Requires-Dist: typer==0.27.2
|
|
33
33
|
Provides-Extra: dev
|
|
34
34
|
Requires-Dist: mypy==2.3.1; extra == 'dev'
|
|
35
35
|
Requires-Dist: pytest-asyncio==1.4.0; extra == 'dev'
|
|
@@ -302,6 +302,7 @@ environment-variable name, never embedded.
|
|
|
302
302
|
"include_test": false,
|
|
303
303
|
"repo_min_age_days": 28,
|
|
304
304
|
"release_max_age_days": 60,
|
|
305
|
+
"graph_batch": 10,
|
|
305
306
|
"order": { "style": "auto" }
|
|
306
307
|
},
|
|
307
308
|
"organizations": [
|
|
@@ -788,10 +789,15 @@ pull request that cannot progress until somebody responds. The count also feeds
|
|
|
788
789
|
the row ranking, so a backlog awaiting review outranks an untouched one of the
|
|
789
790
|
same size.
|
|
790
791
|
|
|
791
|
-
The scan reads
|
|
792
|
-
most expensive part of the prefetch (see the cost figures above).
|
|
793
|
-
|
|
794
|
-
|
|
792
|
+
The scan reads the **newest 20 review threads per pull request**, which is the
|
|
793
|
+
single most expensive part of the prefetch (see the cost figures above). The
|
|
794
|
+
newest end is deliberate: GitHub returns review threads oldest-first and offers
|
|
795
|
+
no way to order them, so reading from the front would window the threads a
|
|
796
|
+
review has already worked through — the resolved ones — and report a pull
|
|
797
|
+
request still waiting on its latest round as clear. Unanswered feedback is the
|
|
798
|
+
recent kind. Where a pull request carries more threads than the window and none
|
|
799
|
+
of the collected ones qualifies, it is treated as **indeterminate** rather than
|
|
800
|
+
clear — see below.
|
|
795
801
|
|
|
796
802
|
#### Automation backlog thresholds
|
|
797
803
|
|
|
@@ -1127,6 +1133,55 @@ Gating decides **collection**; the per-category render toggles above decide
|
|
|
1127
1133
|
**presentation**. A skipped section still renders (as the one-line notice)
|
|
1128
1134
|
unless its category is also disabled.
|
|
1129
1135
|
|
|
1136
|
+
### GraphQL prefetch batch size
|
|
1137
|
+
|
|
1138
|
+
The releases/tags, Dependabot-enablement, open-issues and pull-request data
|
|
1139
|
+
for every in-scope repository comes from one aliased GraphQL query per batch
|
|
1140
|
+
of repositories rather than a round-trip per repository. GitHub bounds how
|
|
1141
|
+
much work a single GraphQL query may do — roughly ten seconds of execution —
|
|
1142
|
+
and reports a breach in one of three shapes, each after the query has already
|
|
1143
|
+
run for the full limit: an **HTTP 502** (or 504) gateway timeout from the
|
|
1144
|
+
edge, an HTTP 200 carrying no `data`, or an HTTP 200 whose `errors` say
|
|
1145
|
+
`Resource limits for this query exceeded` with the unresolved fields nulled.
|
|
1146
|
+
Each aliased repository adds a few hundred milliseconds, so the batch
|
|
1147
|
+
size is bounded by *latency*, not by rate-limit cost: 25 repositories measured
|
|
1148
|
+
at ~9 s against `lfreleng-actions` and failed intermittently; 10 measured at
|
|
1149
|
+
~3.5 s.
|
|
1150
|
+
|
|
1151
|
+
`report.graph_batch` (default `10`; minimum `1`) sets the starting batch size,
|
|
1152
|
+
globally or per organisation. It is a lever for a slow day rather than a hard
|
|
1153
|
+
limit: a batch that still fails on size after the transport's own retries is
|
|
1154
|
+
re-issued at **half** the size, and the smaller size is kept for the rest of
|
|
1155
|
+
that organisation's collection (each organisation in a multi-org run starts
|
|
1156
|
+
from its own configured size, since organisations differ in how much data a
|
|
1157
|
+
repository carries). Only a single-repository query GitHub still cannot
|
|
1158
|
+
answer, or a failure that a smaller query could not fix (a `403`, an
|
|
1159
|
+
exhausted rate limit, or a `500`/`503` outage rather than a `502`/`504`
|
|
1160
|
+
timeout), aborts the run with exit code `3`.
|
|
1161
|
+
|
|
1162
|
+
The value can be set three ways, in this order of precedence:
|
|
1163
|
+
|
|
1164
|
+
1. `--graph-batch N` on the command line (the action's `graph_batch` input).
|
|
1165
|
+
2. A **repository or organisation variable** passed to that input. The
|
|
1166
|
+
bundled `reporting.yaml` passes `vars.GSR_GRAPH_BATCH`, so the size can be
|
|
1167
|
+
adjusted from repository settings without a workflow edit or a release;
|
|
1168
|
+
a caller's own workflow can do the same. (The `vars` context is not
|
|
1169
|
+
available inside a composite action, so the action cannot read it itself.)
|
|
1170
|
+
3. `report.graph_batch` in the configuration; otherwise the built-in `10`.
|
|
1171
|
+
|
|
1172
|
+
Batching changes how many requests carry the data, not how many GraphQL nodes
|
|
1173
|
+
are resolved, so the node work is the same whatever the batch size. A smaller
|
|
1174
|
+
batch does cost slightly more of the rate-limit budget — each query is charged
|
|
1175
|
+
at least one point, and per-query cost rounding adds a little when one query
|
|
1176
|
+
becomes several — and a few more requests (a 124-repository organisation is 13
|
|
1177
|
+
queries at 10, 5 at 25). Against the 5,000-point hourly budget that is noise;
|
|
1178
|
+
a larger value buys nothing except a longer first failure.
|
|
1179
|
+
|
|
1180
|
+
> The flag and input are recent additions. An empty input is not passed to
|
|
1181
|
+
> the tool, so an unset variable cannot break a run; but a pinned tool version
|
|
1182
|
+
> predating the flag will reject a set one with `No such option`, so set
|
|
1183
|
+
> `GSR_GRAPH_BATCH` only once the pinned release supports it.
|
|
1184
|
+
|
|
1130
1185
|
### Pass/fail severity cutoff
|
|
1131
1186
|
|
|
1132
1187
|
The severity-ranked signals (CodeQL, Scorecard, Zizmor, aislop, Dependabot
|
|
@@ -1204,6 +1259,7 @@ and the Slack **bot token** is consumed by the workflow, not the CLI.
|
|
|
1204
1259
|
| `fail_threshold` | No | `none` | `none`/`low`/`medium`/`high`/`critical`/`any` (repo mode) |
|
|
1205
1260
|
| `force_notify` | No | `false` | Post to Slack regardless of `report_day` |
|
|
1206
1261
|
| `hide` | No | `""` | Category keys to suppress on every output (space- or comma-separated). Overrides config, and is one-way: it cannot re-enable a disabled category |
|
|
1262
|
+
| `graph_batch` | No | `""` | Repositories per batched GraphQL prefetch query (minimum `1`; default: config, else `10`). Pass a repository or organisation variable such as `${{ vars.GSR_GRAPH_BATCH }}` to adjust it from settings. A batch that still fails is halved automatically, so this sets the starting size (see [GraphQL prefetch batch size](#graphql-prefetch-batch-size)) |
|
|
1207
1263
|
| `tool_version` | No | `""` | Published PyPI version to install. Empty (the default) uses the Dependabot-managed pin in `.github/runtime-pin/requirements.txt`; set a specific version to override. Ignored on pull requests or when `use_local_source` is `true` (both run from source) |
|
|
1208
1264
|
| `use_local_source` | No | `false` | Run from the checked-out source instead of PyPI (for testing unreleased code from any event) |
|
|
1209
1265
|
|
|
@@ -1247,7 +1303,7 @@ API in the same way.
|
|
|
1247
1303
|
| `0` | The report ran. |
|
|
1248
1304
|
| `1` | Repo mode only: findings met or exceeded `--fail-threshold`. |
|
|
1249
1305
|
| `2` | Usage or configuration error (bad flag, unreadable config). |
|
|
1250
|
-
| `3` | The GitHub API was
|
|
1306
|
+
| `3` | The GitHub API was unusable after the retry budget (see below). |
|
|
1251
1307
|
| `4` | GitHub rejected the credentials (HTTP 401). |
|
|
1252
1308
|
|
|
1253
1309
|
Codes `3` and `4` are **aborts, not reports**: nothing is written and no Pages
|
|
@@ -1258,8 +1314,19 @@ section `No data` or `All Clean` — and a scheduled job would then publish it
|
|
|
1258
1314
|
over the last good one. Reporting false data is worse than reporting none, so
|
|
1259
1315
|
the run stops at the first rejected request.
|
|
1260
1316
|
|
|
1317
|
+
Code `3` covers a GitHub API that could not be reached at all, and a GraphQL
|
|
1318
|
+
prefetch that failed as a whole: either a size-shaped failure (`502`/`504`
|
|
1319
|
+
timeout, `200` with no data, resource limits exceeded) that persisted even
|
|
1320
|
+
after the batch had been halved down to one repository, or a failure that
|
|
1321
|
+
halving could not have fixed (`403`, an exhausted GraphQL rate limit, a
|
|
1322
|
+
`500`/`503` outage), which aborts at once. The message reports the status or
|
|
1323
|
+
cause and ends with the matching next step — a smaller `--graph-batch`, retry
|
|
1324
|
+
later, wait for the rate-limit budget to reset, or check the token's
|
|
1325
|
+
permissions — so read it rather than assuming "retry later" (see
|
|
1326
|
+
[GraphQL prefetch batch size](#graphql-prefetch-batch-size)).
|
|
1327
|
+
|
|
1261
1328
|
The two are separate codes because the remedy differs: `4` means rotate or fix
|
|
1262
|
-
the token, `3` means retry later.
|
|
1329
|
+
the token, `3` usually means retry later.
|
|
1263
1330
|
|
|
1264
1331
|
## Remediation
|
|
1265
1332
|
|
|
@@ -258,6 +258,7 @@ environment-variable name, never embedded.
|
|
|
258
258
|
"include_test": false,
|
|
259
259
|
"repo_min_age_days": 28,
|
|
260
260
|
"release_max_age_days": 60,
|
|
261
|
+
"graph_batch": 10,
|
|
261
262
|
"order": { "style": "auto" }
|
|
262
263
|
},
|
|
263
264
|
"organizations": [
|
|
@@ -744,10 +745,15 @@ pull request that cannot progress until somebody responds. The count also feeds
|
|
|
744
745
|
the row ranking, so a backlog awaiting review outranks an untouched one of the
|
|
745
746
|
same size.
|
|
746
747
|
|
|
747
|
-
The scan reads
|
|
748
|
-
most expensive part of the prefetch (see the cost figures above).
|
|
749
|
-
|
|
750
|
-
|
|
748
|
+
The scan reads the **newest 20 review threads per pull request**, which is the
|
|
749
|
+
single most expensive part of the prefetch (see the cost figures above). The
|
|
750
|
+
newest end is deliberate: GitHub returns review threads oldest-first and offers
|
|
751
|
+
no way to order them, so reading from the front would window the threads a
|
|
752
|
+
review has already worked through — the resolved ones — and report a pull
|
|
753
|
+
request still waiting on its latest round as clear. Unanswered feedback is the
|
|
754
|
+
recent kind. Where a pull request carries more threads than the window and none
|
|
755
|
+
of the collected ones qualifies, it is treated as **indeterminate** rather than
|
|
756
|
+
clear — see below.
|
|
751
757
|
|
|
752
758
|
#### Automation backlog thresholds
|
|
753
759
|
|
|
@@ -1083,6 +1089,55 @@ Gating decides **collection**; the per-category render toggles above decide
|
|
|
1083
1089
|
**presentation**. A skipped section still renders (as the one-line notice)
|
|
1084
1090
|
unless its category is also disabled.
|
|
1085
1091
|
|
|
1092
|
+
### GraphQL prefetch batch size
|
|
1093
|
+
|
|
1094
|
+
The releases/tags, Dependabot-enablement, open-issues and pull-request data
|
|
1095
|
+
for every in-scope repository comes from one aliased GraphQL query per batch
|
|
1096
|
+
of repositories rather than a round-trip per repository. GitHub bounds how
|
|
1097
|
+
much work a single GraphQL query may do — roughly ten seconds of execution —
|
|
1098
|
+
and reports a breach in one of three shapes, each after the query has already
|
|
1099
|
+
run for the full limit: an **HTTP 502** (or 504) gateway timeout from the
|
|
1100
|
+
edge, an HTTP 200 carrying no `data`, or an HTTP 200 whose `errors` say
|
|
1101
|
+
`Resource limits for this query exceeded` with the unresolved fields nulled.
|
|
1102
|
+
Each aliased repository adds a few hundred milliseconds, so the batch
|
|
1103
|
+
size is bounded by *latency*, not by rate-limit cost: 25 repositories measured
|
|
1104
|
+
at ~9 s against `lfreleng-actions` and failed intermittently; 10 measured at
|
|
1105
|
+
~3.5 s.
|
|
1106
|
+
|
|
1107
|
+
`report.graph_batch` (default `10`; minimum `1`) sets the starting batch size,
|
|
1108
|
+
globally or per organisation. It is a lever for a slow day rather than a hard
|
|
1109
|
+
limit: a batch that still fails on size after the transport's own retries is
|
|
1110
|
+
re-issued at **half** the size, and the smaller size is kept for the rest of
|
|
1111
|
+
that organisation's collection (each organisation in a multi-org run starts
|
|
1112
|
+
from its own configured size, since organisations differ in how much data a
|
|
1113
|
+
repository carries). Only a single-repository query GitHub still cannot
|
|
1114
|
+
answer, or a failure that a smaller query could not fix (a `403`, an
|
|
1115
|
+
exhausted rate limit, or a `500`/`503` outage rather than a `502`/`504`
|
|
1116
|
+
timeout), aborts the run with exit code `3`.
|
|
1117
|
+
|
|
1118
|
+
The value can be set three ways, in this order of precedence:
|
|
1119
|
+
|
|
1120
|
+
1. `--graph-batch N` on the command line (the action's `graph_batch` input).
|
|
1121
|
+
2. A **repository or organisation variable** passed to that input. The
|
|
1122
|
+
bundled `reporting.yaml` passes `vars.GSR_GRAPH_BATCH`, so the size can be
|
|
1123
|
+
adjusted from repository settings without a workflow edit or a release;
|
|
1124
|
+
a caller's own workflow can do the same. (The `vars` context is not
|
|
1125
|
+
available inside a composite action, so the action cannot read it itself.)
|
|
1126
|
+
3. `report.graph_batch` in the configuration; otherwise the built-in `10`.
|
|
1127
|
+
|
|
1128
|
+
Batching changes how many requests carry the data, not how many GraphQL nodes
|
|
1129
|
+
are resolved, so the node work is the same whatever the batch size. A smaller
|
|
1130
|
+
batch does cost slightly more of the rate-limit budget — each query is charged
|
|
1131
|
+
at least one point, and per-query cost rounding adds a little when one query
|
|
1132
|
+
becomes several — and a few more requests (a 124-repository organisation is 13
|
|
1133
|
+
queries at 10, 5 at 25). Against the 5,000-point hourly budget that is noise;
|
|
1134
|
+
a larger value buys nothing except a longer first failure.
|
|
1135
|
+
|
|
1136
|
+
> The flag and input are recent additions. An empty input is not passed to
|
|
1137
|
+
> the tool, so an unset variable cannot break a run; but a pinned tool version
|
|
1138
|
+
> predating the flag will reject a set one with `No such option`, so set
|
|
1139
|
+
> `GSR_GRAPH_BATCH` only once the pinned release supports it.
|
|
1140
|
+
|
|
1086
1141
|
### Pass/fail severity cutoff
|
|
1087
1142
|
|
|
1088
1143
|
The severity-ranked signals (CodeQL, Scorecard, Zizmor, aislop, Dependabot
|
|
@@ -1160,6 +1215,7 @@ and the Slack **bot token** is consumed by the workflow, not the CLI.
|
|
|
1160
1215
|
| `fail_threshold` | No | `none` | `none`/`low`/`medium`/`high`/`critical`/`any` (repo mode) |
|
|
1161
1216
|
| `force_notify` | No | `false` | Post to Slack regardless of `report_day` |
|
|
1162
1217
|
| `hide` | No | `""` | Category keys to suppress on every output (space- or comma-separated). Overrides config, and is one-way: it cannot re-enable a disabled category |
|
|
1218
|
+
| `graph_batch` | No | `""` | Repositories per batched GraphQL prefetch query (minimum `1`; default: config, else `10`). Pass a repository or organisation variable such as `${{ vars.GSR_GRAPH_BATCH }}` to adjust it from settings. A batch that still fails is halved automatically, so this sets the starting size (see [GraphQL prefetch batch size](#graphql-prefetch-batch-size)) |
|
|
1163
1219
|
| `tool_version` | No | `""` | Published PyPI version to install. Empty (the default) uses the Dependabot-managed pin in `.github/runtime-pin/requirements.txt`; set a specific version to override. Ignored on pull requests or when `use_local_source` is `true` (both run from source) |
|
|
1164
1220
|
| `use_local_source` | No | `false` | Run from the checked-out source instead of PyPI (for testing unreleased code from any event) |
|
|
1165
1221
|
|
|
@@ -1203,7 +1259,7 @@ API in the same way.
|
|
|
1203
1259
|
| `0` | The report ran. |
|
|
1204
1260
|
| `1` | Repo mode only: findings met or exceeded `--fail-threshold`. |
|
|
1205
1261
|
| `2` | Usage or configuration error (bad flag, unreadable config). |
|
|
1206
|
-
| `3` | The GitHub API was
|
|
1262
|
+
| `3` | The GitHub API was unusable after the retry budget (see below). |
|
|
1207
1263
|
| `4` | GitHub rejected the credentials (HTTP 401). |
|
|
1208
1264
|
|
|
1209
1265
|
Codes `3` and `4` are **aborts, not reports**: nothing is written and no Pages
|
|
@@ -1214,8 +1270,19 @@ section `No data` or `All Clean` — and a scheduled job would then publish it
|
|
|
1214
1270
|
over the last good one. Reporting false data is worse than reporting none, so
|
|
1215
1271
|
the run stops at the first rejected request.
|
|
1216
1272
|
|
|
1273
|
+
Code `3` covers a GitHub API that could not be reached at all, and a GraphQL
|
|
1274
|
+
prefetch that failed as a whole: either a size-shaped failure (`502`/`504`
|
|
1275
|
+
timeout, `200` with no data, resource limits exceeded) that persisted even
|
|
1276
|
+
after the batch had been halved down to one repository, or a failure that
|
|
1277
|
+
halving could not have fixed (`403`, an exhausted GraphQL rate limit, a
|
|
1278
|
+
`500`/`503` outage), which aborts at once. The message reports the status or
|
|
1279
|
+
cause and ends with the matching next step — a smaller `--graph-batch`, retry
|
|
1280
|
+
later, wait for the rate-limit budget to reset, or check the token's
|
|
1281
|
+
permissions — so read it rather than assuming "retry later" (see
|
|
1282
|
+
[GraphQL prefetch batch size](#graphql-prefetch-batch-size)).
|
|
1283
|
+
|
|
1217
1284
|
The two are separate codes because the remedy differs: `4` means rotate or fix
|
|
1218
|
-
the token, `3` means retry later.
|
|
1285
|
+
the token, `3` usually means retry later.
|
|
1219
1286
|
|
|
1220
1287
|
## Remediation
|
|
1221
1288
|
|
|
@@ -45,7 +45,7 @@ keywords = [
|
|
|
45
45
|
]
|
|
46
46
|
dependencies = [
|
|
47
47
|
"httpx[http2]==0.28.1",
|
|
48
|
-
"typer==0.27.
|
|
48
|
+
"typer==0.27.2",
|
|
49
49
|
"rich==15.0.0",
|
|
50
50
|
"jinja2==3.1.6",
|
|
51
51
|
"jsonschema==4.26.0",
|
|
@@ -129,6 +129,15 @@ markers = [
|
|
|
129
129
|
[tool.coverage.run]
|
|
130
130
|
source = ["github_security_report"]
|
|
131
131
|
omit = ["tests/*"]
|
|
132
|
+
# Keep the transient coverage database out of the working tree.
|
|
133
|
+
# coverage.py resolves a relative data_file against the current
|
|
134
|
+
# directory, so the default '.coverage' lands wherever pytest runs and
|
|
135
|
+
# can be picked up by tests that inspect on-disk content. TEMP is
|
|
136
|
+
# set on Windows, where /tmp does not exist; elsewhere it is normally
|
|
137
|
+
# unset and the /tmp fallback applies. An explicit value also
|
|
138
|
+
# satisfies python-test-action's data_file configuration check.
|
|
139
|
+
# COVERAGE_FILE (env) still takes precedence when set.
|
|
140
|
+
data_file = "${TEMP-/tmp}/.coverage.github-security-report"
|
|
132
141
|
|
|
133
142
|
[tool.coverage.report]
|
|
134
143
|
show_missing = true
|
|
@@ -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.14.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 14,
|
|
21
|
+
__version__ = version = '0.14.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 14, 2)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -166,6 +166,11 @@ def report(
|
|
|
166
166
|
"--include-test",
|
|
167
167
|
help="Analyse test repositories, which are excluded by default.",
|
|
168
168
|
),
|
|
169
|
+
graph_batch: int | None = typer.Option(
|
|
170
|
+
None,
|
|
171
|
+
"--graph-batch",
|
|
172
|
+
help="Repositories per batched GraphQL prefetch query (minimum 1; default: config, else 10). GitHub bounds how long one query may run, so a batch that still fails is halved automatically; this sets the starting size.",
|
|
173
|
+
),
|
|
169
174
|
no_color: bool = typer.Option(False, "--no-color", help="Disable coloured output."),
|
|
170
175
|
) -> None:
|
|
171
176
|
"""Generate a security and quality report."""
|
|
@@ -181,6 +186,7 @@ def report(
|
|
|
181
186
|
)
|
|
182
187
|
boundary.check_non_negative(console, "--repo-min-age-days", repo_min_age_days)
|
|
183
188
|
boundary.check_non_negative(console, "--release-max-age-days", release_max_age_days)
|
|
189
|
+
boundary.check_positive(console, "--graph-batch", graph_batch)
|
|
184
190
|
hidden = boundary.resolve_hidden(console, hide)
|
|
185
191
|
|
|
186
192
|
cfg = _load_config(config_file, config_data, org, token_env, console=console)
|
|
@@ -210,6 +216,7 @@ def report(
|
|
|
210
216
|
gating=False if no_gating else None,
|
|
211
217
|
include_archived=True if include_archived else None,
|
|
212
218
|
include_test=True if include_test else None,
|
|
219
|
+
graph_batch=graph_batch,
|
|
213
220
|
),
|
|
214
221
|
hidden=hidden,
|
|
215
222
|
)
|
|
@@ -236,6 +243,7 @@ def report(
|
|
|
236
243
|
("--no-gating", no_gating),
|
|
237
244
|
("--include-archived", include_archived),
|
|
238
245
|
("--include-test", include_test),
|
|
246
|
+
("--graph-batch", graph_batch is not None),
|
|
239
247
|
)
|
|
240
248
|
if supplied
|
|
241
249
|
],
|
|
@@ -41,6 +41,18 @@ def check_non_negative(console: Console, name: str, value: int | None) -> None:
|
|
|
41
41
|
raise typer.Exit(2)
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def check_positive(console: Console, name: str, value: int | None) -> None:
|
|
45
|
+
"""Reject a zero or negative override where 0 has no meaning.
|
|
46
|
+
|
|
47
|
+
For a batch size, unlike a row limit, there is no "unlimited" reading of
|
|
48
|
+
0 -- a query carrying no repositories is not a query -- so the floor is 1,
|
|
49
|
+
matching the config schema's minimum for the same control.
|
|
50
|
+
"""
|
|
51
|
+
if value is not None and value < 1:
|
|
52
|
+
console.print(f"[red]{name} must be 1 or greater[/red]")
|
|
53
|
+
raise typer.Exit(2)
|
|
54
|
+
|
|
55
|
+
|
|
44
56
|
def check_limits(console: Console, limits: Sequence[tuple[str, int | None]]) -> None:
|
|
45
57
|
"""Reject a negative row limit, naming the flag and the 0 convention."""
|
|
46
58
|
for name, value in limits:
|
|
@@ -34,6 +34,10 @@ class ReportOverrides:
|
|
|
34
34
|
not on, and ``--include-archived`` / ``--include-test`` can widen the scope
|
|
35
35
|
but not narrow it, so a flag can loosen what the configuration asked for
|
|
36
36
|
without being able to tighten it behind the operator's back.
|
|
37
|
+
|
|
38
|
+
``graph_batch`` is an operational lever rather than report policy: it
|
|
39
|
+
changes how the GraphQL prefetch is issued, never what the report says, so
|
|
40
|
+
it may move in either direction.
|
|
37
41
|
"""
|
|
38
42
|
|
|
39
43
|
repo_min_age_days: int | None = None
|
|
@@ -42,13 +46,15 @@ class ReportOverrides:
|
|
|
42
46
|
gating: bool | None = None
|
|
43
47
|
include_archived: bool | None = None
|
|
44
48
|
include_test: bool | None = None
|
|
49
|
+
graph_batch: int | None = None
|
|
45
50
|
|
|
46
51
|
def apply(self, org_cfg: OrgConfig) -> tuple[OrgConfig, ReportConfig]:
|
|
47
52
|
"""The org and report configs to collect with, overrides applied.
|
|
48
53
|
|
|
49
|
-
The two age thresholds
|
|
50
|
-
applying one uniformly across every configured
|
|
51
|
-
reader of the flag expects, and matches how
|
|
54
|
+
The two age thresholds, the three booleans and the batch size are
|
|
55
|
+
scalar policy, so applying one uniformly across every configured
|
|
56
|
+
organisation is what a reader of the flag expects, and matches how
|
|
57
|
+
``--top-n`` already behaves.
|
|
52
58
|
|
|
53
59
|
``releases_exclude`` is not scalar: it is a curated per-organisation
|
|
54
60
|
list, and one flag replacing all of them loses data the config
|
|
@@ -63,6 +69,7 @@ class ReportOverrides:
|
|
|
63
69
|
"gating",
|
|
64
70
|
"include_archived",
|
|
65
71
|
"include_test",
|
|
72
|
+
"graph_batch",
|
|
66
73
|
):
|
|
67
74
|
value = getattr(self, name)
|
|
68
75
|
if value is not None:
|
|
@@ -35,6 +35,11 @@ from github_security_report.client.endpoints import (
|
|
|
35
35
|
SCORECARD_API,
|
|
36
36
|
_https_endpoint,
|
|
37
37
|
)
|
|
38
|
+
from github_security_report.client.errors import (
|
|
39
|
+
AuthError,
|
|
40
|
+
GraphBatchError,
|
|
41
|
+
NetworkError,
|
|
42
|
+
)
|
|
38
43
|
from github_security_report.client.org_reads import OrgReadClient
|
|
39
44
|
from github_security_report.client.parsers import (
|
|
40
45
|
_last_published,
|
|
@@ -52,8 +57,6 @@ from github_security_report.client.queries import (
|
|
|
52
57
|
)
|
|
53
58
|
from github_security_report.client.reads import ReadClient
|
|
54
59
|
from github_security_report.client.transport import (
|
|
55
|
-
AuthError,
|
|
56
|
-
NetworkError,
|
|
57
60
|
Transport,
|
|
58
61
|
_endpoint_diagnostics,
|
|
59
62
|
)
|
|
@@ -72,6 +75,7 @@ __all__ = [
|
|
|
72
75
|
"AlertReads",
|
|
73
76
|
"AuthError",
|
|
74
77
|
"GitHubClient",
|
|
78
|
+
"GraphBatchError",
|
|
75
79
|
"NetworkError",
|
|
76
80
|
"OrgReadClient",
|
|
77
81
|
"ReadClient",
|
|
@@ -27,7 +27,8 @@ import logging
|
|
|
27
27
|
from typing import NamedTuple
|
|
28
28
|
|
|
29
29
|
from github_security_report.client.endpoints import BULK_KINDS
|
|
30
|
-
from github_security_report.client.
|
|
30
|
+
from github_security_report.client.errors import AuthError, NetworkError
|
|
31
|
+
from github_security_report.client.transport import Transport
|
|
31
32
|
from github_security_report.secret_patterns import (
|
|
32
33
|
GENERIC_SECRET_TYPES,
|
|
33
34
|
PATTERN_CONFIG_PATH,
|