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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ Ensure that you're using the <a href="https://github.com/Checkmarx/kics-github-a
| secrets_regexes_path | ./mydir/secrets-config.json | path to custom secrets regex rules configuration file | String | No | N/A |
| libraries_path | ./myLibsDir | path to directory with Rego libraries | String | No | N/A |
| cloud_provider | aws,azure | list of cloud providers to scan (alicloud, aws, azure, gcp) | String | No | N/A |
| enable_diff_aware_reporting | true | Enable diff-aware reporting for pull requests (only report findings in changed files/lines) | Boolean | No | false |


## Simple usage example
Expand Down
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ inputs:
required: false
default: "false"
description: "Enable report as jobs summary"
enable_diff_aware_reporting:
required: false
default: "false"
description: "Enable diff-aware reporting for pull requests (only report findings in changed files/lines)"
comments_with_queries:
required: false
default: "false"
Expand Down Expand Up @@ -116,6 +120,7 @@ runs:
INPUT_ENABLE_ANNOTATIONS: ${{ inputs.enable_annotations }}
INPUT_ENABLE_COMMENTS: ${{ inputs.enable_comments }}
INPUT_ENABLE_JOBS_SUMMARY: ${{ inputs.enable_jobs_summary }}
INPUT_ENABLE_DIFF_AWARE_REPORTING: ${{ inputs.enable_diff_aware_reporting }}
INPUT_COMMENTS_WITH_QUERIES: ${{ inputs.comments_with_queries }}
INPUT_EXCLUDED_COLUMNS_FOR_COMMENTS_WITH_QUERIES: ${{ inputs.excluded_column_for_comments_with_queries }}
INPUT_OUTPUT_FORMATS: ${{ inputs.output_formats }}
Expand Down
139 changes: 139 additions & 0 deletions src/filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
async function fetchPRFiles(octokit, repo, prNumber) {
try {
const { data: files } = await octokit.rest.pulls.listFiles({
...repo,
pull_number: prNumber,
});
return files;
} catch (error) {
console.error("Error fetching PR files:", error);
return [];
}
}

function parseChangedLines(prFiles) {
const changedFiles = {};

prFiles.forEach((file) => {
const changedLines = [];

// Skip files without patches (like binary files or deleted files)
if (!file.patch) {
changedFiles[file.filename] = changedLines;
return;
}

// Parse each hunk in the patch to find added/modified lines
const hunkRegex = /@@\s+-\d+(?:,\d+)?\s+\+(\d+)(?:,(\d+))?\s+@@/g;
let match;

while ((match = hunkRegex.exec(file.patch)) !== null) {
const start = parseInt(match[1]);
const count = match[2] ? parseInt(match[2]) : 1;

// Only include lines that were added/modified (count > 0)
if (count > 0) {
for (let i = start; i < start + count; i++) {
changedLines.push(i);
}
}
}

// Remove duplicates and sort
changedFiles[file.filename] = [...new Set(changedLines)].sort(
(a, b) => a - b
);
});

return changedFiles;
}

function filterResultsByChangedFiles(results, changedFiles) {
console.log("Filtering results for diff-aware reporting...");

const filteredResults = JSON.parse(JSON.stringify(results)); // Deep clone
const filteredQueries = [];

// Filter findings based on changed files and lines
for (const query of results.queries) {
const filteredFiles = [];

for (const file of query.files) {
const fileName = file.file_name;
const fileLine = file.line;

// Check if this file was changed in the PR
if (changedFiles.hasOwnProperty(fileName)) {
const changedLines = changedFiles[fileName];

// If no specific lines changed (e.g., new file), include all findings
// Otherwise, only include findings on changed lines
if (changedLines.length === 0 || changedLines.includes(fileLine)) {
filteredFiles.push(file);
}
}
}

// If this query has findings in changed files/lines, include it
if (filteredFiles.length > 0) {
const filteredQuery = { ...query, files: filteredFiles };
filteredQueries.push(filteredQuery);
}
}

// Update the filtered results
filteredResults.queries = filteredQueries;

// Recalculate counters based on filtered findings
const newSeverityCounters = {};
let totalCounter = 0;

// Initialize severity counters
for (const severity of [
"CRITICAL",
"HIGH",
"MEDIUM",
"LOW",
"INFO",
"TRACE",
]) {
newSeverityCounters[severity] = 0;
}

// Count findings by severity
filteredQueries.forEach((query) => {
const severity = query.severity.toUpperCase();
const findingCount = query.files.length;
if (newSeverityCounters.hasOwnProperty(severity)) {
newSeverityCounters[severity] += findingCount;
}
totalCounter += findingCount;
});

filteredResults.severity_counters = newSeverityCounters;
filteredResults.total_counter = totalCounter;

console.log(
`Filtered results: ${totalCounter} findings in changed files (originally ${results.total_counter})`
);

return filteredResults;
}

