# ghqr audits a GitHub org or enterprise against a fixed checklist of settings

> GitHub Quick Review is a Go CLI that scans GitHub organizations, repositories and Enterprise Server instances, then reports misconfigurations as recommendations, per-repository findings and an issues sheet in Markdown, Excel or JSON. It reads settings, not code.

**microsoft/ghqr** — GitHub Quick Review: Evaluate your enterprise and organizations against GitHub best practices

- Repository: https://github.com/microsoft/ghqr
- Website: https://microsoft.github.io/ghqr/
- Stars: 528 · Forks: 35
- Language: Go
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/microsoft-ghqr

## The scope is configuration, not the content of your code

GitHub Quick Review analyses GitHub enterprises, organizations and repositories to check compliance with GitHub best practices and security recommendations. Its stated objective is a comprehensive assessment that lets users identify security gaps, misconfigured settings and areas for improvement.

The important word there is settings. This is a configuration auditor. It reads the administrative surface of your GitHub estate, the branch protection rules, the SAML settings, the Copilot policy, the Actions permissions, the audit log configuration, and it tells you where those disagree with a built-in expectation.

The check catalogue is organised by area and by scope, and the scope column is what tells you what the tool reaches into. Security checks cover org and repo, with Dependabot alerts, secret scanning, code scanning and GHAS. Access control covers org and repo, with 2FA enforcement, member privileges, SAML SSO and CODEOWNERS. Branch protection is repo-scoped, checking required reviews, status checks and admin enforcement. Copilot is org-scoped, covering seat usage, content exclusions, policy configuration and MCP settings. Governance is org-scoped with IP allow lists, repository creation policies and fork policies. Audit log is checked at enterprise and org level for streaming and suspicious event detection. Community health is repo-scoped. Actions covers workflow permissions, allowed actions and self-hosted runners. Dependencies covers Dependabot version and security updates. Metadata covers description, topics, visibility and archival status.

For a GitHub Enterprise Server instance there is a second, server-side catalogue covering server configuration, authentication, licence, security, dependencies, Actions, audit log, infrastructure and admin stats. The auth-mode check is the one worth calling out: it looks at SAML, LDAP or CAS, at whether open signup is on, and at whether password authentication is enabled.

None of this is source code analysis. If your concern is what is inside the repository, ghqr is not looking.

## Credential resolution follows a four-step chain, and GHES needs a different variable

Authentication is resolved through a chain, and the documentation compares it to Azure's DefaultAzureCredential. The order is GH_TOKEN, then GITHUB_TOKEN, then the gh CLI config, where running gh auth login once lets ghqr pick it up automatically, and finally the system keyring, meaning the macOS Keychain or the Linux Secret Service.

That ordering is a sensible design, and the reason is that the gh CLI path means you can avoid putting a token in your shell history or in a CI environment variable. The gh CLI config sits above the keyring but below the explicit variables, so an explicit override still wins.

The GHES case carries a security caveat that is worth knowing before you scan a self-hosted instance. GITHUB_TOKEN is scoped to github.com only, which the documentation attributes to CVE-2024-53859. For a GHES host you should use GH_TOKEN or gh auth login --hostname with the Ghes host instead.

The required token scopes for github.com are read:org for organization settings and members, read:enterprise for enterprise settings, repo for repository settings, branch protection and security features, read:audit_log for audit log configuration, read:user for user information, and copilot for Copilot seat and policy information. For GHES the documentation lists site_admin, which covers server settings, licence, admin stats and audit log, alongside read:org.

That list is a real cost of adoption, because a token with site_admin on a production GHES instance is a high-value credential, and ghqr is asking for it by name.

## Insufficient permissions are recorded, not treated as failures

One behaviour deserves attention because it affects how you read the output.

Organization code security configurations, which appear in the JSON output as settings.security.code_security_configurations, are read with read:org, but GitHub also requires the token user to be an organization owner or security manager. If that is not the case, ghqr records them as not readable rather than failing the scan. The JSON then shows a *_readable: false field with a *_unavailable_reason of forbidden, not_found or error.

