Skip to content
169 changes: 118 additions & 51 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
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'
// Constants for old Lacework CLI flow - kept for reference when re-enabling
// 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 +42,141 @@ 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()

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

// Copy SCA SARIF files from artifacts to expected location for compare
info('Copying SCA files from artifacts')
info(` Old SARIF: ${path.join(artifactOld, 'scan-results/sca/sca-old.sarif')}`)
await callCommand(
'cp',
path.join(artifactOld, 'scan-results/sca/sca-old.sarif'),
'scan-results/sca/sca-old.sarif'
)
info(` New SARIF: ${path.join(artifactNew, 'scan-results/sca/sca-new.sarif')}`)
await callCommand(
'cp',
path.join(artifactNew, 'scan-results/sca/sca-new.sarif'),
'scan-results/sca/sca-new.sarif'
)
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)
// Copy IAC JSON files from artifacts to expected location for compare
info('Checking for IAC files in artifacts')
const iacOldPath = path.join(artifactOld, 'scan-results/iac/iac-old.json')
const iacNewPath = path.join(artifactNew, 'scan-results/iac/iac-new.json')
info(` Old IAC: ${iacOldPath} (exists: ${existsSync(iacOldPath)})`)
info(` New IAC: ${iacNewPath} (exists: ${existsSync(iacNewPath)})`)
if (existsSync(iacOldPath) && existsSync(iacNewPath)) {
info(' Copying IAC files')
await callCommand('cp', iacOldPath, 'scan-results/iac/iac-old.json')
await callCommand('cp', iacNewPath, 'scan-results/iac/iac-new.json')
} else {
throw new Error('SARIF file not found for SCA')
info(' IAC files not found in artifacts, skipping IAC compare')
}

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')
}
info(message)
// Verify SCA files exist (required)
const scaOldExists = existsSync('scan-results/sca/sca-old.sarif')
const scaNewExists = existsSync('scan-results/sca/sca-new.sarif')

if (!scaOldExists || !scaNewExists) {
throw new Error(
`SARIF files not found for comparison. old=${scaOldExists}, new=${scaNewExists}`
)
}

// Run codesec compare mode
await codesecRun('compare', false, false)

// Read the comparison output
// merged-compare.md exists when both SCA and IAC comparisons succeed
// sca-compare.md exists when only SCA comparison succeeds (partial)
const mergedOutput = 'scan-results/compare/merged-compare.md'
const scaOutput = 'scan-results/compare/sca-compare.md'

let message: string
if (existsSync(mergedOutput)) {
info('Using merged comparison output')
message = readMarkdownFile(mergedOutput)
} else if (existsSync(scaOutput)) {
info('Using SCA-only comparison output (partial)')
message = readMarkdownFile(scaOutput)
} else {
throw new Error(`Comparison output not found. Tried: ${mergedOutput}, ${scaOutput}`)
}

// 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)

/*
* OLD FLOW - Commented out, to be removed once codesec is fully tested
*
* const downloadStart = Date.now()
* const artifactOld = await downloadArtifact('results-old')
* const artifactNew = await downloadArtifact('results-new')
* const sarifFileOld = path.join(artifactOld, scaReport)
* const sarifFileNew = path.join(artifactNew, scaReport)
* const compareMessage = await compareResults(sarifFileOld, sarifFileNew)
* if (compareMessage.length > 0 && getInput('token').length > 0) {
* await postCommentIfInPr(compareMessage)
* }
*/
}

async function main() {
Expand Down
5 changes: 5 additions & 0 deletions src/tool.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// DISABLED: This file is no longer used - codesecRun('compare') handles comparison
// Keeping commented for reference when migrating back to Lacework CLI

/*
import { endGroup, startGroup } from '@actions/core'
import { existsSync, readFileSync } from 'fs'
import { callLaceworkCli, debug, generateUILink } from './util'
Expand Down Expand Up @@ -31,3 +35,4 @@ export async function compareResults(
endGroup()
return existsSync(`${tool}.md`) ? readFileSync(`${tool}.md`, 'utf8') : ''
}
*/
Loading