Skip to content

Add dependabot command: org-level Dependabot/SCA health audit - #24

Draft
felickz wants to merge 5 commits into
mainfrom
felickz-symmetrical-engine
Draft

felickz wants to merge 5 commits into
mainfrom
felickz-symmetrical-engine

Conversation

@felickz

@felickz felickz commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new dependabot subcommand alongside code-scanning, auditing whether the dependency graph, Dependabot alerts, version updates, pull request health, and dependency review enforcement are actually working, not just enabled. Shares code-scanning's scope flags, cache, concurrency, and output formats (table/json/ndjson/csv).

gh ghas-audit dependabot --organization my-org
gh ghas-audit dependabot --enterprise my-enterprise --scan-depth diagnostics
gh ghas-audit dependabot -r owner/repo --format json

Key features

  • Dependency graph coverage — detects Maven/Gradle/NuGet manifests that need build-time or lockfile resolution to show transitive dependencies. Checks for a known auto-submission workflow (14+ marketplace actions covering Go, Gradle, Maven, Mix, NuGet/Component Detection, rebar3, Scala/sbt, SPDX, etc.) and, when coverage is already complete, falls back to the repository's SBOM export to also detect GitHub's native no-workflow-file "Automatic Dependency Submission" toggle.
  • Dependabot alerts — open counts by severity (critical/high/medium/low) plus a separate count of alerts classified malware.
  • Pause-for-inactivity — detected authoritatively via the automated-security-fixes endpoint (works even with zero open PRs), falling back to the dependabot-paused label only when that endpoint is unavailable.
  • Security vs. version update PR classification — uses the RepositoryVulnerabilityAlert.dependabotUpdate GraphQL linkage (the only authoritative signal), with a package-name fallback for alerts whose link is stale or missing, and a body-text heuristic as a last resort.
  • Dependency review enforcement — checked via required-status-check patterns at --scan-depth diagnostics. Correctly skipped (not reported as a collection error) for GitHub's temporary private security-advisory forks, identified by their documented -ghsa-xxxx-xxxx-xxxx name suffix — their rules/rulesets endpoints always 404 regardless of the real repository's configuration.
  • --verbose progress reporting (per-repository name, status, elapsed time) and explicit concurrency reporting, applied to both dependabot and code-scanning.
  • GraphQL client hardening — retry coverage extended to secondary rate limits, 5xx responses, GraphQL-level execution timeouts ("timedout"), and truncated/empty response bodies, all previously unretried and causing permanent first-attempt failures.

Testing

  • go build ./..., go vet ./..., go test ./... all pass (all packages, including a Windows-native run)
  • gofmt -l clean on every changed file
  • go mod tidy produces no diff
  • 32 new/updated tests for the dependabot command covering dependency-graph coverage, auto-submission detection (workflow + SBOM), alert severity/malware counting, PR security/version classification, pause detection, dependency-review skip logic, and GraphQL retry behavior
  • Live-verified end-to-end against real repositories (table, JSON, and CSV output) at all three scan depths

Notes for reviewers

  • No changes to the existing code-scanning command's behavior beyond the shared --verbose/concurrency-reporting addition.
  • A real accuracy bug was found and fixed during pre-PR validation: temporary security-advisory fork detection was initially based on the REST API's temp_clone_token field, which turns out to be populated for every private/internal repository, not just these forks. It was replaced with GitHub's documented name-suffix convention for these forks, which is far more specific.

Adds a new `dependabot` subcommand alongside `code-scanning`, auditing
whether the dependency graph, Dependabot alerts, version updates, pull
request health and dependency review enforcement are actually working,
not just enabled.

