Skip to content

feat(codeql): add CodeQL enablement check as a dedicated extension#876

Draft
akiioto wants to merge 27 commits into
open-component-model:masterfrom
akiioto:feat/sast-codeql-check
Draft

feat(codeql): add CodeQL enablement check as a dedicated extension#876
akiioto wants to merge 27 commits into
open-component-model:masterfrom
akiioto:feat/sast-codeql-check

Conversation

@akiioto

@akiioto akiioto commented Jun 30, 2026

Copy link
Copy Markdown

Summary

  • Adds a new codeql extension that checks whether GitHub Advanced Security CodeQL is enabled for each source artefact referenced by an OCM component
  • Implements use case 1 from the CodeQL extension design (enabled/disabled check only — findings processing is out of scope)
  • Follows the same pattern as the existing sast extension

Changes

File Change
src/codeql.py New extension — queries GitHub code-scanning analyses API (tool_name=CodeQL), emits CodeqlFinding(NOT_ENABLED) when CodeQL is absent
src/odg/model.py Add Datatype.CODEQL_FINDING, Datasource.CODEQL, CodeqlStatus, CodeqlFinding, RescoreCodeqlFinding
src/odg/extensions_cfg.py Add Services.CODEQL and CodeqlConfig (BacklogItemMixins, same as SASTConfig)
src/odg/extensions_cfg.yaml Add codeql: enabled: False default
src/odg/findings_cfg.yaml Add finding/codeql block with BLOCKER categorisation; external (github.com) components auto-rescored to NONE
src/odg/findings.py Add CodeqlFindingSelector for categorise_finding() matching on codeql_status
src/artefact_enumerator.py Register CODEQL service so backlog items are created for source artefacts

Design decisions

  • Separate extension, not a SAST sub-type — CodeQL is a distinct scanner (GitHub Advanced Security feature), not a missing-linter variant
  • Reuses ghas.github_api_request() — same GitHub API authentication mechanism as the existing GHAS extension
  • repo_url stored in finding — allows direct navigation to the repository from the dashboard/issue view
  • github.com components exempt — external open-source components are not subject to SAP product standards

Prerequisites for full E2E test

  • GitHub App (app_id: 2019 on github.tools.sap) needs security_events: read permission added
  • Deploy to Dev landscape (delivery-service image must include this branch)

Test plan

  • Verify codeql.py loads and starts polling backlog items
  • Verify artefact-enumerator creates codeql backlog items for source artefacts
  • Verify GitHub API call succeeds once security_events: read permission is granted to GitHub App
  • Verify CodeqlFinding(NOT_ENABLED) is stored in DB for repos without CodeQL
  • Verify no finding is stored for repos with CodeQL enabled
  • Verify github.com components are auto-rescored to NONE

akiioto added 4 commits June 30, 2026 18:48
Introduces a new CodeQL extension that checks whether GitHub Advanced Security
CodeQL is enabled for each source artefact referenced by an OCM component.

Changes:
- odg/model.py: add Datatype.CODEQL_FINDING, Datasource.CODEQL, CodeqlStatus,
  CodeqlFinding, RescoreCodeqlFinding
- odg/extensions_cfg.py: add Services.CODEQL and CodeqlConfig (BacklogItemMixins)
- odg/extensions_cfg.yaml: add codeql default (enabled: False)
- odg/findings_cfg.yaml: add finding/codeql block with BLOCKER categorisation;
  external (github.com) components are auto-rescored to NONE
- codeql.py: new extension — queries GitHub code-scanning analyses API
  (tool_name=CodeQL), emits CodeqlFinding(NOT_ENABLED) when CodeQL is absent
Per discussion with Philipp Heil: findings should contain enough information
to be meaningful in the dashboard and issue view. repo_url points directly
to the GitHub repository where CodeQL needs to be enabled.
Required for categorise_finding() to match on codeql_status values
in the findings_cfg.yaml selector configuration.
Adds the codeql backlog item creation block analogous to sast, so the
artefact-enumerator creates codeql backlog items for source artefacts
when the codeql extension is enabled.
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67edc909-43cd-488b-8fec-6482eed85a74

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

akiioto added 3 commits June 30, 2026 19:09
- settings_url: direct link to repo/settings/security_analysis so
  engineers can enable CodeQL in one click from the dashboard/issue
- language: primary repo language from GitHub API; enables auto-rescoring
  for repos in languages CodeQL does not support
- fix: key now includes repo_url to avoid collisions across artefacts
- refactor: fetch_repo_info() combines /repos and /code-scanning/analyses
  into one function so both calls share the same auth token lookup
languages: [Go, Python, Java] in extensions_cfg limits CodeQL checks
to repos whose primary language matches. Empty list (default) checks all.
Instead of a single enabled/disabled check per repo, the extension now:
- fetches active CodeQL languages from code-scanning/analyses (environment.language)
- emits a separate CodeqlFinding per language configured in codeql_config.languages
  that is NOT actively scanned
