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 and implement it
- 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({
toolDriverName: "npm-groovy-lint",
toolDriverVersion: "9.0.5",
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
import { pathToFileURL } from 'url'
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
? pathToFileURL(fileNm)
: path.relative(process.cwd(), fileNm).replace(/\\/g, '/'),
};
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
import { pathToFileURL } from 'url'
function buildSarifResult(lintResult) {
const sarifBuilder = new SarifBuilder();
const sarifRunBuilder = new SarifRunBuilder().initSimple({
toolDriverName: "npm-groovy-lint",
toolDriverVersion: "9.0.5",
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
? pathToFileURL(fileNm)
: path.relative(process.cwd(), fileNm).replace(/\\/g, '/')
};
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;
}
Test
You can confirm that your generated SARIF logs are valid on https://sarifweb.azurewebsites.net/Validation