The design decision is the right one for an auditor. A tool that aborts when a permission is missing gives you no report at all, whereas a tool that records the gap gives you a report plus a list of what it could not see. The risk is on the reader: a finding of false is ambiguous if you do not also check the readable flag, and a field that was never read looks the same as a field that was read and found to be off.

The documentation also explains why this matters more than it used to. These configurations replaced the deprecated *_enabled_for_new_repos organization fields, which can report false for organizations that set defaults through a default code security configuration. So a naive reading of the older field would produce a false negative, and the switch to the newer configuration is what removes the ambiguity.

For anyone scripting against the JSON output, the readable and unavailable_reason fields are the ones to key on. Treat a false value on a field whose companion readable flag is false as unknown, not as a finding.

## Installing ghqr by script, by container, or from a Makefile

There are four install routes and the choice depends on your environment.

For Linux and macOS the documented one-liner runs the install script from the repository:

```bash
bash -c "$(curl -fsSL https://raw.githubusercontent.com/microsoft/ghqr/main/scripts/install.sh)"
```

Windows has a parallel PowerShell route, and both platforms also allow downloading the latest release from the releases page instead. The Docker route pulls a published image:

```bash
docker pull ghcr.io/microsoft/ghqr:latest
```

And the source route is a clone plus a make:

```bash
git clone https://github.com/microsoft/ghqr.git
cd ghqr
make
```

The Makefile is worth a look if you are building the image yourself, because the Dockerfile is not a compile-from-source image. It copies a pre-built binary into a scratch image, and the Makefile has a build-image target that depends on the binary target first, so the ordering is handled. The image is built for the build platform and copies from bin/linux_${TARGETARCH}/ghqr, with the arch passed in as a build argument, and it sets a default CMD of mcp --mode http --addr :8080 with port 8080 exposed. The entrypoint is the ghqr binary itself.

That default CMD is the more interesting detail, and it is a different mode of operation than a scan. The binary carries an MCP server, the mark3labs/mcp-go dependency is in the module list, and the docs directory sits alongside cmd, internal and scripts. So the same binary that performs a one-shot scan can also be run as a model context protocol server over HTTP, which is how you would wire the assessment into an assistant workflow rather than a terminal.

The build uses CGO_ENABLED=0 with -trimpath and linker flags that strip the binary and stamp the version, and the module requires Go 1.26.0.

## Scanning an org, an enterprise, or a GHES host from one command

The quick start is short because the tool is a single subcommand with a scope flag.

The recommended authentication route is the gh CLI login, and the alternative is a token in the environment. A minimal invocation looks like this:

```bash
gh auth login
export GITHUB_TOKEN=<your-personal-access-token>
ghqr scan -o my-org
ghqr scan -e my-enterprise
```

The three flags map onto the three scopes. -o selects an organization, -e selects a GitHub Enterprise Cloud instance, and --ghes takes the hostname of a GitHub Enterprise Server deployment. For the GHES case, the documentation gives both routes:

```bash
# 4. Scan a GitHub Enterprise Server (GHES) instance
gh auth login --hostname ghes.example.com   # recommended
# or: export GH_TOKEN=<your-ghes-personal-access-token>
ghqr scan --ghes ghes.example.com
```

Note the token variable name changes here, GH_TOKEN rather than GITHUB_TOKEN, and the gh login is scoped to the host with --hostname. Those are not cosmetic differences; using GITHUB_TOKEN against a GHES host is the case the CVE note warns about.

On Windows the same commands are available as .\ghqr with the token set through $env:GITHUB_TOKEN. The flag names and the scanning model are identical across platforms, so a scan script written for one runs on the other with the binary path adjusted.

## Output comes as Markdown, Excel or JSON, with Excel the default

The result is a report, and the report format is a deliberate choice rather than a default dump of API responses.

Outputs are available in Markdown (.md), Excel (.xlsx) which is the default, and JSON. The report content is described as four parts. Recommendations are prioritized findings carrying a severity and a category. Organizations give a summary of all scanned organizations and their posture. Repositories carry per-repository findings on branch protection, security features and access settings. The Issues Sheet holds all findings with recommendations and links to documentation.

