Compare commits

...

36 Commits

Author SHA1 Message Date
Michael B. Gale
181d5eefc2 Merge pull request #2957 from github/update-v3.29.2-4c57370d0
Merge main into releases/v3
2025-06-30 14:00:45 +01:00
Michael B. Gale
c77386a9db Fix changelog PR number 2025-06-30 13:48:01 +01:00
github-actions[bot]
8d43d4ecec Update changelog for v3.29.2 2025-06-30 12:44:54 +00:00
Michael B. Gale
4c57370d03 Merge pull request #2935 from github/mbg/interpret-cq-results
Produce separate SARIF file for `quality-queries` alerts
2025-06-27 14:03:38 +01:00
Michael B. Gale
2830b750e5 Add changelog entry 2025-06-27 13:49:45 +01:00
Michael B. Gale
aa72ddaead Merge branch 'main' into mbg/interpret-cq-results 2025-06-27 13:45:51 +01:00
Michael B. Gale
65d1e45f0b Rename SARIF_UPLOAD_ENDPOINT members 2025-06-27 13:45:14 +01:00
Michael B. Gale
362ebf85da Check both SARIF files in quality-queries.yml test 2025-06-27 12:32:56 +01:00
Michael B. Gale
10a3e4b17d Fix formatting 2025-06-27 12:32:56 +01:00
Arthur Baars
8593ea65e2 Merge pull request #2954 from github/mergeback/v3.29.1-to-main-39edc492
Mergeback v3.29.1 refs/heads/releases/v3 into main
2025-06-27 13:11:54 +02:00
Michael B. Gale
3e95091e3b Add test workflow for upload-sarif with quality results 2025-06-27 12:11:12 +01:00
Michael B. Gale
7b3d150883 Use findSarifFilesInDir in upload-sarif to avoid error when there are no quality.sarif files 2025-06-27 12:08:40 +01:00
github-actions[bot]
2e3a72539c Update checked-in dependencies 2025-06-27 10:52:35 +00:00
github-actions[bot]
baf20c9b52 Update changelog and version after v3.29.1 2025-06-27 10:44:54 +00:00
Michael B. Gale
ad6046ff97 Avoid default arguments with historical values 2025-06-26 13:51:08 +01:00
Michael B. Gale
9ec0bb9605 Fix incorrect getSarifFilePaths call in upload-sarif action 2025-06-26 12:22:08 +01:00
Michael B. Gale
08955dbc0d Move .sarif predicates into UploadTarget instances and rename 2025-06-26 11:43:36 +01:00
Michael B. Gale
71dd63398f Rename SARIF_UPLOAD_TARGET 2025-06-26 11:38:45 +01:00
Michael B. Gale
27db6cb5d6 Document queries parameter for databaseRunQueries 2025-06-26 11:37:10 +01:00
Michael B. Gale
768fc170da Rename resolveQuerySuiteAlias parameter 2025-06-26 11:32:48 +01:00
Michael B. Gale
79049d92c6 Fix config-queries.qls location 2025-06-25 14:42:24 +01:00
Michael B. Gale
e382508853 Prototyping adding quality queries when running queries 2025-06-25 14:24:34 +01:00
Michael B. Gale
2c76207fa4 Upload .quality.sarif files to CQ service in upload-sarif action 2025-06-25 13:43:39 +01:00
Michael B. Gale
86f47e8b74 Add some more comments 2025-06-24 13:59:46 +01:00
Michael B. Gale
9b9286a835 Add test for resolveQuerySuiteAlias 2025-06-24 13:42:52 +01:00
Michael B. Gale
af32bc6d6f Add test for modified validateUniqueCategory 2025-06-24 13:26:34 +01:00
Michael B. Gale
51891595a7 Add test for modified findSarifFilesInDir 2025-06-24 13:24:04 +01:00
Michael B. Gale
f7fbaa019f Support all default query suites and resolve them 2025-06-24 13:08:56 +01:00
Michael B. Gale
6abacdb184 Fix getSarifFilePaths not using right filter 2025-06-23 18:19:43 +01:00
Michael B. Gale
f1834221f2 Allow the same category once for each type of upload 2025-06-23 18:19:43 +01:00
Michael B. Gale
45b3bec064 Upload quality SARIFs to CQ endpoint 2025-06-23 18:19:42 +01:00
Michael B. Gale
22444a650f Add ability to use different filters in findSarifFilesInDir 2025-06-23 18:19:42 +01:00
Michael B. Gale
320f7b0fd6 Resolve code-quality alias 2025-06-23 18:19:42 +01:00
Michael B. Gale
3a7544ea8f Check SARIF with quality results for expected configuration 2025-06-23 18:19:42 +01:00
Michael B. Gale
aba8788d12 Upload both SARIF files in quality-queries check 2025-06-23 18:19:42 +01:00
Michael B. Gale
3963bf423a Interpret results for quality queries and store as separate SARIF file 2025-06-23 18:19:40 +01:00
32 changed files with 601 additions and 122 deletions

View File

@@ -64,37 +64,54 @@ jobs:
with:
output: ${{ runner.temp }}/results
upload-database: false
- name: Upload SARIF
- name: Upload security SARIF
uses: actions/upload-artifact@v4
with:
name: config-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
name: quality-queries-${{ matrix.os }}-${{ matrix.version }}.sarif.json
path: ${{ runner.temp }}/results/javascript.sarif
retention-days: 7
- name: Check config properties appear in SARIF
- name: Upload quality SARIF
uses: actions/upload-artifact@v4
with:
name: quality-queries-${{ matrix.os }}-${{ matrix.version }}.quality.sarif.json
path: ${{ runner.temp }}/results/javascript.quality.sarif
retention-days: 7
- name: Check quality query does not appear in security SARIF
uses: actions/github-script@v7
env:
SARIF_PATH: ${{ runner.temp }}/results/javascript.sarif
EXPECT_PRESENT: 'false'
with:
script: |
const fs = require('fs');
const sarif = JSON.parse(fs.readFileSync(process.env['SARIF_PATH'], 'utf8'));
const run = sarif.runs[0];
const configSummary = run.properties.codeqlConfigSummary;
if (configSummary === undefined) {
core.setFailed('`codeqlConfigSummary` property not found in the SARIF run property bag.');
}
if (configSummary.disableDefaultQueries !== false) {
core.setFailed('`disableDefaultQueries` property incorrect: expected false, got ' +
`${JSON.stringify(configSummary.disableDefaultQueries)}.`);
}
const expectedQueries = [{ type: 'builtinSuite', uses: 'code-quality' }];
// Use JSON.stringify to deep-equal the arrays.
if (JSON.stringify(configSummary.queries) !== JSON.stringify(expectedQueries)) {
core.setFailed(`\`queries\` property incorrect: expected ${JSON.stringify(expectedQueries)}, got ` +
`${JSON.stringify(configSummary.queries)}.`);
}
core.info('Finished config export tests.');
script: ${{ env.CHECK_SCRIPT }}
- name: Check quality query appears in quality SARIF
uses: actions/github-script@v7
env:
SARIF_PATH: ${{ runner.temp }}/results/javascript.quality.sarif
EXPECT_PRESENT: 'true'
with:
script: ${{ env.CHECK_SCRIPT }}
env:
CHECK_SCRIPT: |
const fs = require('fs');
const sarif = JSON.parse(fs.readFileSync(process.env['SARIF_PATH'], 'utf8'));
const expectPresent = JSON.parse(process.env['EXPECT_PRESENT']);
const run = sarif.runs[0];
const extensions = run.tool.extensions;
if (extensions === undefined) {
core.setFailed('`extensions` property not found in the SARIF run property bag.');
}
// ID of a query we want to check the presence for
const targetId = 'js/regex/always-matches';
const found = extensions.find(extension => extension.rules && extension.rules.find(rule => rule.id === targetId));
if (found && expectPresent) {
console.log(`Found rule with id '${targetId}'.`);
} else if (!found && !expectPresent) {
console.log(`Rule with id '${targetId}' was not found.`);
} else {
core.setFailed(`${ found ? "Found" : "Didn't find" } rule ${targetId}`);
}
CODEQL_ACTION_TEST_MODE: true