- key includes language: not-enabled|{repo_url}|{language}

When languages is empty (default), falls back to single finding if CodeQL
is not enabled at all for the repo.

@8R0WNI3 8R0WNI3 left a comment

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 already looks very promising, thank you for your contribution! :-)

Comment thread src/odg/extensions_cfg.py Outdated
Comment thread src/odg/findings.py
Comment thread src/odg/findings_cfg.yaml Outdated
Comment thread src/codeql.py Outdated
Comment thread src/codeql.py Outdated
Comment thread src/codeql.py Outdated
Comment thread src/odg/model.py Outdated
akiioto added 9 commits July 1, 2026 14:17
Addresses review comment: Services, Datatype, Datasource.datasource(),
and ExtensionsConfiguration fields are now in alphabetical order.
- CodeqlFinding.repo_url and language are now str (not optional) —
  findings are never created without both values
- Remove settings_url from CodeqlFinding — it is derivable as
  repo_url + '/settings/security_analysis' and does not need to be stored
- Remove /repos API call (was only needed for settings_url)
- Add /languages API call to skip findings for languages not present in repo
- languages config is now required — empty list logs warning and skips
- RescoreCodeqlFinding key updated to match CodeqlFinding key
Each finding type has a corresponding _validate_* method in findings.py
that checks categorisation selectors are correctly typed. Added
_validate_codeql() using CodeqlFindingSelector, registered in _validate()
match block alphabetically.
- Remove hostname fallback to 'github.com' — return None with warning instead
- Remove access.branch (field does not exist on GithubAccess) — use access.ref
- Return early with warning when ref is None
- Normalize ref to refs/heads/{ref} if not already prefixed with refs/
process_backlog_items() already creates and injects secret_factory into
the callback — no need to create it manually and pass via functools.partial.
Also removes the now-unused ctx_util import.
Extensions should not depend on each other. Moved github_api_request,
github_api_request_paginated, find_token_for_repo_url and
find_token_for_api_url from ghas.py to github_util.py.

codeql.py now imports from github_util directly. ghas.py retains its
public API via thin wrappers delegating to github_util.
The intent is that all repos get a finding if CodeQL is not enabled —
there are no automatic exemptions. Users can manually rescore if needed.
No CodeqlRescoringRule implementation exists anyway.
OCM GithubAccess.repoUrl may omit the https:// scheme. Add it when
missing before parsing hostname/path.
…api_url

github.com API URLs have format /repos/{org}/{repo}/...  (org at index 1)
while GitHub Enterprise URLs have /api/v3/repos/{org}/{repo}/... (org at index 3).
Previously only Enterprise format was handled, causing token lookup to fail
for public github.com repositories.
Comment thread src/ghas.py Outdated

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.

Due to the move of functions to github_util.py, I think there are now quite some unused imports:

import requests
import requests.adapters
import urllib3.util.retry

import secret_mgmt.github

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in efedbc4. Removed requests, requests.adapters, urllib3.util.retry and secret_mgmt.github from ghas.py — none of them are used directly anymore since the HTTP helpers were moved to github_util.

Comment thread src/ghas.py Outdated
@@ -52,37 +53,20 @@ def find_token_for_repo_url(

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.

We don't have an established public Python API of this ghas module, so you might just drop these extra wrappers.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 3d21b35. Dropped all four wrapper functions (find_token_for_repo_url, find_token_for_api_url, github_api_request, github_api_request_paginated) from ghas.py and updated the four internal call sites to reference github_util directly. Also removed the now-unused functools import.

Comment thread src/odg/findings_cfg.yaml Outdated
value: 0
rescoring: manual
allowed_processing_time: ~
- id: BLOCKER

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.

nit: These severities originated from vulnerabilities, but you are free to choose a more expressive name for CodeQL here, e.g. not-enabled or something like that :-)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 5572baf. Replaced BLOCKER/NONE with not-enabled/accepted — names that describe the CodeQL state rather than a generic vulnerability severity level.

Comment thread src/codeql.py
)
return

if not codeql_config.languages:

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 does not match the behaviour described in the docstring:

If empty or omitted, all repositories are checked regardless of language.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 9b0d2d8. Updated the languages docstring in CodeqlConfig to reflect that an empty list causes all artefacts to be skipped, not checked unconditionally.

