Merge branch 'main' into aeisenberg/remote-history-label-editing
This commit is contained in:
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -147,13 +147,11 @@ jobs:
|
||||
If this was an authentication problem, please make sure the \
|
||||
auth token hasn't expired."
|
||||
|
||||
# TODO This job is currently broken and is blocked on https://github.com/github/vscode-codeql/issues/1085
|
||||
open-vsx-publish:
|
||||
name: Publish to Open VSX Registry
|
||||
needs: build
|
||||
environment: publish-open-vsx
|
||||
runs-on: ubuntu-latest
|
||||
if: 1 == 0
|
||||
env:
|
||||
OPEN_VSX_TOKEN: ${{ secrets.OPEN_VSX_TOKEN }}
|
||||
steps:
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
- Re-enable publishing to open-vsx. [#1285](https://github.com/github/vscode-codeql/pull/1285)
|
||||
|
||||
## 1.6.4 - 6 April 2022
|
||||
|
||||
No user facing changes.
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
"Programming Languages"
|
||||
],
|
||||
"extensionDependencies": [
|
||||
"hbenl.vscode-test-explorer",
|
||||
"ms-vscode.test-adapter-converter"
|
||||
"hbenl.vscode-test-explorer"
|
||||
],
|
||||
"capabilities": {
|
||||
"untrustedWorkspaces": {
|
||||
@@ -1222,7 +1221,7 @@
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"./**/*.{json,css,scss,md}": [
|
||||
"./**/*.{json,css,scss}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"./**/*.{ts,tsx}": [
|
||||
|
||||
@@ -7,7 +7,7 @@ const GITHUB_AUTH_PROVIDER_ID = 'github';
|
||||
// https://docs.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps
|
||||
const SCOPES = ['repo'];
|
||||
|
||||
/**
|
||||
/**
|
||||
* Handles authentication to GitHub, using the VS Code [authentication API](https://code.visualstudio.com/api/references/vscode-api#authentication).
|
||||
*/
|
||||
export class Credentials {
|
||||
@@ -18,6 +18,15 @@ export class Credentials {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
private constructor() { }
|
||||
|
||||
/**
|
||||
* Initializes an instance of credentials with an octokit instance.
|
||||
*
|
||||
* Do not call this method until you know you actually need an instance of credentials.
|
||||
* since calling this method will require the user to log in.
|
||||
*
|
||||
* @param context The extension context.
|
||||
* @returns An instance of credentials.
|
||||
*/
|
||||
static async initialize(context: vscode.ExtensionContext): Promise<Credentials> {
|
||||
const c = new Credentials();
|
||||
c.registerListeners(context);
|
||||
|
||||
@@ -667,11 +667,11 @@ export class CodeQLCliServer implements Disposable {
|
||||
|
||||
/**
|
||||
* Generate a summary of an evaluation log.
|
||||
* @param endSummaryPath The path to write only the end of query part of the human-readable summary to.
|
||||
* @param endSummaryPath The path to write only the end of query part of the human-readable summary to.
|
||||
* @param inputPath The path of an evaluation event log.
|
||||
* @param outputPath The path to write a human-readable summary of it to.
|
||||
*/
|
||||
async generateLogSummary(
|
||||
async generateLogSummary(
|
||||
inputPath: string,
|
||||
outputPath: string,
|
||||
endSummaryPath: string,
|
||||
@@ -1255,7 +1255,7 @@ export class CliVersionConstraint {
|
||||
public static CLI_VERSION_WITH_NO_PRECOMPILE = new SemVer('2.7.1');
|
||||
|
||||
/**
|
||||
* CLI version where remote queries are supported.
|
||||
* CLI version where remote queries (variant analysis) are supported.
|
||||
*/
|
||||
public static CLI_VERSION_REMOTE_QUERIES = new SemVer('2.6.3');
|
||||
|
||||
@@ -1280,16 +1280,16 @@ export class CliVersionConstraint {
|
||||
*/
|
||||
public static CLI_VERSION_WITH_STRUCTURED_EVAL_LOG = new SemVer('2.8.2');
|
||||
|
||||
/**
|
||||
* CLI version that supports rotating structured logs to produce one per query.
|
||||
*
|
||||
* Note that 2.8.4 supports generating the evaluation logs and summaries,
|
||||
* but 2.9.0 includes a new option to produce the end-of-query summary logs to
|
||||
* the query server console. For simplicity we gate all features behind 2.9.0,
|
||||
* but if a user is tied to the 2.8 release, we can enable evaluator logs
|
||||
* and summaries for them.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_PER_QUERY_EVAL_LOG = new SemVer('2.9.0');
|
||||
/**
|
||||
* CLI version that supports rotating structured logs to produce one per query.
|
||||
*
|
||||
* Note that 2.8.4 supports generating the evaluation logs and summaries,
|
||||
* but 2.9.0 includes a new option to produce the end-of-query summary logs to
|
||||
* the query server console. For simplicity we gate all features behind 2.9.0,
|
||||
* but if a user is tied to the 2.8 release, we can enable evaluator logs
|
||||
* and summaries for them.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_PER_QUERY_EVAL_LOG = new SemVer('2.9.0');
|
||||
|
||||
constructor(private readonly cli: CodeQLCliServer) {
|
||||
/**/
|
||||
|
||||
@@ -322,7 +322,7 @@ export function isCanary() {
|
||||
*/
|
||||
export const NO_CACHE_AST_VIEWER = new Setting('disableCache', AST_VIEWER_SETTING);
|
||||
|
||||
// Settings for remote queries
|
||||
// Settings for variant analysis
|
||||
const REMOTE_QUERIES_SETTING = new Setting('variantAnalysis', ROOT_SETTING);
|
||||
|
||||
/**
|
||||
|
||||
@@ -843,7 +843,7 @@ async function activateWithInstalledDistribution(
|
||||
)
|
||||
);
|
||||
|
||||
void logger.log('Initializing remote queries interface.');
|
||||
void logger.log('Initializing variant analysis results view.');
|
||||
const rqm = new RemoteQueriesManager(ctx, cliServer, qhm, queryStorageDir, logger);
|
||||
ctx.subscriptions.push(rqm);
|
||||
|
||||
@@ -873,7 +873,7 @@ async function activateWithInstalledDistribution(
|
||||
token
|
||||
);
|
||||
} else {
|
||||
throw new Error('Remote queries require the CodeQL Canary version to run.');
|
||||
throw new Error('Variant analysis requires the CodeQL Canary version to run.');
|
||||
}
|
||||
}, {
|
||||
title: 'Run Variant Analysis',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as Sarif from 'sarif';
|
||||
import { HighlightedRegion } from '../remote-queries/shared/analysis-result';
|
||||
import { ResolvableLocationValue } from './bqrs-cli-types';
|
||||
|
||||
export interface SarifLink {
|
||||
@@ -173,3 +174,65 @@ export function parseSarifRegion(
|
||||
export function isNoLocation(loc: ParsedSarifLocation): loc is NoLocation {
|
||||
return 'hint' in loc;
|
||||
}
|
||||
|
||||
// Some helpers for highlighting specific regions from a SARIF code snippet
|
||||
|
||||
/**
|
||||
* Checks whether a particular line (determined by its line number in the original file)
|
||||
* is part of the highlighted region of a SARIF code snippet.
|
||||
*/
|
||||
export function shouldHighlightLine(
|
||||
lineNumber: number,
|
||||
highlightedRegion: HighlightedRegion
|
||||
): boolean {
|
||||
if (lineNumber < highlightedRegion.startLine) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (highlightedRegion.endLine == undefined) {
|
||||
return lineNumber == highlightedRegion.startLine;
|
||||
}
|
||||
|
||||
return lineNumber <= highlightedRegion.endLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* A line of code split into: plain text before the highlighted section, the highlighted
|
||||
* text itself, and plain text after the highlighted section.
|
||||
*/
|
||||
export interface PartiallyHighlightedLine {
|
||||
plainSection1: string;
|
||||
highlightedSection: string;
|
||||
plainSection2: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a line of code into the highlighted and non-highlighted sections.
|
||||
*/
|
||||
export function parseHighlightedLine(
|
||||
line: string,
|
||||
lineNumber: number,
|
||||
highlightedRegion: HighlightedRegion
|
||||
): PartiallyHighlightedLine {
|
||||
const isSingleLineHighlight = highlightedRegion.endLine === undefined;
|
||||
const isFirstHighlightedLine = lineNumber === highlightedRegion.startLine;
|
||||
const isLastHighlightedLine = lineNumber === highlightedRegion.endLine;
|
||||
|
||||
const highlightStartColumn = isSingleLineHighlight
|
||||
? highlightedRegion.startColumn
|
||||
: isFirstHighlightedLine
|
||||
? highlightedRegion.startColumn
|
||||
: 0;
|
||||
|
||||
const highlightEndColumn = isSingleLineHighlight
|
||||
? highlightedRegion.endColumn
|
||||
: isLastHighlightedLine
|
||||
? highlightedRegion.endColumn
|
||||
: line.length + 1;
|
||||
|
||||
const plainSection1 = line.substring(0, highlightStartColumn - 1);
|
||||
const highlightedSection = line.substring(highlightStartColumn - 1, highlightEndColumn - 1);
|
||||
const plainSection2 = line.substring(highlightEndColumn - 1, line.length);
|
||||
|
||||
return { plainSection1, highlightedSection, plainSection2 };
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ import { slurpQueryHistory, splatQueryHistory } from './query-serialization';
|
||||
import * as fs from 'fs-extra';
|
||||
import { CliVersionConstraint } from './cli';
|
||||
import { HistoryItemLabelProvider } from './history-item-label-provider';
|
||||
import { Credentials } from './authentication';
|
||||
import { cancelRemoteQuery } from './remote-queries/gh-actions-api-client';
|
||||
|
||||
/**
|
||||
* query-history.ts
|
||||
@@ -320,7 +322,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
private readonly qs: QueryServerClient,
|
||||
private readonly dbm: DatabaseManager,
|
||||
private readonly queryStorageDir: string,
|
||||
ctx: ExtensionContext,
|
||||
private readonly ctx: ExtensionContext,
|
||||
private readonly queryHistoryConfigListener: QueryHistoryConfig,
|
||||
private readonly labelProvider: HistoryItemLabelProvider,
|
||||
private readonly doCompareCallback: (
|
||||
@@ -518,6 +520,10 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
this.registerQueryHistoryScrubber(queryHistoryConfigListener, ctx);
|
||||
}
|
||||
|
||||
private getCredentials() {
|
||||
return Credentials.initialize(this.ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register and create the history scrubber.
|
||||
*/
|
||||
@@ -836,11 +842,20 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
// In the future, we may support cancelling remote queries, but this is not a short term plan.
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
|
||||
(finalMultiSelect || [finalSingleItem]).forEach((item) => {
|
||||
if (item.status === QueryStatus.InProgress && item.t === 'local') {
|
||||
item.cancel();
|
||||
const selected = finalMultiSelect || [finalSingleItem];
|
||||
const results = selected.map(async item => {
|
||||
if (item.status === QueryStatus.InProgress) {
|
||||
if (item.t === 'local') {
|
||||
item.cancel();
|
||||
} else if (item.t === 'remote') {
|
||||
void showAndLogInformationMessage('Cancelling variant analysis. This may take a while.');
|
||||
const credentials = await this.getCredentials();
|
||||
await cancelRemoteQuery(credentials, item.remoteQuery);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(results);
|
||||
}
|
||||
|
||||
async handleShowQueryText(
|
||||
|
||||
@@ -74,6 +74,18 @@ export async function getRemoteQueryIndex(
|
||||
};
|
||||
}
|
||||
|
||||
export async function cancelRemoteQuery(
|
||||
credentials: Credentials,
|
||||
remoteQuery: RemoteQuery
|
||||
): Promise<void> {
|
||||
const octokit = await credentials.getOctokit();
|
||||
const { actionsWorkflowRunId, controllerRepository: { owner, name } } = remoteQuery;
|
||||
const response = await octokit.request(`POST /repos/${owner}/${name}/actions/runs/${actionsWorkflowRunId}/cancel`);
|
||||
if (response.status >= 300) {
|
||||
throw new Error(`Error cancelling variant analysis: ${response.status} ${response?.data?.message || ''}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function downloadArtifactFromLink(
|
||||
credentials: Credentials,
|
||||
storagePath: string,
|
||||
|
||||
@@ -38,7 +38,7 @@ export class RemoteQueriesInterfaceManager {
|
||||
private readonly analysesResultsManager: AnalysesResultsManager
|
||||
) {
|
||||
this.panelLoadedCallBacks.push(() => {
|
||||
void logger.log('Remote queries view loaded');
|
||||
void logger.log('Variant analysis results view loaded');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import * as fs from 'fs-extra';
|
||||
import { Credentials } from '../authentication';
|
||||
import { CodeQLCliServer } from '../cli';
|
||||
import { ProgressCallback } from '../commandRunner';
|
||||
import { createTimestampFile, showAndLogErrorMessage, showInformationMessageWithAction } from '../helpers';
|
||||
import { createTimestampFile, showAndLogErrorMessage, showAndLogInformationMessage, showInformationMessageWithAction } from '../helpers';
|
||||
import { Logger } from '../logging';
|
||||
import { runRemoteQuery } from './run-remote-query';
|
||||
import { RemoteQueriesInterfaceManager } from './remote-queries-interface';
|
||||
@@ -154,13 +154,20 @@ export class RemoteQueriesManager extends DisposableObject {
|
||||
queryItem.status = QueryStatus.Failed;
|
||||
}
|
||||
} else if (queryWorkflowResult.status === 'CompletedUnsuccessfully') {
|
||||
queryItem.failureReason = queryWorkflowResult.error;
|
||||
queryItem.status = QueryStatus.Failed;
|
||||
void showAndLogErrorMessage(`Variant analysis execution failed. Error: ${queryWorkflowResult.error}`);
|
||||
if (queryWorkflowResult.error?.includes('cancelled')) {
|
||||
// workflow was cancelled on the server
|
||||
queryItem.failureReason = 'Cancelled';
|
||||
queryItem.status = QueryStatus.Failed;
|
||||
void showAndLogInformationMessage('Variant analysis was cancelled');
|
||||
} else {
|
||||
queryItem.failureReason = queryWorkflowResult.error;
|
||||
queryItem.status = QueryStatus.Failed;
|
||||
void showAndLogErrorMessage(`Variant analysis execution failed. Error: ${queryWorkflowResult.error}`);
|
||||
}
|
||||
} else if (queryWorkflowResult.status === 'Cancelled') {
|
||||
queryItem.failureReason = 'Cancelled';
|
||||
queryItem.status = QueryStatus.Failed;
|
||||
void showAndLogErrorMessage('Variant analysis monitoring was cancelled');
|
||||
void showAndLogErrorMessage('Variant analysis was cancelled');
|
||||
} else if (queryWorkflowResult.status === 'InProgress') {
|
||||
// Should not get here. Only including this to ensure `assertNever` uses proper type checking.
|
||||
void showAndLogErrorMessage(`Unexpected status: ${queryWorkflowResult.status}`);
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import { createRemoteFileRef } from '../pure/location-link-utils';
|
||||
import { RemoteQuery } from './remote-query';
|
||||
import { AnalysisAlert, AnalysisResults, FileLink } from './shared/analysis-result';
|
||||
|
||||
// Each array item is a line of the markdown file.
|
||||
export type MarkdownFile = string[];
|
||||
|
||||
/**
|
||||
* Generates markdown files with variant analysis results.
|
||||
*/
|
||||
export function generateMarkdown(query: RemoteQuery, analysesResults: AnalysisResults[]): MarkdownFile[] {
|
||||
const files: MarkdownFile[] = [];
|
||||
// Generate summary file with links to individual files
|
||||
const summaryLines: MarkdownFile = generateMarkdownSummary(query);
|
||||
for (const analysisResult of analysesResults) {
|
||||
if (analysisResult.interpretedResults.length === 0) {
|
||||
// TODO: We'll add support for non-interpreted results later.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Append nwo and results count to the summary table
|
||||
const nwo = analysisResult.nwo;
|
||||
const resultsCount = analysisResult.interpretedResults.length;
|
||||
const link = createGistRelativeLink(nwo);
|
||||
summaryLines.push(`| ${nwo} | [${resultsCount} result(s)](${link}) |`);
|
||||
|
||||
// Generate individual markdown file for each repository
|
||||
const lines = [
|
||||
`### ${analysisResult.nwo}`,
|
||||
''
|
||||
];
|
||||
for (const interpretedResult of analysisResult.interpretedResults) {
|
||||
const individualResult = generateMarkdownForInterpretedResult(interpretedResult, query.language);
|
||||
lines.push(...individualResult);
|
||||
}
|
||||
files.push(lines);
|
||||
}
|
||||
return [summaryLines, ...files];
|
||||
}
|
||||
|
||||
export function generateMarkdownSummary(query: RemoteQuery): MarkdownFile {
|
||||
const lines: MarkdownFile = [];
|
||||
// Title
|
||||
lines.push(
|
||||
`### Results for "${query.queryName}"`,
|
||||
''
|
||||
);
|
||||
|
||||
// Expandable section containing query text
|
||||
const queryCodeBlock = [
|
||||
'```ql',
|
||||
...query.queryText.split('\n'),
|
||||
'```',
|
||||
];
|
||||
lines.push(
|
||||
...buildExpandableMarkdownSection('Query', queryCodeBlock)
|
||||
);
|
||||
|
||||
// Padding between sections
|
||||
lines.push(
|
||||
'<br />',
|
||||
'',
|
||||
);
|
||||
|
||||
// Summary table
|
||||
lines.push(
|
||||
'### Summary',
|
||||
'',
|
||||
'| Repository | Results |',
|
||||
'| --- | --- |',
|
||||
);
|
||||
// nwo and result count will be appended to this table
|
||||
return lines;
|
||||
}
|
||||
|
||||
function generateMarkdownForInterpretedResult(interpretedResult: AnalysisAlert, language: string): MarkdownFile {
|
||||
const lines: MarkdownFile = [];
|
||||
lines.push(createMarkdownRemoteFileRef(
|
||||
interpretedResult.fileLink,
|
||||
interpretedResult.highlightedRegion?.startLine,
|
||||
interpretedResult.highlightedRegion?.endLine
|
||||
));
|
||||
lines.push('');
|
||||
const codeSnippet = interpretedResult.codeSnippet?.text;
|
||||
if (codeSnippet) {
|
||||
lines.push(
|
||||
...generateMarkdownForCodeSnippet(codeSnippet, language),
|
||||
);
|
||||
}
|
||||
const alertMessage = buildMarkdownAlertMessage(interpretedResult);
|
||||
lines.push(alertMessage);
|
||||
|
||||
// Padding between results
|
||||
lines.push(
|
||||
'',
|
||||
'----------------------------------------',
|
||||
'',
|
||||
);
|
||||
return lines;
|
||||
}
|
||||
|
||||
function generateMarkdownForCodeSnippet(codeSnippet: string, language: string): MarkdownFile {
|
||||
const lines: MarkdownFile = [];
|
||||
lines.push(
|
||||
`\`\`\`${language}`,
|
||||
...codeSnippet.split('\n'),
|
||||
'```',
|
||||
);
|
||||
lines.push('');
|
||||
return lines;
|
||||
}
|
||||
|
||||
function buildMarkdownAlertMessage(interpretedResult: AnalysisAlert): string {
|
||||
let alertMessage = '';
|
||||
for (const token of interpretedResult.message.tokens) {
|
||||
if (token.t === 'text') {
|
||||
alertMessage += token.text;
|
||||
} else if (token.t === 'location') {
|
||||
alertMessage += createMarkdownRemoteFileRef(
|
||||
token.location.fileLink,
|
||||
token.location.highlightedRegion?.startLine,
|
||||
token.location.highlightedRegion?.endLine,
|
||||
token.text,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Italicize the alert message
|
||||
return `*${alertMessage}*`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a markdown link to a remote file.
|
||||
* If the "link text" is not provided, we use the file path.
|
||||
*/
|
||||
export function createMarkdownRemoteFileRef(
|
||||
fileLink: FileLink,
|
||||
startLine?: number,
|
||||
endLine?: number,
|
||||
linkText?: string,
|
||||
): string {
|
||||
const markdownLink = `[${linkText || fileLink.filePath}](${createRemoteFileRef(fileLink, startLine, endLine)})`;
|
||||
return markdownLink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an expandable markdown section of the form:
|
||||
* <details>
|
||||
* <summary>title</summary>
|
||||
*
|
||||
* contents
|
||||
*
|
||||
* </details>
|
||||
*/
|
||||
function buildExpandableMarkdownSection(title: string, contents: MarkdownFile): MarkdownFile {
|
||||
const expandableLines: MarkdownFile = [];
|
||||
expandableLines.push(
|
||||
'<details>',
|
||||
`<summary>${title}</summary>`,
|
||||
'',
|
||||
...contents,
|
||||
'',
|
||||
'</details>',
|
||||
''
|
||||
);
|
||||
return expandableLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates anchor link to a file in the gist. This is of the form:
|
||||
* '#file-<name>-<file-extension>'
|
||||
*
|
||||
* TODO: Make sure these names align with the actual file names once we upload them to a gist.
|
||||
*/
|
||||
function createGistRelativeLink(nwo: string): string {
|
||||
const [owner, repo] = nwo.split('/');
|
||||
return `#file-${owner}-${repo}-md`;
|
||||
}
|
||||
@@ -171,7 +171,7 @@ export async function runRemoteQuery(
|
||||
token: CancellationToken
|
||||
): Promise<void | RemoteQuerySubmissionResult> {
|
||||
if (!(await cliServer.cliConstraints.supportsRemoteQueries())) {
|
||||
throw new Error(`Remote queries are not supported by this version of CodeQL. Please upgrade to v${cli.CliVersionConstraint.CLI_VERSION_REMOTE_QUERIES
|
||||
throw new Error(`Variant analysis is not supported by this version of CodeQL. Please upgrade to v${cli.CliVersionConstraint.CLI_VERSION_REMOTE_QUERIES
|
||||
} or later.`);
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ export async function runRemoteQuery(
|
||||
if (!controllerRepo || !REPO_REGEX.test(controllerRepo)) {
|
||||
void logger.log(controllerRepo ? 'Invalid controller repository name.' : 'No controller repository defined.');
|
||||
controllerRepo = await window.showInputBox({
|
||||
title: 'Controller repository in which to display progress and results of remote queries',
|
||||
title: 'Controller repository in which to display progress and results of variant analysis',
|
||||
placeHolder: '<owner>/<repo>',
|
||||
prompt: 'Enter the name of a GitHub repository in the format <owner>/<repo>',
|
||||
ignoreFocusOut: true,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CodeSnippet, FileLink, HighlightedRegion, AnalysisMessage, ResultSeveri
|
||||
import { Box, Link } from '@primer/react';
|
||||
import VerticalSpace from './VerticalSpace';
|
||||
import { createRemoteFileRef } from '../../pure/location-link-utils';
|
||||
import { parseHighlightedLine, shouldHighlightLine } from '../../pure/sarif-utils';
|
||||
|
||||
const borderColor = 'var(--vscode-editor-snippetFinalTabstopHighlightBorder)';
|
||||
const warningColor = '#966C23';
|
||||
@@ -22,18 +23,6 @@ const getSeverityColor = (severity: ResultSeverity) => {
|
||||
|
||||
const replaceSpaceChar = (text: string) => text.replaceAll(' ', '\u00a0');
|
||||
|
||||
const shouldHighlightLine = (lineNumber: number, highlightedRegion: HighlightedRegion) => {
|
||||
if (lineNumber < highlightedRegion.startLine) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (highlightedRegion.endLine == undefined) {
|
||||
return lineNumber == highlightedRegion.startLine;
|
||||
}
|
||||
|
||||
return lineNumber <= highlightedRegion.endLine;
|
||||
};
|
||||
|
||||
const Container = styled.div`
|
||||
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
||||
font-size: x-small;
|
||||
@@ -142,31 +131,13 @@ const CodeLine = ({
|
||||
return <PlainLine text={line} />;
|
||||
}
|
||||
|
||||
const isSingleLineHighlight = highlightedRegion.endLine === undefined;
|
||||
const isFirstHighlightedLine = lineNumber === highlightedRegion.startLine;
|
||||
const isLastHighlightedLine = lineNumber === highlightedRegion.endLine;
|
||||
|
||||
const highlightStartColumn = isSingleLineHighlight
|
||||
? highlightedRegion.startColumn
|
||||
: isFirstHighlightedLine
|
||||
? highlightedRegion.startColumn
|
||||
: 0;
|
||||
|
||||
const highlightEndColumn = isSingleLineHighlight
|
||||
? highlightedRegion.endColumn
|
||||
: isLastHighlightedLine
|
||||
? highlightedRegion.endColumn
|
||||
: line.length + 1;
|
||||
|
||||
const section1 = line.substring(0, highlightStartColumn - 1);
|
||||
const section2 = line.substring(highlightStartColumn - 1, highlightEndColumn - 1);
|
||||
const section3 = line.substring(highlightEndColumn - 1, line.length);
|
||||
const partiallyHighlightedLine = parseHighlightedLine(line, lineNumber, highlightedRegion);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PlainLine text={section1} />
|
||||
<HighlightedLine text={section2} />
|
||||
<PlainLine text={section3} />
|
||||
<PlainLine text={partiallyHighlightedLine.plainSection1} />
|
||||
<HighlightedLine text={partiallyHighlightedLine.highlightedSection} />
|
||||
<PlainLine text={partiallyHighlightedLine.plainSection2} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { expect } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { Credentials } from '../../../authentication';
|
||||
import { cancelRemoteQuery } from '../../../remote-queries/gh-actions-api-client';
|
||||
import { RemoteQuery } from '../../../remote-queries/remote-query';
|
||||
|
||||
describe('gh-actions-api-client', () => {
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
let mockCredentials: Credentials;
|
||||
let mockResponse: sinon.SinonStub<any, Promise<{ status: number }>>;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
mockCredentials = {
|
||||
getOctokit: () => Promise.resolve({
|
||||
request: mockResponse
|
||||
})
|
||||
} as unknown as Credentials;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('cancelRemoteQuery', () => {
|
||||
it('should cancel a remote query', async () => {
|
||||
mockResponse = sinon.stub().resolves({ status: 202 });
|
||||
await cancelRemoteQuery(mockCredentials, createMockRemoteQuery());
|
||||
|
||||
expect(mockResponse.calledOnce).to.be.true;
|
||||
expect(mockResponse.firstCall.args[0]).to.equal('POST /repos/github/codeql/actions/runs/123/cancel');
|
||||
});
|
||||
|
||||
it('should fail to cancel a remote query', async () => {
|
||||
mockResponse = sinon.stub().resolves({ status: 409, data: { message: 'Uh oh!' } });
|
||||
|
||||
await expect(cancelRemoteQuery(mockCredentials, createMockRemoteQuery())).to.be.rejectedWith(/Error cancelling variant analysis: 409 Uh oh!/);
|
||||
expect(mockResponse.calledOnce).to.be.true;
|
||||
expect(mockResponse.firstCall.args[0]).to.equal('POST /repos/github/codeql/actions/runs/123/cancel');
|
||||
});
|
||||
|
||||
function createMockRemoteQuery(): RemoteQuery {
|
||||
return {
|
||||
actionsWorkflowRunId: 123,
|
||||
controllerRepository: {
|
||||
owner: 'github',
|
||||
name: 'codeql'
|
||||
}
|
||||
} as unknown as RemoteQuery;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,626 @@
|
||||
[
|
||||
{
|
||||
"nwo": "github/codeql",
|
||||
"status": "Completed",
|
||||
"interpretedResults": [
|
||||
{
|
||||
"message": {
|
||||
"tokens": [
|
||||
{
|
||||
"t": "text",
|
||||
"text": "This shell command depends on an uncontrolled "
|
||||
},
|
||||
{
|
||||
"t": "location",
|
||||
"text": "absolute path",
|
||||
"location": {
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/src/Security/CWE-078/examples/shell-command-injection-from-environment.js"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 4,
|
||||
"startColumn": 35,
|
||||
"endLine": 4,
|
||||
"endColumn": 44
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "t": "text", "text": "." }
|
||||
]
|
||||
},
|
||||
"shortDescription": "This shell command depends on an uncontrolled ,absolute path,.",
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/src/Security/CWE-078/examples/shell-command-injection-from-environment.js"
|
||||
},
|
||||
"severity": "Warning",
|
||||
"codeSnippet": {
|
||||
"startLine": 3,
|
||||
"endLine": 6,
|
||||
"text": "function cleanupTemp() {\n let cmd = \"rm -rf \" + path.join(__dirname, \"temp\");\n cp.execSync(cmd); // BAD\n}\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 5,
|
||||
"startColumn": 15,
|
||||
"endLine": 5,
|
||||
"endColumn": 18
|
||||
},
|
||||
"codeFlows": [
|
||||
{
|
||||
"threadFlows": [
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/src/Security/CWE-078/examples/shell-command-injection-from-environment.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 2,
|
||||
"endLine": 6,
|
||||
"text": " path = require(\"path\");\nfunction cleanupTemp() {\n let cmd = \"rm -rf \" + path.join(__dirname, \"temp\");\n cp.execSync(cmd); // BAD\n}\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 4,
|
||||
"startColumn": 35,
|
||||
"endLine": 4,
|
||||
"endColumn": 44
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/src/Security/CWE-078/examples/shell-command-injection-from-environment.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 2,
|
||||
"endLine": 6,
|
||||
"text": " path = require(\"path\");\nfunction cleanupTemp() {\n let cmd = \"rm -rf \" + path.join(__dirname, \"temp\");\n cp.execSync(cmd); // BAD\n}\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 4,
|
||||
"startColumn": 25,
|
||||
"endLine": 4,
|
||||
"endColumn": 53
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/src/Security/CWE-078/examples/shell-command-injection-from-environment.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 2,
|
||||
"endLine": 6,
|
||||
"text": " path = require(\"path\");\nfunction cleanupTemp() {\n let cmd = \"rm -rf \" + path.join(__dirname, \"temp\");\n cp.execSync(cmd); // BAD\n}\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 4,
|
||||
"startColumn": 13,
|
||||
"endLine": 4,
|
||||
"endColumn": 53
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/src/Security/CWE-078/examples/shell-command-injection-from-environment.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 2,
|
||||
"endLine": 6,
|
||||
"text": " path = require(\"path\");\nfunction cleanupTemp() {\n let cmd = \"rm -rf \" + path.join(__dirname, \"temp\");\n cp.execSync(cmd); // BAD\n}\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 4,
|
||||
"startColumn": 7,
|
||||
"endLine": 4,
|
||||
"endColumn": 53
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/src/Security/CWE-078/examples/shell-command-injection-from-environment.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 3,
|
||||
"endLine": 6,
|
||||
"text": "function cleanupTemp() {\n let cmd = \"rm -rf \" + path.join(__dirname, \"temp\");\n cp.execSync(cmd); // BAD\n}\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 5,
|
||||
"startColumn": 15,
|
||||
"endLine": 5,
|
||||
"endColumn": 18
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"tokens": [
|
||||
{
|
||||
"t": "text",
|
||||
"text": "This shell command depends on an uncontrolled "
|
||||
},
|
||||
{
|
||||
"t": "location",
|
||||
"text": "absolute path",
|
||||
"location": {
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 6,
|
||||
"startColumn": 36,
|
||||
"endLine": 6,
|
||||
"endColumn": 45
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "t": "text", "text": "." }
|
||||
]
|
||||
},
|
||||
"shortDescription": "This shell command depends on an uncontrolled ,absolute path,.",
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"severity": "Warning",
|
||||
"codeSnippet": {
|
||||
"startLine": 4,
|
||||
"endLine": 8,
|
||||
"text": "(function() {\n\tcp.execFileSync('rm', ['-rf', path.join(__dirname, \"temp\")]); // GOOD\n\tcp.execSync('rm -rf ' + path.join(__dirname, \"temp\")); // BAD\n\n\texeca.shell('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 6,
|
||||
"startColumn": 14,
|
||||
"endLine": 6,
|
||||
"endColumn": 54
|
||||
},
|
||||
"codeFlows": [
|
||||
{
|
||||
"threadFlows": [
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 4,
|
||||
"endLine": 8,
|
||||
"text": "(function() {\n\tcp.execFileSync('rm', ['-rf', path.join(__dirname, \"temp\")]); // GOOD\n\tcp.execSync('rm -rf ' + path.join(__dirname, \"temp\")); // BAD\n\n\texeca.shell('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 6,
|
||||
"startColumn": 36,
|
||||
"endLine": 6,
|
||||
"endColumn": 45
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 4,
|
||||
"endLine": 8,
|
||||
"text": "(function() {\n\tcp.execFileSync('rm', ['-rf', path.join(__dirname, \"temp\")]); // GOOD\n\tcp.execSync('rm -rf ' + path.join(__dirname, \"temp\")); // BAD\n\n\texeca.shell('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 6,
|
||||
"startColumn": 26,
|
||||
"endLine": 6,
|
||||
"endColumn": 54
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 4,
|
||||
"endLine": 8,
|
||||
"text": "(function() {\n\tcp.execFileSync('rm', ['-rf', path.join(__dirname, \"temp\")]); // GOOD\n\tcp.execSync('rm -rf ' + path.join(__dirname, \"temp\")); // BAD\n\n\texeca.shell('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 6,
|
||||
"startColumn": 14,
|
||||
"endLine": 6,
|
||||
"endColumn": 54
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"tokens": [
|
||||
{
|
||||
"t": "text",
|
||||
"text": "This shell command depends on an uncontrolled "
|
||||
},
|
||||
{
|
||||
"t": "location",
|
||||
"text": "absolute path",
|
||||
"location": {
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 8,
|
||||
"startColumn": 36,
|
||||
"endLine": 8,
|
||||
"endColumn": 45
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "t": "text", "text": "." }
|
||||
]
|
||||
},
|
||||
"shortDescription": "This shell command depends on an uncontrolled ,absolute path,.",
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"severity": "Warning",
|
||||
"codeSnippet": {
|
||||
"startLine": 6,
|
||||
"endLine": 10,
|
||||
"text": "\tcp.execSync('rm -rf ' + path.join(__dirname, \"temp\")); // BAD\n\n\texeca.shell('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\texeca.shellSync('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 8,
|
||||
"startColumn": 14,
|
||||
"endLine": 8,
|
||||
"endColumn": 54
|
||||
},
|
||||
"codeFlows": [
|
||||
{
|
||||
"threadFlows": [
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 6,
|
||||
"endLine": 10,
|
||||
"text": "\tcp.execSync('rm -rf ' + path.join(__dirname, \"temp\")); // BAD\n\n\texeca.shell('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\texeca.shellSync('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 8,
|
||||
"startColumn": 36,
|
||||
"endLine": 8,
|
||||
"endColumn": 45
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 6,
|
||||
"endLine": 10,
|
||||
"text": "\tcp.execSync('rm -rf ' + path.join(__dirname, \"temp\")); // BAD\n\n\texeca.shell('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\texeca.shellSync('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 8,
|
||||
"startColumn": 26,
|
||||
"endLine": 8,
|
||||
"endColumn": 54
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 6,
|
||||
"endLine": 10,
|
||||
"text": "\tcp.execSync('rm -rf ' + path.join(__dirname, \"temp\")); // BAD\n\n\texeca.shell('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\texeca.shellSync('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 8,
|
||||
"startColumn": 14,
|
||||
"endLine": 8,
|
||||
"endColumn": 54
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"tokens": [
|
||||
{
|
||||
"t": "text",
|
||||
"text": "This shell command depends on an uncontrolled "
|
||||
},
|
||||
{
|
||||
"t": "location",
|
||||
"text": "absolute path",
|
||||
"location": {
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 9,
|
||||
"startColumn": 40,
|
||||
"endLine": 9,
|
||||
"endColumn": 49
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "t": "text", "text": "." }
|
||||
]
|
||||
},
|
||||
"shortDescription": "This shell command depends on an uncontrolled ,absolute path,.",
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"severity": "Warning",
|
||||
"codeSnippet": {
|
||||
"startLine": 7,
|
||||
"endLine": 11,
|
||||
"text": "\n\texeca.shell('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\texeca.shellSync('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\n\tconst safe = \"\\\"\" + path.join(__dirname, \"temp\") + \"\\\"\";\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 9,
|
||||
"startColumn": 18,
|
||||
"endLine": 9,
|
||||
"endColumn": 58
|
||||
},
|
||||
"codeFlows": [
|
||||
{
|
||||
"threadFlows": [
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 7,
|
||||
"endLine": 11,
|
||||
"text": "\n\texeca.shell('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\texeca.shellSync('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\n\tconst safe = \"\\\"\" + path.join(__dirname, \"temp\") + \"\\\"\";\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 9,
|
||||
"startColumn": 40,
|
||||
"endLine": 9,
|
||||
"endColumn": 49
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 7,
|
||||
"endLine": 11,
|
||||
"text": "\n\texeca.shell('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\texeca.shellSync('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\n\tconst safe = \"\\\"\" + path.join(__dirname, \"temp\") + \"\\\"\";\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 9,
|
||||
"startColumn": 30,
|
||||
"endLine": 9,
|
||||
"endColumn": 58
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b",
|
||||
"filePath": "javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 7,
|
||||
"endLine": 11,
|
||||
"text": "\n\texeca.shell('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\texeca.shellSync('rm -rf ' + path.join(__dirname, \"temp\")); // NOT OK\n\n\tconst safe = \"\\\"\" + path.join(__dirname, \"temp\") + \"\\\"\";\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 9,
|
||||
"startColumn": 18,
|
||||
"endLine": 9,
|
||||
"endColumn": 58
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nwo": "test/no-results",
|
||||
"status": "Completed",
|
||||
"interpretedResults": []
|
||||
},
|
||||
{
|
||||
"nwo": "meteor/meteor",
|
||||
"status": "Completed",
|
||||
"interpretedResults": [
|
||||
{
|
||||
"message": {
|
||||
"tokens": [
|
||||
{
|
||||
"t": "text",
|
||||
"text": "This shell command depends on an uncontrolled "
|
||||
},
|
||||
{
|
||||
"t": "location",
|
||||
"text": "absolute path",
|
||||
"location": {
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/meteor/meteor/blob/73b538fe201cbfe89dd0c709689023f9b3eab1ec",
|
||||
"filePath": "npm-packages/meteor-installer/config.js"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 39,
|
||||
"startColumn": 20,
|
||||
"endLine": 39,
|
||||
"endColumn": 61
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "t": "text", "text": "." }
|
||||
]
|
||||
},
|
||||
"shortDescription": "This shell command depends on an uncontrolled ,absolute path,.",
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/meteor/meteor/blob/73b538fe201cbfe89dd0c709689023f9b3eab1ec",
|
||||
"filePath": "npm-packages/meteor-installer/install.js"
|
||||
},
|
||||
"severity": "Warning",
|
||||
"codeSnippet": {
|
||||
"startLine": 257,
|
||||
"endLine": 261,
|
||||
"text": " if (isWindows()) {\n //set for the current session and beyond\n child_process.execSync(`setx path \"${meteorPath}/;%path%`);\n return;\n }\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 259,
|
||||
"startColumn": 28,
|
||||
"endLine": 259,
|
||||
"endColumn": 62
|
||||
},
|
||||
"codeFlows": [
|
||||
{
|
||||
"threadFlows": [
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/meteor/meteor/blob/73b538fe201cbfe89dd0c709689023f9b3eab1ec",
|
||||
"filePath": "npm-packages/meteor-installer/config.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 37,
|
||||
"endLine": 41,
|
||||
"text": "\nconst meteorLocalFolder = '.meteor';\nconst meteorPath = path.resolve(rootPath, meteorLocalFolder);\n\nmodule.exports = {\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 39,
|
||||
"startColumn": 20,
|
||||
"endLine": 39,
|
||||
"endColumn": 61
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/meteor/meteor/blob/73b538fe201cbfe89dd0c709689023f9b3eab1ec",
|
||||
"filePath": "npm-packages/meteor-installer/config.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 37,
|
||||
"endLine": 41,
|
||||
"text": "\nconst meteorLocalFolder = '.meteor';\nconst meteorPath = path.resolve(rootPath, meteorLocalFolder);\n\nmodule.exports = {\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 39,
|
||||
"startColumn": 7,
|
||||
"endLine": 39,
|
||||
"endColumn": 61
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/meteor/meteor/blob/73b538fe201cbfe89dd0c709689023f9b3eab1ec",
|
||||
"filePath": "npm-packages/meteor-installer/config.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 42,
|
||||
"endLine": 46,
|
||||
"text": " METEOR_LATEST_VERSION,\n extractPath: rootPath,\n meteorPath,\n release: process.env.INSTALL_METEOR_VERSION || METEOR_LATEST_VERSION,\n rootPath,\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 44,
|
||||
"startColumn": 3,
|
||||
"endLine": 44,
|
||||
"endColumn": 13
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/meteor/meteor/blob/73b538fe201cbfe89dd0c709689023f9b3eab1ec",
|
||||
"filePath": "npm-packages/meteor-installer/install.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 10,
|
||||
"endLine": 14,
|
||||
"text": "const os = require('os');\nconst {\n meteorPath,\n release,\n startedPath,\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 12,
|
||||
"startColumn": 3,
|
||||
"endLine": 12,
|
||||
"endColumn": 13
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/meteor/meteor/blob/73b538fe201cbfe89dd0c709689023f9b3eab1ec",
|
||||
"filePath": "npm-packages/meteor-installer/install.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 9,
|
||||
"endLine": 25,
|
||||
"text": "const tmp = require('tmp');\nconst os = require('os');\nconst {\n meteorPath,\n release,\n startedPath,\n extractPath,\n isWindows,\n rootPath,\n sudoUser,\n isSudo,\n isMac,\n METEOR_LATEST_VERSION,\n shouldSetupExecPath,\n} = require('./config.js');\nconst { uninstall } = require('./uninstall');\nconst {\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 11,
|
||||
"startColumn": 7,
|
||||
"endLine": 23,
|
||||
"endColumn": 27
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/meteor/meteor/blob/73b538fe201cbfe89dd0c709689023f9b3eab1ec",
|
||||
"filePath": "npm-packages/meteor-installer/install.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 257,
|
||||
"endLine": 261,
|
||||
"text": " if (isWindows()) {\n //set for the current session and beyond\n child_process.execSync(`setx path \"${meteorPath}/;%path%`);\n return;\n }\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 259,
|
||||
"startColumn": 42,
|
||||
"endLine": 259,
|
||||
"endColumn": 52
|
||||
}
|
||||
},
|
||||
{
|
||||
"fileLink": {
|
||||
"fileLinkPrefix": "https://github.com/meteor/meteor/blob/73b538fe201cbfe89dd0c709689023f9b3eab1ec",
|
||||
"filePath": "npm-packages/meteor-installer/install.js"
|
||||
},
|
||||
"codeSnippet": {
|
||||
"startLine": 257,
|
||||
"endLine": 261,
|
||||
"text": " if (isWindows()) {\n //set for the current session and beyond\n child_process.execSync(`setx path \"${meteorPath}/;%path%`);\n return;\n }\n"
|
||||
},
|
||||
"highlightedRegion": {
|
||||
"startLine": 259,
|
||||
"startColumn": 28,
|
||||
"endLine": 259,
|
||||
"endColumn": 62
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"queryName": "Shell command built from environment values",
|
||||
"queryFilePath": "c:\\git-repo\\vscode-codeql-starter\\ql\\javascript\\ql\\src\\Security\\CWE-078\\ShellCommandInjectionFromEnvironment.ql",
|
||||
"queryText": "/**\n * @name Shell command built from environment values\n * @description Building a shell command string with values from the enclosing\n * environment may cause subtle bugs or vulnerabilities.\n * @kind path-problem\n * @problem.severity warning\n * @security-severity 6.3\n * @precision high\n * @id js/shell-command-injection-from-environment\n * @tags correctness\n * security\n * external/cwe/cwe-078\n * external/cwe/cwe-088\n */\n\nimport javascript\nimport DataFlow::PathGraph\nimport semmle.javascript.security.dataflow.ShellCommandInjectionFromEnvironmentQuery\n\nfrom\n Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight,\n Source sourceNode\nwhere\n sourceNode = source.getNode() and\n cfg.hasFlowPath(source, sink) and\n if cfg.isSinkWithHighlight(sink.getNode(), _)\n then cfg.isSinkWithHighlight(sink.getNode(), highlight)\n else highlight = sink.getNode()\nselect highlight, source, sink, \"This shell command depends on an uncontrolled $@.\", sourceNode,\n sourceNode.getSourceType()\n",
|
||||
"language": "javascript",
|
||||
"controllerRepository": { "owner": "dsp-testing", "name": "qc-controller" },
|
||||
"executionStartTime": 1649419081990,
|
||||
"actionsWorkflowRunId": 2115000864
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
### github/codeql
|
||||
|
||||
[javascript/ql/src/Security/CWE-078/examples/shell-command-injection-from-environment.js](https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b/javascript/ql/src/Security/CWE-078/examples/shell-command-injection-from-environment.js#L5-L5)
|
||||
|
||||
```javascript
|
||||
function cleanupTemp() {
|
||||
let cmd = "rm -rf " + path.join(__dirname, "temp");
|
||||
cp.execSync(cmd); // BAD
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
*This shell command depends on an uncontrolled [absolute path](https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b/javascript/ql/src/Security/CWE-078/examples/shell-command-injection-from-environment.js#L4-L4).*
|
||||
|
||||
----------------------------------------
|
||||
|
||||
[javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js](https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b/javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js#L6-L6)
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
cp.execFileSync('rm', ['-rf', path.join(__dirname, "temp")]); // GOOD
|
||||
cp.execSync('rm -rf ' + path.join(__dirname, "temp")); // BAD
|
||||
|
||||
execa.shell('rm -rf ' + path.join(__dirname, "temp")); // NOT OK
|
||||
|
||||
```
|
||||
|
||||
*This shell command depends on an uncontrolled [absolute path](https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b/javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js#L6-L6).*
|
||||
|
||||
----------------------------------------
|
||||
|
||||
[javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js](https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b/javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js#L8-L8)
|
||||
|
||||
```javascript
|
||||
cp.execSync('rm -rf ' + path.join(__dirname, "temp")); // BAD
|
||||
|
||||
execa.shell('rm -rf ' + path.join(__dirname, "temp")); // NOT OK
|
||||
execa.shellSync('rm -rf ' + path.join(__dirname, "temp")); // NOT OK
|
||||
|
||||
|
||||
```
|
||||
|
||||
*This shell command depends on an uncontrolled [absolute path](https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b/javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js#L8-L8).*
|
||||
|
||||
----------------------------------------
|
||||
|
||||
[javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js](https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b/javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js#L9-L9)
|
||||
|
||||
```javascript
|
||||
|
||||
execa.shell('rm -rf ' + path.join(__dirname, "temp")); // NOT OK
|
||||
execa.shellSync('rm -rf ' + path.join(__dirname, "temp")); // NOT OK
|
||||
|
||||
const safe = "\"" + path.join(__dirname, "temp") + "\"";
|
||||
|
||||
```
|
||||
|
||||
*This shell command depends on an uncontrolled [absolute path](https://github.com/github/codeql/blob/48015e5a2e6202131f2d1062cc066dc33ed69a9b/javascript/ql/test/query-tests/Security/CWE-078/tst_shell-command-injection-from-environment.js#L9-L9).*
|
||||
|
||||
----------------------------------------
|
||||
@@ -0,0 +1,16 @@
|
||||
### meteor/meteor
|
||||
|
||||
[npm-packages/meteor-installer/install.js](https://github.com/meteor/meteor/blob/73b538fe201cbfe89dd0c709689023f9b3eab1ec/npm-packages/meteor-installer/install.js#L259-L259)
|
||||
|
||||
```javascript
|
||||
if (isWindows()) {
|
||||
//set for the current session and beyond
|
||||
child_process.execSync(`setx path "${meteorPath}/;%path%`);
|
||||
return;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
*This shell command depends on an uncontrolled [absolute path](https://github.com/meteor/meteor/blob/73b538fe201cbfe89dd0c709689023f9b3eab1ec/npm-packages/meteor-installer/config.js#L39-L39).*
|
||||
|
||||
----------------------------------------
|
||||
@@ -0,0 +1,49 @@
|
||||
### Results for "Shell command built from environment values"
|
||||
|
||||
<details>
|
||||
<summary>Query</summary>
|
||||
|
||||
```ql
|
||||
/**
|
||||
* @name Shell command built from environment values
|
||||
* @description Building a shell command string with values from the enclosing
|
||||
* environment may cause subtle bugs or vulnerabilities.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 6.3
|
||||
* @precision high
|
||||
* @id js/shell-command-injection-from-environment
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-078
|
||||
* external/cwe/cwe-088
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.ShellCommandInjectionFromEnvironmentQuery
|
||||
|
||||
from
|
||||
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight,
|
||||
Source sourceNode
|
||||
where
|
||||
sourceNode = source.getNode() and
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
if cfg.isSinkWithHighlight(sink.getNode(), _)
|
||||
then cfg.isSinkWithHighlight(sink.getNode(), highlight)
|
||||
else highlight = sink.getNode()
|
||||
select highlight, source, sink, "This shell command depends on an uncontrolled $@.", sourceNode,
|
||||
sourceNode.getSourceType()
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<br />
|
||||
|
||||
### Summary
|
||||
|
||||
| Repository | Results |
|
||||
| --- | --- |
|
||||
| github/codeql | [4 result(s)](#file-github-codeql-md) |
|
||||
| meteor/meteor | [1 result(s)](#file-meteor-meteor-md) |
|
||||
@@ -0,0 +1,42 @@
|
||||
import { expect } from 'chai';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
import { generateMarkdown } from '../../../../../src/remote-queries/remote-queries-markdown-generation';
|
||||
|
||||
describe('markdown generation', async function() {
|
||||
it('should generate markdown file for each repo with results', async function() {
|
||||
const problemQuery = JSON.parse(
|
||||
await fs.readFile(path.join(__dirname, 'data/problem-query.json'), 'utf8')
|
||||
);
|
||||
|
||||
const analysesResults = JSON.parse(
|
||||
await fs.readFile(path.join(__dirname, 'data/analyses-results.json'), 'utf8')
|
||||
);
|
||||
const markdownFiles = generateMarkdown(problemQuery, analysesResults);
|
||||
|
||||
// Check that query has results for two repositories, plus a summary file
|
||||
expect(markdownFiles.length).to.equal(3);
|
||||
|
||||
const markdownFile0 = markdownFiles[0]; // summary file
|
||||
const markdownFile1 = markdownFiles[1]; // results for github/codeql repo
|
||||
const markdownFile2 = markdownFiles[2]; // results for meteor/meteor repo
|
||||
|
||||
const expectedSummaryFile = await readTestOutputFile('data/summary.md');
|
||||
const expectedTestOutput1 = await readTestOutputFile('data/results-repo1.md');
|
||||
const expectedTestOutput2 = await readTestOutputFile('data/results-repo2.md');
|
||||
|
||||
// Check that markdown output is correct, after making line endings consistent
|
||||
expect(markdownFile0.join('\n')).to.equal(expectedSummaryFile);
|
||||
expect(markdownFile1.join('\n')).to.equal(expectedTestOutput1);
|
||||
expect(markdownFile2.join('\n')).to.equal(expectedTestOutput2);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Reads a test output file and returns it as a string.
|
||||
* Replaces line endings with '\n' for consistency across operating systems.
|
||||
*/
|
||||
async function readTestOutputFile(relativePath: string): Promise<string> {
|
||||
const file = await fs.readFile(path.join(__dirname, relativePath), 'utf8');
|
||||
return file.replace(/\r?\n/g, '\n');
|
||||
}
|
||||
Reference in New Issue
Block a user