78
.github/workflows/__upload-quality-sarif.yml generated vendored Normal file
View File

@@ -0,0 +1,78 @@
# Warning: This file is generated automatically, and should not be modified.
# Instead, please modify the template in the pr-checks directory and run:
# (cd pr-checks; pip install ruamel.yaml@0.17.31 && python3 sync.py)
# to regenerate this file.
name: 'PR Check - Upload-sarif: code quality endpoint'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GO111MODULE: auto
on:
push:
branches:
- main
- releases/v*
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
schedule:
- cron: '0 5 * * *'
workflow_dispatch: {}
jobs:
upload-quality-sarif:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
version: default
- os: macos-latest
version: default
- os: windows-latest
version: default
name: 'Upload-sarif: code quality endpoint'
permissions:
contents: read
security-events: read
timeout-minutes: 45
runs-on: ${{ matrix.os }}
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Prepare test
id: prepare-test
uses: ./.github/actions/prepare-test
with:
version: ${{ matrix.version }}
use-all-platform-bundle: 'false'
setup-kotlin: 'true'
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '>=1.21.0'
cache: false
- uses: ./../action/init
with:
tools: ${{ steps.prepare-test.outputs.tools-url }}
languages: cpp,csharp,java,javascript,python
config-file: ${{ github.repository }}/tests/multi-language-repo/.github/codeql/custom-queries.yml@${{
github.sha }}
quality-queries: code-quality
- name: Build code
shell: bash
run: ./build.sh
# Generate some SARIF we can upload with the upload-sarif step
- uses: ./../action/analyze
with:
ref: refs/heads/main
sha: 5e235361806c361d4d3f8859e3c897658025a9a2
upload: never
- uses: ./../action/upload-sarif
with:
ref: refs/heads/main
sha: 5e235361806c361d4d3f8859e3c897658025a9a2
env:
CODEQL_ACTION_TEST_MODE: true

View File

