Skip to content

Add github.code_scanning for CodeQL default setup - #122

Open
ppkarwasz wants to merge 2 commits into
apache:mainfrom
ppkarwasz:code-scanning
Open

Add github.code_scanning for CodeQL default setup#122
ppkarwasz wants to merge 2 commits into
apache:mainfrom
ppkarwasz:code-scanning

Conversation

@ppkarwasz

@ppkarwasz ppkarwasz commented Aug 13, 2026

Copy link
Copy Markdown
Member

Allows projects to self-service enable GitHub code scanning (CodeQL default setup) from .asf.yaml, as requested in #94 (code scanning part only; secret scanning can be a follow-up):

github:
  code_scanning: true

or, for more control:

github:
  code_scanning:
    query_suite: extended       # "default" or "extended"
    threat_model: remote        # "remote" or "remote_and_local"
    languages:                  # restricts analysis; auto-detected when omitted
      - java-kotlin

Design notes:

  • The section's presence is the toggle (no enabled key), matching how rulesets works; a plain boolean is accepted for the simple case.
  • The directive GETs /repos/{org}/{repo}/code-scanning/default-setup first and only PATCHes when the desired settings differ, so unrelated .asf.yaml edits do not re-trigger analysis runs, and both 200 and 202 (async validation run) are treated as success.
  • Removing (or commenting out) the section disables the setup only if it was previously managed via .asf.yaml (same previous_yaml semantics as Clear branch protection rules when protected_branches is removed #121); a setup enabled manually through the GitHub UI is left untouched.
  • languages values are deliberately not validated client-side, since GitHub extends the CodeQL language set over time; invalid values surface as a GitHub 422 relayed to the committer by email, as are the 403/409 cases (archived repo, setup change in progress, Actions disabled).
  • Tests follow the github_copilot_code_review.py fake-requester pattern; README documents the new section.

Fixes #94

🤖 Generated with Claude Code

Allow projects to self-service enable GitHub code scanning (CodeQL
default setup) from .asf.yaml, as requested in apache#94:

    github:
      code_scanning: true

or, for more control:

    github:
      code_scanning:
        query_suite: extended
        threat_model: remote
        languages:
          - java-kotlin

The directive reads the current default setup configuration first and
only issues a PATCH when the desired settings differ, avoiding
redundant analysis runs. Removing the section disables the setup only
if it was previously managed via .asf.yaml, following the semantics
established in apache#121. Language identifiers are deliberately not
validated client-side, as GitHub extends the CodeQL language set over
time.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>
if enabled:
desired: dict[str, Any] = {
"state": "configured",
"query_suite": settings.get("query_suite", "default"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

query_suite is unconditionally included in the PATCH payload, unlike threat_model and languages below, which are only sent when explicitly configured.

Consider a repo where an admin already enabled CodeQL default setup through the UI with the extended suite, and the project now adds code_scanning: true to bring it under .asf.yaml management. _matches_current compares query_suite: extended (current) against default (desired), doesn't match, and the directive PATCHes the suite down to default — silently downgrading the analysis and re-triggering a full scan. The same happens with the map form whenever query_suite is omitted, since strictyaml injects the default default.

Suggest treating it like the other two — only send it when the user actually set it — or documenting in the README that enabling via .asf.yaml resets the suite.

"state": "configured",
"query_suite": settings.get("query_suite", "default"),
}
# Fields not specified in .asf.yaml are left for GitHub to manage (auto-detection).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Because absent fields are omitted from the payload and skipped by _matches_current, settings are one-way sticky: they can be set and changed, but never reverted.

The README says of languages: "if omitted, GitHub auto-detects eligible languages" (README.md:837). But a project that configures languages: [python] and later deletes the key expecting auto-detection to resume gets no PATCH at all — analysis stays pinned to python indefinitely, with no error. Same for dropping threat_model, and for downgrading from the map form back to code_scanning: true.

Either send an explicit reset when a previously-managed field disappears, or adjust the README wording to say the values stick until changed.

return
# Section removed after having been managed by .asf.yaml: disable the setup.
enabled = False
elif isinstance(scanning, bool):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

code_scanning: false will disable a default setup that .asf.yaml never managed. The sibling directives guard exactly this case — pr_creation_cap.py:81 and copilot_code_review.py:78 both return early on not enabled and not was_previously_configured.

The PR description says UI-enabled setups are left untouched, but that only holds for the section-removal path above; it doesn't hold for an explicit false. A project copying an .asf.yaml template containing code_scanning: false into a repo whose scanning an admin turned on in the UI would have it switched off on the next push.

Also worth noting: code_scanning: false isn't documented in the README, so the behaviour is invisible to users either way.


def update_default_setup(self: ASFGitHubFeature, payload: dict[str, Any]) -> None:
status, _headers, body = self.ghrepo._requester.requestJson("PATCH", _default_setup_endpoint(self), input=payload)
if status in (200, 202):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

status in (200, 202) rejects any other 2xx; pr_creation_cap.py:45 uses 200 <= status < 300 for the equivalent call.

If GitHub answers a no-op PATCH with 204 (or adds another 2xx code later), the update actually succeeded but the committer gets an "Unexpected response while updating code scanning default setup: HTTP 204" error email — and since the exception aborts the github feature before the settings cache is written, the same push path will error again next time rather than settling.

status, _headers, body = self.ghrepo._requester.requestJson("GET", _default_setup_endpoint(self))
repo = f"{self.repository.org_id}/{self.repository.name}"
if status == 200:
payload = json.loads(body)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This json.loads is unguarded, while every other body parse in the module goes through _parse_detail's try/except.

A 200 with a non-JSON body (proxy interposing an HTML error page, an empty body during a GitHub incident) surfaces a raw json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) to the committer instead of one of the deliberately helpful messages the rest of this module produces.

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.

Allow enabling secret and code scanning

2 participants