Security and quality reporting across GitHub organisations
Project description
🔐 GitHub Security Report
Security and quality reporting (not scanning) across GitHub organisations. Aggregates existing signals — CodeQL, OpenSSF Scorecard, zizmor, Dependabot, and secret scanning — and ranks the worst offenders so remediation effort goes where it is needed.
🗒️ Published reports
https://lfreleng-actions.github.io/github-security-report-action/
What it does
For each in-scope repository, every signal is classified into one of four states and rendered worst-first:
- Offenders — enabled with open findings (a ranked table row).
- Clean — enabled with zero findings (a count beneath the table).
- Not enabled — supported but switched off (a nag list prompting you to enable it).
- Unknown — indeterminate (insufficient permission), footnoted separately.
The single GitHub code-scanning feed is partitioned by tool.name into CodeQL,
Scorecard, and zizmor; Scorecard prefers the external aggregate score and falls
back to code-scanning findings. See docs/BRIEF.md and
docs/phase0-findings.md for the full design and the
API research it is built on.
Two further sections report configuration posture and freshness as plain tables (org mode):
- Dependabot — three tables: repositories with vulnerability alerts not
enabled, repositories with security updates not enabled, and ecosystems
with no update
cooldownconfigured (mandatory; any value passes). - Releases / Tagging — repositories overdue a release or tag, ranked by a
hidden compound staleness score. Repositories younger than
release_min_age_days(default 28;0includes all) and those inreleases_excludeare omitted.
Operating modes
| Mode | Token | Scope | Output |
|---|---|---|---|
org |
fine-grained PAT (single org) or classic PAT (multiple orgs) | one or more organisations | GitHub Pages + Slack + terminal |
repo |
GITHUB_TOKEN |
the current repository only | job summary + outputs + optional PR gate |
scope: auto resolves to org mode when configuration is supplied, otherwise
repo mode for the detected repository. The ephemeral GITHUB_TOKEN cannot read
org-wide security data, so org mode requires a PAT — see
Token permissions for the exact scopes.
Token permissions
Repo mode needs nothing beyond the workflow's ephemeral GITHUB_TOKEN. Org mode
needs a Personal Access Token; choose one of the two options below depending
on how many organisations the report covers.
All required access is read-only. The tool degrades any read it is not permitted to make to an "unknown" status rather than reporting a repository as clean, so an under-scoped token surfaces as unknowns in the report instead of silently wrong results — start minimal and widen if you see unknowns.
Single organisation — fine-grained PAT
A fine-grained PAT is bound to one resource owner, so it works for a report covering a single organisation. Create it with Resource owner set to the organisation and Repository access set to All repositories, then grant:
Repository permissions (all Read-only):
| Permission | Used for |
|---|---|
| Metadata | Mandatory baseline; listing organisation repositories |
| Contents | .github/dependabot.yml, latest release, and tag dates |
| Dependabot alerts | Open Dependabot vulnerability alerts |
| Code scanning alerts | CodeQL / Scorecard / zizmor findings |
| Secret scanning alerts | Open secret-scanning alerts |
| Administration | Dependabot enablement + security-updates status, and effective branch rules |
Organization permissions (Read-only):
| Permission | Used for |
|---|---|
| Administration | Organisation rulesets (detect tools enabled via a required workflow) |
A fine-grained token cannot span organisations. For a report covering more than one org, use a classic PAT (below).
Multiple organisations — classic PAT
A classic PAT is authorised across every organisation its creator can access (subject to SSO authorisation), so a single token can report on multiple organisations. Grant these scopes:
| Scope | Used for |
|---|---|
repo |
Repository data, including private repositories |
security_events |
Code scanning, secret scanning, and Dependabot alerts (org-bulk and per-repo) |
read:org |
Listing organisation repositories and reading organisation rulesets |
For organisations that enforce SSO, the PAT must be SSO-authorised for each target organisation, or the org-level endpoints return
403(reported as unknown). Store the token as a secret (e.g.SECURITY_REPORT_PAT) and reference it by env-var name viatoken_env; never embed it in the config.
Usage
Org mode (scheduled report)
- name: "Security report"
id: report
uses: lfreleng-actions/github-security-report-action@v0.1.0
with:
scope: "org"
config: "${{ secrets.GSR_CONFIG || vars.GSR_CONFIG }}"
token: "${{ secrets.SECURITY_REPORT_PAT }}"
output_dir: "site"
pages_url: "https://lfreleng-actions.github.io/github-security-report-action/"
A ready-to-use scheduled workflow lives in
.github/workflows/reporting.yaml: it runs
daily at 09:00 UTC, publishes to GitHub Pages every day, and posts a Slack
digest only on the configured report_day (default Tuesday).
Repo mode (PR gate)
- name: "Security report"
uses: lfreleng-actions/github-security-report-action@v0.1.0
with:
scope: "repo"
token: "${{ github.token }}"
fail_threshold: "high" # fail the job on any open high/critical finding
# requires: permissions: { security-events: read }
Configuration
Configuration is JSON, supplied as a plain vars. entry or base64-encoded in a
secrets. entry (base64 only to stop JSON braces tripping GitHub's log
redaction — it is encoding, not encryption). Tokens are referenced by
environment-variable name, never embedded.
{
"slack": { "channel": "releng-scm", "report_day": "tuesday" },
"report": {
"top_n": 10,
"top_n_report": 10,
"top_n_cli": 10,
"top_n_slack": 10,
"include_archived": false,
"include_test": false,
"release_min_age_days": 28
},
"organizations": [
{
"name": "lfreleng-actions",
"token_env": "GITHUB_TOKEN",
"exclude": ["actions-template"],
"releases_exclude": ["internal-only-repo"]
}
]
}
report_day accepts a single weekday, a list of weekdays, "never", or
"always".
top_n controls how many offenders are shown per signal. It is the shared
default for all three outputs; set any of top_n_report (GitHub Pages),
top_n_cli (terminal), or top_n_slack (Slack digest) to override an
individual output. Each can also be set at the CLI with --top-n,
--top-n-report, --top-n-cli, and --top-n-slack.
report.release_min_age_days (default 28, 0 = include all) and the per-org
releases_exclude tune the Releases / Tagging section; they can be overridden
locally with --release-min-age-days and the repeatable --releases-exclude.
The per-org exclude list removes repositories from analysis entirely; they are
reported as excluded (distinct from "not enabled"), so an intentional
exclusion is visible rather than silently dropped.
slack.channel is optional. The action's slack_channel input (wired to the
SLACK_CHANNEL_ID variable in reporting.yaml) overrides it, so the channel
can live as an org/repo variable rather than in the config JSON. It must be the
channel ID (C0…), not the name.
Config file location
For local use you can drop the same JSON at a conventional per-user path and run
with no flags — it is picked up automatically when no --config,
--config-data, or --org is given (instead of erroring):
$XDG_CONFIG_HOME/github-security-report/config.json
# or, when XDG_CONFIG_HOME is unset:
~/.config/github-security-report/config.json
An explicit --config, --config-data, or --org always takes precedence, and
the action itself never reads this path (it is supplied configuration directly).
Secrets stay out of the file: reference the token by environment-variable name
via token_env (e.g. SECURITY_REPORT_PAT, exported in your shell or sourced
from a secrets file) — the channel ID is the only Slack value the file holds,
and the Slack bot token is consumed by the workflow, not the CLI.
Inputs
| Name | Required | Default | Description |
|---|---|---|---|
scope |
No | auto |
auto, org, or repo |
config |
No | — | JSON config (raw or base64) |
org |
No | — | Single organisation (shorthand for org mode) |
repo |
No | detected | owner/name for repo mode |
token |
No | ${{ github.token }} |
PAT (org mode) or GITHUB_TOKEN (repo mode) |
token_env |
No | GITHUB_TOKEN |
Env var name the tool reads the token from |
output_dir |
No | — | Directory for Pages output (org mode) |
pages_url |
No | — | Published Pages URL (used in the Slack link) |
slack_channel |
No | — | Slack channel ID; overrides the config slack.channel (e.g. the SLACK_CHANNEL_ID variable) |
top_n |
No | 10 |
Offenders per signal across all outputs (shared default) |
top_n_report |
No | — | Offenders per signal in the GitHub Pages output (overrides top_n) |
top_n_cli |
No | — | Offenders per signal in the terminal output (overrides top_n) |
top_n_slack |
No | — | Offenders per signal in the Slack digest (overrides top_n) |
fail_threshold |
No | none |
none/low/medium/high/critical/any (repo mode) |
force_notify |
No | false |
Post to Slack regardless of report_day |
tool_version |
No | 0.1.0 |
Published PyPI version (ignored on pull requests) |
use_local_source |
No | false |
Run from the checked-out source instead of PyPI (for testing unreleased code from any event) |
Outputs
| Name | Description |
|---|---|
should_notify |
Whether today is a Slack notification day |
slack_payload |
Prebuilt Slack chat.postMessage payload (JSON) |
failed |
Whether the repo-mode fail threshold was met |
Running locally
The tool is published to PyPI and runs with uvx. Inside a Git checkout with a
GITHUB_TOKEN exported, it auto-detects the repository (preferring the
upstream remote, then origin) and prints a Rich table report:
export GITHUB_TOKEN=ghp_your_token
uvx github-security-report report
# Or org mode locally with a PAT:
uvx github-security-report report --org lfreleng-actions
Development
uv sync --extra dev
uv run pytest
uv run ruff check src/ tests/
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file github_security_report-0.1.0.tar.gz.
File metadata
- Download URL: github_security_report-0.1.0.tar.gz
- Upload date:
- Size: 53.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
afb60847eb4b9781a737caab55fb1370a4e16b4d09a2e897799b99b872c5306a
|
|
| MD5 |
b8ee51465de256a887bf6e92393d0758
|
|
| BLAKE2b-256 |
ff640e54781cc67c374afeb3ac5675c442173dc363c7de9b520286d5db14bc9d
|
File details
Details for the file github_security_report-0.1.0-py3-none-any.whl.
File metadata
- Download URL: github_security_report-0.1.0-py3-none-any.whl
- Upload date:
- Size: 65.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
92024e41300e2c478227675738eb0c5b121ed1f4366a48c7936e7695eea77490
|
|
| MD5 |
d5c46e3e0ecc1804f8b100faf1e7b242
|
|
| BLAKE2b-256 |
022cc68eee2cff0eacddb5d94dea09538a3bb75ff52c2a0ef03901cd3c704e1c
|