That last item is the one that makes this usable in an organisation that is not a single team. An issues sheet with severity, category and a documentation link per finding is something you can put in a tracker, assign and close. A stream of API responses is not.

The Excel default is the opposite of what a CLI tool usually does, and it tells you the intended consumer. A scan of a large estate produces hundreds of findings, and the person who acts on them is a platform owner with a spreadsheet, not a person reading a terminal. The JSON exists for the other case, where you want to diff two runs or feed the results into your own tooling, and the JSON carries the structured readable and unavailable_reason fields discussed earlier.

The GraphQL dependency, shurcooL/githubv4, is there because some organisation settings are only available through the GraphQL API, while go-github/v83 handles the REST surface. The mix is a practical consequence of what each API exposes.

## Where ghqr is the wrong tool

The first wrong-tool case is a code-security problem. ghqr checks whether secret scanning and code scanning are switched on and whether Dependabot is configured, and it will tell you that GitHub Advanced Security is off. It does not find the secret that is already committed, and it does not analyse your code. For either of those you need a scanner that reads content, not a settings auditor.

The second case is a compliance programme needing an evidence trail. The output is a report, and the issues sheet has severity and category and links to documentation, but nothing in the description suggests attestation, sign-off, or a record of who ran a scan when. If your auditors need a defensible chain, this is a source of findings rather than a system of record.

The third case is a small estate. For one organisation with a dozen repositories, a curl to the API and a read of the settings page is often faster, and a scanner that needs a token with read:enterprise and site_admin for GHES is a lot of privilege to hand out for a small job.

The alternative approach is worth naming concretely, because the difference is in what gets compared. A configuration audit against a fixed internal checklist is the other approach, and it is what most platform teams already have in a wiki page. The difference in approach is that ghqr applies a vendor-supplied, versioned catalogue of what GitHub considers best practice, and it does so consistently across every organization in an estate and produces a comparable artifact each time. A hand-maintained checklist is specific to your organisation, which is its advantage, but it is applied unevenly, produces no diff between runs, and nobody updates it when GitHub ships a new setting. The tradeoff runs the other way when your requirements genuinely differ from GitHub's defaults, because then a vendor checklist will keep flagging things you have deliberately chosen.

## Conclusion

ghqr suits a platform engineer who needs a repeatable configuration audit across many organizations or repositories, and who wants the result in a spreadsheet rather than a set of API calls. It does not suit a team looking for code scanning, secret detection inside content, or a compliance audit with a formal evidence trail, since it reads settings and configuration. Before adopting, run ghqr scan -o on one organization you already know well and reconcile the findings with what you know, then decide whether the token scopes read:org, read:enterprise, repo and read:audit_log are acceptable for your environment.

## FAQ

### How do I install and run the ghqr CLI?

On Linux and macOS run the install script one-liner, or pull the ghcr.io/microsoft/ghqr image with Docker, or clone the repository and run make. Then authenticate with gh auth login and run ghqr scan -o my-org for an organization.

### What token scopes does ghqr need?

On github.com it needs read:org, read:enterprise, repo, read:audit_log, read:user and copilot. For GitHub Enterprise Server it needs site_admin plus read:org, and GITHUB_TOKEN is scoped to github.com only, so use GH_TOKEN or gh auth login --hostname for a GHES host.

### What output formats does ghqr produce?

Markdown (.md), Excel (.xlsx) which is the default, and JSON. The report contains prioritized recommendations with severity and category, an organizations summary, per-repository findings, and an issues sheet with recommendations and documentation links.

### Does ghqr fail when it lacks a permission?

No. Fields it cannot read are recorded as not readable, with a *_readable: false value and a *_unavailable_reason of forbidden, not_found or error, so the scan still completes.

## Sources

- [License: MIT](https://github.com/microsoft/ghqr/blob/main/LICENSE)
- [microsoft/ghqr on GitHub](https://github.com/microsoft/ghqr)
- [Project website](https://microsoft.github.io/ghqr/)
- [README](https://github.com/microsoft/ghqr/blob/main/README.md)
- [Releases](https://github.com/microsoft/ghqr/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/microsoft-ghqr
