Skip to content
Draft
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
155 changes: 101 additions & 54 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,24 @@
import { error, getInput, info, setOutput, warning } from '@actions/core'
import { appendFileSync, existsSync } from 'fs'
import { appendFileSync, existsSync, mkdirSync } from 'fs'
import * as path from 'path'
import {
downloadArtifact,
postCommentIfInPr,
resolveExistingCommentIfFound,
uploadArtifact,
} from './actions'
import { compareResults } from './tool'
import {
callCommand,
callLaceworkCli,
debug,
codesecRun,
getActionRef,
getMsSinceStart,
getOptionalEnvVariable,
getRequiredEnvVariable,
getRunUrl,
readMarkdownFile,
telemetryCollector,
} from './util'

import path from 'path'

const scaSarifReport = 'scaReport/output.sarif'
const scaReport = 'sca.sarif'
const scaLWJSONReport = 'scaReport/output-lw.json'
const scaDir = 'scaReport'

async function runAnalysis() {
const target = getInput('target')

Expand All @@ -43,73 +36,127 @@ async function runAnalysis() {
telemetryCollector.addField('tools', 'sca')
const toUpload: string[] = []

// command to print both sarif and lwjson formats
var args = ['sca', 'scan', '.', '-o', scaDir, '--formats', 'sarif,lw-json', '--deployment', 'ci']
if (target === 'push') {
args.push('--save-results')
// Run codesec Docker scanner
// targetScan: 'new'/'old' for PR mode, 'scan' for push mode (uploads to Lacework UI)
var targetScan = target
if (target == 'push') {
targetScan = 'scan'
}
if (debug()) {
args.push('--debug')
const resultsPath = await codesecRun('scan', true, true, targetScan)

// Upload SCA SARIF from the returned results path
const scaSarifFile = path.join(resultsPath, 'sca', `sca-${targetScan}.sarif`)
if (existsSync(scaSarifFile)) {
info(`Found SCA SARIF file to upload: ${scaSarifFile}`)
toUpload.push(scaSarifFile)
} else {
info(`SCA SARIF file not found at: ${scaSarifFile}`)
}
await callLaceworkCli(...args)
// make a copy of the sarif file
args = [scaSarifReport, scaReport]
await callCommand('cp', ...args)

toUpload.push(scaReport)
// Upload IAC JSON from the returned results path
const iacJsonFile = path.join(resultsPath, 'iac', `iac-${targetScan}.json`)
if (existsSync(iacJsonFile)) {
info(`Found IAC JSON file to upload: ${iacJsonFile}`)
toUpload.push(iacJsonFile)
} else {
info(`IAC JSON file not found at: ${iacJsonFile}`)
}

const uploadStart = Date.now()
const artifactPrefix = getInput('artifact-prefix')
if (artifactPrefix !== '') {
await uploadArtifact(artifactPrefix + '-results-' + target, ...toUpload)
} else {
await uploadArtifact('results-' + target, ...toUpload)
}
const artifactName =
artifactPrefix !== '' ? artifactPrefix + '-results-' + target : 'results-' + target
info(`Uploading artifact '${artifactName}' with ${toUpload.length} file(s)`)
await uploadArtifact(artifactName, ...toUpload)
telemetryCollector.addField('duration.upload-artifacts', (Date.now() - uploadStart).toString())
setOutput(`${target}-completed`, true)
}

async function displayResults() {
info('Displaying results')
const downloadStart = Date.now()

// Download artifacts from previous jobs
const artifactOld = await downloadArtifact('results-old')
const artifactNew = await downloadArtifact('results-new')
telemetryCollector.addField(
'duration.download-artifacts',
(Date.now() - downloadStart).toString()
)
const sarifFileOld = path.join(artifactOld, scaReport)
const sarifFileNew = path.join(artifactNew, scaReport)

const issuesByTool: { [tool: string]: string } = {}
if (existsSync(sarifFileOld) && existsSync(sarifFileNew)) {
issuesByTool['sca'] = await compareResults('sca', sarifFileOld, sarifFileNew)
} else {
throw new Error('SARIF file not found for SCA')

// Create local scan-results directory for compare
mkdirSync('scan-results/sca', { recursive: true })
mkdirSync('scan-results/iac', { recursive: true })

// Check and copy files for each scanner type
const scaAvailable = await prepareScannerFiles('sca', artifactOld, artifactNew)
const iacAvailable = await prepareScannerFiles('iac', artifactOld, artifactNew)

// Need at least one scanner to compare
if (!scaAvailable && !iacAvailable) {
info('No scanner files available for comparison. Nothing to compare.')
setOutput('display-completed', true)
return
}

const commentStart = Date.now()
if (Object.values(issuesByTool).some((x) => x.length > 0) && getInput('token').length > 0) {
info('Posting comment to GitHub PR as there were new issues introduced:')
let message = ''
for (const [, issues] of Object.entries(issuesByTool)) {
if (issues.length > 0) {
message += issues
}
}
if (getInput('footer') !== '') {
message += '\n\n' + getInput('footer')
// Run codesec compare mode with available scanners
await codesecRun('compare', iacAvailable, scaAvailable)

// Read comparison output - check all possible outputs
const outputs = [
'scan-results/compare/merged-compare.md',
'scan-results/compare/sca-compare.md',
'scan-results/compare/iac-compare.md',
]

let message: string | null = null
for (const output of outputs) {
if (existsSync(output)) {
info(`Using comparison output: ${output}`)
message = readMarkdownFile(output)
break
}
info(message)
}

if (!message) {
info('No comparison output produced. No changes detected.')
setOutput('display-completed', true)
return
}

// Check if there are new violations (non-zero count in "Found N new potential violations")
const hasViolations = /Found\s+[1-9]\d*\s+/.test(message)

if (hasViolations && getInput('token').length > 0) {
info('Posting comment to GitHub PR as there were new issues introduced')
const commentUrl = await postCommentIfInPr(message)
if (commentUrl !== undefined) {
setOutput('posted-comment', commentUrl)
}
} else {
// No new violations or no token - resolve existing comment if found
await resolveExistingCommentIfFound()
}
telemetryCollector.addField('duration.comment', (Date.now() - commentStart).toString())
setOutput(`display-completed`, true)

setOutput('display-completed', true)
}

async function prepareScannerFiles(
scanner: 'sca' | 'iac',
artifactOld: string,
artifactNew: string
): Promise<boolean> {
const ext = scanner === 'sca' ? 'sarif' : 'json'
const oldPath = path.join(artifactOld, 'scan-results', scanner, `${scanner}-old.${ext}`)
const newPath = path.join(artifactNew, 'scan-results', scanner, `${scanner}-new.${ext}`)

const oldExists = existsSync(oldPath)
const newExists = existsSync(newPath)

if (!oldExists || !newExists) {
info(`${scanner.toUpperCase()} files not found for compare. old=${oldExists}, new=${newExists}`)
return false
}

info(`Copying ${scanner.toUpperCase()} files for compare`)
await callCommand('cp', oldPath, path.join('scan-results', scanner, `${scanner}-old.${ext}`))
await callCommand('cp', newPath, path.join('scan-results', scanner, `${scanner}-new.${ext}`))
return true
}

async function main() {
Expand Down
33 changes: 0 additions & 33 deletions src/tool.ts

This file was deleted.

Loading