Skip to content

Commit

Permalink
Fixinator Client 4.1.0
Browse files Browse the repository at this point in the history
* Avoid throwing an error on timeout, show warning instead
* Add experimental Github sarif report format
  • Loading branch information
pfreitag committed Apr 5, 2024
1 parent a9b1d41 commit 64830ac
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 11 deletions.
4 changes: 2 additions & 2 deletions box.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name":"fixinator",
"version":"4.0.0",
"version":"4.1.0",
"author":"Foundeo Inc.",
"location":"foundeo/fixinator#v4.0.0",
"location":"foundeo/fixinator#v4.1.0",
"homepage":"https://fixinator.app/",
"documentation":"https://github.com/foundeo/fixinator/wiki",
"repository":{
Expand Down
13 changes: 8 additions & 5 deletions commands/fixinator.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ component extends="commandbox.system.BaseCommand" excludeFromHelp=false {
/**
* @path.hint A file or directory to scan
* @resultFile.hint A file path to write the results to - see resultFormat
* @resultFormat.hint The format to write the results in [json,html,pdf,junit,findbugs,sast,csv]
* @resultFormat.hint The format to write the results in [json,html,pdf,junit,findbugs,sast,csv,sarif]
* @resultFormat.optionsUDF resultFormatComplete
* @verbose.hint When false limits the output
* @listBy.hint Show results by type or file
Expand Down Expand Up @@ -639,10 +639,13 @@ component extends="commandbox.system.BaseCommand" excludeFromHelp=false {
}*/
if (arguments.verbose && arrayLen(local.results.warnings)) {
print.line();
print.boldOrangeLine("WARNINGS");
print.boldYellowLine("WARNINGS");
local.lastWarning = "";
for (local.w in local.results.warnings) {
if (local.w.keyExists("message") && local.w.keyExists("path")) {
print.grayLine(local.w.message);
if (isStruct(local.w) && local.w.keyExists("message") && local.w.keyExists("path")) {
if (local.lastWarning != local.w.message) {
print.grayLine(local.w.message);
}
print.grayLine(" " & replaceNoCase(local.w.path, getDirectoryFromPath(arguments.path), ""));
} else {
print.grayLine(serializeJSON(local.w));
Expand Down Expand Up @@ -687,7 +690,7 @@ component extends="commandbox.system.BaseCommand" excludeFromHelp=false {
}

function resultFormatComplete() {
return [ 'html', 'json', 'pdf', 'junit', 'findbugs', 'sast', 'csv' ];
return [ 'html', 'json', 'pdf', 'junit', 'findbugs', 'sast', 'csv', 'sarif' ];
}

function confidenceComplete() {
Expand Down
31 changes: 27 additions & 4 deletions models/fixinator/FixinatorClient.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ component singleton="true" {
if (local.result.keyExists("categories")) {
element.results["categories"] = local.result.categories;
}
if (local.result.keyExists("warnings") && isArray(local.result.warnings) && arrayLen(local.result.warnings)) {
arrayAppend(element.results.warnings, local.result.warnings, true);
}
payload.result = local.result;
//arrayAppend(results.payloads, payload);
local.size = 0;
Expand Down Expand Up @@ -304,6 +307,9 @@ component singleton="true" {
}
//arrayAppend(results.payloads, payload);
arrayAppend(element.results.results, local.result.results, true);
if (local.result.keyExists("warnings") && isArray(local.result.warnings) && arrayLen(local.result.warnings)) {
arrayAppend(element.results.warnings, local.result.warnings, true);
}
}

} catch (any e) {
Expand Down Expand Up @@ -378,18 +384,20 @@ component singleton="true" {

public function sendPayload(payload, isRetry=0) {
var httpResult = "";
local.payloadID = left(createUUID(),12);
if (isDebugModeEnabled()) {
local.payloadID = createUUID();

local.payloadPaths = arrayMap(arguments.payload.files, function(item) {
return item.path;
});
debugger("Sending Payload #local.payloadID# to #getAPIURL()# of #arrayLen(arguments.payload.files)# files. isRetry:#arguments.isRetry#");
debugger("Payload Paths #local.payloadID#: #serializeJSON(local.payloadPaths)#");
local.tick = getTickCount();
}
local.tick = getTickCount();
cfhttp(url=getAPIURL(), method="POST", result="httpResult", timeout=getAPITimeout()) {
cfhttpparam(type="header", name="Content-Type", value="application/json");
cfhttpparam(type="header", name="x-api-key", value=getAPIKey());
cfhttpparam(type="header", name="X-Payload-ID", value=local.payloadID);
cfhttpparam(type="header", name="X-Client-Version", value=getClientVersion());
cfhttpparam(value="#serializeJSON(payload)#", type="body");
}
Expand Down Expand Up @@ -425,7 +433,22 @@ component singleton="true" {
local.payloadPaths = arrayMap(arguments.payload.files, function(item) {
return item.path;
});
throw(message="Fixinator API Returned #httpResult.statusCode# Status Code. Please try again shortly or contact Foundeo Inc. if the problem persists.", detail="Paths: #serializeJSON(local.payloadPaths)#", type="FixinatorClient");
if (isDebugModeEnabled()) {
debugger("#local.payloadID#: #httpResult.statusCode# Status Code -- #httpResult.fileContent#");
}
/*
Timeouts may happen when really large files take too long to process
Instead of erroring out the entire scan with a throw, add warnings
and let the scan continue.
*/
//throw(message="Fixinator API Returned #httpResult.statusCode# Status Code. Please try again shortly or contact Foundeo Inc. if the problem persists.", detail="Paths: #serializeJSON(local.payloadPaths)#", type="FixinatorClient");
local.result = {"warnings":[], "results":[]};
local.fileOrFiles = arrayLen(local.payloadPaths) == 1 ? "file" : "files";
for (local.path in local.payloadPaths) {
arrayAppend(local.result.warnings, {"message":"Fixinator API Returned #httpResult.statusCode#, took #getTickCount()-local.tick#ms, attempts: #arguments.isRetry#, skipped #arrayLen(arguments.payload.files)# #local.fileOrFiles# [#local.payloadID#]", "path":local.path});
}

return local.result;
} else {

if (isDebugModeEnabled()) {
Expand Down Expand Up @@ -547,7 +570,7 @@ component singleton="true" {
public function fixCode(basePath, fixes, writeFiles=true) {
var fix = "";
var basePathInfo = getFileInfo(arguments.basePath);
var results = {"fixes"={}, warnings=[]};
var results = {"fixes"={}, "warnings"=[]};
var i=0;
//sort issues by file first then by position
arraySort(
Expand Down
90 changes: 90 additions & 0 deletions models/fixinator/FixinatorReport.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
<cfset fileWrite(arguments.resultFile, generateFindBugsReport(data=arguments.data))>
<cfelseif format IS "csv">
<cfset fileWrite(arguments.resultFile, generateCSVReport(data=arguments.data))>
<cfelseif format IS "sarif">
<cfset fileWrite(arguments.resultFile, generateSarifReport(data=arguments.data))>
<cfelse>
<cfthrow message="Unsupported result file format">
</cfif>
Expand Down Expand Up @@ -461,6 +463,94 @@
<cfreturn xml>
</cffunction>

<cffunction name="generateSarifReport" returntype="string" output="false">
<cfargument name="data">
<cfset var sarif = {
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"version": "2.1.0",
"runs": []
}>
<cfset var run = {}>
<cfset var i = "">
<cfset var rule = "">
<cfset var rules = {}>
<cfset var result = "">
<cfset var location = "">

<cfset run["tool"] = { "driver" = {"name"="Fixinator", "rules":[], "version"=data.fixinator_client_version, "informationUri"="https://fixinator.app/"}}>
<cfset run.tool.driver["fullName"] = run.tool.driver.name & " " & run.tool.driver.version>
<cfset run["results"] = []>
<cfset run["automationDetails"] = {"id":"fixinator/" & reReplace(arguments.data.timestamp, "[^0-9]", "", "ALL")}>
<!---
docs: https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning#sarif-output-file-examples
validator: https://sarifweb.azurewebsites.net/
--->
<cfloop array="#arguments.data.results#" index="i">
<cfset rule = { "id": "fixinator-#i.scanner#-#i.id#-s#i.severity#-c#i.confidence#" }>
<cfif NOT rules.keyExists(rule.id)>
<cfset rule["properties"] = {}>
<cfset rule["shortDescription"] = {"text":i.description}>
<cfset rule["fullDescription"] = rule.shortDescription>
<cfset rule["help"] = rule.shortDescription>
<cfset rule["name"] = replace(i.title, " ", "", "ALL")>
<cfset rule.properties["name"] = rule.name>
<cfif i.severity IS 3>
<cfset rule.properties["problem.severity"] = "error">
<cfelseif i.severity IS 2>
<cfset rule.properties["problem.severity"] = "warning">
<cfelse>
<cfset rule.properties["problem.severity"] = "recommendation">
</cfif>
<cfif i.keyExists("confidence") AND isNumeric(i.confidence)>
<cfif i.severity IS 3>
<cfset rule.properties["precision"] = "high">
<cfelseif i.severity IS 2>
<cfset rule.properties["precision"] = "medium">
<cfelse>
<cfset rule.properties["precision"] = "low">
</cfif>
</cfif>
<cfset rule["helpUri"] = "https://foundeo.com/security/guide/">
<cfif i.keyExists("link") AND len(i.link)>
<cfset rule.helpUri = i.link>
</cfif>
<cfset arrayAppend(run.tool.driver.rules, rule)>
</cfif>
<cfset result = {"ruleId":rule.id, "message":{"text":i.category & ": " & i.message}, "locations":[]}>
<cfset location = {"physicalLocation": {"artifactLocation":{"uri":""}}}>

<cfif i.keyExists("path")>
<cfset local.p = i.path>
<cfif left(local.p, 1) IS "/">
<cfset local.p = replace(local.p, "/", "", "ONE")>
</cfif>
<cfset location.physicalLocation.artifactLocation.uri = local.p>
</cfif>
<cfset location.physicalLocation["region"] = {"startLine":1, "startColumn":1, "endLine":1, "endColumn":1}>
<cfif i.keyExists("line") AND isValid("integer", i.line)>
<cfset location.physicalLocation.region.startLine = javaCast("int", i.line)>
<cfset location.physicalLocation.region.endLine = javaCast("int", i.line)>
</cfif>
<cfif i.keyExists("context") AND len(i.context)>
<cfset location.physicalLocation.region["snippet"] = {"text":i.context}>
</cfif>
<cfset arrayAppend(result.locations, location)>
<cfset local.fingerPrintRaw = "#location.physicalLocation.artifactLocation.uri#:#location.physicalLocation.region.startLine#">
<cfif i.keyExists("column")>
<cfset local.fingerPrintRaw &= ":#i.column#">
</cfif>
<cfif i.keyExists("context")>
<cfset local.fingerPrintRaw &= ":#i.context#">
</cfif>
<cfset result["partialFingerprints"] = {"primaryLocationLineHash":"#hash(local.fingerPrintRaw, "SHA-256")#:#location.physicalLocation.region.startLine#"}>

<cfset arrayAppend(run.results, result)>
</cfloop>
<cfset arrayAppend(sarif.runs, run)>
<!---<cfset sarif["fixinator"] = arguments.data>--->
<cfreturn serializeJSON(sarif)>
</cffunction>

<cffunction name="getClassNameFromFilePath" returntype="string" output="false">
<cfargument name="path">
<cfset var p = arguments.path>
Expand Down

0 comments on commit 64830ac

Please sign in to comment.