Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,11 @@ pages:
path: /
cname: docs.example.com
https_enforced: true

code_scanning:
default_setup:
enabled: true
query_suite: extended
```

Every section is **optional**, and an absent section is **unmanaged** — nothing
Expand Down Expand Up @@ -386,6 +391,7 @@ rather than a guess.
| Actions variables, at repository and environment scope | ✅ |
| Actions general settings (permissions, retention, fork PR approval) | ✅ |
| GitHub Pages (source, custom domain, HTTPS) | ✅ |
| Code scanning (CodeQL default setup) | ✅ |
| Inheritance from a shared repository (`extends:`) | ✅ |

Declaring a `pages:` section enables Pages; it is never turned back off, because
Expand Down
3 changes: 2 additions & 1 deletion docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,12 @@ actually enforces.
| `actions` | Metadata: read, Administration: write, Actions policies: write † | `repo` | ✘ |
| `variables` | Metadata: read, Actions: read, Variables: write, Environments: write | `repo` | ✘ |
| `pages` | Metadata: read, Pages: write, Administration: write | `repo` | ✔ |
| `code_scanning` | Metadata: read, Administration: write | `repo` | ✘ |
| `extends` | Contents: read | `repo` | ✘ |

† GitHub's own reference does not settle this mapping — it is either absent or ambiguous there, so this is our best understanding and the minimal claim, not a guarantee. `gh settings doctor` will tell you what your token can actually do.

`repository`, `topics`, `autolinks`, `rulesets`, `environments` and `actions` require Administration: write, which cannot be granted to GITHUB_TOKEN — the workflow `permissions:` block has no key that grants it. Use a personal access token or a GitHub App token.
`repository`, `topics`, `autolinks`, `rulesets`, `environments`, `actions` and `code_scanning` require Administration: write, which cannot be granted to GITHUB_TOKEN — the workflow `permissions:` block has no key that grants it. Use a personal access token or a GitHub App token.

`variables` requires Variables: write, which cannot be granted to GITHUB_TOKEN — the workflow `permissions:` block has no key that grants it. Use a personal access token or a GitHub App token.

Expand Down
37 changes: 37 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,43 @@ A single autolink reference.
| `url_template` | string | yes | Target URL, containing the `<num>` placeholder. |


## `code_scanning`

Code scanning.

Covers CodeQL default setup, which `default_setup.enabled` switches on
and off. An omitted section leaves code scanning as it is.

| Field | Type | Required | Description |
|---|---|---|---|
| `default_setup` | object | no | CodeQL default setup, which analyses the repository on GitHub's own workflow without a workflow file. |


### `code_scanning.default_setup`

CodeQL default setup, which analyses the repository on GitHub's own
workflow without a workflow file.

GitHub applies a change to the query suite, threat model or languages
of a setup that is already enabled through a *CodeQL Setup* workflow
run, and reports the previous values until that run finishes. A plan
made in the meantime shows the change again.

GitHub also accepts enabling it on a repository with no language CodeQL
can analyse, then reports no languages and does not start analysing
when one is added. Enable it once the repository has code, or turn it
off and on again after adding some.

| Field | Type | Required | Description |
|---|---|---|---|
| `enabled` | boolean | yes | Whether default setup is enabled. |
| `languages` | list of string | no | Languages to analyse, for example `actions`, `python` or `javascript-typescript`. |
| `query_suite` | `default` \| `extended` | no | Which CodeQL queries to run. |
| `runner_label` | string | no | Label of the self-hosted runners to use, with `runner_type: labeled`. |
| `runner_type` | `standard` \| `labeled` | no | Which runners the analysis runs on. |
| `threat_model` | `remote` \| `remote_and_local` | no | Which sources of untrusted data CodeQL considers. |


## `environments`

Deployment environments and their protection rules.
Expand Down
1 change: 1 addition & 0 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ eventually be manageable here.*
| Actions general settings | ✅ | allowed actions, workflow permissions, artifact and log retention, fork PR approval |
| Actions variables | ✅ | repository and environment scope; values are readable, so they diff |
| Pages | ✅ | build type, source, custom domain and HTTPS; declaring the section enables the site, nothing ever disables it. The `public` flag is reported by the API but not settable through it, so it is not offered |
| Code scanning | ✅ | CodeQL default setup: query suite, threat model, languages and runners. A changed query suite, threat model or added language reads back only once the setup run GitHub starts has finished |
| Custom properties | planned | |
| Webhooks | planned | |
| Collaborators & teams | planned | |
Expand Down
138 changes: 138 additions & 0 deletions schema/v1/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,24 @@
}
]
},
"CodeScanningSettings": {
"additionalProperties": false,
"description": "The `code_scanning` configuration section.\n\n```yaml\ncode_scanning:\n default_setup:\n enabled: true\n```",
"properties": {
"default_setup": {
"anyOf": [
{
"$ref": "#/$defs/DefaultSetup"
},
{
"type": "null"
}
],
"description": "CodeQL default setup, which analyses the repository on GitHub's own\nworkflow without a workflow file.\n\nGitHub applies a change to the query suite, threat model or languages\nof a setup that is already enabled through a *CodeQL Setup* workflow\nrun, and reports the previous values until that run finishes. A plan\nmade in the meantime shows the change again.\n\nGitHub also accepts enabling it on a repository with no language CodeQL\ncan analyse, then reports no languages and does not start analysing\nwhen one is added. Enable it once the repository has code, or turn it\noff and on again after adding some."
}
},
"type": "object"
},
"Conditions": {
"additionalProperties": false,
"description": "Which refs a ruleset applies to.",
Expand All @@ -264,6 +282,70 @@
},
"type": "object"
},
"DefaultSetup": {
"additionalProperties": false,
"description": "CodeQL default setup.\n\n```yaml\ndefault_setup:\n enabled: true\n query_suite: extended\n languages: [actions, python]\n```",
"properties": {
"enabled": {
"description": "Whether default setup is enabled.\n\n`false` turns it off, and the other fields are then ignored.",
"type": "boolean"
},
"languages": {
"description": "Languages to analyse, for example `actions`, `python` or\n`javascript-typescript`.\n\nOmit it to let GitHub analyse every supported language it detects.\n`javascript` and `typescript` both mean `javascript-typescript`; likewise\n`c` and `cpp` mean `c-cpp`, and `java` and `kotlin` mean `java-kotlin`.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"query_suite": {
"anyOf": [
{
"$ref": "#/$defs/QuerySuite"
},
{
"type": "null"
}
],
"description": "Which CodeQL queries to run."
},
"runner_label": {
"description": "Label of the self-hosted runners to use, with `runner_type: labeled`.",
"type": [
"string",
"null"
]
},
"runner_type": {
"anyOf": [
{
"$ref": "#/$defs/RunnerType"
},
{
"type": "null"
}
],
"description": "Which runners the analysis runs on."
},
"threat_model": {
"anyOf": [
{
"$ref": "#/$defs/ThreatModel"
},
{
"type": "null"
}
],
"description": "Which sources of untrusted data CodeQL considers."
}
},
"required": [
"enabled"
],
"type": "object"
},
"DeploymentBranchPolicy": {
"anyOf": [
{
Expand Down Expand Up @@ -748,6 +830,21 @@
],
"description": "A collection section that can opt into pruning."
},
"QuerySuite": {
"description": "Which CodeQL queries default setup runs.",
"oneOf": [
{
"const": "default",
"description": "The default suite, tuned for a low false-positive rate.",
"type": "string"
},
{
"const": "extended",
"description": "The default suite plus lower-severity and lower-precision queries.",
"type": "string"
}
]
},
"RefNameCondition": {
"additionalProperties": false,
"description": "Ref name matching.",
Expand Down Expand Up @@ -1056,6 +1153,21 @@
],
"type": "object"
},
"RunnerType": {
"description": "Which runners default setup's analysis runs on.",
"oneOf": [
{
"const": "standard",
"description": "GitHub-hosted runners.",
"type": "string"
},
{
"const": "labeled",
"description": "Runners carrying the label named by `runner_label`.",
"type": "string"
}
]
},
"SecuritySettings": {
"additionalProperties": false,
"description": "Security and analysis features.\n\nThe API models these as `{ status: \"enabled\" | \"disabled\" }` objects rather\nthan booleans; the configuration exposes plain booleans and the resource\ntranslates.",
Expand Down Expand Up @@ -1205,6 +1317,21 @@
}
]
},
"ThreatModel": {
"description": "Which sources of untrusted data CodeQL considers.",
"oneOf": [
{
"const": "remote",
"description": "Remote sources only, such as network requests.",
"type": "string"
},
{
"const": "remote_and_local",
"description": "Remote sources and local ones, such as files and environment variables.",
"type": "string"
}
]
},
"Variable": {
"additionalProperties": false,
"description": "A single Actions variable.",
Expand Down Expand Up @@ -1267,6 +1394,17 @@
],
"description": "Autolink references."
},
"code_scanning": {
"anyOf": [
{
"$ref": "#/$defs/CodeScanningSettings"
},
{
"type": "null"
}
],
"description": "Code scanning.\n\nCovers CodeQL default setup, which `default_setup.enabled` switches on\nand off. An omitted section leaves code scanning as it is."
},
"environments": {
"anyOf": [
{
Expand Down
10 changes: 10 additions & 0 deletions scripts/live-sandbox.sh
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,16 @@ elif api "repos/$repo/pages" >/dev/null 2>&1; then
fi
fi

# Like the Actions settings below, CodeQL default setup has a value rather than
# an existence, so the pre-flight cannot see a run that left it enabled.
if [[ "$(api "repos/$repo/code-scanning/default-setup" --jq .state 2>/dev/null || true)" == configured ]]; then
if try "disabling CodeQL default setup" \
gh api "repos/$repo/code-scanning/default-setup" --method PATCH \
--field state=not-configured --silent; then
echo "disabled CodeQL default setup"
fi
fi

if try "clearing description and homepage" \
gh api "repos/$repo" --method PATCH --field description= --field homepage= --silent; then
echo "cleared description and homepage"
Expand Down
39 changes: 39 additions & 0 deletions src/config/merge/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use super::source::SourceId;
use super::spans::SpanIndex;
use crate::config::Prunable;
use crate::resources::actions::{ActionsSettings, ForkPrWorkflowsPrivateRepos, SelectedActions};
use crate::resources::code_scanning::CodeScanningSettings;
use crate::resources::labels::model::key as label_key;
use crate::resources::pages::PagesSettings;
use crate::resources::repository::{RepositorySettings, SecuritySettings};
Expand Down Expand Up @@ -59,6 +60,7 @@ pub fn merge(base: &Layer<'_>, child: &Layer<'_>) -> (Settings, Provenance) {
let repository = merge_repository(&mut provenance, base, child);
let pages = merge_pages(&mut provenance, base, child);
let actions = merge_actions(&mut provenance, base, child);
let code_scanning = merge_code_scanning(&mut provenance, base, child);

let topics = merge_section(
&mut provenance,
Expand Down Expand Up @@ -125,6 +127,7 @@ pub fn merge(base: &Layer<'_>, child: &Layer<'_>) -> (Settings, Provenance) {
variables,
pages,
actions,
code_scanning,
};

(settings, provenance)
Expand Down Expand Up @@ -298,6 +301,42 @@ fn merge_pages(
})
}

/// Merge the code scanning section.
///
/// `default_setup` is taken whole rather than merged: `enabled` is not an
/// `Option`, so a child overriding only the query suite would otherwise
/// inherit an `enabled` it never mentioned. Same reasoning as `pages.source`.
fn merge_code_scanning(
provenance: &mut Provenance,
base: &Layer<'_>,
child: &Layer<'_>,
) -> Option<CodeScanningSettings> {
let base_section = base.settings.code_scanning.as_ref();
let child_section = child.settings.code_scanning.as_ref();

if base_section.is_none() && child_section.is_none() {
return None;
}

let empty = CodeScanningSettings::default();
let b = base_section.unwrap_or(&empty);
let c = child_section.unwrap_or(&empty);

// Exhaustive, with no `..`, for the same reason as `merge_repository`.
let CodeScanningSettings { default_setup } = c;

Some(CodeScanningSettings {
default_setup: pick(
provenance,
"code_scanning.default_setup",
base.id,
b.default_setup.as_ref(),
child.id,
default_setup.as_ref(),
),
})
}

/// Merge the security block, which must recurse rather than be taken whole.
///
/// Taking the child's block outright would unmanage every setting it did not
Expand Down
31 changes: 31 additions & 0 deletions src/config/merge/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,34 @@ fn pages_are_unmanaged_when_neither_document_declares_them() {
let (settings, _, _) = merged("labels: []\n", "topics: []\n");
assert!(settings.pages.is_none());
}

// --- code scanning ----------------------------------------------------------

#[test]
fn an_inherited_default_setup_is_available_to_a_child_that_declares_none() {
let (settings, _, _) = merged(
"code_scanning:\n default_setup:\n enabled: true\n",
"labels: []\n",
);
let setup = settings.code_scanning.unwrap().default_setup.unwrap();
assert!(setup.enabled);
}

#[test]
fn an_inherited_default_setup_is_replaced_whole_rather_than_field_by_field() {
// `enabled` is not an `Option`, so a child redeclaring the setup states
// all of it: here, a base's query suite does not survive the override.
let (settings, _, _) = merged(
"code_scanning:\n default_setup:\n enabled: true\n query_suite: extended\n",
"code_scanning:\n default_setup:\n enabled: true\n languages: [python]\n",
);
let setup = settings.code_scanning.unwrap().default_setup.unwrap();
assert_eq!(setup.query_suite, None);
assert_eq!(setup.languages, Some(vec!["python".to_string()]));
}

#[test]
fn code_scanning_is_unmanaged_when_neither_document_declares_it() {
let (settings, _, _) = merged("labels: []\n", "topics: []\n");
assert!(settings.code_scanning.is_none());
}
Loading