Key features:
- Dependency graph coverage detection for Maven/Gradle/NuGet manifests
  that need build-time or lockfile resolution to show transitive
  dependencies, with auto-submission mitigation detection (workflow-file
  scan across 14+ known marketplace actions, plus an SBOM-based fallback
  for GitHub's native no-workflow-file toggle).
- Dependabot alert counts by severity (critical/high/medium/low) plus a
  separate malware-classification count.
- Authoritative pause-for-inactivity detection via the
  automated-security-fixes endpoint (works even with zero open PRs),
  falling back to the dependabot-paused label when unavailable.
- Security-update vs. version-update pull request classification via the
  RepositoryVulnerabilityAlert.dependabotUpdate GraphQL linkage, with a
  package-name fallback for stale/missing links.
- Dependency review enforcement checked via required-status-check
  patterns at diagnostics depth, correctly skipped (not reported as an
  error) for GitHub's temporary private security-advisory forks,
  identified by their documented -GHSA-xxxx-xxxx-xxxx name suffix.
- Shared concurrency/--verbose progress reporting improvements applied to
  both commands.
- GraphQL client retry coverage extended to secondary rate limits, 5xx
  responses, GraphQL-level execution timeouts, and truncated response
  bodies.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@felickz
felickz requested a review from theztefan as a code owner October 14, 2026 21:22
@felickz
felickz requested a balanced review from Copilot October 14, 2026 21:22
@felickz
felickz marked this pull request as draft October 14, 2026 21:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Incomplete evidence and health-detection errors can produce misleading healthy verdicts and inaccurate audit results.

15 open findings
What changed in this PR

Adds Dependabot health auditing alongside CodeQL auditing, reusing shared scope, cache, concurrency, and output infrastructure.

Changes:

  • Adds dependency graph, alert, update PR, and dependency review assessment.
  • Introduces report models, output writers, documentation, and tests.
  • Adds verbose progress reporting and broader GraphQL retries.
File Description
README.md Documents Dependabot auditing and verbose output.
internal/​output/​dependabot.go Adds table, JSON, NDJSON, and CSV writers.
internal/​output/​dependabot_test.go Tests report rendering and serialization.
internal/​model/​dependabot.go Defines report contracts and health classification.
internal/​model/​dependabot_test.go Tests classification and aggregation.
internal/​ghapi/​client.go Expands GraphQL retry handling.
internal/​ghapi/​client_test.go Tests transient GraphQL failures.
internal/​collector/​dependabot.go Collects and evaluates Dependabot evidence.
internal/​collector/​dependabot_test.go Tests collection and detection scenarios.
internal/​collector/​dependabot_apitypes.go Defines API and configuration response types.
internal/​collector/​collector.go Adds verbose CodeQL scan progress.
internal/​collector/​collector_test.go Tests verbose progress reporting.
go.mod Makes YAML parsing a direct dependency.
cmd/​root.go Registers the Dependabot command.
cmd/​dependabot.go Implements command options and report delivery.
cmd/​dependabot_test.go Tests validation, filtering, and output.
cmd/​code_scanning.go Exposes the verbose flag.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/collector/dependabot.go Resolved
Comment thread internal/collector/dependabot.go Resolved
Comment thread internal/collector/dependabot.go Resolved
Comment thread internal/collector/dependabot.go Resolved
Comment thread internal/collector/dependabot.go Resolved
Comment thread internal/collector/dependabot.go Resolved
Comment thread cmd/dependabot.go Resolved
Comment thread internal/collector/dependabot.go Resolved
Comment thread internal/collector/dependabot.go Resolved
Comment thread internal/collector/dependabot.go Resolved
- Set Status.Incomplete whenever repo.Errors is non-empty (the single most
  impactful fix: this was never set, so collection failures could still
  silently roll up to a healthy verdict).
- Config-depth now explicitly sets DependabotAlerts=not-evaluated instead
  of leaving it a zero-value empty string.
- Move the authoritative paused-for-inactivity assignment to the start of
  collectPullRequests so it survives every early return, including zero
  open pull requests or a failed pull request list fetch.
- Detect failing checks via both the legacy Combined Status endpoint and
  the separate Checks API (what GitHub Actions actually posts); track
  pull requests whose check status could not be established at all as
  "unchecked" rather than implicitly healthy.
- Sum open-pull-requests-limit per ecosystem across all configured update
  blocks (VersionUpdates.EcosystemLimits), instead of multiplying a single
  default limit by the count of distinct ecosystems, so two blocks for the
  same ecosystem no longer collapse into one block's worth of headroom.
- Add gradle to the dependency-graph/alert ecosystem coverage map (Gradle
  dependencies are tracked under the GraphQL MAVEN enum value).
- Reject --security-configuration for the dependabot command instead of
  silently ignoring it (no filtering mechanism exists for it today).
- Record alert-list and vulnerability-alert-linkage pagination-cap
  truncation as collection errors instead of silently reporting a
  truncated result as complete.
- Record unparseable/oversized manifests as collection errors so they
  correctly feed Status.Incomplete.
- Treat a manifest whose dependency sample was capped by the GraphQL
  query's own per-manifest limit, with no transitive dependency found in
  the inspected subset, as inconclusive rather than a confirmed gap.
- Scope auto-submission mitigation to the ecosystem(s) a detected action
  can actually resolve, so a Maven-only submission action no longer
  mitigates an unrelated NuGet manifest gap.
- Strip commented-out workflow lines before substring-matching
  auto-submission markers, and remove gradle/actions/setup-gradle and
  gradle/gradle-build-action (both default dependency-graph submission to
  disabled) from the known-detector list.
- Also check classic branch protection (required_status_checks) for a
  dependency-review-matching check, not just rulesets, which are two
  independent systems.

Adds 17 new unit tests covering every fix above.
…gging inconclusive

The dependencyGraphManifests nested dependencies connection caps each
manifest at the first 100 dependencies in the bulk query, but GitHub's
schema (DependencyGraphDependencyConnection) actually supports full cursor
pagination via the Node interface. Rather than leaving a manifest with
>100 dependencies and no transitive dependency in that first-100 sample as
permanently inconclusive, page through its remaining dependencies (bounded
by a 10-page safety cap) looking for a transitive one, with an early exit
as soon as one is found. Only confirmed-gap or confirmed-coverage results
in a settled classification now; "inconclusive" is reserved for the rare
case where the safety cap is hit before the manifest's dependency list is
exhausted.

This replaces the earlier scope-limited fix (always inconclusive, never
confirmed) with a more complete one, after confirming via the public
GraphQL schema reference and live API testing that pagination is actually
available here, and that it successfully returns a confident transitive-
relationship signal beyond the first page for a real Maven repository.
The 10-page cap was an arbitrary round number, not derived from any real
worst case. This cap is fundamentally different from the per-repo alert
(3 page) and vulnerability-alert-linkage (5 page) caps elsewhere in this
collector: those apply to every repository that has any alerts, so their
cost multiplies across an entire org scan. This one only ever applies to
an ambiguous Maven/Gradle/NuGet manifest with zero transitive dependencies
in its first 100 (npm, pip, etc. never reach this code path at all, since
their relationships are already known from a static lockfile parse) --
a live scan of 100 real repositories never hit this even once. Raised to
50 pages (5,100 total dependencies inspected) as a generous safety net
against a pathological manifest or a non-terminating cursor, not as a
practically-expected limit.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Copilot was unable to run its full agentic suite in this review.

Copilot review overview

7 open findings
15 resolved since last review

🧠 Review effort: Lite

Comment thread internal/collector/dependabot.go Resolved
Comment thread README.md Resolved
Comment thread cmd/dependabot.go Resolved
Comment thread internal/collector/dependabot.go Resolved
Comment thread internal/collector/dependabot.go Resolved
Comment thread internal/output/dependabot.go Resolved
…, brittle error matching

- SBOM creator parsing: require creator entries to actually start with
  "Tool:" before treating them as a tool name; strings.TrimPrefix silently
  passes through non-tool entries (Person:/Organization:) unchanged, which
  were being misread as unrecognized auto-submission tools.
- README: corrected two stale "Limitations" bullets describing behavior
  this PR had already superseded (re-parsing open-pull-requests-limit per
  block, and the temp_clone_token-based temporary-fork signal), updating
  them to describe the actual current collector logic.
- mergeDependabotOrgErrors: sort the rebuilt organization list by login
  (mirroring code scanning's mergeOrgErrors), since appending previously-
  errored orgs back in map-iteration order made this user-facing output
  non-deterministic across runs.
- Replace the collector's brittle, substring-based isNotFoundError (which
  misclassified any error whose text happened to mention "404" or "not
  found") with the existing, type-safe ghapi.IsNotFound helper already used
  throughout the code-scanning collector.
- manifestEcosystem: classify .vbproj as "nuget", matching
  buildTimeResolutionManifest, which already treats it as a NuGet
  build-time-resolution manifest; the mismatch left .vbproj-only
  repositories showing no ecosystem at all.
- ResolveDependabotPropertyColumns: sort the discovered property columns
  and pick the lexicographically-smallest spelling per folded name
  (mirroring code scanning's PropertyColumns), fixing non-deterministic
  CSV/table column ordering and casing.
- Corrected a stale comment describing vulnerabilityAlertPullRequestNode's
  State field as used to filter the package-name fallback; filtering
  already happens at the GraphQL query level (states:[OPEN,AUTO_DISMISSED]),
  so the field is not read anywhere in the collector today.

Adds 6 new tests covering the behavioral fixes above.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants