From 8830f33772dfe74ac381c1e6c400ab82e74e81dc Mon Sep 17 00:00:00 2001 From: David Wiggs Date: Tue, 4 Nov 2025 09:46:09 -0600 Subject: [PATCH 1/4] Add support for managing languages supported by Default Setup --- README.md | 63 ++++++- docs/sample-settings/settings.yml | 21 ++- index.js | 18 ++ lib/plugins/code_scanning.js | 295 ++++++++++++++++++++++++++++++ lib/settings.js | 3 +- schema/settings.json | 58 ++++++ 6 files changed, 454 insertions(+), 4 deletions(-) create mode 100644 lib/plugins/code_scanning.js diff --git a/README.md b/README.md index 07a626748..ce404be50 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,8 @@ The App listens to the following webhook events: - __custom_property_values__: If new repository properties are set for a repository, `safe-settings` will run to so that if a sub-org config is defined by that property, it will be applied for the repo +- **workflow_run.completed**: When a Code Scanning Default Setup workflow completes (identified by the path `dynamic/github-code-scanning/codeql`), `safe-settings` will validate that only approved languages are being scanned and enforce compliance by updating the configuration if unauthorized languages are detected. + ### Use `safe-settings` to rename repos If you rename a `` that corresponds to a repo, safe-settings will rename the repo to the new name. This behavior will take effect whether the env variable `BLOCK_REPO_RENAME_BY_HUMAN` is set or not. @@ -470,6 +472,7 @@ The following can be configured: - `Repository name validation` using regex pattern - `Rulesets` - `Environments` - wait timer, required reviewers, prevent self review, protected branches deployment branch policy, custom deployment branch policy, variables, deployment protection rules +- `Code Scanning Default Setup` - enforce allowed/blocked languages for code scanning See [`docs/sample-settings/settings.yml`](docs/sample-settings/settings.yml) for a sample settings file. @@ -491,8 +494,64 @@ See [`docs/sample-settings/settings.yml`](docs/sample-settings/settings.yml) for > - name: Other-team > permission: push > include: -> - '*-config' -> ``` + - '*-config' + ``` + +### Code Scanning Default Setup + +`Safe-settings` can enforce policies for GitHub Code Scanning Default Setup, ensuring that only approved programming languages are scanned across your organization. + +#### How it works + +1. **Reactive Enforcement**: When a Code Scanning Default Setup workflow completes, the `workflow_run.completed` webhook is triggered. Safe-settings identifies these workflows by their path `dynamic/github-code-scanning/codeql` and validates the configuration. + +2. **Proactive Enforcement**: During scheduled syncs (if `CRON` is configured), safe-settings checks all repositories to ensure compliance and prevent configuration drift. + +3. **Language Validation**: The app fetches the current Code Scanning Default Setup configuration via the GitHub API and validates that only allowed languages are being scanned. + +4. **Automatic Remediation**: If unauthorized languages are detected, safe-settings automatically updates the configuration to remove them, keeping only the approved languages. + +#### Configuration + +Code scanning policies are defined in your `settings.yml` file and are **enforced at the org level only** - they cannot be overridden by suborg or repo-level configurations. + +```yaml +code_scanning: + default_setup: + # Enable or disable enforcement (default: true) + enabled: true + languages: + # Define allowed languages (if specified, only these are permitted) + allowed: + - javascript-typescript + - python + - java-kotlin + # Optionally define explicitly blocked languages + blocked: + - ruby + - go +``` + +**Supported Languages:** +- `c-cpp` +- `csharp` +- `go` +- `java-kotlin` +- `javascript-typescript` +- `python` +- `ruby` +- `swift` + +#### Behavior + +- If `allowed` is specified, only languages in this list will be permitted +- If `blocked` is specified, these languages will be explicitly denied +- Both can be used together for fine-grained control +- Unauthorized languages are automatically removed from the repository's configuration +- Changes are logged and reported in check runs during PR validation + +> [!IMPORTANT] +> This feature only manages **Default Setup** configurations created through GitHub's UI. It does not affect custom Code Scanning workflows (Advanced Setup) that you create manually. ### Additional values diff --git a/docs/sample-settings/settings.yml b/docs/sample-settings/settings.yml index 7e19d3354..50d8a3fa8 100644 --- a/docs/sample-settings/settings.yml +++ b/docs/sample-settings/settings.yml @@ -24,7 +24,26 @@ repository: enableVulnerabilityAlerts: true enableAutomatedSecurityFixes: true - # Either `true` to make the repository private, or `false` to make it public. +# Code Scanning Default Setup +# Enforce which languages are allowed to be scanned by GitHub Code Scanning Default Setup +# This is enforced at the org level and cannot be overridden by suborg or repo configs +code_scanning: + default_setup: + # Whether to enforce code scanning default setup language restrictions + enabled: true + languages: + # List of languages that are allowed to be scanned + # Valid values: c-cpp, csharp, go, java-kotlin, javascript-typescript, python, ruby, swift + allowed: + - javascript-typescript + - python + - java-kotlin + # Optional: List of languages that are explicitly blocked + # blocked: + # - ruby + # - swift + +# Repository settings # If this value is changed and if org members cannot change the visibility of repos # it would result in an error when updating a repo private: true diff --git a/index.js b/index.js index ca2f6dc61..90ac310d4 100644 --- a/index.js +++ b/index.js @@ -637,6 +637,24 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) => return syncSettings(false, context) }) + robot.on('workflow_run.completed', async context => { + const { payload } = context + const { workflow_run, sender } = payload + + robot.log.debug(`Workflow run completed: ${workflow_run.name} in ${payload.repository.name}`) + + // Check if this is a Code Scanning Default Setup workflow + // These workflows have a specific path pattern + if (workflow_run.path === 'dynamic/github-code-scanning/codeql') { + robot.log.debug(`Code Scanning Default Setup workflow detected for ${payload.repository.name}`) + + // Trigger sync to validate code scanning configuration + return syncSettings(false, context) + } + + robot.log.debug('Not a Code Scanning Default Setup workflow, skipping...') + }) + if (process.env.CRON) { /* # ┌────────────── second (optional) diff --git a/lib/plugins/code_scanning.js b/lib/plugins/code_scanning.js new file mode 100644 index 000000000..425db2853 --- /dev/null +++ b/lib/plugins/code_scanning.js @@ -0,0 +1,295 @@ +const ErrorStash = require('./errorStash') +const NopCommand = require('../nopcommand') +const MergeDeep = require('../mergeDeep') + +const ignorableFields = [] + +/** + * Code Scanning plugin to manage GitHub Code Scanning Default Setup configuration + * This plugin ensures that only approved languages are enabled for code scanning + * and enforces org-level policies across all repositories. + */ +module.exports = class CodeScanning extends ErrorStash { + constructor (nop, github, repo, settings, log, errors) { + super(errors) + this.github = github + this.repo = repo + this.settings = settings + this.log = log + this.nop = nop + } + + /** + * Main sync method that validates and enforces code scanning configuration + */ + sync () { + const resArray = [] + + // Skip if code_scanning is not configured + if (!this.settings || !this.settings.default_setup) { + this.log.debug(`No code_scanning configuration found for ${this.repo.repo}`) + return Promise.resolve([]) + } + + const { default_setup: defaultSetup } = this.settings + + // If default_setup is disabled in config, we skip management + if (defaultSetup.enabled === false) { + this.log.debug(`Code scanning default setup is disabled in config for ${this.repo.repo}`) + return Promise.resolve([]) + } + + return this.getCurrentDefaultSetup() + .then(currentConfig => { + // If code scanning is not configured in GitHub, nothing to validate + if (!currentConfig) { + this.log.debug(`No code scanning default setup found in GitHub for ${this.repo.repo}`) + if (this.nop) { + resArray.push( + new NopCommand( + this.constructor.name, + this.repo, + null, + 'No code scanning default setup configured in repository', + 'INFO' + ) + ) + return Promise.resolve(resArray) + } + return Promise.resolve([]) + } + + // Validate languages + const validation = this.validateLanguages(currentConfig, defaultSetup) + + if (!validation.isValid) { + this.log.warn( + `Code scanning language validation failed for ${this.repo.repo}: ${validation.message}` + ) + + if (this.nop) { + resArray.push( + new NopCommand( + this.constructor.name, + this.repo, + null, + { + msg: 'Code scanning language validation failed', + current_languages: validation.currentLanguages, + allowed_languages: validation.allowedLanguages, + unauthorized_languages: validation.unauthorizedLanguages, + message: validation.message + }, + 'ERROR' + ) + ) + return Promise.resolve(resArray) + } + + // Enforce by updating to only allowed languages + return this.enforceAllowedLanguages(currentConfig, validation, resArray) + } else { + this.log.debug( + `Code scanning configuration is valid for ${this.repo.repo}: ${validation.message}` + ) + if (this.nop) { + resArray.push( + new NopCommand( + this.constructor.name, + this.repo, + null, + { + msg: 'Code scanning configuration is valid', + current_languages: validation.currentLanguages, + allowed_languages: validation.allowedLanguages, + message: validation.message + }, + 'INFO' + ) + ) + return Promise.resolve(resArray) + } + return Promise.resolve([]) + } + }) + .catch(e => { + this.logError(`Error syncing code scanning for ${this.repo.repo}: ${e.message}`) + if (this.nop) { + resArray.push( + new NopCommand( + this.constructor.name, + this.repo, + null, + `Error: ${e.message}`, + 'ERROR' + ) + ) + return Promise.resolve(resArray) + } + return Promise.resolve([]) + }) + } + + /** + * Fetch the current Code Scanning Default Setup configuration from GitHub + * @returns {Promise} The current configuration or null if not configured + */ + async getCurrentDefaultSetup () { + try { + // GitHub API endpoint to get code scanning default setup + // GET /repos/{owner}/{repo}/code-scanning/default-setup + const response = await this.github.request( + 'GET /repos/{owner}/{repo}/code-scanning/default-setup', + { + owner: this.repo.owner, + repo: this.repo.repo + } + ) + + this.log.debug( + `Retrieved code scanning default setup for ${this.repo.repo}: ${JSON.stringify(response.data)}` + ) + + return response.data + } catch (e) { + if (e.status === 404) { + // Code scanning default setup is not configured + return null + } + throw e + } + } + + /** + * Validate that the current languages match the allowed list + * @param {Object} currentConfig - Current GitHub configuration + * @param {Object} desiredConfig - Desired configuration from safe-settings + * @returns {Object} Validation result with isValid flag and details + */ + validateLanguages (currentConfig, desiredConfig) { + const currentLanguages = currentConfig.languages || [] + const allowedLanguages = desiredConfig.languages?.allowed || [] + const blockedLanguages = desiredConfig.languages?.blocked || [] + + // Normalize to lowercase for comparison + const normalizedCurrent = currentLanguages.map(lang => lang.toLowerCase()) + const normalizedAllowed = allowedLanguages.map(lang => lang.toLowerCase()) + const normalizedBlocked = blockedLanguages.map(lang => lang.toLowerCase()) + + // Find unauthorized languages (either not in allowed list or in blocked list) + const unauthorizedLanguages = [] + + for (const lang of normalizedCurrent) { + // If allowed list is specified and language is not in it + if (normalizedAllowed.length > 0 && !normalizedAllowed.includes(lang)) { + unauthorizedLanguages.push(lang) + } + // If language is in blocked list + if (normalizedBlocked.includes(lang)) { + unauthorizedLanguages.push(lang) + } + } + + // Remove duplicates + const uniqueUnauthorized = [...new Set(unauthorizedLanguages)] + + const isValid = uniqueUnauthorized.length === 0 + + let message = '' + if (isValid) { + message = 'All languages are authorized' + } else { + message = `Unauthorized languages detected: ${uniqueUnauthorized.join(', ')}` + if (normalizedAllowed.length > 0) { + message += `. Allowed: ${normalizedAllowed.join(', ')}` + } + if (normalizedBlocked.length > 0) { + message += `. Blocked: ${normalizedBlocked.join(', ')}` + } + } + + return { + isValid, + currentLanguages: normalizedCurrent, + allowedLanguages: normalizedAllowed, + blockedLanguages: normalizedBlocked, + unauthorizedLanguages: uniqueUnauthorized, + message + } + } + + /** + * Enforce allowed languages by updating the default setup configuration + * @param {Object} currentConfig - Current GitHub configuration + * @param {Object} validation - Validation result + * @param {Array} resArray - Array to collect NopCommand results + * @returns {Promise} Resolution of the enforcement action + */ + async enforceAllowedLanguages (currentConfig, validation, resArray) { + // Calculate the languages that should be enabled + // (current languages minus unauthorized ones) + const languagesToKeep = validation.currentLanguages.filter( + lang => !validation.unauthorizedLanguages.includes(lang) + ) + + // If allowed list is specified, only keep languages that are in both current and allowed + const allowedLanguages = validation.allowedLanguages + let finalLanguages = languagesToKeep + + if (allowedLanguages.length > 0) { + finalLanguages = languagesToKeep.filter(lang => allowedLanguages.includes(lang)) + } + + this.log.debug( + `Enforcing code scanning languages for ${this.repo.repo}: ${finalLanguages.join(', ')}` + ) + + const updateParams = { + owner: this.repo.owner, + repo: this.repo.repo, + state: currentConfig.state, + query_suite: currentConfig.query_suite, + languages: finalLanguages + } + + if (this.nop) { + resArray.push( + new NopCommand( + this.constructor.name, + this.repo, + this.github.request.endpoint( + 'PATCH /repos/{owner}/{repo}/code-scanning/default-setup', + updateParams + ), + { + msg: 'Update code scanning default setup to remove unauthorized languages', + modifications: { + languages: { + before: validation.currentLanguages, + after: finalLanguages, + removed: validation.unauthorizedLanguages + } + } + } + ) + ) + return Promise.resolve(resArray) + } + + // Perform the actual update + return this.github + .request('PATCH /repos/{owner}/{repo}/code-scanning/default-setup', updateParams) + .then(response => { + this.log.info( + `Successfully updated code scanning default setup for ${this.repo.repo}` + ) + return response + }) + .catch(e => { + this.logError( + `Failed to update code scanning default setup for ${this.repo.repo}: ${e.message}` + ) + throw e + }) + } +} diff --git a/lib/settings.js b/lib/settings.js index 8d9e07b2b..28b142c46 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -1004,7 +1004,8 @@ Settings.PLUGINS = { rulesets: require('./plugins/rulesets'), environments: require('./plugins/environments'), custom_properties: require('./plugins/custom_properties.js'), - variables: require('./plugins/variables') + variables: require('./plugins/variables'), + code_scanning: require('./plugins/code_scanning') } module.exports = Settings diff --git a/schema/settings.json b/schema/settings.json index 4d390b38f..c0893e5a4 100644 --- a/schema/settings.json +++ b/schema/settings.json @@ -191,6 +191,64 @@ "items": { "$ref": "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2022-11-28.json#/paths/~1orgs~1{org}~1rulesets/post/requestBody/content/application~1json/schema" } + }, + "code_scanning": { + "description": "Code Scanning Default Setup configuration to enforce allowed languages", + "type": "object", + "properties": { + "default_setup": { + "description": "Configuration for Code Scanning Default Setup", + "type": "object", + "properties": { + "enabled": { + "description": "Whether to enforce code scanning default setup configuration. Set to false to skip management.", + "type": "boolean", + "default": true + }, + "languages": { + "description": "Language restrictions for code scanning", + "type": "object", + "properties": { + "allowed": { + "description": "List of languages that are allowed to be scanned. If specified, only these languages will be permitted.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "c-cpp", + "csharp", + "go", + "java-kotlin", + "javascript-typescript", + "python", + "ruby", + "swift" + ] + } + }, + "blocked": { + "description": "List of languages that are explicitly blocked from being scanned.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "c-cpp", + "csharp", + "go", + "java-kotlin", + "javascript-typescript", + "python", + "ruby", + "swift" + ] + } + } + } + } + }, + "required": ["enabled"] + } + } } } } From f6393ccb231aa18a9bf92c9c2b197940e0a4afa5 Mon Sep 17 00:00:00 2001 From: David Wiggs <107562400+david-wiggs@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:54:28 -0600 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- index.js | 4 ++-- lib/plugins/code_scanning.js | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 90ac310d4..8cecfa8bd 100644 --- a/index.js +++ b/index.js @@ -639,7 +639,7 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) => robot.on('workflow_run.completed', async context => { const { payload } = context - const { workflow_run, sender } = payload + const { workflow_run } = payload robot.log.debug(`Workflow run completed: ${workflow_run.name} in ${payload.repository.name}`) @@ -647,7 +647,7 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) => // These workflows have a specific path pattern if (workflow_run.path === 'dynamic/github-code-scanning/codeql') { robot.log.debug(`Code Scanning Default Setup workflow detected for ${payload.repository.name}`) - + // Trigger sync to validate code scanning configuration return syncSettings(false, context) } diff --git a/lib/plugins/code_scanning.js b/lib/plugins/code_scanning.js index 425db2853..290fd5b8c 100644 --- a/lib/plugins/code_scanning.js +++ b/lib/plugins/code_scanning.js @@ -1,8 +1,6 @@ const ErrorStash = require('./errorStash') const NopCommand = require('../nopcommand') -const MergeDeep = require('../mergeDeep') -const ignorableFields = [] /** * Code Scanning plugin to manage GitHub Code Scanning Default Setup configuration @@ -184,8 +182,8 @@ module.exports = class CodeScanning extends ErrorStash { if (normalizedAllowed.length > 0 && !normalizedAllowed.includes(lang)) { unauthorizedLanguages.push(lang) } - // If language is in blocked list - if (normalizedBlocked.includes(lang)) { + // If language is in blocked list (but not already added above) + else if (normalizedBlocked.includes(lang)) { unauthorizedLanguages.push(lang) } } From 4a58e36e2150f2650adb7f2a6eca73873a4361a0 Mon Sep 17 00:00:00 2001 From: David Wiggs Date: Mon, 10 Nov 2025 09:37:49 -0600 Subject: [PATCH 3/4] Ensure javascript-typescript is handled correctly --- README.md | 3 ++- docs/sample-settings/settings.yml | 3 +++ index.js | 15 +++++++++---- lib/configManager.js | 6 ++++++ lib/plugins/code_scanning.js | 35 ++++++++++++++++++++++++++++--- 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ce404be50..53dd200fa 100644 --- a/README.md +++ b/README.md @@ -537,7 +537,7 @@ code_scanning: - `csharp` - `go` - `java-kotlin` -- `javascript-typescript` +- `javascript-typescript` (note: GitHub API may return `javascript`, `typescript`, and `javascript-typescript` separately, but configuration only accepts `javascript-typescript`) - `python` - `ruby` - `swift` @@ -549,6 +549,7 @@ code_scanning: - Both can be used together for fine-grained control - Unauthorized languages are automatically removed from the repository's configuration - Changes are logged and reported in check runs during PR validation +- Language validation automatically normalizes `javascript` and `typescript` to `javascript-typescript` for consistency > [!IMPORTANT] > This feature only manages **Default Setup** configurations created through GitHub's UI. It does not affect custom Code Scanning workflows (Advanced Setup) that you create manually. diff --git a/docs/sample-settings/settings.yml b/docs/sample-settings/settings.yml index 50d8a3fa8..da770fdae 100644 --- a/docs/sample-settings/settings.yml +++ b/docs/sample-settings/settings.yml @@ -34,6 +34,9 @@ code_scanning: languages: # List of languages that are allowed to be scanned # Valid values: c-cpp, csharp, go, java-kotlin, javascript-typescript, python, ruby, swift + # Note: Use 'javascript-typescript' for JavaScript/TypeScript repos. The API may return + # 'javascript', 'typescript', and 'javascript-typescript' separately, but configuration + # only accepts 'javascript-typescript'. The validation automatically normalizes these. allowed: - javascript-typescript - python diff --git a/index.js b/index.js index 8cecfa8bd..fbbe26a22 100644 --- a/index.js +++ b/index.js @@ -639,17 +639,24 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) => robot.on('workflow_run.completed', async context => { const { payload } = context - const { workflow_run } = payload + const { workflow_run, repository } = payload - robot.log.debug(`Workflow run completed: ${workflow_run.name} in ${payload.repository.name}`) + robot.log.debug(`Workflow run completed: ${workflow_run.name} in ${repository.name}`) // Check if this is a Code Scanning Default Setup workflow // These workflows have a specific path pattern if (workflow_run.path === 'dynamic/github-code-scanning/codeql') { - robot.log.debug(`Code Scanning Default Setup workflow detected for ${payload.repository.name}`) + robot.log.debug(`Code Scanning Default Setup workflow detected for ${repository.name}`) + + // Create a proper context with repo information + // workflow_run events need explicit repo info + const repoContext = { + ...context, + repo: () => ({ owner: repository.owner.login, repo: repository.name }) + } // Trigger sync to validate code scanning configuration - return syncSettings(false, context) + return syncSettings(false, repoContext) } robot.log.debug('Not a Code Scanning Default Setup workflow, skipping...') diff --git a/lib/configManager.js b/lib/configManager.js index 58f5bb436..6ceb88851 100644 --- a/lib/configManager.js +++ b/lib/configManager.js @@ -21,8 +21,14 @@ module.exports = class ConfigManager { const params = Object.assign(repo, { path: filePath, ref: this.ref }) const response = await this.context.octokit.repos.getContent(params).catch(e => { this.log.error(`Error getting settings ${e}`) + throw e }) + // Check if response is valid + if (!response || !response.data) { + return null + } + // Ignore in case path is a folder // - https://developer.github.com/v3/repos/contents/#response-if-content-is-a-directory if (Array.isArray(response.data)) { diff --git a/lib/plugins/code_scanning.js b/lib/plugins/code_scanning.js index 290fd5b8c..257e58297 100644 --- a/lib/plugins/code_scanning.js +++ b/lib/plugins/code_scanning.js @@ -174,10 +174,26 @@ module.exports = class CodeScanning extends ErrorStash { const normalizedAllowed = allowedLanguages.map(lang => lang.toLowerCase()) const normalizedBlocked = blockedLanguages.map(lang => lang.toLowerCase()) + // GitHub API returns javascript, typescript, and javascript-typescript as separate entries + // when the language is detected, but we can only configure javascript-typescript + // We need to normalize these for validation + const normalizeLanguageForValidation = (lang) => { + if (lang === 'javascript' || lang === 'typescript') { + return 'javascript-typescript' + } + return lang + } + + // Normalize current languages for validation + // This converts javascript/typescript to javascript-typescript and removes duplicates + const normalizedCurrentForValidation = [...new Set( + normalizedCurrent.map(normalizeLanguageForValidation) + )] + // Find unauthorized languages (either not in allowed list or in blocked list) const unauthorizedLanguages = [] - for (const lang of normalizedCurrent) { + for (const lang of normalizedCurrentForValidation) { // If allowed list is specified and language is not in it if (normalizedAllowed.length > 0 && !normalizedAllowed.includes(lang)) { unauthorizedLanguages.push(lang) @@ -224,9 +240,22 @@ module.exports = class CodeScanning extends ErrorStash { * @returns {Promise} Resolution of the enforcement action */ async enforceAllowedLanguages (currentConfig, validation, resArray) { + // Normalize language helper + const normalizeLanguageForValidation = (lang) => { + if (lang === 'javascript' || lang === 'typescript') { + return 'javascript-typescript' + } + return lang + } + + // Normalize current languages to the format used for validation + const normalizedCurrentForApi = [...new Set( + validation.currentLanguages.map(normalizeLanguageForValidation) + )] + // Calculate the languages that should be enabled - // (current languages minus unauthorized ones) - const languagesToKeep = validation.currentLanguages.filter( + // Start with normalized current languages minus unauthorized ones + const languagesToKeep = normalizedCurrentForApi.filter( lang => !validation.unauthorizedLanguages.includes(lang) ) From 9e46a8f5a689eafd1a5dbcae8fa72aa096142b07 Mon Sep 17 00:00:00 2001 From: David Wiggs <107562400+david-wiggs@users.noreply.github.com> Date: Sat, 15 Nov 2025 12:04:04 -0600 Subject: [PATCH 4/4] Update README.md Co-authored-by: Yadhav Jayaraman <57544838+decyjphr@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 53dd200fa..23b95720c 100644 --- a/README.md +++ b/README.md @@ -494,8 +494,8 @@ See [`docs/sample-settings/settings.yml`](docs/sample-settings/settings.yml) for > - name: Other-team > permission: push > include: - - '*-config' - ``` + > - '*-config' + > ``` ### Code Scanning Default Setup