async function applyDiffAwareFiltering(parsedResults, octokit, repo, prNumber) {
console.log("Diff-aware reporting enabled for PR #" + prNumber);
const prFiles = await fetchPRFiles(octokit, repo, prNumber);
if (prFiles.length > 0) {
const changedFiles = parseChangedLines(prFiles);
return filterResultsByChangedFiles(parsedResults, changedFiles);
} else {
console.log(
"No PR files found or error fetching files, using original results"
);
return parsedResults;
}
}

module.exports = {
applyDiffAwareFiltering,
};
19 changes: 17 additions & 2 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const commenter = require("./commenter");
const annotator = require("./annotator");
const filter = require("./filter");
const core = require("@actions/core");
const github = require("@actions/github");
const io = require("@actions/io");
Expand Down Expand Up @@ -50,6 +51,7 @@ async function main() {
let enableAnnotations = process.env.INPUT_ENABLE_ANNOTATIONS;
let enableComments = process.env.INPUT_ENABLE_COMMENTS;
let enableJobsSummary = process.env.INPUT_ENABLE_JOBS_SUMMARY;
let enableDiffAwareReporting = process.env.INPUT_ENABLE_DIFF_AWARE_REPORTING;
const commentsWithQueries = process.env.INPUT_COMMENTS_WITH_QUERIES;
const excludedColumnsForCommentsWithQueries = process.env.INPUT_EXCLUDED_COLUMNS_FOR_COMMENTS_WITH_QUERIES.split(',');
const outputPath = processOutputPath(process.env.INPUT_OUTPUT_PATH);
Expand All @@ -75,8 +77,21 @@ async function main() {
enableAnnotations = enableAnnotations ? enableAnnotations : "false"
enableComments = enableComments ? enableComments : "false"
enableJobsSummary = enableJobsSummary ? enableJobsSummary : "false"
enableDiffAwareReporting = enableDiffAwareReporting ? enableDiffAwareReporting : "false"

const parsedResults = readJSON(outputPath.resultsJSONFile);
let parsedResults = readJSON(outputPath.resultsJSONFile);
let finalExitCode = exitCode;

if (enableDiffAwareReporting.toLocaleLowerCase() === "true" && prNumber) {
parsedResults = await filter.applyDiffAwareFiltering(parsedResults, octokit, repo, prNumber);

// If diff-aware filtering resulted in zero findings, override exit code to success
if (parsedResults.total_counter === 0) {
console.log("Diff-aware filtering resulted in zero findings. Setting workflow status to success.");
finalExitCode = "0";
}
}

if (enableAnnotations.toLocaleLowerCase() === "true") {
annotator.annotateChangesWithResults(parsedResults);
}
Expand All @@ -87,7 +102,7 @@ async function main() {
await commenter.postJobSummary(parsedResults, commentsWithQueries.toLocaleLowerCase() === "true", excludedColumnsForCommentsWithQueries);
}

setWorkflowStatus(exitCode);
setWorkflowStatus(finalExitCode);
cleanupOutput(outputPath.resultsJSONFile, outputFormats);
} catch (e) {
console.error(e);
Expand Down