Comment thread src/codeql.py Outdated
default branch (from code-scanning/analyses environment field).
"""
access = source_node.source.access
if not isinstance(access, ocm.GithubAccess):

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.

To be consistent and already exit more early, you should rather move this check in the is_supported function and define a list of supported_access_types.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 8ed7d79. Added supported_access_types = (ocm.GithubAccess,) and an access parameter to CodeqlConfig.is_supported. The isinstance guard is now handled there alongside the existing artefact_kind check and removed from fetch_repo_info.

Comment thread src/codeql.py Outdated

org, repo, api_base = coords

if not access.ref:

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.

Since the ref is not a required property in the spec, I would suggest to rather fallback to the default branch in that case instead of not reporting any findings. Wdyt?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in ccf557a. When access.ref is None, the extension now fetches default_branch from GET /repos/{org}/odg-core (a call we already make for other data) and uses that as the ref. Only falls back to skipping if the default branch cannot be determined.

Comment thread src/codeql.py Outdated
continue
env = analysis.get('environment', {})
if isinstance(env, str):
import json as _json

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.

Since this is (according to the response schema) the expected case, I don't see a benefit of this late import and would rather suggest to move it as a module level import instead. Also, I think there is no conflict when defining no alias for it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 3daea00. Moved import json to module level and removed the _json alias.

Comment thread src/codeql.py
try:
env = _json.loads(env)
except Exception:
continue

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.

I think we should at least log the exception in this case, wdyt?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 5673ae9. Added logger.warning in the except block with the raw env value and the exception message so malformed analysis environment payloads are visible in the logs.

Comment thread src/codeql.py
Comment thread src/codeql.py Outdated
),
)

delivery_service_client.update_metadata(data=all_metadata)

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.

What is not yet covered by the extension is the case when a finding has been created because of missing CodeQL scans, but at a later point in time the scans were enabled. In that case, we would either have to delete the finding from the database again (not ideal because the audit trail is not given) or create an automatic rescoring for it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in f9fab7d. On each scan, the extension now queries existing CODEQL_FINDING entries for the artefact from the DB. Any finding whose key is absent from the newly generated findings (i.e. CodeQL is now enabled for that language) gets an automatic rescoring to accepted with a comment. Pattern follows the GHAS extension stale-finding handling.

@8R0WNI3 8R0WNI3 left a comment

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.

Please also add the respective documentation (for the extension and the new finding type) to the charts/bootstrapping/values.documentation.yaml file :-)

Also, what is still missing as well is the Helm chart for the new extension, see documentation.

requests, requests.adapters, urllib3.util.retry and secret_mgmt.github
are no longer used directly in ghas.py since the HTTP helper functions
were moved to github_util. Added tests to verify the cleanup.
@akiioto akiioto force-pushed the feat/sast-codeql-check branch from 0852afc to efedbc4 Compare July 7, 2026 08:00
akiioto added 7 commits July 7, 2026 10:10
Since ghas.py has no established public Python API, there is no need
to keep wrapper functions that solely delegate to github_util. Removed
find_token_for_repo_url, find_token_for_api_url, github_api_request
and github_api_request_paginated wrappers; internal call sites now
reference github_util directly. Also removed the now-unused functools
import.
Replace generic BLOCKER/NONE with CodeQL-specific ids:
- not-enabled: CodeQL is not scanning for the configured language
- accepted: finding has been manually acknowledged

Addresses review feedback that BLOCKER/NONE originate from vulnerability
findings and are not expressive for a CodeQL enablement check.
…iour

The docstring incorrectly stated that an empty languages list would check
all repositories. In practice, an empty list causes the extension to skip
all artefacts with a warning. Updated the docstring to reflect this.
Added supported_access_types = (ocm.GithubAccess,) to CodeqlConfig.is_supported
and an access parameter so the check is done consistently alongside the
artefact_kind check. Removed the isinstance guard from fetch_repo_info.
…g list

The generator returned by github_api_request_paginated is only consumed
once, so wrapping it in list() is unnecessary. Iterating directly avoids
loading all pages into memory before processing.
Per the OCM spec, ref is not a required field on GithubAccess. When
ref is absent, the extension now fetches the repository default_branch
from the GitHub API and uses that instead of skipping the artefact.
Only skips when the default branch cannot be determined.
The import json statement was inside fetch_repo_info, causing it to be
re-evaluated on every call. Moved to module level and removed the _json
alias.
akiioto added 3 commits July 7, 2026 10:57
Silent except made it impossible to diagnose malformed analysis
environment payloads. Now logs a warning with the raw value and the
exception message before continuing to the next analysis.
When a finding exists in the DB for a language that is now actively
scanned by CodeQL, the extension creates an automatic rescoring with
severity 'accepted' and a comment explaining why. This ensures stale
findings are surfaced rather than silently remaining open.

Pattern follows the existing GHAS extension stale-finding handling.
- charts/extensions/charts/codeql/: new Deployment-based Helm chart
  following the SAST pattern (scaled by backlog-controller, mounts
  github, github-app, extensions-cfg, findings-cfg, ocm-repo-mappings)
- charts/extensions/Chart.yaml: register codeql as subchart
- charts/bootstrapping/values.documentation.yaml: document
  extensions_cfg.codeql (enabled, delivery_service_url, interval,
  languages, on_unsupported) and add finding/codeql to finding type
  option lists
@akiioto akiioto force-pushed the feat/sast-codeql-check branch from 0d0662d to 129305b Compare July 8, 2026 06:31
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