Merge branch 'master' into analysisName
This commit is contained in:
commit
52cd1f2261
15 changed files with 279 additions and 75 deletions
12
README.md
12
README.md
|
|
@ -2,6 +2,12 @@
|
||||||
|
|
||||||
This action runs GitHub's industry-leading static analysis engine, CodeQL, against a repository's source code to find security vulnerabilities. It then automatically uploads the results to GitHub so they can be displayed in the repository's security tab. CodeQL runs an extensible set of [queries](https://github.com/semmle/ql), which have been developed by the community and the [GitHub Security Lab](https://securitylab.github.com/) to find common vulnerabilities in your code.
|
This action runs GitHub's industry-leading static analysis engine, CodeQL, against a repository's source code to find security vulnerabilities. It then automatically uploads the results to GitHub so they can be displayed in the repository's security tab. CodeQL runs an extensible set of [queries](https://github.com/semmle/ql), which have been developed by the community and the [GitHub Security Lab](https://securitylab.github.com/) to find common vulnerabilities in your code.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is released under the [MIT License](LICENSE).
|
||||||
|
|
||||||
|
The underlying CodeQL CLI, used in this action, is licensed under the [GitHub CodeQL Terms and Conditions](https://securitylab.github.com/tools/codeql/license). As such, this action may be used on open source projects hosted on GitHub, and on private repositories that are owned by an organisation with GitHub Advanced Security enabled.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
To get code scanning results from CodeQL analysis on your repo you can use the following workflow as a template:
|
To get code scanning results from CodeQL analysis on your repo you can use the following workflow as a template:
|
||||||
|
|
@ -137,7 +143,7 @@ env:
|
||||||
|
|
||||||
to `github/codeql-action/analyze`.
|
to `github/codeql-action/analyze`.
|
||||||
|
|
||||||
### If you do not use a vendor directory
|
#### If you do not use a vendor directory
|
||||||
|
|
||||||
Dependencies on public repositories should just work. If you have dependencies on private repositories, one option is to use `git config` and a [personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) to authenticate when downloading dependencies. Add a section like
|
Dependencies on public repositories should just work. If you have dependencies on private repositories, one option is to use `git config` and a [personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) to authenticate when downloading dependencies. Add a section like
|
||||||
|
|
||||||
|
|
@ -163,6 +169,6 @@ dotnet build /p:UseSharedCompilation=false
|
||||||
|
|
||||||
Version 3 does not require the additional flag.
|
Version 3 does not require the additional flag.
|
||||||
|
|
||||||
## License
|
### Analysing Go together with other languages on `macos-latest`
|
||||||
|
|
||||||
This project is released under the [MIT License](LICENSE).
|
When running on macos it is currently not possible to analyze Go in conjunction with any of Java, C/C++, or C#. Each language can still be analyzed separately.
|
||||||
10
lib/autobuild.js
generated
10
lib/autobuild.js
generated
|
|
@ -22,12 +22,16 @@ async function run() {
|
||||||
// We want pick the dominant language in the repo from the ones we're able to build
|
// We want pick the dominant language in the repo from the ones we're able to build
|
||||||
// The languages are sorted in order specified by user or by lines of code if we got
|
// The languages are sorted in order specified by user or by lines of code if we got
|
||||||
// them from the GitHub API, so try to build the first language on the list.
|
// them from the GitHub API, so try to build the first language on the list.
|
||||||
const language = (_a = process.env[sharedEnv.CODEQL_ACTION_TRACED_LANGUAGES]) === null || _a === void 0 ? void 0 : _a.split(',')[0];
|
const autobuildLanguages = ((_a = process.env[sharedEnv.CODEQL_ACTION_TRACED_LANGUAGES]) === null || _a === void 0 ? void 0 : _a.split(',')) || [];
|
||||||
|
const language = autobuildLanguages[0];
|
||||||
if (!language) {
|
if (!language) {
|
||||||
core.info("None of the languages in this project require extra build steps");
|
core.info("None of the languages in this project require extra build steps");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
core.debug(`Detected dominant traced language: ${language}`);
|
core.debug(`Detected dominant traced language: ${language}`);
|
||||||
|
if (autobuildLanguages.length > 1) {
|
||||||
|
core.warning(`We will only automatically build ${language} code. If you wish to scan ${autobuildLanguages.slice(1).join(' and ')}, you must replace this block with custom build steps.`);
|
||||||
|
}
|
||||||
core.startGroup(`Attempting to automatically build ${language} code`);
|
core.startGroup(`Attempting to automatically build ${language} code`);
|
||||||
// TODO: share config accross actions better via env variables
|
// TODO: share config accross actions better via env variables
|
||||||
const codeqlCmd = util.getRequiredEnvParam(sharedEnv.CODEQL_ACTION_CMD);
|
const codeqlCmd = util.getRequiredEnvParam(sharedEnv.CODEQL_ACTION_CMD);
|
||||||
|
|
@ -44,13 +48,13 @@ async function run() {
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
core.setFailed(error.message);
|
core.setFailed("We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. " + error.message);
|
||||||
await util.reportActionFailed('autobuild', error.message, error.stack);
|
await util.reportActionFailed('autobuild', error.message, error.stack);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await util.reportActionSucceeded('autobuild');
|
await util.reportActionSucceeded('autobuild');
|
||||||
}
|
}
|
||||||
run().catch(e => {
|
run().catch(e => {
|
||||||
core.setFailed("autobuild action failed: " + e);
|
core.setFailed("autobuild action failed. " + e);
|
||||||
console.log(e);
|
console.log(e);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
3
lib/external-queries.js
generated
3
lib/external-queries.js
generated
|
|
@ -11,8 +11,9 @@ const core = __importStar(require("@actions/core"));
|
||||||
const exec = __importStar(require("@actions/exec"));
|
const exec = __importStar(require("@actions/exec"));
|
||||||
const fs = __importStar(require("fs"));
|
const fs = __importStar(require("fs"));
|
||||||
const path = __importStar(require("path"));
|
const path = __importStar(require("path"));
|
||||||
|
const util = __importStar(require("./util"));
|
||||||
async function checkoutExternalQueries(config) {
|
async function checkoutExternalQueries(config) {
|
||||||
const folder = process.env['RUNNER_WORKSPACE'] || '/tmp/codeql-action';
|
const folder = util.getRequiredEnvParam('RUNNER_WORKSPACE');
|
||||||
for (const externalQuery of config.externalQueries) {
|
for (const externalQuery of config.externalQueries) {
|
||||||
core.info('Checking out ' + externalQuery.repository);
|
core.info('Checking out ' + externalQuery.repository);
|
||||||
const checkoutLocation = path.join(folder, externalQuery.repository);
|
const checkoutLocation = path.join(folder, externalQuery.repository);
|
||||||
|
|
|
||||||
5
lib/finalize-db.js
generated
5
lib/finalize-db.js
generated
|
|
@ -125,7 +125,10 @@ async function run() {
|
||||||
core.info('Analyzing database');
|
core.info('Analyzing database');
|
||||||
await runQueries(codeqlCmd, databaseFolder, sarifFolder, config);
|
await runQueries(codeqlCmd, databaseFolder, sarifFolder, config);
|
||||||
if ('true' === core.getInput('upload')) {
|
if ('true' === core.getInput('upload')) {
|
||||||
await upload_lib.upload(sarifFolder);
|
if (!await upload_lib.upload(sarifFolder)) {
|
||||||
|
await util.reportActionFailed('failed', 'upload');
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
|
|
||||||
87
lib/upload-lib.js
generated
87
lib/upload-lib.js
generated
|
|
@ -54,23 +54,77 @@ function combineSarifFiles(sarifFiles) {
|
||||||
return JSON.stringify(combinedSarif);
|
return JSON.stringify(combinedSarif);
|
||||||
}
|
}
|
||||||
exports.combineSarifFiles = combineSarifFiles;
|
exports.combineSarifFiles = combineSarifFiles;
|
||||||
|
// Upload the given payload.
|
||||||
|
// If the request fails then this will retry a small number of times.
|
||||||
|
async function uploadPayload(payload) {
|
||||||
|
core.info('Uploading results');
|
||||||
|
const githubToken = core.getInput('token');
|
||||||
|
const ph = new auth.BearerCredentialHandler(githubToken);
|
||||||
|
const client = new http.HttpClient('Code Scanning : Upload SARIF', [ph]);
|
||||||
|
const url = 'https://api.github.com/repos/' + process.env['GITHUB_REPOSITORY'] + '/code-scanning/analysis';
|
||||||
|
// Make up to 4 attempts to upload, and sleep for these
|
||||||
|
// number of seconds between each attempt.
|
||||||
|
// We don't want to backoff too much to avoid wasting action
|
||||||
|
// minutes, but just waiting a little bit could maybe help.
|
||||||
|
const backoffPeriods = [1, 5, 15];
|
||||||
|
for (let attempt = 0; attempt <= backoffPeriods.length; attempt++) {
|
||||||
|
const res = await client.put(url, payload);
|
||||||
|
core.debug('response status: ' + res.message.statusCode);
|
||||||
|
const statusCode = res.message.statusCode;
|
||||||
|
if (statusCode === 202) {
|
||||||
|
core.info("Successfully uploaded results");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const requestID = res.message.headers["x-github-request-id"];
|
||||||
|
// On any other status code that's not 5xx mark the upload as failed
|
||||||
|
if (!statusCode || statusCode < 500 || statusCode >= 600) {
|
||||||
|
core.setFailed('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// On a 5xx status code we may retry the request
|
||||||
|
if (attempt < backoffPeriods.length) {
|
||||||
|
// Log the failure as a warning but don't mark the action as failed yet
|
||||||
|
core.warning('Upload attempt (' + (attempt + 1) + ' of ' + (backoffPeriods.length + 1) +
|
||||||
|
') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] +
|
||||||
|
' seconds: (' + statusCode + ') ' + await res.readBody());
|
||||||
|
// Sleep for the backoff period
|
||||||
|
await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If the upload fails with 5xx then we assume it is a temporary problem
|
||||||
|
// and not an error that the user has caused or can fix.
|
||||||
|
// We avoid marking the job as failed to avoid breaking CI workflows.
|
||||||
|
core.error('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// Uploads a single sarif file or a directory of sarif files
|
// Uploads a single sarif file or a directory of sarif files
|
||||||
// depending on what the path happens to refer to.
|
// depending on what the path happens to refer to.
|
||||||
|
// Returns true iff the upload occurred and succeeded
|
||||||
async function upload(input) {
|
async function upload(input) {
|
||||||
if (fs.lstatSync(input).isDirectory()) {
|
if (fs.lstatSync(input).isDirectory()) {
|
||||||
const sarifFiles = fs.readdirSync(input)
|
const sarifFiles = fs.readdirSync(input)
|
||||||
.filter(f => f.endsWith(".sarif"))
|
.filter(f => f.endsWith(".sarif"))
|
||||||
.map(f => path.resolve(input, f));
|
.map(f => path.resolve(input, f));
|
||||||
await uploadFiles(sarifFiles);
|
if (sarifFiles.length === 0) {
|
||||||
|
core.setFailed("No SARIF files found to upload in \"" + input + "\".");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return await uploadFiles(sarifFiles);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await uploadFiles([input]);
|
return await uploadFiles([input]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.upload = upload;
|
exports.upload = upload;
|
||||||
// Uploads the given set of sarif files.
|
// Uploads the given set of sarif files.
|
||||||
|
// Returns true iff the upload occurred and succeeded
|
||||||
async function uploadFiles(sarifFiles) {
|
async function uploadFiles(sarifFiles) {
|
||||||
core.startGroup("Uploading results");
|
core.startGroup("Uploading results");
|
||||||
|
let succeeded = false;
|
||||||
try {
|
try {
|
||||||
// Check if an upload has happened before. If so then abort.
|
// Check if an upload has happened before. If so then abort.
|
||||||
// This is intended to catch when the finish and upload-sarif actions
|
// This is intended to catch when the finish and upload-sarif actions
|
||||||
|
|
@ -78,7 +132,7 @@ async function uploadFiles(sarifFiles) {
|
||||||
const sentinelFile = await getSentinelFilePath();
|
const sentinelFile = await getSentinelFilePath();
|
||||||
if (fs.existsSync(sentinelFile)) {
|
if (fs.existsSync(sentinelFile)) {
|
||||||
core.info("Aborting as an upload has already happened from this job");
|
core.info("Aborting as an upload has already happened from this job");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
const commitOid = util.getRequiredEnvParam('GITHUB_SHA');
|
const commitOid = util.getRequiredEnvParam('GITHUB_SHA');
|
||||||
const workflowRunIDStr = util.getRequiredEnvParam('GITHUB_RUN_ID');
|
const workflowRunIDStr = util.getRequiredEnvParam('GITHUB_RUN_ID');
|
||||||
|
|
@ -86,7 +140,7 @@ async function uploadFiles(sarifFiles) {
|
||||||
const analysisKey = await util.getAnalysisKey();
|
const analysisKey = await util.getAnalysisKey();
|
||||||
const analysisName = util.getRequiredEnvParam('GITHUB_WORKFLOW');
|
const analysisName = util.getRequiredEnvParam('GITHUB_WORKFLOW');
|
||||||
const startedAt = process.env[sharedEnv.CODEQL_ACTION_STARTED_AT];
|
const startedAt = process.env[sharedEnv.CODEQL_ACTION_STARTED_AT];
|
||||||
core.debug("Uploading sarif files: " + JSON.stringify(sarifFiles));
|
core.info("Uploading sarif files: " + JSON.stringify(sarifFiles));
|
||||||
let sarifPayload = combineSarifFiles(sarifFiles);
|
let sarifPayload = combineSarifFiles(sarifFiles);
|
||||||
sarifPayload = fingerprints.addFingerprints(sarifPayload);
|
sarifPayload = fingerprints.addFingerprints(sarifPayload);
|
||||||
const zipped_sarif = zlib_1.default.gzipSync(sarifPayload).toString('base64');
|
const zipped_sarif = zlib_1.default.gzipSync(sarifPayload).toString('base64');
|
||||||
|
|
@ -95,7 +149,7 @@ async function uploadFiles(sarifFiles) {
|
||||||
const workflowRunID = parseInt(workflowRunIDStr, 10);
|
const workflowRunID = parseInt(workflowRunIDStr, 10);
|
||||||
if (Number.isNaN(workflowRunID)) {
|
if (Number.isNaN(workflowRunID)) {
|
||||||
core.setFailed('GITHUB_RUN_ID must define a non NaN workflow run ID');
|
core.setFailed('GITHUB_RUN_ID must define a non NaN workflow run ID');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
let matrix = core.getInput('matrix');
|
let matrix = core.getInput('matrix');
|
||||||
if (matrix === "null" || matrix === "") {
|
if (matrix === "null" || matrix === "") {
|
||||||
|
|
@ -114,26 +168,8 @@ async function uploadFiles(sarifFiles) {
|
||||||
"started_at": startedAt,
|
"started_at": startedAt,
|
||||||
"tool_names": toolNames,
|
"tool_names": toolNames,
|
||||||
});
|
});
|
||||||
core.info('Uploading results');
|
// Make the upload
|
||||||
const githubToken = core.getInput('token');
|
succeeded = await uploadPayload(payload);
|
||||||
const ph = new auth.BearerCredentialHandler(githubToken);
|
|
||||||
const client = new http.HttpClient('Code Scanning : Upload SARIF', [ph]);
|
|
||||||
const url = 'https://api.github.com/repos/' + process.env['GITHUB_REPOSITORY'] + '/code-scanning/analysis';
|
|
||||||
const res = await client.put(url, payload);
|
|
||||||
const requestID = res.message.headers["x-github-request-id"];
|
|
||||||
core.debug('response status: ' + res.message.statusCode);
|
|
||||||
if (res.message.statusCode === 500) {
|
|
||||||
// If the upload fails with 500 then we assume it is a temporary problem
|
|
||||||
// with turbo-scan and not an error that the user has caused or can fix.
|
|
||||||
// We avoid marking the job as failed to avoid breaking CI workflows.
|
|
||||||
core.error('Upload failed (' + requestID + '): ' + await res.readBody());
|
|
||||||
}
|
|
||||||
else if (res.message.statusCode !== 202) {
|
|
||||||
core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
core.info("Successfully uploaded results");
|
|
||||||
}
|
|
||||||
// Mark that we have made an upload
|
// Mark that we have made an upload
|
||||||
fs.writeFileSync(sentinelFile, '');
|
fs.writeFileSync(sentinelFile, '');
|
||||||
}
|
}
|
||||||
|
|
@ -141,4 +177,5 @@ async function uploadFiles(sarifFiles) {
|
||||||
core.setFailed(error.message);
|
core.setFailed(error.message);
|
||||||
}
|
}
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
|
return succeeded;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
lib/upload-sarif.js
generated
10
lib/upload-sarif.js
generated
|
|
@ -15,16 +15,20 @@ async function run() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await upload_lib.upload(core.getInput('sarif_file'));
|
if (await upload_lib.upload(core.getInput('sarif_file'))) {
|
||||||
|
await util.reportActionSucceeded('upload-sarif');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await util.reportActionFailed('upload-sarif', 'upload');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
core.setFailed(error.message);
|
core.setFailed(error.message);
|
||||||
await util.reportActionFailed('upload-sarif', error.message, error.stack);
|
await util.reportActionFailed('upload-sarif', error.message, error.stack);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await util.reportActionSucceeded('upload-sarif');
|
|
||||||
}
|
}
|
||||||
run().catch(e => {
|
run().catch(e => {
|
||||||
core.setFailed("upload-sarif action failed: " + e);
|
core.setFailed("codeql/upload-sarif action failed: " + e);
|
||||||
console.log(e);
|
console.log(e);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
10
lib/util.js
generated
10
lib/util.js
generated
|
|
@ -15,6 +15,8 @@ const http = __importStar(require("@actions/http-client"));
|
||||||
const auth = __importStar(require("@actions/http-client/auth"));
|
const auth = __importStar(require("@actions/http-client/auth"));
|
||||||
const octokit = __importStar(require("@octokit/rest"));
|
const octokit = __importStar(require("@octokit/rest"));
|
||||||
const console_log_level_1 = __importDefault(require("console-log-level"));
|
const console_log_level_1 = __importDefault(require("console-log-level"));
|
||||||
|
const fs = __importStar(require("fs"));
|
||||||
|
const os = __importStar(require("os"));
|
||||||
const path = __importStar(require("path"));
|
const path = __importStar(require("path"));
|
||||||
const sharedEnv = __importStar(require("./shared-environment"));
|
const sharedEnv = __importStar(require("./shared-environment"));
|
||||||
/**
|
/**
|
||||||
|
|
@ -321,3 +323,11 @@ function getToolNames(sarifContents) {
|
||||||
return Object.keys(toolNames);
|
return Object.keys(toolNames);
|
||||||
}
|
}
|
||||||
exports.getToolNames = getToolNames;
|
exports.getToolNames = getToolNames;
|
||||||
|
// Creates a random temporary directory, runs the given body, and then deletes the directory.
|
||||||
|
// Mostly intended for use within tests.
|
||||||
|
async function withTmpDir(body) {
|
||||||
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codeql-action-'));
|
||||||
|
await body(tmpDir);
|
||||||
|
fs.rmdirSync(tmpDir, { recursive: true });
|
||||||
|
}
|
||||||
|
exports.withTmpDir = withTmpDir;
|
||||||
|
|
|
||||||
65
queries/undeclared-action-input.ql
Normal file
65
queries/undeclared-action-input.ql
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* @name Undeclared action input
|
||||||
|
* @description Code tries to use an input parameter that is not defined for this action.
|
||||||
|
Perhaps this code is shared by multiple actions.
|
||||||
|
* @kind problem
|
||||||
|
* @problem.severity error
|
||||||
|
* @id javascript/codeql-action/undeclared-action-input
|
||||||
|
*/
|
||||||
|
|
||||||
|
import javascript
|
||||||
|
|
||||||
|
class ActionDeclaration extends File {
|
||||||
|
ActionDeclaration() {
|
||||||
|
getRelativePath().matches("%/action.yml")
|
||||||
|
}
|
||||||
|
|
||||||
|
string getName() {
|
||||||
|
result = getRelativePath().regexpCapture("(.*)/action.yml", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
YAMLDocument getRootNode() {
|
||||||
|
result.getFile() = this
|
||||||
|
}
|
||||||
|
|
||||||
|
string getAnInput() {
|
||||||
|
result = getRootNode().(YAMLMapping).lookup("inputs").(YAMLMapping).getKey(_).(YAMLString).getValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionDeclStmt getEntrypoint() {
|
||||||
|
result.getFile().getRelativePath() = getRootNode().
|
||||||
|
(YAMLMapping).lookup("runs").
|
||||||
|
(YAMLMapping).lookup("main").
|
||||||
|
(YAMLString).getValue().regexpReplaceAll("\\.\\./lib/(.*)\\.js", "src/$1.ts") and
|
||||||
|
result.getName() = "run"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr getAFunctionChildExpr(Function f) {
|
||||||
|
result.getContainer() = f
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Result is a function that is called from the body of the given function `f`
|
||||||
|
*/
|
||||||
|
Function calledBy(Function f) {
|
||||||
|
result = getAFunctionChildExpr(f).(InvokeExpr).getResolvedCallee()
|
||||||
|
or
|
||||||
|
result.getEnclosingContainer() = f // assume outer function causes inner function to be called
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetInputMethodCallExpr extends MethodCallExpr {
|
||||||
|
GetInputMethodCallExpr() {
|
||||||
|
getMethodName() = "getInput"
|
||||||
|
}
|
||||||
|
|
||||||
|
string getInputName() {
|
||||||
|
result = getArgument(0).(StringLiteral).getValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
from ActionDeclaration action, GetInputMethodCallExpr getInputCall, string inputName
|
||||||
|
where getAFunctionChildExpr(calledBy*(action.getEntrypoint())) = getInputCall and
|
||||||
|
inputName = getInputCall.getInputName() and
|
||||||
|
not inputName = action.getAnInput()
|
||||||
|
select getInputCall, "The $@ input is not defined for the $@ action", inputName, inputName, action, action.getName()
|
||||||
|
|
@ -15,7 +15,8 @@ async function run() {
|
||||||
// We want pick the dominant language in the repo from the ones we're able to build
|
// We want pick the dominant language in the repo from the ones we're able to build
|
||||||
// The languages are sorted in order specified by user or by lines of code if we got
|
// The languages are sorted in order specified by user or by lines of code if we got
|
||||||
// them from the GitHub API, so try to build the first language on the list.
|
// them from the GitHub API, so try to build the first language on the list.
|
||||||
const language = process.env[sharedEnv.CODEQL_ACTION_TRACED_LANGUAGES]?.split(',')[0];
|
const autobuildLanguages = process.env[sharedEnv.CODEQL_ACTION_TRACED_LANGUAGES]?.split(',') || [];
|
||||||
|
const language = autobuildLanguages[0];
|
||||||
|
|
||||||
if (!language) {
|
if (!language) {
|
||||||
core.info("None of the languages in this project require extra build steps");
|
core.info("None of the languages in this project require extra build steps");
|
||||||
|
|
@ -24,6 +25,10 @@ async function run() {
|
||||||
|
|
||||||
core.debug(`Detected dominant traced language: ${language}`);
|
core.debug(`Detected dominant traced language: ${language}`);
|
||||||
|
|
||||||
|
if (autobuildLanguages.length > 1) {
|
||||||
|
core.warning(`We will only automatically build ${language} code. If you wish to scan ${autobuildLanguages.slice(1).join(' and ')}, you must replace this block with custom build steps.`);
|
||||||
|
}
|
||||||
|
|
||||||
core.startGroup(`Attempting to automatically build ${language} code`);
|
core.startGroup(`Attempting to automatically build ${language} code`);
|
||||||
// TODO: share config accross actions better via env variables
|
// TODO: share config accross actions better via env variables
|
||||||
const codeqlCmd = util.getRequiredEnvParam(sharedEnv.CODEQL_ACTION_CMD);
|
const codeqlCmd = util.getRequiredEnvParam(sharedEnv.CODEQL_ACTION_CMD);
|
||||||
|
|
@ -44,7 +49,7 @@ async function run() {
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.setFailed(error.message);
|
core.setFailed("We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. " + error.message);
|
||||||
await util.reportActionFailed('autobuild', error.message, error.stack);
|
await util.reportActionFailed('autobuild', error.message, error.stack);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -53,6 +58,6 @@ async function run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
run().catch(e => {
|
run().catch(e => {
|
||||||
core.setFailed("autobuild action failed: " + e);
|
core.setFailed("autobuild action failed. " + e);
|
||||||
console.log(e);
|
console.log(e);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,19 @@ import * as path from "path";
|
||||||
|
|
||||||
import * as configUtils from "./config-utils";
|
import * as configUtils from "./config-utils";
|
||||||
import * as externalQueries from "./external-queries";
|
import * as externalQueries from "./external-queries";
|
||||||
|
import * as util from "./util";
|
||||||
|
|
||||||
test("checkoutExternalQueries", async () => {
|
test("checkoutExternalQueries", async () => {
|
||||||
let config = new configUtils.Config();
|
let config = new configUtils.Config();
|
||||||
config.externalQueries = [
|
config.externalQueries = [
|
||||||
new configUtils.ExternalQuery("github/codeql-go", "df4c6869212341b601005567381944ed90906b6b"),
|
new configUtils.ExternalQuery("github/codeql-go", "df4c6869212341b601005567381944ed90906b6b"),
|
||||||
];
|
];
|
||||||
await externalQueries.checkoutExternalQueries(config);
|
|
||||||
|
|
||||||
let destination = process.env["RUNNER_WORKSPACE"] || "/tmp/codeql-action/";
|
await util.withTmpDir(async tmpDir => {
|
||||||
// COPYRIGHT file existed in df4c6869212341b601005567381944ed90906b6b but not in master
|
process.env["RUNNER_WORKSPACE"] = tmpDir;
|
||||||
expect(fs.existsSync(path.join(destination, "github", "codeql-go", "COPYRIGHT"))).toBeTruthy();
|
await externalQueries.checkoutExternalQueries(config);
|
||||||
|
|
||||||
|
// COPYRIGHT file existed in df4c6869212341b601005567381944ed90906b6b but not in master
|
||||||
|
expect(fs.existsSync(path.join(tmpDir, "github", "codeql-go", "COPYRIGHT"))).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import * as configUtils from './config-utils';
|
import * as configUtils from './config-utils';
|
||||||
|
import * as util from './util';
|
||||||
|
|
||||||
export async function checkoutExternalQueries(config: configUtils.Config) {
|
export async function checkoutExternalQueries(config: configUtils.Config) {
|
||||||
const folder = process.env['RUNNER_WORKSPACE'] || '/tmp/codeql-action';
|
const folder = util.getRequiredEnvParam('RUNNER_WORKSPACE');
|
||||||
|
|
||||||
for (const externalQuery of config.externalQueries) {
|
for (const externalQuery of config.externalQueries) {
|
||||||
core.info('Checking out ' + externalQuery.repository);
|
core.info('Checking out ' + externalQuery.repository);
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,10 @@ async function run() {
|
||||||
await runQueries(codeqlCmd, databaseFolder, sarifFolder, config);
|
await runQueries(codeqlCmd, databaseFolder, sarifFolder, config);
|
||||||
|
|
||||||
if ('true' === core.getInput('upload')) {
|
if ('true' === core.getInput('upload')) {
|
||||||
await upload_lib.upload(sarifFolder);
|
if (!await upload_lib.upload(sarifFolder)) {
|
||||||
|
await util.reportActionFailed('failed', 'upload');
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -47,22 +47,86 @@ export function combineSarifFiles(sarifFiles: string[]): string {
|
||||||
return JSON.stringify(combinedSarif);
|
return JSON.stringify(combinedSarif);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Upload the given payload.
|
||||||
|
// If the request fails then this will retry a small number of times.
|
||||||
|
async function uploadPayload(payload): Promise<boolean> {
|
||||||
|
core.info('Uploading results');
|
||||||
|
|
||||||
|
const githubToken = core.getInput('token');
|
||||||
|
const ph: auth.BearerCredentialHandler = new auth.BearerCredentialHandler(githubToken);
|
||||||
|
const client = new http.HttpClient('Code Scanning : Upload SARIF', [ph]);
|
||||||
|
const url = 'https://api.github.com/repos/' + process.env['GITHUB_REPOSITORY'] + '/code-scanning/analysis';
|
||||||
|
|
||||||
|
// Make up to 4 attempts to upload, and sleep for these
|
||||||
|
// number of seconds between each attempt.
|
||||||
|
// We don't want to backoff too much to avoid wasting action
|
||||||
|
// minutes, but just waiting a little bit could maybe help.
|
||||||
|
const backoffPeriods = [1, 5, 15];
|
||||||
|
|
||||||
|
for (let attempt = 0; attempt <= backoffPeriods.length; attempt++) {
|
||||||
|
|
||||||
|
const res: http.HttpClientResponse = await client.put(url, payload);
|
||||||
|
core.debug('response status: ' + res.message.statusCode);
|
||||||
|
|
||||||
|
const statusCode = res.message.statusCode;
|
||||||
|
if (statusCode === 202) {
|
||||||
|
core.info("Successfully uploaded results");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestID = res.message.headers["x-github-request-id"];
|
||||||
|
|
||||||
|
// On any other status code that's not 5xx mark the upload as failed
|
||||||
|
if (!statusCode || statusCode < 500 || statusCode >= 600) {
|
||||||
|
core.setFailed('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// On a 5xx status code we may retry the request
|
||||||
|
if (attempt < backoffPeriods.length) {
|
||||||
|
// Log the failure as a warning but don't mark the action as failed yet
|
||||||
|
core.warning('Upload attempt (' + (attempt + 1) + ' of ' + (backoffPeriods.length + 1) +
|
||||||
|
') failed (' + requestID + '). Retrying in ' + backoffPeriods[attempt] +
|
||||||
|
' seconds: (' + statusCode + ') ' + await res.readBody());
|
||||||
|
// Sleep for the backoff period
|
||||||
|
await new Promise(r => setTimeout(r, backoffPeriods[attempt] * 1000));
|
||||||
|
continue;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// If the upload fails with 5xx then we assume it is a temporary problem
|
||||||
|
// and not an error that the user has caused or can fix.
|
||||||
|
// We avoid marking the job as failed to avoid breaking CI workflows.
|
||||||
|
core.error('Upload failed (' + requestID + '): (' + statusCode + ') ' + await res.readBody());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Uploads a single sarif file or a directory of sarif files
|
// Uploads a single sarif file or a directory of sarif files
|
||||||
// depending on what the path happens to refer to.
|
// depending on what the path happens to refer to.
|
||||||
export async function upload(input: string) {
|
// Returns true iff the upload occurred and succeeded
|
||||||
|
export async function upload(input: string): Promise<boolean> {
|
||||||
if (fs.lstatSync(input).isDirectory()) {
|
if (fs.lstatSync(input).isDirectory()) {
|
||||||
const sarifFiles = fs.readdirSync(input)
|
const sarifFiles = fs.readdirSync(input)
|
||||||
.filter(f => f.endsWith(".sarif"))
|
.filter(f => f.endsWith(".sarif"))
|
||||||
.map(f => path.resolve(input, f));
|
.map(f => path.resolve(input, f));
|
||||||
await uploadFiles(sarifFiles);
|
if (sarifFiles.length === 0) {
|
||||||
|
core.setFailed("No SARIF files found to upload in \"" + input + "\".");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return await uploadFiles(sarifFiles);
|
||||||
} else {
|
} else {
|
||||||
await uploadFiles([input]);
|
return await uploadFiles([input]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uploads the given set of sarif files.
|
// Uploads the given set of sarif files.
|
||||||
async function uploadFiles(sarifFiles: string[]) {
|
// Returns true iff the upload occurred and succeeded
|
||||||
|
async function uploadFiles(sarifFiles: string[]): Promise<boolean> {
|
||||||
core.startGroup("Uploading results");
|
core.startGroup("Uploading results");
|
||||||
|
let succeeded = false;
|
||||||
try {
|
try {
|
||||||
// Check if an upload has happened before. If so then abort.
|
// Check if an upload has happened before. If so then abort.
|
||||||
// This is intended to catch when the finish and upload-sarif actions
|
// This is intended to catch when the finish and upload-sarif actions
|
||||||
|
|
@ -70,7 +134,7 @@ async function uploadFiles(sarifFiles: string[]) {
|
||||||
const sentinelFile = await getSentinelFilePath();
|
const sentinelFile = await getSentinelFilePath();
|
||||||
if (fs.existsSync(sentinelFile)) {
|
if (fs.existsSync(sentinelFile)) {
|
||||||
core.info("Aborting as an upload has already happened from this job");
|
core.info("Aborting as an upload has already happened from this job");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const commitOid = util.getRequiredEnvParam('GITHUB_SHA');
|
const commitOid = util.getRequiredEnvParam('GITHUB_SHA');
|
||||||
|
|
@ -80,7 +144,7 @@ async function uploadFiles(sarifFiles: string[]) {
|
||||||
const analysisName = util.getRequiredEnvParam('GITHUB_WORKFLOW');
|
const analysisName = util.getRequiredEnvParam('GITHUB_WORKFLOW');
|
||||||
const startedAt = process.env[sharedEnv.CODEQL_ACTION_STARTED_AT];
|
const startedAt = process.env[sharedEnv.CODEQL_ACTION_STARTED_AT];
|
||||||
|
|
||||||
core.debug("Uploading sarif files: " + JSON.stringify(sarifFiles));
|
core.info("Uploading sarif files: " + JSON.stringify(sarifFiles));
|
||||||
let sarifPayload = combineSarifFiles(sarifFiles);
|
let sarifPayload = combineSarifFiles(sarifFiles);
|
||||||
sarifPayload = fingerprints.addFingerprints(sarifPayload);
|
sarifPayload = fingerprints.addFingerprints(sarifPayload);
|
||||||
|
|
||||||
|
|
@ -91,7 +155,7 @@ async function uploadFiles(sarifFiles: string[]) {
|
||||||
|
|
||||||
if (Number.isNaN(workflowRunID)) {
|
if (Number.isNaN(workflowRunID)) {
|
||||||
core.setFailed('GITHUB_RUN_ID must define a non NaN workflow run ID');
|
core.setFailed('GITHUB_RUN_ID must define a non NaN workflow run ID');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let matrix: string | undefined = core.getInput('matrix');
|
let matrix: string | undefined = core.getInput('matrix');
|
||||||
|
|
@ -114,25 +178,8 @@ async function uploadFiles(sarifFiles: string[]) {
|
||||||
"tool_names": toolNames,
|
"tool_names": toolNames,
|
||||||
});
|
});
|
||||||
|
|
||||||
core.info('Uploading results');
|
// Make the upload
|
||||||
const githubToken = core.getInput('token');
|
succeeded = await uploadPayload(payload);
|
||||||
const ph: auth.BearerCredentialHandler = new auth.BearerCredentialHandler(githubToken);
|
|
||||||
const client = new http.HttpClient('Code Scanning : Upload SARIF', [ph]);
|
|
||||||
const url = 'https://api.github.com/repos/' + process.env['GITHUB_REPOSITORY'] + '/code-scanning/analysis';
|
|
||||||
const res: http.HttpClientResponse = await client.put(url, payload);
|
|
||||||
const requestID = res.message.headers["x-github-request-id"];
|
|
||||||
|
|
||||||
core.debug('response status: ' + res.message.statusCode);
|
|
||||||
if (res.message.statusCode === 500) {
|
|
||||||
// If the upload fails with 500 then we assume it is a temporary problem
|
|
||||||
// with turbo-scan and not an error that the user has caused or can fix.
|
|
||||||
// We avoid marking the job as failed to avoid breaking CI workflows.
|
|
||||||
core.error('Upload failed (' + requestID + '): ' + await res.readBody());
|
|
||||||
} else if (res.message.statusCode !== 202) {
|
|
||||||
core.setFailed('Upload failed (' + requestID + '): ' + await res.readBody());
|
|
||||||
} else {
|
|
||||||
core.info("Successfully uploaded results");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark that we have made an upload
|
// Mark that we have made an upload
|
||||||
fs.writeFileSync(sentinelFile, '');
|
fs.writeFileSync(sentinelFile, '');
|
||||||
|
|
@ -141,4 +188,6 @@ async function uploadFiles(sarifFiles: string[]) {
|
||||||
core.setFailed(error.message);
|
core.setFailed(error.message);
|
||||||
}
|
}
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
|
|
||||||
|
return succeeded;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,19 @@ async function run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await upload_lib.upload(core.getInput('sarif_file'));
|
if (await upload_lib.upload(core.getInput('sarif_file'))) {
|
||||||
|
await util.reportActionSucceeded('upload-sarif');
|
||||||
|
} else {
|
||||||
|
await util.reportActionFailed('upload-sarif', 'upload');
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.setFailed(error.message);
|
core.setFailed(error.message);
|
||||||
await util.reportActionFailed('upload-sarif', error.message, error.stack);
|
await util.reportActionFailed('upload-sarif', error.message, error.stack);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await util.reportActionSucceeded('upload-sarif');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run().catch(e => {
|
run().catch(e => {
|
||||||
core.setFailed("upload-sarif action failed: " + e);
|
core.setFailed("codeql/upload-sarif action failed: " + e);
|
||||||
console.log(e);
|
console.log(e);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
10
src/util.ts
10
src/util.ts
|
|
@ -3,6 +3,8 @@ import * as http from '@actions/http-client';
|
||||||
import * as auth from '@actions/http-client/auth';
|
import * as auth from '@actions/http-client/auth';
|
||||||
import * as octokit from '@octokit/rest';
|
import * as octokit from '@octokit/rest';
|
||||||
import consoleLogLevel from 'console-log-level';
|
import consoleLogLevel from 'console-log-level';
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import * as sharedEnv from './shared-environment';
|
import * as sharedEnv from './shared-environment';
|
||||||
|
|
@ -361,3 +363,11 @@ export function getToolNames(sarifContents: string): string[] {
|
||||||
|
|
||||||
return Object.keys(toolNames);
|
return Object.keys(toolNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates a random temporary directory, runs the given body, and then deletes the directory.
|
||||||
|
// Mostly intended for use within tests.
|
||||||
|
export async function withTmpDir(body: (tmpDir: string) => Promise<void>) {
|
||||||
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codeql-action-'));
|
||||||
|
await body(tmpDir);
|
||||||
|
fs.rmdirSync(tmpDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue