node-sarif-builder
Introduction
Until today, every SAST tool (not exhaustive list available at https://analysis-tools.dev/) is using its own custom output format.
In order to unify SAST tools output format, more and more tools and services are implementing SARIF format (example)
SARIF logs can be:
Example of linters that can output logs in SARIF format:
- bandit (python)
- checkov (terraform)
- checkstyle (java)
- cfn-lint (AWS CloudFormation)
- codeql (multi-language)
- devskim (security)
- eslint (javascript,typescript,json)
- gitleaks (security)
- ktlint (Kotlin)
- hadolint (Dockerfile)
- MegaLinter (linters orchestrator)
- psalm (php)
- semgrep (multi-language)
- revive (Go)
- tflint (terraform)
- terrascan (terrasform)
- trivy (security)
- and many more...
If you are a maintainer of any javascript/typescript based SAST tool, but also IaC tool, or any type of tool that can return a list of errors with a level of severity, you can either read the whole OASIS Specification, or simply use this library to add SARIF as additional output format, so your tool will be natively compliant with any of SARIF-compliant tools !
Installation
npm install node-sarif-builder
yarn add node-sarif-builder
Use
With node-sarif-builder, you can generate complex SARIF format with simple methods
- Start by importing module
const { SarifBuilder, SarifRunBuilder, SarifResultBuilder, SarifRuleBuilder } = require("node-sarif-builder");
- Create and init SarifBuilder and SarifRunBuilder objects
const sarifBuilder = new SarifBuilder();
const sarifRunBuilder = new SarifRunBuilder().initSimple({
name: "npm-groovy-lint",
url: "https://nvuillam.github.io/npm-groovy-lint/"
});
- Add all rules that can be found in your results (recommended but optional)
for (const rule of rules) {
const sarifRuleBuiler = new SarifRuleBuilder().initSimple({
ruleId: rule.id,
shortDescriptionText: rule.description,
helpUri: rule.docUrl
});
sarifRunBuilder.addRule(sarifRuleBuiler);
}
- For each found issue, create a SarifResultBuilder and add it to the SarifRunBuilder object
for (const issue of issues) {
const sarifResultBuilder = new SarifResultBuilder();
const sarifResultInit = {
level: issue.severity === "info" ? "note" : issue.severity,
messageText: err.msg,
ruleId: err.rule,
fileUri: process.env.SARIF_URI_ABSOLUTE
? "file:///" + fileNm.replace(/\\/g, "/")
: path.relative(process.cwd(), fileNm)
};
if (issue.range) {
sarifResultInit.startLine = issue.range.start.line;
sarifResultInit.startColumn = issue.range.start.character;
sarifResultInit.endLine = issue.range.end.line;
sarifResultInit.endColumn = issue.range.end.character;
}
sarifResultBuilder.initSimple(sarifResultInit);
sarifRunBuilder.addResult(sarifResultBuilder);
}
- Add run to sarifBuilder then generate JSON SARIF output file
sarifBuilder.addRun(sarifRunBuilder);
const sarifJsonString = sarifBuilder.buildSarifJsonString({ indent: false });
fs.writeFileSync(outputSarifFile,sarifJsonString);
Full example
function buildSarifResult(lintResult) {
const sarifBuilder = new SarifBuilder();
const sarifRunBuilder = new SarifRunBuilder().initSimple({
name: "npm-groovy-lint",
url: "https://nvuillam.github.io/npm-groovy-lint/"
});
for (const ruleId of Object.keys(lintResult.rules || {})) {
const rule = lintResult.rules[ruleId];
const sarifRuleBuiler = new SarifRuleBuilder().initSimple({
ruleId: ruleId,
shortDescriptionText: rule.description,
helpUri: rule.docUrl
});
sarifRunBuilder.addRule(sarifRuleBuiler);
}
for (const fileNm of Object.keys(lintResult.files)) {
const fileErrors = lintResult.files[fileNm].errors;
for (const err of fileErrors) {
const sarifResultBuilder = new SarifResultBuilder();
const sarifResultInit = {
level: err.severity === "info" ? "note" : err.severity,
messageText: err.msg,
ruleId: err.rule,
fileUri: process.env.SARIF_URI_ABSOLUTE
? "file:///" + fileNm.replace(/\\/g, "/")
: path.relative(process.cwd(), fileNm)
};
if (err.range) {
sarifResultInit.startLine = fixLine(err.range.start.line);
sarifResultInit.startColumn = fixCol(err.range.start.character);
sarifResultInit.endLine = fixLine(err.range.end.line);
sarifResultInit.endColumn = fixCol(err.range.end.character);
}
sarifResultBuilder.initSimple(sarifResultInit);
sarifRunBuilder.addResult(sarifResultBuilder);
}
}
sarifBuilder.addRun(sarifRunBuilder);
return sarifBuilder.buildSarifJsonString({ indent: false });
}
function fixLine(val) {
if (val === null) {
return undefined;
}
return val === 0 ? 1 : val;
}
function fixCol(val) {
if (val === null) {
return undefined;
}
return val === 0 ? 1 : val + 1;
}