github-security-report 0.11.3__tar.gz → 0.12.1__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.11.3 → github_security_report-0.12.1}/PKG-INFO +429 -18
- {github_security_report-0.11.3 → github_security_report-0.12.1}/README.md +428 -17
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/_version.py +2 -2
- github_security_report-0.12.1/src/github_security_report/authors.py +183 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/categories.py +54 -1
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/cli/__init__.py +2 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/cli/app.py +42 -1
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/cli/modes.py +55 -13
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/cli/outputs.py +87 -15
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/cli/serialise.py +42 -13
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/client/__init__.py +2 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/client/org_reads.py +198 -15
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/client/parsers.py +139 -1
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/client/queries.py +78 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/client/transport.py +208 -94
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/collect/extras.py +46 -3
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/collect/protocols.py +17 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/config/loader.py +19 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/config/models.py +43 -2
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/config/schema.py +15 -6
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/issues.py +60 -7
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/models.py +54 -2
- github_security_report-0.12.1/src/github_security_report/ordering.py +440 -0
- github_security_report-0.12.1/src/github_security_report/pulls/__init__.py +148 -0
- github_security_report-0.12.1/src/github_security_report/pulls/columns.py +53 -0
- github_security_report-0.12.1/src/github_security_report/pulls/counting.py +140 -0
- github_security_report-0.12.1/src/github_security_report/pulls/presentation.py +133 -0
- github_security_report-0.12.1/src/github_security_report/pulls/table.py +218 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/render/html.py +10 -1
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/render/markdown.py +33 -18
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/render/slack.py +21 -3
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/render/terminal.py +50 -7
- github_security_report-0.12.1/src/github_security_report/report/__init__.py +103 -0
- github_security_report-0.12.1/src/github_security_report/report/aggregate.py +129 -0
- github_security_report-0.12.1/src/github_security_report/report/display.py +181 -0
- github_security_report-0.12.1/src/github_security_report/report/signals.py +95 -0
- github_security_report-0.12.1/src/github_security_report/report/tables.py +148 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/templates/report.html.j2 +20 -1
- github_security_report-0.11.3/src/github_security_report/ordering.py +0 -222
- github_security_report-0.11.3/src/github_security_report/report.py +0 -419
- {github_security_report-0.11.3 → github_security_report-0.12.1}/.gitignore +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/LICENSE +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/LICENSES/Apache-2.0.txt +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/pyproject.toml +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/scripts/README.md +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/__init__.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/classify.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/cli/__main__.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/client/endpoints.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/client/reads.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/client/writes.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/collect/__init__.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/collect/context.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/collect/org.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/collect/repo.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/config/__init__.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/gating.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/gitctx.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/posture/__init__.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/posture/enablement.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/posture/facts.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/posture/releases.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/py.typed +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/remediate.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/render/__init__.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/render/slack_limits.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/rulesets.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/runner.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/scope.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/severity.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/src/github_security_report/summary.py +0 -0
- {github_security_report-0.11.3 → github_security_report-0.12.1}/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.12.1
|
|
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
|
|
@@ -445,30 +445,69 @@ total open issues, then on the oldest issue.
|
|
|
445
445
|
|
|
446
446
|
Ordering is resolved once, when the report is built, so every surface and
|
|
447
447
|
`report.json` agree. It applies to the generic tables (GitHub Issues, Releases /
|
|
448
|
-
Tagging, Mutable Releases, the Dependabot posture tables)
|
|
449
|
-
tables
|
|
450
|
-
|
|
451
|
-
|
|
448
|
+
Tagging, Mutable Releases, the Dependabot posture tables) **and** to the severity
|
|
449
|
+
signal tables (CodeQL, OpenSSF Scorecard, zizmor, AI Slop, Dependabot alerts,
|
|
450
|
+
secret scanning).
|
|
451
|
+
|
|
452
|
+
The signal tables resolve their terms against a fixed vocabulary rather than a
|
|
453
|
+
rendered column list, because their columns vary by surface and by data (Slack
|
|
454
|
+
abbreviates the headers and drops `Total`, and `Info` appears only when some
|
|
455
|
+
repository carries note-level findings):
|
|
456
|
+
|
|
457
|
+
| Signal | Accepted sort names |
|
|
458
|
+
| ------ | ------------------- |
|
|
459
|
+
| OpenSSF Scorecard | `repository`, `score`, `critical`, `high`, `medium`, `low`, `info`, `total` |
|
|
460
|
+
| CodeQL, zizmor, AI Slop, Dependabot alerts | `repository`, `critical`, `high`, `medium`, `low`, `info`, `total` |
|
|
461
|
+
| Secret Scanning | `repository`, `open` |
|
|
462
|
+
|
|
463
|
+
`informational` is accepted for `info`, and `total` for secret scanning's `open`.
|
|
464
|
+
|
|
465
|
+
A bare `score` sorts **ascending**, because the rule is "worst first" and a lower
|
|
466
|
+
Scorecard score is the weaker repository — so `sort: ["score"]` agrees with the
|
|
467
|
+
default ranking instead of contradicting it. Every count sorts descending. A
|
|
468
|
+
repository with no published score sorts last in either direction: unknown health
|
|
469
|
+
is not bad health.
|
|
470
|
+
|
|
471
|
+
Ranking by remediation volume rather than by score is the common case, since the
|
|
472
|
+
score is a health rating and not a count of work:
|
|
473
|
+
|
|
474
|
+
```json
|
|
475
|
+
{
|
|
476
|
+
"report": {
|
|
477
|
+
"categories": {
|
|
478
|
+
"scorecard": { "sort": ["total", "score"] }
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
"organizations": [{ "name": "lfreleng-actions" }]
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Omitting `sort` keeps each signal's default ranking, which is not expressible as
|
|
486
|
+
a column list — Scorecard cascades through the worst severity rung any offender
|
|
487
|
+
actually carries, so a lone Critical is never buried by a weaker repository with
|
|
488
|
+
a lower score.
|
|
452
489
|
|
|
453
490
|
### GitHub Issues
|
|
454
491
|
|
|
455
492
|
The `github_issues` category counts each repository's **open issues**, split by
|
|
456
493
|
label into columns. It reads from the same batched GraphQL prefetch as the
|
|
457
|
-
release and Dependabot data, so it costs no extra
|
|
494
|
+
release and Dependabot data, so it costs no extra requests per repository; the
|
|
495
|
+
`Ext` column adds one organisation-membership read per run, shared with the
|
|
496
|
+
Pull Requests table.
|
|
458
497
|
|
|
459
498
|
```text
|
|
460
499
|
GitHub Issues
|
|
461
|
-
|
|
462
|
-
┃ Repository ┃ Bug ┃ Feature ┃ Docs ┃ Other ┃ Untriaged ┃ Total ┃ Oldest ┃
|
|
463
|
-
|
|
464
|
-
│ .github │ 1 │ 0 │ 0 │ 0 │ 10 │ 11 │ 16 days │
|
|
465
|
-
│ tag-validate-action │ 0 │ 0 │ 0 │ 0 │ 8 │ 8 │ 25 days │
|
|
466
|
-
│ security-workflows │ 0 │ 5 │ 1 │ 0 │ 0 │ 6 │ today │
|
|
467
|
-
│ github-security-report-action │ 0 │ 0 │ 0 │ 3 │ 1 │ 4 │ 52 days │
|
|
468
|
-
│ dependamerge │ 0 │ 1 │ 0 │ 1 │ 1 │ 3 │ 52 days │
|
|
469
|
-
|
|
470
|
-
│ Total │ 1 │ 6 │ 1 │ 4 │ 20 │ 32 │ │
|
|
471
|
-
|
|
500
|
+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━┳━━━━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┳━━━━━┳━━━━━━━━━┓
|
|
501
|
+
┃ Repository ┃ Bug ┃ Feature ┃ Docs ┃ Other ┃ Untriaged ┃ Total ┃ Ext ┃ Oldest ┃
|
|
502
|
+
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━╇━━━━━━╇━━━━━━━╇━━━━━━━━━━━╇━━━━━━━╇━━━━━╇━━━━━━━━━┩
|
|
503
|
+
│ .github │ 1 │ 0 │ 0 │ 0 │ 10 │ 11 │ 0 │ 16 days │
|
|
504
|
+
│ tag-validate-action │ 0 │ 0 │ 0 │ 0 │ 8 │ 8 │ 0 │ 25 days │
|
|
505
|
+
│ security-workflows │ 0 │ 5 │ 1 │ 0 │ 0 │ 6 │ 0 │ today │
|
|
506
|
+
│ github-security-report-action │ 0 │ 0 │ 0 │ 3 │ 1 │ 4 │ 0 │ 52 days │
|
|
507
|
+
│ dependamerge │ 0 │ 1 │ 0 │ 1 │ 1 │ 3 │ 0 │ 52 days │
|
|
508
|
+
├───────────────────────────────┼─────┼─────────┼──────┼───────┼───────────┼───────┼─────┼─────────┤
|
|
509
|
+
│ Total │ 1 │ 6 │ 1 │ 4 │ 20 │ 32 │ 0 │ │
|
|
510
|
+
└───────────────────────────────┴─────┴─────────┴──────┴───────┴───────────┴───────┴─────┴─────────┘
|
|
472
511
|
… and 11 more
|
|
473
512
|
❌ 16 With open issues
|
|
474
513
|
✅ 87 No open issues
|
|
@@ -485,7 +524,11 @@ Two columns are always present and are not configurable:
|
|
|
485
524
|
- **Untriaged** — the issue has no labels at all. This is the column to watch:
|
|
486
525
|
an unlabelled issue is one nobody has categorised.
|
|
487
526
|
|
|
488
|
-
|
|
527
|
+
A third, **Ext**, counts the issues raised from **outside the organisation** —
|
|
528
|
+
see [Inside or outside the organisation](#inside-or-outside-the-organisation).
|
|
529
|
+
|
|
530
|
+
All three names are reserved, as are `Repository`, `Total` and `Oldest`:
|
|
531
|
+
configuring a
|
|
489
532
|
column with one of those names is rejected, because it would either share a
|
|
490
533
|
counter with the implicit column — stopping the class columns summing to `Total`
|
|
491
534
|
— or duplicate a header, which would also make `sort: ["repository"]` resolve to
|
|
@@ -541,6 +584,352 @@ with HTTP 200 and this one field null, so affected repositories are reported as
|
|
|
541
584
|
`❓ Unknown` rather than counted as having no open issues — an unreadable backlog
|
|
542
585
|
is never presented as a clean one.
|
|
543
586
|
|
|
587
|
+
### Pull Requests
|
|
588
|
+
|
|
589
|
+
The `pull_requests` category counts each repository's **open pull requests**,
|
|
590
|
+
split by who raised them and by what is holding them up. The per-repository data
|
|
591
|
+
rides the same batched GraphQL prefetch as the issues data, so it costs **no
|
|
592
|
+
extra requests per repository** — measured against a five-repository batch,
|
|
593
|
+
adding the connection, the head commit's check rollup and the assignee list
|
|
594
|
+
moved the query cost from 1 point to 4, against an hourly budget of 5,000.
|
|
595
|
+
|
|
596
|
+
Identifying contributors does add **two requests per organisation**, made once
|
|
597
|
+
per run and reused by every repository and both author-aware tables:
|
|
598
|
+
organisation membership (one more per 100 members) and the authenticated
|
|
599
|
+
account. See [Inside or outside the
|
|
600
|
+
organisation](#inside-or-outside-the-organisation):
|
|
601
|
+
|
|
602
|
+
```text
|
|
603
|
+
Pull Requests
|
|
604
|
+
┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━┳━━━━━━┳━━━━━━━━━━┳━━━━━━┳━━━━━━━┳━━━━━━━┓
|
|
605
|
+
┃ Repository ┃ Human ┃ Ext ┃ Auto ┃ Conflict ┃ Fail ┃ Draft ┃ Total ┃
|
|
606
|
+
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━╇━━━━━━╇━━━━━━━━━━╇━━━━━━╇━━━━━━━╇━━━━━━━┩
|
|
607
|
+
│ lftools-uv │ 8 │ 0 │ 0 │ 0 │ 0 │ 0 │ 8 │
|
|
608
|
+
│ dependamerge │ 3 │ 0 │ 0 │ 0 │ 0 │ 0 │ 3 │
|
|
609
|
+
│ gha-workflow-linter │ 2 │ 0 │ 0 │ 0 │ 1 │ 1 │ 2 │
|
|
610
|
+
│ harden-runner-block- │ 1 │ 0 │ 0 │ 1 │ 0 │ 1 │ 1 │
|
|
611
|
+
│ action │ │ │ │ │ │ │ │
|
|
612
|
+
├─────────────────────┼───────┼─────┼──────┼──────────┼──────┼───────┼───────┤
|
|
613
|
+
│ Total │ 14 │ 0 │ 0 │ 1 │ 1 │ 2 │ 14 │
|
|
614
|
+
└─────────────────────┴───────┴─────┴──────┴──────────┴──────┴───────┴───────┘
|
|
615
|
+
… and 9 more
|
|
616
|
+
❌ 13 With open pull requests
|
|
617
|
+
✅ 104 No open pull requests
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
The columns form **two independent groupings**:
|
|
621
|
+
|
|
622
|
+
| Column | Meaning |
|
|
623
|
+
| ------ | ------- |
|
|
624
|
+
| `Human` / `Auto` | Partition the total by author. `Auto` is recognised automation; `Human` is everyone else. |
|
|
625
|
+
| `Ext` | Human pull requests raised from outside the organisation — a **subset of Human**, which is why it sits beside it. |
|
|
626
|
+
| `Conflict` | Blocked on a merge conflict. |
|
|
627
|
+
| `Fail` | Latest checks did not pass. The rollup includes optional checks, so this is not by itself proof that the merge is blocked. |
|
|
628
|
+
| `Draft` | Marked as a draft. |
|
|
629
|
+
|
|
630
|
+
`Conflict`, `Fail` and `Draft` **overlap** each other and the author split, so
|
|
631
|
+
they do not sum to `Total` and are not meant to: one pull request that is
|
|
632
|
+
conflicting, failing *and* a draft is counted once in each of those three
|
|
633
|
+
columns, and once under `Human` or `Auto`. Only `Human` + `Auto` reconciles with
|
|
634
|
+
the collected total. They are ordered worst-first — a conflict needs a human to
|
|
635
|
+
rebase, a failing check may only need a re-run, and a draft is not blocked at
|
|
636
|
+
all.
|
|
637
|
+
|
|
638
|
+
#### Automation backlog thresholds
|
|
639
|
+
|
|
640
|
+
On the terminal the counts are coloured so the table reads at a glance: `Human`
|
|
641
|
+
and `Ext` **green**, `Conflict` and `Fail` **red**, and `Auto` coloured against
|
|
642
|
+
two configured thresholds.
|
|
643
|
+
|
|
644
|
+
Dependabot stops raising pull requests once a repository reaches its
|
|
645
|
+
`open-pull-requests-limit`, so the repository quietly stops receiving dependency
|
|
646
|
+
updates — an outage in waiting rather than an untidy queue. Two thresholds let
|
|
647
|
+
you watch the automation backlog against that risk:
|
|
648
|
+
|
|
649
|
+
```json
|
|
650
|
+
{
|
|
651
|
+
"report": {
|
|
652
|
+
"dependabot_warn_threshold": 2,
|
|
653
|
+
"dependabot_error_threshold": 5
|
|
654
|
+
},
|
|
655
|
+
"organizations": [{ "name": "lfreleng-actions" }]
|
|
656
|
+
}
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
- **Yellow** above `dependabot_warn_threshold` (default `2`) — a backlog worth
|
|
660
|
+
keeping an eye on.
|
|
661
|
+
- **Red** at or above `dependabot_error_threshold` (default `5`) — a backlog
|
|
662
|
+
worth investigating now.
|
|
663
|
+
|
|
664
|
+
> **These are configured investigation levels, not measurements.** `Auto` is an
|
|
665
|
+
> aggregate across every automation author, while GitHub applies its
|
|
666
|
+
> `open-pull-requests-limit` **per package ecosystem**. So neither level
|
|
667
|
+
> establishes that Dependabot has stopped: a repository can sit above the red
|
|
668
|
+
> threshold on Renovate pull requests alone, or be stalled in one ecosystem
|
|
669
|
+
> while sitting below it. What the colour tells you is that the backlog has
|
|
670
|
+
> reached a size you said you wanted to hear about, which is worth a look
|
|
671
|
+
> either way.
|
|
672
|
+
|
|
673
|
+
The defaults track **GitHub's own** `open-pull-requests-limit`, which defaults
|
|
674
|
+
to `5`. If your organisation raises that limit, raise these to match — they
|
|
675
|
+
describe your policy, and the defaults are only the most useful guess in the
|
|
676
|
+
absence of one:
|
|
677
|
+
|
|
678
|
+
```json
|
|
679
|
+
{
|
|
680
|
+
"report": {
|
|
681
|
+
"dependabot_warn_threshold": 12,
|
|
682
|
+
"dependabot_error_threshold": 15
|
|
683
|
+
},
|
|
684
|
+
"organizations": [{ "name": "lfreleng-actions" }]
|
|
685
|
+
}
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
Either may be set to `0` to switch that level off, matching the `0 = no limit`
|
|
689
|
+
idiom the row limits use. `dependabot_error_threshold` must be **strictly
|
|
690
|
+
greater** than `dependabot_warn_threshold` (or `0`): because the error level is
|
|
691
|
+
checked first and inclusively, an equal or lower error threshold would make the
|
|
692
|
+
warning colour unreachable, so it is rejected at load rather than silently
|
|
693
|
+
ignored. Both are settable per organisation.
|
|
694
|
+
|
|
695
|
+
> **A note on the name.** These are named for Dependabot because its
|
|
696
|
+
> `open-pull-requests-limit` is the constraint the defaults track.
|
|
697
|
+
|
|
698
|
+
Only **non-zero** counts are coloured. A table of red zeros would train the eye
|
|
699
|
+
to ignore the colour, which costs exactly the signal it exists to carry, so a
|
|
700
|
+
clean row stays plain and the rows with something wrong stand out. The totals
|
|
701
|
+
row is never coloured: it sums columns that disagree about what good looks like.
|
|
702
|
+
Colour is a terminal affordance — Markdown and Slack have none, and the HTML
|
|
703
|
+
pages style their tables from the stylesheet.
|
|
704
|
+
|
|
705
|
+
#### Assignment breakdown
|
|
706
|
+
|
|
707
|
+
Beneath the totals, the same pull requests are split again by **who is expected
|
|
708
|
+
to move them**:
|
|
709
|
+
|
|
710
|
+
```text
|
|
711
|
+
├─────────────────────┼───────┼─────┼──────┼──────────┼──────┼───────┼───────┤
|
|
712
|
+
│ Total │ 17 │ 0 │ 0 │ 1 │ 1 │ 2 │ 17 │
|
|
713
|
+
├─────────────────────┼───────┼─────┼──────┼──────────┼──────┼───────┼───────┤
|
|
714
|
+
│ Unassigned │ │ │ │ │ │ │ 1 │
|
|
715
|
+
│ Others │ │ │ │ │ │ │ 14 │
|
|
716
|
+
│ Mine │ │ │ │ │ │ │ 2 │
|
|
717
|
+
└─────────────────────┴───────┴─────┴──────┴──────────┴──────┴───────┴───────┘
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
The values sit under **`Total`**, because that is what they are a breakdown of:
|
|
721
|
+
they partition every collected pull request, automation included, so aligning
|
|
722
|
+
them under `Human` would file an unassigned bot pull request as a human one.
|
|
723
|
+
|
|
724
|
+
This is a **partition**, not more columns: every collected pull request falls in
|
|
725
|
+
exactly one bucket. It is a breakdown *of* the rows rather than *within* them,
|
|
726
|
+
which is why it cannot be a column without counting the same pull requests
|
|
727
|
+
twice.
|
|
728
|
+
|
|
729
|
+
- **Unassigned** — nobody has picked it up.
|
|
730
|
+
- **Mine** — assigned to the account the report ran as.
|
|
731
|
+
- **Others** — assigned to somebody else.
|
|
732
|
+
|
|
733
|
+
A pull request assigned to several people, one of them you, counts as **Mine**:
|
|
734
|
+
it is in your queue regardless of who else is on it. Like the totals row, the
|
|
735
|
+
breakdown sums the **displayed** rows, so a row limit does not put it out of
|
|
736
|
+
step with the table above it.
|
|
737
|
+
|
|
738
|
+
On rows **not** marked `+`, the buckets reconcile against `Total`. On a marked
|
|
739
|
+
row they cannot: the buckets cover only the collected window while `Total`
|
|
740
|
+
remains the exact backlog, which is what the marker is there to warn you about.
|
|
741
|
+
|
|
742
|
+
##### Only `Unassigned` is universal
|
|
743
|
+
|
|
744
|
+
`Unassigned` is a fact about the pull request. `Mine` and `Others` are read
|
|
745
|
+
against the account the report authenticated as, and are drawn **only** where
|
|
746
|
+
that reading holds — which needs two things to be true at once:
|
|
747
|
+
|
|
748
|
+
1. **The run authenticated as a person**, i.e. `viewer { login }` resolved to a
|
|
749
|
+
human account. A bot or App token has no queue, so `Mine` would be empty by
|
|
750
|
+
construction and `Others` would collapse into "assigned to somebody" — a
|
|
751
|
+
split drawn against a person who is not there.
|
|
752
|
+
2. **The surface is read by that account**, i.e. the terminal. A Pages site,
|
|
753
|
+
Slack digest, `report.md` or `report.json` is read by everybody *except* the
|
|
754
|
+
token owner, for whom `Mine` names a stranger's queue as their own.
|
|
755
|
+
|
|
756
|
+
Where either fails, the breakdown is a single `Unassigned` row. Nothing is lost:
|
|
757
|
+
the assigned count is the totals row minus that one figure. So the daily
|
|
758
|
+
scheduled run publishes this to Pages, whatever token it holds:
|
|
759
|
+
|
|
760
|
+
```text
|
|
761
|
+
├─────────────────────┼───────┼─────┼──────┼──────────┼──────┼───────┼───────┤
|
|
762
|
+
│ Total │ 17 │ 0 │ 0 │ 1 │ 1 │ 2 │ 17 │
|
|
763
|
+
├─────────────────────┼───────┼─────┼──────┼──────────┼──────┼───────┼───────┤
|
|
764
|
+
│ Unassigned │ │ │ │ │ │ │ 1 │
|
|
765
|
+
└─────────────────────┴───────┴─────┴──────┴──────────┴──────┴───────┴───────┘
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
This is not a configurable toggle. A published `Mine` is not a preference but a
|
|
769
|
+
false statement, so there is nothing to opt into.
|
|
770
|
+
|
|
771
|
+
### Assigned to Me
|
|
772
|
+
|
|
773
|
+
The `pull_requests_assigned` category repeats the Pull Requests table narrowed
|
|
774
|
+
to pull requests assigned to the account the report ran as — the same columns
|
|
775
|
+
over the same data, so the two read alike, with only the population changed. A
|
|
776
|
+
repository with open pull requests but none of yours counts as clean rather than
|
|
777
|
+
appearing as a row of zeros, keeping the table to your actual inbox.
|
|
778
|
+
|
|
779
|
+
> **"Mine" follows the token, not a configured name.** It is whoever
|
|
780
|
+
> `viewer { login }` resolves to, so the same report run with a different
|
|
781
|
+
> token legitimately answers a different question. A scheduled run under a bot
|
|
782
|
+
> or GitHub App token has no personal queue at all: the category is not
|
|
783
|
+
> collected, and the section is **absent** rather than reporting every
|
|
784
|
+
> repository clean — a reassuring "nothing assigned to you" about an inbox that
|
|
785
|
+
> does not exist is worse than saying nothing.
|
|
786
|
+
|
|
787
|
+
**It is a terminal-only table by default.** A personal review queue keyed to
|
|
788
|
+
whichever account ran the report has no business in a published Pages site or a
|
|
789
|
+
shared Slack digest, so `pull_requests_assigned` ships with `cli` on and
|
|
790
|
+
`markdown`, `html` and `slack` off. No configuration is needed to keep it out of
|
|
791
|
+
shared output, and tuning another of its settings (a `top_n`, say) will not
|
|
792
|
+
silently publish it, since the per-category blocks merge key by key. Opt in
|
|
793
|
+
deliberately if you want it published:
|
|
794
|
+
|
|
795
|
+
```json
|
|
796
|
+
{
|
|
797
|
+
"report": {
|
|
798
|
+
"categories": {
|
|
799
|
+
"pull_requests_assigned": { "outputs": { "html": true } }
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
"organizations": [{ "name": "lfreleng-actions" }]
|
|
803
|
+
}
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
If you publish it from a workflow, make sure the Action's `hide` input does not
|
|
807
|
+
name the category as well: `--hide` outranks this toggle and cannot be
|
|
808
|
+
countermanded by it. The input is empty by default, so this only matters if you
|
|
809
|
+
have set it.
|
|
810
|
+
|
|
811
|
+
### Hiding a category for one run
|
|
812
|
+
|
|
813
|
+
`--hide <category>` (repeatable) suppresses a category on **every** output for
|
|
814
|
+
that invocation, and **outranks the configuration**:
|
|
815
|
+
|
|
816
|
+
```bash
|
|
817
|
+
github-security-report report --hide pull_requests_assigned
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
The Action exposes the same control through a `hide` input:
|
|
821
|
+
|
|
822
|
+
```yaml
|
|
823
|
+
- uses: lfreleng-actions/github-security-report-action@v1
|
|
824
|
+
with:
|
|
825
|
+
hide: "pull_requests_assigned" # space- or comma-separated
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
It is **empty by default**, and does not need setting to keep the personal
|
|
829
|
+
queue out of shared artifacts — `pull_requests_assigned` already defaults to the
|
|
830
|
+
terminal only, so Pages, Markdown and Slack never render it, and `report.json`
|
|
831
|
+
omits it for the same reason (see below). Leaving the input empty also keeps the
|
|
832
|
+
Action compatible with whichever published version the runtime pin installs: a
|
|
833
|
+
non-empty default would be passed to releases predating the flag, which reject
|
|
834
|
+
it outright.
|
|
835
|
+
|
|
836
|
+
It is deliberately **one-way**: naming a category can only suppress it, never
|
|
837
|
+
re-enable one the configuration disabled. That means a CI invocation can keep
|
|
838
|
+
something off a published surface without knowing, or contradicting, what the
|
|
839
|
+
shared configuration asked for — and cannot accidentally publish something an
|
|
840
|
+
operator switched off. An unrecognised category name is rejected with exit `2`
|
|
841
|
+
rather than ignored, since a silently-ignored typo would publish the very thing
|
|
842
|
+
the flag was meant to hide.
|
|
843
|
+
|
|
844
|
+
`report.json` **does** honour `--hide`, and it also omits any category that no
|
|
845
|
+
published surface carries. It is otherwise the complete dataset: the per-surface
|
|
846
|
+
`outputs` toggles do not filter it, so a category hidden from just the terminal,
|
|
847
|
+
or just Slack, is still present in full.
|
|
848
|
+
|
|
849
|
+
The two exceptions exist because the file is written into the **published**
|
|
850
|
+
Pages directory alongside the HTML. Ignoring `--hide` there would publish
|
|
851
|
+
exactly what you asked to keep out of shared output; and serialising a
|
|
852
|
+
terminal-only category would publish it through the back door while every
|
|
853
|
+
rendered surface correctly omitted it. Opting such a category into any
|
|
854
|
+
published surface puts it back in the JSON.
|
|
855
|
+
|
|
856
|
+
`Auto` recognises the same automation accounts as the
|
|
857
|
+
[`dependamerge`](https://github.com/lfreleng-actions/dependamerge) tool:
|
|
858
|
+
Dependabot, Renovate, pre-commit.ci, `github-actions`, Copilot and
|
|
859
|
+
Allcontributors, by their bare or `[bot]`-suffixed login, plus any actor GitHub
|
|
860
|
+
reports as an App and any unrecognised login carrying the `[bot]` marker — so a
|
|
861
|
+
future bot is classified as automation rather than mistaken for an outside
|
|
862
|
+
contributor.
|
|
863
|
+
|
|
864
|
+
`Fail` and `Conflict` count only **established** states. GitHub computes
|
|
865
|
+
mergeability lazily and answers `UNKNOWN` until it settles, and reports no check
|
|
866
|
+
rollup at all when no checks have run; neither absence is evidence that a pull
|
|
867
|
+
request is ready, so neither is counted either way.
|
|
868
|
+
|
|
869
|
+
Repositories with no open pull requests are counted in the footer rather than
|
|
870
|
+
listed. Rows rank by total open pull requests, then by those failing or
|
|
871
|
+
conflicting (`Fail` + `Conflict`), so two repositories with equal backlogs
|
|
872
|
+
surface the more
|
|
873
|
+
stuck one first.
|
|
874
|
+
|
|
875
|
+
> **Accuracy note.** As with the issues table, `Total` is exact at any size,
|
|
876
|
+
> while the breakdown columns are computed from a bounded, oldest-first window
|
|
877
|
+
> of 25 open pull requests per repository. A repository whose backlog exceeds
|
|
878
|
+
> that window shows a trailing `+` on its `Total` cell, marking the breakdown as
|
|
879
|
+
> partial; the total itself stays exact.
|
|
880
|
+
|
|
881
|
+
**Permissions.** A fine-grained PAT needs **Pull requests: read**; a classic
|
|
882
|
+
PAT's `repo` scope already covers it. Without it the connection comes back null
|
|
883
|
+
and affected repositories are reported as `❓ Unknown` rather than as having no
|
|
884
|
+
open pull requests.
|
|
885
|
+
|
|
886
|
+
### Inside or outside the organisation
|
|
887
|
+
|
|
888
|
+
The `Ext` column on both the Issues and Pull Requests tables counts
|
|
889
|
+
contributions from **outside the organisation**. Two pieces of evidence decide
|
|
890
|
+
it, in this order:
|
|
891
|
+
|
|
892
|
+
1. **The organisation's membership**, collected once per organisation in a
|
|
893
|
+
single GraphQL query (one page per 100 members) and reused for every
|
|
894
|
+
repository and every table.
|
|
895
|
+
2. **GitHub's per-item `authorAssociation`**, consulted when the author is not a
|
|
896
|
+
known member. This is what recognises a repository-level *collaborator* who
|
|
897
|
+
holds no organisation membership.
|
|
898
|
+
|
|
899
|
+
The membership query is not redundant, and this ordering is deliberate.
|
|
900
|
+
`authorAssociation` is computed **relative to the requesting token**: where an
|
|
901
|
+
organisation's members keep their membership private — GitHub's default — the
|
|
902
|
+
same issue reports `MEMBER` to a token with organisation visibility and
|
|
903
|
+
`CONTRIBUTOR` to one without. Classifying on that field alone would make the
|
|
904
|
+
counts depend on which token produced the report, and a token lacking
|
|
905
|
+
`read:org` would file an entire organisation as outsiders.
|
|
906
|
+
|
|
907
|
+
**When membership cannot be read at all**, the fallback is not enough on its
|
|
908
|
+
own: an association naming an *insider* is still trusted (only a token that can
|
|
909
|
+
see the relationship reports it), but one naming an outsider proves nothing,
|
|
910
|
+
because that is exactly how a private member appears to an under-privileged
|
|
911
|
+
token. Those authors are reported as indeterminate rather than external, so
|
|
912
|
+
`Ext` **undercounts and should be read as a lower bound** — the table says so in
|
|
913
|
+
its description whenever this happens, and the run logs a warning. Membership is
|
|
914
|
+
never used partially: a failed page or an organisation beyond the pagination
|
|
915
|
+
guard is treated as unreadable rather than as a member list with people missing
|
|
916
|
+
from it, since every absent member would otherwise read as an outsider.
|
|
917
|
+
|
|
918
|
+
**Automation is never counted as external.** Bots are outsiders by association —
|
|
919
|
+
`dependabot[bot]` genuinely reports `CONTRIBUTOR` or `NONE` — so counting on
|
|
920
|
+
association alone would file every dependency-update pull request as an external
|
|
921
|
+
contribution and bury the genuine outside contributors the column exists to
|
|
922
|
+
surface. Automation is reported under `Auto` instead.
|
|
923
|
+
|
|
924
|
+
An author who cannot be classified at all — a deleted account, or an
|
|
925
|
+
association value GitHub has newly introduced — is **not** counted as external.
|
|
926
|
+
The column understates rather than inventing an outsider.
|
|
927
|
+
|
|
928
|
+
**Permissions.** Reading organisation membership needs `read:org` (classic) or
|
|
929
|
+
**Members: read** (fine-grained). Without it the tool logs a warning and
|
|
930
|
+
reports externally-raised counts as a lower bound rather than guessing from
|
|
931
|
+
`authorAssociation` alone.
|
|
932
|
+
|
|
544
933
|
### Organisation feature gating
|
|
545
934
|
|
|
546
935
|
The workflow-driven signals (OpenSSF Scorecard, zizmor, aislop) need
|
|
@@ -642,6 +1031,7 @@ and the Slack **bot token** is consumed by the workflow, not the CLI.
|
|
|
642
1031
|
| `top_n_slack` | No | — | Offenders per signal in the Slack digest (`0` = no limit; overrides `top_n`) |
|
|
643
1032
|
| `fail_threshold` | No | `none` | `none`/`low`/`medium`/`high`/`critical`/`any` (repo mode) |
|
|
644
1033
|
| `force_notify` | No | `false` | Post to Slack regardless of `report_day` |
|
|
1034
|
+
| `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 |
|
|
645
1035
|
| `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) |
|
|
646
1036
|
| `use_local_source` | No | `false` | Run from the checked-out source instead of PyPI (for testing unreleased code from any event) |
|
|
647
1037
|
|
|
@@ -678,6 +1068,27 @@ your enterprise endpoints (Actions sets these automatically on GHES
|
|
|
678
1068
|
runners). `SCORECARD_API_URL` overrides the external OpenSSF Scorecard
|
|
679
1069
|
API in the same way.
|
|
680
1070
|
|
|
1071
|
+
### Exit codes
|
|
1072
|
+
|
|
1073
|
+
| Code | Meaning |
|
|
1074
|
+
| ---- | ------- |
|
|
1075
|
+
| `0` | The report ran. |
|
|
1076
|
+
| `1` | Repo mode only: findings met or exceeded `--fail-threshold`. |
|
|
1077
|
+
| `2` | Usage or configuration error (bad flag, unreadable config). |
|
|
1078
|
+
| `3` | The GitHub API was unreachable after the retry budget. |
|
|
1079
|
+
| `4` | GitHub rejected the credentials (HTTP 401). |
|
|
1080
|
+
|
|
1081
|
+
Codes `3` and `4` are **aborts, not reports**: nothing is written and no Pages
|
|
1082
|
+
artifact is produced. That is deliberate. A token that has expired, been revoked
|
|
1083
|
+
or been rotated makes every read fail, and a run that degraded instead would
|
|
1084
|
+
render a complete, confidently clean report — `0 repositories analysed`, every
|
|
1085
|
+
section `No data` or `All Clean` — and a scheduled job would then publish it
|
|
1086
|
+
over the last good one. Reporting false data is worse than reporting none, so
|
|
1087
|
+
the run stops at the first rejected request.
|
|
1088
|
+
|
|
1089
|
+
The two are separate codes because the remedy differs: `4` means rotate or fix
|
|
1090
|
+
the token, `3` means retry later.
|
|
1091
|
+
|
|
681
1092
|
## Remediation
|
|
682
1093
|
|
|
683
1094
|
The `remediate` subcommand is the in-tool counterpart to the report: it runs the
|