@@ -2,6 +2,10 @@
See the [releases page](https://github.com/github/codeql-action/releases) for the relevant changes to the CodeQL CLI and language packs.
## 3.29.2 - 30 Jun 2025
- Experimental: When the `quality-queries` input for the `init` action is provided with an argument, separate `.quality.sarif` files are produced and uploaded for each language with the results of the specified queries. Do not use this in production as it is part of an internal experiment and subject to change at any time. [#2935](https://github.com/github/codeql-action/pull/2935)
## 3.29.1 - 27 Jun 2025
- Fix bug in PR analysis where user-provided `include` query filter fails to exclude non-included queries. [#2938](https://github.com/github/codeql-action/pull/2938)

6
lib/analyze-action.js generated
View File

@@ -216,8 +216,12 @@ async function run() {
core.setOutput("sarif-output", path_1.default.resolve(outputDir));
const uploadInput = actionsUtil.getOptionalInput("upload");
if (runStats && actionsUtil.getUploadValue(uploadInput) === "always") {
uploadResult = await uploadLib.uploadFiles(outputDir, actionsUtil.getRequiredInput("checkout_path"), actionsUtil.getOptionalInput("category"), features, logger);
uploadResult = await uploadLib.uploadFiles(outputDir, actionsUtil.getRequiredInput("checkout_path"), actionsUtil.getOptionalInput("category"), features, logger, uploadLib.CodeScanningTarget);
core.setOutput("sarif-id", uploadResult.sarifID);
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
const qualityUploadResult = await uploadLib.uploadFiles(outputDir, actionsUtil.getRequiredInput("checkout_path"), actionsUtil.getOptionalInput("category"), features, logger, uploadLib.CodeQualityTarget);
core.setOutput("quality-sarif-id", qualityUploadResult.sarifID);
}
}
else {
logger.info("Not uploading results");

File diff suppressed because one or more lines are too long

42
lib/analyze.js generated
View File

@@ -36,10 +36,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.exportedForTesting = exports.CodeQLAnalysisError = void 0;
exports.exportedForTesting = exports.defaultSuites = exports.CodeQLAnalysisError = void 0;
exports.runExtraction = runExtraction;
exports.dbIsFinalized = dbIsFinalized;
exports.setupDiffInformedQueryRun = setupDiffInformedQueryRun;
exports.resolveQuerySuiteAlias = resolveQuerySuiteAlias;
exports.runQueries = runQueries;
exports.runFinalize = runFinalize;
exports.warnIfGoInstalledAfterInit = warnIfGoInstalledAfterInit;
@@ -385,6 +386,28 @@ extensions:
(0, diff_informed_analysis_utils_1.writeDiffRangesJsonFile)(logger, ranges);
return diffRangeDir;
}
// A set of default query suite names that are understood by the CLI.
exports.defaultSuites = new Set([
"security-experimental",
"security-extended",
"security-and-quality",
"code-quality",
"code-scanning",
]);
/**
* If `maybeSuite` is the name of a default query suite, it is resolved into the corresponding
* query suite name for the given `language`. Otherwise, `maybeSuite` is returned as is.
*
* @param language The language for which to resolve the default query suite name.
* @param maybeSuite The string that potentially contains the name of a default query suite.
* @returns Returns the resolved query suite name, or the unmodified input.
*/
function resolveQuerySuiteAlias(language, maybeSuite) {
if (exports.defaultSuites.has(maybeSuite)) {
return `${language}-${maybeSuite}.qls`;
}
return maybeSuite;
}
// Runs queries and creates sarif files in the given folder
async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag, cleanupLevel, diffRangePackDir, automationDetailsId, config, logger, features) {
const statusReport = {};
@@ -404,6 +427,14 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
for (const language of config.languages) {
try {
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
const queries = [];
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
queries.push(path.join(util.getCodeQLDatabasePath(config, language), "temp", "config-queries.qls"));
for (const qualityQuery of config.augmentationProperties
.qualityQueriesInput) {
queries.push(resolveQuerySuiteAlias(language, qualityQuery.uses));
}
}
// The work needed to generate the query suites
// is done in the CLI. We just need to make a single
// call to run all the queries for each language and
@@ -411,7 +442,7 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
logger.startGroup(`Running queries for ${language}`);
const startTimeRunQueries = new Date().getTime();
const databasePath = util.getCodeQLDatabasePath(config, language);
await codeql.databaseRunQueries(databasePath, queryFlags);
await codeql.databaseRunQueries(databasePath, queryFlags, queries);
logger.debug(`Finished running queries for ${language}.`);
// TODO should not be using `builtin` here. We should be using `all` instead.
// The status report does not support `all` yet.
@@ -420,6 +451,13 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
logger.startGroup(`Interpreting results for ${language}`);
const startTimeInterpretResults = new Date();
const analysisSummary = await runInterpretResults(language, undefined, sarifFile, config.debugMode);
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
logger.info(`Interpreting quality results for ${language}`);
const qualitySarifFile = path.join(sarifFolder, `${language}.quality.sarif`);
const qualityAnalysisSummary = await runInterpretResults(language, config.augmentationProperties.qualityQueriesInput.map((i) => resolveQuerySuiteAlias(language, i.uses)), qualitySarifFile, config.debugMode);
// TODO: move
logger.info(qualityAnalysisSummary);
}
const endTimeInterpretResults = new Date();
statusReport[`interpret_results_${language}_duration_ms`] =
endTimeInterpretResults.getTime() - startTimeInterpretResults.getTime();

File diff suppressed because one or more lines are too long

13
lib/analyze.test.js generated
View File

@@ -313,4 +313,17 @@ function runGetDiffRanges(changes, patch) {
const diffRanges = runGetDiffRanges(2, ["@@ 30 +50,2 @@", "+1", "+2"]);
t.deepEqual(diffRanges, undefined);
});
(0, ava_1.default)("resolveQuerySuiteAlias", (t) => {
// default query suite names should resolve to something language-specific ending in `.qls`.
for (const suite of analyze_1.defaultSuites) {
const resolved = (0, analyze_1.resolveQuerySuiteAlias)(languages_1.Language.go, suite);
t.assert(resolved.endsWith(".qls"), "Resolved default suite doesn't end in .qls");
t.assert(resolved.indexOf(languages_1.Language.go) >= 0, "Resolved default suite doesn't contain language name");
}
// other inputs should be returned unchanged
const names = ["foo", "bar", "codeql/go-queries@1.0"];
for (const name of names) {
t.deepEqual((0, analyze_1.resolveQuerySuiteAlias)(languages_1.Language.go, name), name);
}
});
//# sourceMappingURL=analyze.test.js.map

File diff suppressed because one or more lines are too long

11
lib/codeql.js generated
View File

@@ -450,7 +450,7 @@ async function getCodeQLForCmd(cmd, checkVersion) {
throw new Error(`Unexpected output from codeql resolve build-environment: ${e} in\n${output}`);
}
},
async databaseRunQueries(databasePath, flags) {
async databaseRunQueries(databasePath, flags, queries = []) {
const codeqlArgs = [
"database",
"run-queries",
@@ -459,6 +459,7 @@ async function getCodeQLForCmd(cmd, checkVersion) {
"--intra-layer-parallelism",
"--min-disk-free=1024", // Try to leave at least 1GB free
"-v",
...queries,
...getExtraOptionsFromEnv(["database", "run-queries"], {
ignoringOptions: ["--expect-discarded-cache"],
}),
@@ -759,14 +760,12 @@ async function generateCodeScanningConfig(config, logger) {
// make a copy so we can modify it
const augmentedConfig = (0, util_1.cloneObject)(config.originalUserInput);
// Inject the queries from the input
if (config.augmentationProperties.queriesInput ||
config.augmentationProperties.qualityQueriesInput) {
const queryInputs = (config.augmentationProperties.queriesInput || []).concat(config.augmentationProperties.qualityQueriesInput || []);
if (config.augmentationProperties.queriesInput) {
if (config.augmentationProperties.queriesInputCombines) {
augmentedConfig.queries = (augmentedConfig.queries || []).concat(queryInputs);
augmentedConfig.queries = (augmentedConfig.queries || []).concat(config.augmentationProperties.queriesInput);
}
else {
augmentedConfig.queries = queryInputs;
augmentedConfig.queries = config.augmentationProperties.queriesInput;
}
}
if (augmentedConfig.queries?.length === 0) {

File diff suppressed because one or more lines are too long

View File

@@ -87,7 +87,7 @@ async function maybeUploadFailedSarif(config, repositoryNwo, features, logger) {
await codeql.databaseExportDiagnostics(databasePath, sarifFile, category);
}
logger.info(`Uploading failed SARIF file ${sarifFile}`);
const uploadResult = await uploadLib.uploadFiles(sarifFile, checkoutPath, category, features, logger);
const uploadResult = await uploadLib.uploadFiles(sarifFile, checkoutPath, category, features, logger, uploadLib.CodeScanningTarget);
await uploadLib.waitForProcessing(repositoryNwo, uploadResult.sarifID, logger, { isUnsuccessfulExecution: true });
return uploadResult
? { ...uploadResult.statusReport, sarifID: uploadResult.sarifID }

File diff suppressed because one or more lines are too long

56
lib/upload-lib.js generated
View File

@@ -36,14 +36,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InvalidSarifUploadError = void 0;
exports.InvalidSarifUploadError = exports.CodeQualityTarget = exports.CodeScanningTarget = exports.SARIF_UPLOAD_ENDPOINT = void 0;
exports.shouldShowCombineSarifFilesDeprecationWarning = shouldShowCombineSarifFilesDeprecationWarning;
exports.populateRunAutomationDetails = populateRunAutomationDetails;
exports.findSarifFilesInDir = findSarifFilesInDir;
exports.getSarifFilePaths = getSarifFilePaths;
exports.readSarifFile = readSarifFile;
exports.validateSarifFileSchema = validateSarifFileSchema;
exports.buildPayload = buildPayload;
exports.uploadFiles = uploadFiles;
exports.uploadSpecifiedFiles = uploadSpecifiedFiles;
exports.waitForProcessing = waitForProcessing;
exports.shouldConsiderConfigurationError = shouldConsiderConfigurationError;
exports.shouldConsiderInvalidRequest = shouldConsiderInvalidRequest;
@@ -234,9 +236,15 @@ function getAutomationID(category, analysis_key, environment) {
}
return api.computeAutomationID(analysis_key, environment);
}
// Enumerates API endpoints that accept SARIF files.
var SARIF_UPLOAD_ENDPOINT;
(function (SARIF_UPLOAD_ENDPOINT) {
SARIF_UPLOAD_ENDPOINT["CODE_SCANNING"] = "PUT /repos/:owner/:repo/code-scanning/analysis";
SARIF_UPLOAD_ENDPOINT["CODE_QUALITY"] = "PUT /repos/:owner/:repo/code-quality/analysis";
})(SARIF_UPLOAD_ENDPOINT || (exports.SARIF_UPLOAD_ENDPOINT = SARIF_UPLOAD_ENDPOINT = {}));
// Upload the given payload.
// If the request fails then this will retry a small number of times.
async function uploadPayload(payload, repositoryNwo, logger) {
async function uploadPayload(payload, repositoryNwo, logger, target) {
logger.info("Uploading results");
// If in test mode we don't want to upload the results
if (util.isInTestMode()) {
@@ -248,7 +256,7 @@ async function uploadPayload(payload, repositoryNwo, logger) {
}
const client = api.getApiClient();
try {
const response = await client.request("PUT /repos/:owner/:repo/code-scanning/analysis", {
const response = await client.request(target, {
owner: repositoryNwo.owner,
repo: repositoryNwo.repo,
data: payload,
@@ -276,12 +284,12 @@ async function uploadPayload(payload, repositoryNwo, logger) {
}
// Recursively walks a directory and returns all SARIF files it finds.
// Does not follow symlinks.
function findSarifFilesInDir(sarifPath) {
function findSarifFilesInDir(sarifPath, isSarif) {
const sarifFiles = [];
const walkSarifFiles = (dir) => {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile() && entry.name.endsWith(".sarif")) {
if (entry.isFile() && isSarif(entry.name)) {
sarifFiles.push(path.resolve(dir, entry.name));
}
else if (entry.isDirectory()) {
@@ -292,14 +300,14 @@ function findSarifFilesInDir(sarifPath) {
walkSarifFiles(sarifPath);
return sarifFiles;
}
function getSarifFilePaths(sarifPath) {
function getSarifFilePaths(sarifPath, isSarif) {
if (!fs.existsSync(sarifPath)) {
// This is always a configuration error, even for first-party runs.
throw new util_1.ConfigurationError(`Path does not exist: ${sarifPath}`);
}
let sarifFiles;
if (fs.lstatSync(sarifPath).isDirectory()) {
sarifFiles = findSarifFilesInDir(sarifPath);
sarifFiles = findSarifFilesInDir(sarifPath, isSarif);
if (sarifFiles.length === 0) {
// This is always a configuration error, even for first-party runs.
throw new util_1.ConfigurationError(`No SARIF files found to upload in "${sarifPath}".`);
@@ -409,13 +417,33 @@ function buildPayload(commitOid, ref, analysisKey, analysisName, zippedSarif, wo
}
return payloadObj;
}
// Represents the Code Scanning upload target.
exports.CodeScanningTarget = {
name: "code scanning",
target: SARIF_UPLOAD_ENDPOINT.CODE_SCANNING,
sarifPredicate: (name) => name.endsWith(".sarif") && !exports.CodeQualityTarget.sarifPredicate(name),
sentinelPrefix: "CODEQL_UPLOAD_SARIF_",
};
// Represents the Code Quality upload target.
exports.CodeQualityTarget = {
name: "code quality",
target: SARIF_UPLOAD_ENDPOINT.CODE_QUALITY,
sarifPredicate: (name) => name.endsWith(".quality.sarif"),
sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_",
};
/**
* Uploads a single SARIF file or a directory of SARIF files depending on what `inputSarifPath` refers
* to.
*/
async function uploadFiles(inputSarifPath, checkoutPath, category, features, logger) {
const sarifPaths = getSarifFilePaths(inputSarifPath);
logger.startGroup("Uploading results");
async function uploadFiles(inputSarifPath, checkoutPath, category, features, logger, uploadTarget) {
const sarifPaths = getSarifFilePaths(inputSarifPath, uploadTarget.sarifPredicate);
return uploadSpecifiedFiles(sarifPaths, checkoutPath, category, features, logger, uploadTarget);
}
/**
* Uploads the given array of SARIF files.
*/
async function uploadSpecifiedFiles(sarifPaths, checkoutPath, category, features, logger, uploadTarget = exports.CodeScanningTarget) {
logger.startGroup(`Uploading ${uploadTarget.name} results`);
logger.info(`Processing sarif files: ${JSON.stringify(sarifPaths)}`);
const gitHubVersion = await (0, api_client_1.getGitHubVersion)();
let sarif;
@@ -439,7 +467,7 @@ async function uploadFiles(inputSarifPath, checkoutPath, category, features, log
sarif = populateRunAutomationDetails(sarif, category, analysisKey, environment);
const toolNames = util.getToolNames(sarif);
logger.debug(`Validating that each SARIF run has a unique category`);
validateUniqueCategory(sarif);
validateUniqueCategory(sarif, uploadTarget.sentinelPrefix);
logger.debug(`Serializing SARIF for upload`);
const sarifPayload = JSON.stringify(sarif);
logger.debug(`Compressing serialized SARIF`);
@@ -454,7 +482,7 @@ async function uploadFiles(inputSarifPath, checkoutPath, category, features, log
const numResultInSarif = countResultsInSarif(sarifPayload);
logger.debug(`Number of results in upload: ${numResultInSarif}`);
// Make the upload
const sarifID = await uploadPayload(payload, (0, repository_1.getRepositoryNwo)(), logger);
const sarifID = await uploadPayload(payload, (0, repository_1.getRepositoryNwo)(), logger, uploadTarget.target);
logger.endGroup();
return {
statusReport: {
@@ -588,7 +616,7 @@ function handleProcessingResultForUnsuccessfulExecution(response, status, logger
util.assertNever(status);
}
}
function validateUniqueCategory(sarif) {
function validateUniqueCategory(sarif, sentinelPrefix = exports.CodeScanningTarget.sentinelPrefix) {
// duplicate categories are allowed in the same sarif file
// but not across multiple sarif files
const categories = {};
@@ -599,7 +627,7 @@ function validateUniqueCategory(sarif) {
categories[category] = { id, tool };
}
for (const [category, { id, tool }] of Object.entries(categories)) {
const sentinelEnvVar = `CODEQL_UPLOAD_SARIF_${category}`;
const sentinelEnvVar = `${sentinelPrefix}${category}`;
if (process.env[sentinelEnvVar]) {
// This is always a configuration error, even for first-party runs.
throw new util_1.ConfigurationError("Aborting upload: only one run of the codeql/analyze or codeql/upload-sarif actions is allowed per job per tool/category. " +

File diff suppressed because one or more lines are too long

14
lib/upload-lib.test.js generated
View File

@@ -91,13 +91,21 @@ ava_1.default.beforeEach(() => {
fs.mkdirSync(path.join(tmpDir, "dir3"));
fs.symlinkSync(tmpDir, path.join(tmpDir, "dir3", "symlink1"), "dir");
fs.symlinkSync(path.join(tmpDir, "a.sarif"), path.join(tmpDir, "dir3", "symlink2.sarif"), "file");
const sarifFiles = uploadLib.findSarifFilesInDir(tmpDir);
// add some `.quality.sarif` files that should be ignored, unless we look for them specifically
fs.writeFileSync(path.join(tmpDir, "a.quality.sarif"), "");
fs.writeFileSync(path.join(tmpDir, "dir1", "b.quality.sarif"), "");
const sarifFiles = uploadLib.findSarifFilesInDir(tmpDir, uploadLib.CodeScanningTarget.sarifPredicate);
t.deepEqual(sarifFiles, [
path.join(tmpDir, "a.sarif"),
path.join(tmpDir, "b.sarif"),
path.join(tmpDir, "dir1", "d.sarif"),
path.join(tmpDir, "dir1", "dir2", "e.sarif"),
]);
const qualitySarifFiles = uploadLib.findSarifFilesInDir(tmpDir, uploadLib.CodeQualityTarget.sarifPredicate);
t.deepEqual(qualitySarifFiles, [
path.join(tmpDir, "a.quality.sarif"),
path.join(tmpDir, "dir1", "b.quality.sarif"),
]);
});
});
(0, ava_1.default)("populateRunAutomationDetails", (t) => {
@@ -194,6 +202,10 @@ ava_1.default.beforeEach(() => {
t.throws(() => uploadLib.validateUniqueCategory(sarif1));
t.throws(() => uploadLib.validateUniqueCategory(sarif2));
});
(0, ava_1.default)("validateUniqueCategory with different prefixes", (t) => {
t.notThrows(() => uploadLib.validateUniqueCategory(createMockSarif()));
t.notThrows(() => uploadLib.validateUniqueCategory(createMockSarif(), uploadLib.CodeQualityTarget.sentinelPrefix));
});
(0, ava_1.default)("accept results with invalid artifactLocation.uri value", (t) => {
const loggedMessages = [];
const mockLogger = {

File diff suppressed because one or more lines are too long

View File

@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const core = __importStar(require("@actions/core"));
const actionsUtil = __importStar(require("./actions-util"));
const actions_util_1 = require("./actions-util");
@@ -68,14 +69,28 @@ async function run() {
await (0, status_report_1.sendStatusReport)(startingStatusReportBase);
}
try {
const uploadResult = await upload_lib.uploadFiles(actionsUtil.getRequiredInput("sarif_file"), actionsUtil.getRequiredInput("checkout_path"), actionsUtil.getOptionalInput("category"), features, logger);
const sarifPath = actionsUtil.getRequiredInput("sarif_file");
const checkoutPath = actionsUtil.getRequiredInput("checkout_path");
const category = actionsUtil.getOptionalInput("category");
const uploadResult = await upload_lib.uploadFiles(sarifPath, checkoutPath, category, features, logger, upload_lib.CodeScanningTarget);
core.setOutput("sarif-id", uploadResult.sarifID);
// If there are `.quality.sarif` files in `sarifPath`, then upload those to the code quality service.
// Code quality can currently only be enabled on top of security, so we'd currently always expect to
// have a directory for the results here.
if (fs.lstatSync(sarifPath).isDirectory()) {
const qualitySarifFiles = upload_lib.findSarifFilesInDir(sarifPath, upload_lib.CodeQualityTarget.sarifPredicate);
if (qualitySarifFiles.length !== 0) {
await upload_lib.uploadSpecifiedFiles(qualitySarifFiles, checkoutPath, category, features, logger, upload_lib.CodeQualityTarget);
}
}
// We don't upload results in test mode, so don't wait for processing
if ((0, util_1.isInTestMode)()) {
core.debug("In test mode. Waiting for processing is disabled.");
}
else if (actionsUtil.getRequiredInput("wait-for-processing") === "true") {
await upload_lib.waitForProcessing((0, repository_1.getRepositoryNwo)(), uploadResult.sarifID, logger);
// The code quality service does not currently have an endpoint to wait for SARIF processing,
// so we can't wait for that here.
}
await sendSuccessStatusReport(startedAt, uploadResult.statusReport, logger);
}

View File

@@ -1 +1 @@
{"version":3,"file":"upload-sarif-action.js","sourceRoot":"","sources":["../src/upload-sarif-action.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAAsC;AAEtC,4DAA8C;AAC9C,iDAAyE;AACzE,6CAAgD;AAChD,mDAA2C;AAC3C,uCAAqD;AACrD,6CAAgD;AAChD,mDAOyB;AACzB,yDAA2C;AAC3C,iCAQgB;AAMhB,KAAK,UAAU,uBAAuB,CACpC,SAAe,EACf,WAA0C,EAC1C,MAAc;IAEd,MAAM,gBAAgB,GAAG,MAAM,IAAA,sCAAsB,EACnD,0BAAU,CAAC,WAAW,EACtB,SAAS,EACT,SAAS,EACT,SAAS,EACT,MAAM,IAAA,qBAAc,EAAC,MAAM,CAAC,EAC5B,MAAM,CACP,CAAC;IACF,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACnC,MAAM,YAAY,GAA4B;YAC5C,GAAG,gBAAgB;YACnB,GAAG,WAAW;SACf,CAAC;QACF,MAAM,IAAA,gCAAgB,EAAC,YAAY,CAAC,CAAC;IACvC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,GAAG;IAChB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,IAAA,0BAAgB,GAAE,CAAC;IAClC,IAAA,4BAAqB,EAAC,IAAA,+BAAgB,GAAE,CAAC,CAAC;IAE1C,MAAM,aAAa,GAAG,MAAM,IAAA,6BAAgB,GAAE,CAAC;IAC/C,IAAA,yBAAkB,EAAC,IAAA,+BAAgB,GAAE,EAAE,aAAa,CAAC,CAAC;IAEtD,6CAA6C;IAC7C,WAAW,CAAC,aAAa,EAAE,CAAC;IAE5B,MAAM,aAAa,GAAG,IAAA,6BAAgB,GAAE,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,wBAAQ,CAC3B,aAAa,EACb,aAAa,EACb,IAAA,oCAAqB,GAAE,EACvB,MAAM,CACP,CAAC;IAEF,MAAM,wBAAwB,GAAG,MAAM,IAAA,sCAAsB,EAC3D,0BAAU,CAAC,WAAW,EACtB,UAAU,EACV,SAAS,EACT,SAAS,EACT,MAAM,IAAA,qBAAc,EAAC,MAAM,CAAC,EAC5B,MAAM,CACP,CAAC;IACF,IAAI,wBAAwB,KAAK,SAAS,EAAE,CAAC;QAC3C,MAAM,IAAA,gCAAgB,EAAC,wBAAwB,CAAC,CAAC;IACnD,CAAC;IAED,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,MAAM,UAAU,CAAC,WAAW,CAC/C,WAAW,CAAC,gBAAgB,CAAC,YAAY,CAAC,EAC1C,WAAW,CAAC,gBAAgB,CAAC,eAAe,CAAC,EAC7C,WAAW,CAAC,gBAAgB,CAAC,UAAU,CAAC,EACxC,QAAQ,EACR,MAAM,CACP,CAAC;QACF,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;QAEjD,qEAAqE;QACrE,IAAI,IAAA,mBAAY,GAAE,EAAE,CAAC;YACnB,IAAI,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;QAClE,CAAC;aAAM,IAAI,WAAW,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,KAAK,MAAM,EAAE,CAAC;YAC1E,MAAM,UAAU,CAAC,iBAAiB,CAChC,IAAA,6BAAgB,GAAE,EAClB,YAAY,CAAC,OAAO,EACpB,MAAM,CACP,CAAC;QACJ,CAAC;QACD,MAAM,uBAAuB,CAAC,SAAS,EAAE,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC9E,CAAC;IAAC,OAAO,cAAc,EAAE,CAAC;QACxB,MAAM,KAAK,GACT,IAAA,oCAAoB,EAAC,0BAAU,CAAC,WAAW,CAAC;YAC5C,cAAc,YAAY,UAAU,CAAC,uBAAuB;YAC1D,CAAC,CAAC,IAAI,yBAAkB,CAAC,cAAc,CAAC,OAAO,CAAC;YAChD,CAAC,CAAC,IAAA,gBAAS,EAAC,cAAc,CAAC,CAAC;QAChC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QAC9B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAExB,MAAM,qBAAqB,GAAG,MAAM,IAAA,sCAAsB,EACxD,0BAAU,CAAC,WAAW,EACtB,IAAA,gCAAgB,EAAC,KAAK,CAAC,EACvB,SAAS,EACT,SAAS,EACT,MAAM,IAAA,qBAAc,EAAC,MAAM,CAAC,EAC5B,MAAM,EACN,OAAO,EACP,KAAK,CAAC,KAAK,CACZ,CAAC;QACF,IAAI,qBAAqB,KAAK,SAAS,EAAE,CAAC;YACxC,MAAM,IAAA,gCAAgB,EAAC,qBAAqB,CAAC,CAAC;QAChD,CAAC;QACD,OAAO;IACT,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU;IACvB,IAAI,CAAC;QACH,MAAM,GAAG,EAAE,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,SAAS,CACZ,sCAAsC,IAAA,sBAAe,EAAC,KAAK,CAAC,EAAE,CAC/D,CAAC;IACJ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,EAAE,CAAC"}
{"version":3,"file":"upload-sarif-action.js","sourceRoot":"","sources":["../src/upload-sarif-action.ts"],"names":[],"mappings":"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"}

2
node_modules/.package-lock.json generated vendored
View File

@@ -1,6 +1,6 @@
{
"name": "codeql",
"version": "3.29.1",
"version": "3.29.2",
"lockfileVersion": 3,
"requires": true,
"packages": {

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "codeql",
"version": "3.29.1",
"version": "3.29.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codeql",
"version": "3.29.1",
"version": "3.29.2",
"license": "MIT",
"dependencies": {
"@actions/artifact": "^2.3.1",

View File

@@ -1,6 +1,6 @@
{
"name": "codeql",
"version": "3.29.1",
"version": "3.29.2",
"private": true,
"description": "CodeQL action",
"scripts": {

View File

@@ -1,6 +1,30 @@
name: "Quality queries input"
description: "Tests that queries specified in the quality-queries input are used."
versions: ["linked", "nightly-latest"]
env:
CHECK_SCRIPT: |
const fs = require('fs');
const sarif = JSON.parse(fs.readFileSync(process.env['SARIF_PATH'], 'utf8'));
const expectPresent = JSON.parse(process.env['EXPECT_PRESENT']);
const run = sarif.runs[0];
const extensions = run.tool.extensions;
if (extensions === undefined) {
core.setFailed('`extensions` property not found in the SARIF run property bag.');
}
// ID of a query we want to check the presence for
const targetId = 'js/regex/always-matches';
const found = extensions.find(extension => extension.rules && extension.rules.find(rule => rule.id === targetId));
if (found && expectPresent) {
console.log(`Found rule with id '${targetId}'.`);
} else if (!found && !expectPresent) {
console.log(`Rule with id '${targetId}' was not found.`);
} else {
core.setFailed(`${ found ? "Found" : "Didn't find" } rule ${targetId}`);
}
steps:
- uses: ./../action/init
with:
@@ -11,35 +35,29 @@ steps:
with:
output: "${{ runner.temp }}/results"
upload-database: false
- name: Upload SARIF
- name: Upload security SARIF
uses: actions/upload-artifact@v4
with:
name: config-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
name: quality-queries-${{ matrix.os }}-${{ matrix.version }}.sarif.json
path: "${{ runner.temp }}/results/javascript.sarif"
retention-days: 7
- name: Check config properties appear in SARIF
- name: Upload quality SARIF
uses: actions/upload-artifact@v4
with:
name: quality-queries-${{ matrix.os }}-${{ matrix.version }}.quality.sarif.json
path: "${{ runner.temp }}/results/javascript.quality.sarif"
retention-days: 7
- name: Check quality query does not appear in security SARIF
uses: actions/github-script@v7
env:
SARIF_PATH: "${{ runner.temp }}/results/javascript.sarif"
EXPECT_PRESENT: "false"
with:
script: |
const fs = require('fs');
const sarif = JSON.parse(fs.readFileSync(process.env['SARIF_PATH'], 'utf8'));
const run = sarif.runs[0];
const configSummary = run.properties.codeqlConfigSummary;
if (configSummary === undefined) {
core.setFailed('`codeqlConfigSummary` property not found in the SARIF run property bag.');
}
if (configSummary.disableDefaultQueries !== false) {
core.setFailed('`disableDefaultQueries` property incorrect: expected false, got ' +
`${JSON.stringify(configSummary.disableDefaultQueries)}.`);
}
const expectedQueries = [{ type: 'builtinSuite', uses: 'code-quality' }];
// Use JSON.stringify to deep-equal the arrays.
if (JSON.stringify(configSummary.queries) !== JSON.stringify(expectedQueries)) {
core.setFailed(`\`queries\` property incorrect: expected ${JSON.stringify(expectedQueries)}, got ` +
`${JSON.stringify(configSummary.queries)}.`);
}
core.info('Finished config export tests.');
script: ${{ env.CHECK_SCRIPT }}
- name: Check quality query appears in quality SARIF
uses: actions/github-script@v7
env:
SARIF_PATH: "${{ runner.temp }}/results/javascript.quality.sarif"
EXPECT_PRESENT: "true"
with:
script: ${{ env.CHECK_SCRIPT }}

View File

@@ -0,0 +1,24 @@
name: "Upload-sarif: code quality endpoint"
description: "Checks that uploading SARIFs to the code quality endpoint works"
versions: ["default"]
installGo: "true"
steps:
- uses: ./../action/init
with:
tools: ${{ steps.prepare-test.outputs.tools-url }}
languages: cpp,csharp,java,javascript,python
config-file: ${{ github.repository }}/tests/multi-language-repo/.github/codeql/custom-queries.yml@${{ github.sha }}
quality-queries: code-quality
- name: Build code
shell: bash
run: ./build.sh
# Generate some SARIF we can upload with the upload-sarif step
- uses: ./../action/analyze
with:
ref: 'refs/heads/main'
sha: '5e235361806c361d4d3f8859e3c897658025a9a2'
upload: never
- uses: ./../action/upload-sarif
with:
ref: 'refs/heads/main'
sha: '5e235361806c361d4d3f8859e3c897658025a9a2'

View File

@@ -327,8 +327,21 @@ async function run() {
actionsUtil.getOptionalInput("category"),
features,
logger,
uploadLib.CodeScanningTarget,
);
core.setOutput("sarif-id", uploadResult.sarifID);
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
const qualityUploadResult = await uploadLib.uploadFiles(
outputDir,
actionsUtil.getRequiredInput("checkout_path"),
actionsUtil.getOptionalInput("category"),
features,
logger,
uploadLib.CodeQualityTarget,
);
core.setOutput("quality-sarif-id", qualityUploadResult.sarifID);
}
} else {
logger.info("Not uploading results");
}

View File

@@ -5,7 +5,12 @@ import test from "ava";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import { exportedForTesting, runQueries } from "./analyze";
import {
exportedForTesting,
runQueries,
defaultSuites,
resolveQuerySuiteAlias,
} from "./analyze";
import { setCodeQL } from "./codeql";
import { Feature } from "./feature-flags";
import { Language } from "./languages";
@@ -320,3 +325,25 @@ test("getDiffRanges: malformed thunk header", async (t) => {
const diffRanges = runGetDiffRanges(2, ["@@ 30 +50,2 @@", "+1", "+2"]);
t.deepEqual(diffRanges, undefined);
});
test("resolveQuerySuiteAlias", (t) => {
// default query suite names should resolve to something language-specific ending in `.qls`.
for (const suite of defaultSuites) {
const resolved = resolveQuerySuiteAlias(Language.go, suite);
t.assert(
resolved.endsWith(".qls"),
"Resolved default suite doesn't end in .qls",
);
t.assert(
resolved.indexOf(Language.go) >= 0,
"Resolved default suite doesn't contain language name",
);
}
// other inputs should be returned unchanged
const names = ["foo", "bar", "codeql/go-queries@1.0"];
for (const name of names) {
t.deepEqual(resolveQuerySuiteAlias(Language.go, name), name);
}
});

View File

@@ -561,6 +561,34 @@ extensions:
return diffRangeDir;
}
// A set of default query suite names that are understood by the CLI.
export const defaultSuites: Set<string> = new Set([
"security-experimental",
"security-extended",
"security-and-quality",
"code-quality",
"code-scanning",
]);
/**
* If `maybeSuite` is the name of a default query suite, it is resolved into the corresponding
* query suite name for the given `language`. Otherwise, `maybeSuite` is returned as is.
*
* @param language The language for which to resolve the default query suite name.
* @param maybeSuite The string that potentially contains the name of a default query suite.
* @returns Returns the resolved query suite name, or the unmodified input.
*/
export function resolveQuerySuiteAlias(
language: Language,
maybeSuite: string,
): string {
if (defaultSuites.has(maybeSuite)) {
return `${language}-${maybeSuite}.qls`;
}
return maybeSuite;
}
// Runs queries and creates sarif files in the given folder
export async function runQueries(
sarifFolder: string,
@@ -596,6 +624,22 @@ export async function runQueries(
try {
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
const queries: string[] = [];
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
queries.push(
path.join(
util.getCodeQLDatabasePath(config, language),
"temp",
"config-queries.qls",
),
);
for (const qualityQuery of config.augmentationProperties
.qualityQueriesInput) {
queries.push(resolveQuerySuiteAlias(language, qualityQuery.uses));
}
}
// The work needed to generate the query suites
// is done in the CLI. We just need to make a single
// call to run all the queries for each language and
@@ -603,7 +647,7 @@ export async function runQueries(
logger.startGroup(`Running queries for ${language}`);
const startTimeRunQueries = new Date().getTime();
const databasePath = util.getCodeQLDatabasePath(config, language);
await codeql.databaseRunQueries(databasePath, queryFlags);
await codeql.databaseRunQueries(databasePath, queryFlags, queries);
logger.debug(`Finished running queries for ${language}.`);
// TODO should not be using `builtin` here. We should be using `all` instead.
// The status report does not support `all` yet.
@@ -618,6 +662,24 @@ export async function runQueries(
sarifFile,
config.debugMode,
);
if (config.augmentationProperties.qualityQueriesInput !== undefined) {
logger.info(`Interpreting quality results for ${language}`);
const qualitySarifFile = path.join(
sarifFolder,
`${language}.quality.sarif`,
);
const qualityAnalysisSummary = await runInterpretResults(
language,
config.augmentationProperties.qualityQueriesInput.map((i) =>
resolveQuerySuiteAlias(language, i.uses),
),
qualitySarifFile,
config.debugMode,
);
// TODO: move
logger.info(qualityAnalysisSummary);
}
const endTimeInterpretResults = new Date();
statusReport[`interpret_results_${language}_duration_ms`] =
endTimeInterpretResults.getTime() - startTimeInterpretResults.getTime();

View File

@@ -157,9 +157,15 @@ export interface CodeQL {
dbName: string,
): Promise<void>;
/**
* Run 'codeql database run-queries'.
* Run 'codeql database run-queries'. If no `queries` are specified, then the CLI
* will automatically use the `config-queries.qls` (if it exists) or default queries
* for the language.
*/
databaseRunQueries(databasePath: string, flags: string[]): Promise<void>;
databaseRunQueries(
databasePath: string,
flags: string[],
queries?: string[],
): Promise<void>;
/**
* Run 'codeql database interpret-results'.
*/
@@ -806,6 +812,7 @@ export async function getCodeQLForCmd(
async databaseRunQueries(
databasePath: string,
flags: string[],
queries: string[] = [],
): Promise<void> {
const codeqlArgs = [
"database",
@@ -815,6 +822,7 @@ export async function getCodeQLForCmd(
"--intra-layer-parallelism",
"--min-disk-free=1024", // Try to leave at least 1GB free
"-v",
...queries,
...getExtraOptionsFromEnv(["database", "run-queries"], {
ignoringOptions: ["--expect-discarded-cache"],
}),
@@ -1219,20 +1227,13 @@ async function generateCodeScanningConfig(
const augmentedConfig = cloneObject(config.originalUserInput);
// Inject the queries from the input
if (
config.augmentationProperties.queriesInput ||
config.augmentationProperties.qualityQueriesInput
) {
const queryInputs = (
config.augmentationProperties.queriesInput || []
).concat(config.augmentationProperties.qualityQueriesInput || []);
if (config.augmentationProperties.queriesInput) {
if (config.augmentationProperties.queriesInputCombines) {
augmentedConfig.queries = (augmentedConfig.queries || []).concat(
queryInputs,
config.augmentationProperties.queriesInput,
);
} else {
augmentedConfig.queries = queryInputs;
augmentedConfig.queries = config.augmentationProperties.queriesInput;
}
}
if (augmentedConfig.queries?.length === 0) {

View File

@@ -104,6 +104,7 @@ async function maybeUploadFailedSarif(
category,
features,
logger,
uploadLib.CodeScanningTarget,
);
await uploadLib.waitForProcessing(
repositoryNwo,

View File

@@ -122,7 +122,14 @@ test("finding SARIF files", async (t) => {
"file",
);
const sarifFiles = uploadLib.findSarifFilesInDir(tmpDir);
// add some `.quality.sarif` files that should be ignored, unless we look for them specifically
fs.writeFileSync(path.join(tmpDir, "a.quality.sarif"), "");
fs.writeFileSync(path.join(tmpDir, "dir1", "b.quality.sarif"), "");
const sarifFiles = uploadLib.findSarifFilesInDir(
tmpDir,
uploadLib.CodeScanningTarget.sarifPredicate,
);
t.deepEqual(sarifFiles, [
path.join(tmpDir, "a.sarif"),
@@ -130,6 +137,16 @@ test("finding SARIF files", async (t) => {
path.join(tmpDir, "dir1", "d.sarif"),
path.join(tmpDir, "dir1", "dir2", "e.sarif"),
]);
const qualitySarifFiles = uploadLib.findSarifFilesInDir(
tmpDir,
uploadLib.CodeQualityTarget.sarifPredicate,
);
t.deepEqual(qualitySarifFiles, [
path.join(tmpDir, "a.quality.sarif"),
path.join(tmpDir, "dir1", "b.quality.sarif"),
]);
});
});
@@ -313,6 +330,16 @@ test("validateUniqueCategory for multiple runs", (t) => {
t.throws(() => uploadLib.validateUniqueCategory(sarif2));
});
test("validateUniqueCategory with different prefixes", (t) => {
t.notThrows(() => uploadLib.validateUniqueCategory(createMockSarif()));
t.notThrows(() =>
uploadLib.validateUniqueCategory(
createMockSarif(),
uploadLib.CodeQualityTarget.sentinelPrefix,
),
);
});
test("accept results with invalid artifactLocation.uri value", (t) => {
const loggedMessages: string[] = [];
const mockLogger = {

View File

@@ -306,12 +306,19 @@ function getAutomationID(
return api.computeAutomationID(analysis_key, environment);
}
// Enumerates API endpoints that accept SARIF files.
export enum SARIF_UPLOAD_ENDPOINT {
CODE_SCANNING = "PUT /repos/:owner/:repo/code-scanning/analysis",
CODE_QUALITY = "PUT /repos/:owner/:repo/code-quality/analysis",
}
// Upload the given payload.
// If the request fails then this will retry a small number of times.
async function uploadPayload(
payload: any,
repositoryNwo: RepositoryNwo,
logger: Logger,
target: SARIF_UPLOAD_ENDPOINT,
): Promise<string> {
logger.info("Uploading results");
@@ -332,14 +339,11 @@ async function uploadPayload(
const client = api.getApiClient();
try {
const response = await client.request(
"PUT /repos/:owner/:repo/code-scanning/analysis",
{
owner: repositoryNwo.owner,
repo: repositoryNwo.repo,
data: payload,
},
);
const response = await client.request(target, {
owner: repositoryNwo.owner,
repo: repositoryNwo.repo,
data: payload,
});
logger.debug(`response status: ${response.status}`);
logger.info("Successfully uploaded results");
@@ -378,12 +382,15 @@ export interface UploadResult {
// Recursively walks a directory and returns all SARIF files it finds.
// Does not follow symlinks.
export function findSarifFilesInDir(sarifPath: string): string[] {
export function findSarifFilesInDir(
sarifPath: string,
isSarif: (name: string) => boolean,
): string[] {
const sarifFiles: string[] = [];
const walkSarifFiles = (dir: string) => {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile() && entry.name.endsWith(".sarif")) {
if (entry.isFile() && isSarif(entry.name)) {
sarifFiles.push(path.resolve(dir, entry.name));
} else if (entry.isDirectory()) {
walkSarifFiles(path.resolve(dir, entry.name));
@@ -394,7 +401,10 @@ export function findSarifFilesInDir(sarifPath: string): string[] {
return sarifFiles;
}
function getSarifFilePaths(sarifPath: string) {
export function getSarifFilePaths(
sarifPath: string,
isSarif: (name: string) => boolean,
) {
if (!fs.existsSync(sarifPath)) {
// This is always a configuration error, even for first-party runs.
throw new ConfigurationError(`Path does not exist: ${sarifPath}`);
@@ -402,7 +412,7 @@ function getSarifFilePaths(sarifPath: string) {
let sarifFiles: string[];
if (fs.lstatSync(sarifPath).isDirectory()) {
sarifFiles = findSarifFilesInDir(sarifPath);
sarifFiles = findSarifFilesInDir(sarifPath, isSarif);
if (sarifFiles.length === 0) {
// This is always a configuration error, even for first-party runs.
throw new ConfigurationError(
@@ -567,6 +577,31 @@ export function buildPayload(
return payloadObj;
}
// Represents configurations for different services that we can upload SARIF to.
export interface UploadTarget {
name: string;
target: SARIF_UPLOAD_ENDPOINT;
sarifPredicate: (name: string) => boolean;
sentinelPrefix: string;
}
// Represents the Code Scanning upload target.
export const CodeScanningTarget: UploadTarget = {
name: "code scanning",
target: SARIF_UPLOAD_ENDPOINT.CODE_SCANNING,
sarifPredicate: (name) =>
name.endsWith(".sarif") && !CodeQualityTarget.sarifPredicate(name),
sentinelPrefix: "CODEQL_UPLOAD_SARIF_",
};
// Represents the Code Quality upload target.
export const CodeQualityTarget: UploadTarget = {
name: "code quality",
target: SARIF_UPLOAD_ENDPOINT.CODE_QUALITY,
sarifPredicate: (name) => name.endsWith(".quality.sarif"),
sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_",
};
/**
* Uploads a single SARIF file or a directory of SARIF files depending on what `inputSarifPath` refers
* to.
@@ -577,10 +612,35 @@ export async function uploadFiles(
category: string | undefined,
features: FeatureEnablement,
logger: Logger,
uploadTarget: UploadTarget,
): Promise<UploadResult> {
const sarifPaths = getSarifFilePaths(inputSarifPath);
const sarifPaths = getSarifFilePaths(
inputSarifPath,
uploadTarget.sarifPredicate,
);
logger.startGroup("Uploading results");
return uploadSpecifiedFiles(
sarifPaths,
checkoutPath,
category,
features,
logger,
uploadTarget,
);
}
/**
* Uploads the given array of SARIF files.
*/
export async function uploadSpecifiedFiles(
sarifPaths: string[],
checkoutPath: string,
category: string | undefined,
features: FeatureEnablement,
logger: Logger,
uploadTarget: UploadTarget = CodeScanningTarget,
): Promise<UploadResult> {
logger.startGroup(`Uploading ${uploadTarget.name} results`);
logger.info(`Processing sarif files: ${JSON.stringify(sarifPaths)}`);
const gitHubVersion = await getGitHubVersion();
@@ -621,7 +681,7 @@ export async function uploadFiles(
const toolNames = util.getToolNames(sarif);
logger.debug(`Validating that each SARIF run has a unique category`);
validateUniqueCategory(sarif);
validateUniqueCategory(sarif, uploadTarget.sentinelPrefix);
logger.debug(`Serializing SARIF for upload`);
const sarifPayload = JSON.stringify(sarif);
logger.debug(`Compressing serialized SARIF`);
@@ -651,7 +711,12 @@ export async function uploadFiles(
logger.debug(`Number of results in upload: ${numResultInSarif}`);
// Make the upload
const sarifID = await uploadPayload(payload, getRepositoryNwo(), logger);
const sarifID = await uploadPayload(
payload,
getRepositoryNwo(),
logger,
uploadTarget.target,
);
logger.endGroup();
@@ -833,7 +898,10 @@ function handleProcessingResultForUnsuccessfulExecution(
}
}
export function validateUniqueCategory(sarif: SarifFile): void {
export function validateUniqueCategory(
sarif: SarifFile,
sentinelPrefix: string = CodeScanningTarget.sentinelPrefix,
): void {
// duplicate categories are allowed in the same sarif file
// but not across multiple sarif files
const categories = {} as Record<string, { id?: string; tool?: string }>;
@@ -846,7 +914,7 @@ export function validateUniqueCategory(sarif: SarifFile): void {
}
for (const [category, { id, tool }] of Object.entries(categories)) {
const sentinelEnvVar = `CODEQL_UPLOAD_SARIF_${category}`;
const sentinelEnvVar = `${sentinelPrefix}${category}`;
if (process.env[sentinelEnvVar]) {
// This is always a configuration error, even for first-party runs.
throw new ConfigurationError(

View File

@@ -1,3 +1,5 @@
import * as fs from "fs";
import * as core from "@actions/core";
import * as actionsUtil from "./actions-util";
@@ -83,15 +85,41 @@ async function run() {
}
try {
const sarifPath = actionsUtil.getRequiredInput("sarif_file");
const checkoutPath = actionsUtil.getRequiredInput("checkout_path");
const category = actionsUtil.getOptionalInput("category");
const uploadResult = await upload_lib.uploadFiles(
actionsUtil.getRequiredInput("sarif_file"),
actionsUtil.getRequiredInput("checkout_path"),
actionsUtil.getOptionalInput("category"),
sarifPath,
checkoutPath,
category,
features,
logger,
upload_lib.CodeScanningTarget,
);
core.setOutput("sarif-id", uploadResult.sarifID);
// If there are `.quality.sarif` files in `sarifPath`, then upload those to the code quality service.
// Code quality can currently only be enabled on top of security, so we'd currently always expect to
// have a directory for the results here.
if (fs.lstatSync(sarifPath).isDirectory()) {
const qualitySarifFiles = upload_lib.findSarifFilesInDir(
sarifPath,
upload_lib.CodeQualityTarget.sarifPredicate,
);
if (qualitySarifFiles.length !== 0) {
await upload_lib.uploadSpecifiedFiles(
qualitySarifFiles,
checkoutPath,
category,
features,
logger,
upload_lib.CodeQualityTarget,
);
}
}
// We don't upload results in test mode, so don't wait for processing
if (isInTestMode()) {
core.debug("In test mode. Waiting for processing is disabled.");
@@ -101,6 +129,8 @@ async function run() {
uploadResult.sarifID,
logger,
);
// The code quality service does not currently have an endpoint to wait for SARIF processing,
// so we can't wait for that here.
}
await sendSuccessStatusReport(startedAt, uploadResult.statusReport, logger);
} catch (unwrappedError) {