Skip to content

feat(code_scanning): manage CodeQL default setup - #1131

Open
nicomazzarello wants to merge 4 commits into
github-community-projects:main-enterprisefrom
nicomazzarello:nicomazzarello/code-scanning-default-setup
Open

nicomazzarello wants to merge 4 commits into
github-community-projects:main-enterprisefrom
nicomazzarello:nicomazzarello/code-scanning-default-setup

Conversation

@nicomazzarello

@nicomazzarello nicomazzarello commented Nov 4, 2026 •

Copy link
Copy Markdown

Problem

safe-settings can't manage CodeQL default setup today. Orgs that want default setup on (or off) across their repos have to click through each repo or use a separate mechanism, outside the config that manages everything else.

#891 is related but different: it restricts which languages a repo that already uses default setup may scan. It never turns default setup on or off.

Change

A new code_scanning plugin, configured like any other repository setting at the org, suborg or repo level:

code_scanning:
  default_setup:
    state: configured            # or not-configured
    query_suite: extended        # default | extended
    threat_model: remote         # remote | remote_and_local
    languages: [csharp, actions] # omit to let GitHub analyze the languages it detects
    # runner_type / runner_label

On each sync it reads GET /repos/{owner}/{repo}/code-scanning/default-setup and, if needed, calls PATCH on the same endpoint:

  • Only the declared fields are compared and sent. Fields left out stay as GitHub has them.
  • A languages list from a higher layer replaces the inherited one instead of being concatenated by the generic merge, so a suborg or repo can narrow or clear it.
  • languages are compared regardless of order.
  • With state: not-configured, only the state is compared and applied, since GitHub keeps no other settings once default setup is off.
  • In NOP mode the plugin reports the PATCH it would send, like the Repository plugin: GitHub's current value for each differing field goes in deletions (rendered as before), and the new value in modifications (after). A field cleared to []/null appears only in deletions, so the report shows it as removed instead of dropping it as empty. Errors become ERROR results, and a 404 is ignored as in Diffable, because the repo may not exist yet in a dry run.
  • On a real sync, a GitHub error (for example 403 when Code Security isn't enabled on a private repo) is recorded with logError for that repo. The rest of the sync continues.

Also included:

  • code_scanning in the settings, suborgs and repos schemas. The $ref points to GitHub's code-scanning-default-setup-update schema, the same way autolinks does.
  • code_scanning added to the disable_plugins names. It's not an additive plugin.
  • A README section and an entry in docs/sample-settings/settings.yml.

The dereferenced schemas only gain the code_scanning parts. Running npm run build:schema today also pulls unrelated description changes from GitHub's current OpenAPI description (ruleset parameters and the like), so I left those out to keep this PR focused.

Relation to #891: both use the code_scanning.default_setup key. The schema here only accepts the API's fields, so #891's enabled/allowed-language options would need adding to that schema if both land.

Tests

  • test/unit/lib/plugins/code_scanning.test.js: no config; enabling with the declared fields; no update when the fields match, in any language order; duplicate languages; updating a single differing field and a different language list; disabling (sends only the state); a disabled repo with extra fields declared (no update); a rejected update recorded as an error; declaredLanguages(); and in NOP mode, the reported change, clearing languages/runner_label reported as deletions that the report keeps, ERROR results (both named code_scanning), and an ignored 404.
  • test/unit/lib/settings.test.js: a code_scanning section creates the plugin, a repo file overrides the org default_setup, and languages from a higher layer replace the inherited list (narrowed, cleared with [], and set at the suborg level).
  • test/unit/lib/settings-results.test.js: the plugin's NOP output through handleResults() renders before/after for a modified field and a removed row for a cleared one.
  • test/unit/schema.test.js: all three schemas accept a valid default_setup, and reject an unknown state, an unknown language, and unknown fields.

npm run test:unit passes (1048 tests, 12 skipped as before). The new and changed files are clean under eslint and standard, apart from errors that lib/settings.js and test/unit/lib/settings.test.js already had on main-enterprise.

Not yet exercised against a live org. I haven't confirmed which GitHub App permission the default setup endpoints require, so app.yml is unchanged. Happy to add one if they need a permission safe-settings doesn't request yet.

🤖 Generated with Claude Code

Add a code_scanning plugin that reconciles a repository's CodeQL default
setup with code_scanning.default_setup in settings, suborg or repo files.

Only the declared fields (state, query_suite, threat_model, languages,
runner_type, runner_label) are compared and sent, languages are compared
regardless of order, and with state: not-configured only the state is
applied. GitHub errors such as a missing Code Security license are
recorded for the repository without stopping the sync.

Add the section to the settings, suborgs and repos schemas and to the
disable_plugins names, and document it in the README and sample settings.
Copilot AI balanced review requested due to automatic review settings November 4, 2026 17:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

CodeQL language lists are concatenated rather than overridden, blocking repository-level narrowing or clearing and risking repeated PATCHes, while an incorrect plugin identifier causes unrelated settings PRs to report pre-existing drift.

2 open issues
What changed in this PR

Adds CodeQL default setup management to safe-settings through a new code_scanning plugin, allowing configuration at organization, suborganization, and repository levels.

Changes:

  • Reconciles declared default setup fields, with dry-run reporting and repository-level error handling.
  • Adds schema support and registers code_scanning as a disableable, non-additive plugin.
  • Documents configuration and adds plugin, inheritance, and schema tests.
File Description
test/​unit/​schema.test.js Tests accepted and rejected code scanning configurations.
test/​unit/​lib/​settings.test.js Tests plugin creation and repository overrides.
test/​unit/​lib/​plugins/​code_scanning.test.js Tests reconciliation, dry runs, and errors.
schema/​suborgs.json Adds suborganization configuration support.
schema/​settings.json Adds organization configuration and disable-plugin entries.
schema/​repos.json Adds repository configuration support.
schema/​dereferenced/​suborgs.json Expands the suborganization code scanning schema.
schema/​dereferenced/​settings.json Expands organization schema and disable-plugin entries.
schema/​dereferenced/​repos.json Expands the repository code scanning schema.
README.md Documents plugin behavior and configuration.
lib/​settings.js Registers the new plugin.
lib/​plugins/​code_scanning.js Implements CodeQL default setup reconciliation.
docs/​sample-settings/​settings.yml Adds an example default setup configuration.

Review effort: Balanced

Comment thread lib/plugins/code_scanning.js Resolved
Comment thread lib/settings.js Resolved
…by section

The generic deep merge concatenates arrays, so a repo's languages were
appended to the org's instead of replacing them: a repo could not narrow
or clear the list, and the duplicates never matched GitHub's list, so the
plugin updated the repo on every sync. The highest-priority layer that
declares default_setup.languages now replaces the inherited list, and
languages are compared as sets.

NOP results used the class name as their plugin. The dry-run drift filter
lowercases it to find the config section, and 'codescanning' matched no
section, so pre-existing code scanning drift was reported on unrelated
settings PRs. They now use the section name, code_scanning.
Copilot AI balanced review requested due to automatic review settings November 4, 2026 18:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Explicitly clearing languages or runner_label is omitted from PR/check-run reports even though the sync applies it, leaving a reporting bug that needs a fix and regression test.

1 open issue
2 resolved since last review

Review effort: Balanced

Comment thread lib/plugins/code_scanning.js Resolved
Clearing languages to [] or runner_label to null was reported as a
modification holding the empty value. Settings.isEmptyChange() treats
that as no change, so the PR and check-run report left it out even
though a real sync sends the update. Cleared fields are now deletions
that hold GitHub's previous value.
Copilot AI balanced review requested due to automatic review settings November 4, 2026 19:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Changes to query_suite omit the previous value, causing dry-run PR/check-run reports to show an empty before-value and potentially misclassify modifications as additions.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Preserve before-values for modified settings in dry-run reports

lib/​plugins/​code_scanning.js:103

For a query_suite change from default to extended, this retains only the new value. The renderer in lib/settings.js obtains before-values from deletions, so the PR/check-run report shows an empty before instead of default; combining a modification with a clear can also make the modification appear as an addition. Preserve current[field] for ordinary modifications too so the dry-run report has accurate before/after values.

1 resolved since last review

Review effort: Balanced

…orts

The NOP report reads deletions as before values and modifications as
after values, as the Repository plugin reports them. Only cleared fields
kept GitHub's value in deletions, so an ordinary change such as
query_suite default -> extended rendered with an empty before value.
Every changed field now keeps GitHub's value in deletions.
Copilot AI balanced review requested due to automatic review settings November 4, 2026 19:34
@nicomazzarello

Copy link
Copy Markdown
Author

Fixed the before-values issue from the latest Copilot review in dcbd5bd. Every changed field now keeps GitHub's value in deletions, which the NOP report reads as the before value, the same way the Repository plugin reports its changes. A new test in settings-results.test.js runs the plugin's NOP output through handleResults() and checks the rendered report: query_suite shows before default and after extended, and a cleared languages shows as removed with its previous values. It fails without the fix.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Organization-wide CodeQL enable/disable operations lack live validation, leaving endpoint permissions and asynchronous setup transitions unverified before rollout.

No open issues 🎉

Review effort: Balanced

This branch has not been deployed

No deployments
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