diff --git a/src/commands/scan/cmd-scan-report.test.ts b/src/commands/scan/cmd-scan-report.test.ts index 6f3c4fa1a..85f472f9b 100644 --- a/src/commands/scan/cmd-scan-report.test.ts +++ b/src/commands/scan/cmd-scan-report.test.ts @@ -24,7 +24,7 @@ describe('socket scan report', async () => { $ socket scan report [path to output file] API Token Requirements - - Quota: 3 units + - Quota: 2 units - Permissions: full-scans:list security-policy:read Options @@ -32,9 +32,9 @@ describe('socket scan report', async () => { --fold Fold reported alerts to some degree --help Print this help --json Output result as json + --license Also report the license policy status. Default: false --markdown Output result as markdown --reportLevel Which policy level alerts should be reported - --security Report the security policy status. Default: true --short Report only the healthy status By default the result is a nested object that looks like this: @@ -47,7 +47,8 @@ describe('socket scan report', async () => { Short responses: JSON: \`{healthy:bool}\`, markdown: \`healthy = bool\`, text: \`OK/ERR\` Examples - $ socket scan report FakeOrg 000aaaa1-0000-0a0a-00a0-00a0000000a0 --json --fold=version" + $ socket scan report FakeOrg 000aaaa1-0000-0a0a-00a0-00a0000000a0 --json --fold=version + $ socket scan report FakeOrg 000aaaa1-0000-0a0a-00a0-00a0000000a0 --license --markdown --short" ` ) expect(`\n ${stderr}`).toMatchInlineSnapshot(` diff --git a/src/commands/scan/cmd-scan-report.ts b/src/commands/scan/cmd-scan-report.ts index 4f69aa519..35d01a592 100644 --- a/src/commands/scan/cmd-scan-report.ts +++ b/src/commands/scan/cmd-scan-report.ts @@ -39,15 +39,10 @@ const config: CliCommandConfig = { default: false, description: 'Report only the healthy status' }, - // license: { - // type: 'boolean', - // default: true, - // description: 'Report the license policy status. Default: true' - // }, - security: { + license: { type: 'boolean', - default: true, - description: 'Report the security policy status. Default: true' + default: false, + description: 'Also report the license policy status. Default: false' } }, help: (command, config) => ` @@ -55,7 +50,7 @@ const config: CliCommandConfig = { $ ${command} [path to output file] API Token Requirements - - Quota: 3 units + - Quota: 2 units - Permissions: full-scans:list security-policy:read Options @@ -72,6 +67,7 @@ const config: CliCommandConfig = { Examples $ ${command} FakeOrg 000aaaa1-0000-0a0a-00a0-00a0000000a0 --json --fold=version + $ ${command} FakeOrg 000aaaa1-0000-0a0a-00a0-00a0000000a0 --license --markdown --short ` } @@ -96,10 +92,9 @@ async function run( const { fold = 'none', json, - // license, + license, markdown, - reportLevel = 'warn', - security + reportLevel = 'warn' } = cli.flags const defaultOrgSlug = getConfigValue('defaultOrg') @@ -150,8 +145,7 @@ async function run( await handleScanReport({ orgSlug, scanId: scanId, - includeLicensePolicy: false, // !!license, - includeSecurityPolicy: typeof security === 'boolean' ? security : true, + includeLicensePolicy: !!license, outputKind: json ? 'json' : markdown ? 'markdown' : 'text', filePath: file, fold: fold as 'none' | 'file' | 'pkg' | 'version', diff --git a/src/commands/scan/fetch-report-data.ts b/src/commands/scan/fetch-report-data.ts index 0f9f18f92..51be60f0e 100644 --- a/src/commands/scan/fetch-report-data.ts +++ b/src/commands/scan/fetch-report-data.ts @@ -15,24 +15,20 @@ import type { components } from '@socketsecurity/sdk/types/api' /** * This fetches all the relevant pieces of data to generate a report, given a * full scan ID. - * It can optionally only fetch the security or license side of things. */ export async function fetchReportData( orgSlug: string, scanId: string, - // includeLicensePolicy: boolean, - includeSecurityPolicy: boolean + includeLicensePolicy: boolean ): Promise< | { ok: true scan: Array - // licensePolicy: undefined | SocketSdkReturnType<'getOrgSecurityPolicy'> securityPolicy: undefined | SocketSdkReturnType<'getOrgSecurityPolicy'> } | { ok: false scan: undefined - // licensePolicy: undefined securityPolicy: undefined } > { @@ -46,7 +42,6 @@ export async function fetchReportData( const sockSdk = await setupSdk(apiToken) let haveScan = false - // let haveLicensePolicy = false let haveSecurityPolicy = false // Lazily access constants.spinner. @@ -55,54 +50,32 @@ export async function fetchReportData( function updateProgress() { const needs = [ !haveScan ? 'scan' : undefined, - // includeLicensePolicy && !haveLicensePolicy ? 'license policy' : undefined, - includeSecurityPolicy && !haveSecurityPolicy - ? 'security policy' - : undefined + !haveSecurityPolicy ? 'security policy' : undefined ].filter(Boolean) - if (needs.length > 2) { - // .toOxford() - needs[needs.length - 1] = `and ${needs[needs.length - 1]}` - } const haves = [ haveScan ? 'scan' : undefined, - // includeLicensePolicy && haveLicensePolicy ? 'license policy' : undefined, - includeSecurityPolicy && haveSecurityPolicy - ? 'security policy' - : undefined + haveSecurityPolicy ? 'security policy' : undefined ].filter(Boolean) - if (haves.length > 2) { - // .toOxford() - haves[haves.length - 1] = `and ${haves[haves.length - 1]}` - } if (needs.length) { spinner.start( - `Fetching ${needs.join(needs.length > 2 ? ', ' : ' and ')}...${haves.length ? ` Completed fetching ${haves.join(haves.length > 2 ? ', ' : ' and ')}.` : ''}` + `Fetching ${needs.join(' and ')}...${haves.length ? ` Completed fetching ${haves.join(' and ')}.` : ''}` ) } else { - spinner.successAndStop( - `Completed fetching ${haves.join(haves.length > 2 ? ', ' : ' and ')}` - ) + spinner.successAndStop(`Completed fetching ${haves.join(' and ')}.`) } } updateProgress() - // @ts-ignore - const [ - scan, - // licensePolicyMaybe, - securityPolicyMaybe - ]: [ + const [scan, securityPolicyMaybe]: [ undefined | Array, - // undefined | SocketSdkResultType<'getOrgSecurityPolicy'>, - undefined | SocketSdkResultType<'getOrgSecurityPolicy'> + SocketSdkResultType<'getOrgSecurityPolicy'> ] = await Promise.all([ (async () => { try { const response = await queryApi( - `orgs/${orgSlug}/full-scans/${encodeURIComponent(scanId)}`, + `orgs/${orgSlug}/full-scans/${encodeURIComponent(scanId)}${includeLicensePolicy ? '?include_license_details=true' : ''}`, apiToken ) @@ -138,26 +111,12 @@ export async function fetchReportData( throw e } })(), - // includeLicensePolicy && - // (async () => { - // const r = await sockSdk.getOrgSecurityPolicy(orgSlug) - // haveLicensePolicy = true - // updateProgress() - // return await handleApiCall( - // r, - // "looking up organization's license policy" - // ) - // })(), - includeSecurityPolicy && - (async () => { - const r = await sockSdk.getOrgSecurityPolicy(orgSlug) - haveSecurityPolicy = true - updateProgress() - return await handleApiCall( - r, - "looking up organization's security policy" - ) - })() + (async () => { + const r = await sockSdk.getOrgSecurityPolicy(orgSlug) + haveSecurityPolicy = true + updateProgress() + return await handleApiCall(r, "looking up organization's security policy") + })() ]).finally(() => spinner.stop()) if (!Array.isArray(scan)) { @@ -166,50 +125,27 @@ export async function fetchReportData( return { ok: false, scan: undefined, - // licensePolicy: undefined, securityPolicy: undefined } } - // // Note: security->license once the api ships in the sdk - // let licensePolicy: undefined | SocketSdkReturnType<'getOrgSecurityPolicy'> = - // undefined - // if (includeLicensePolicy) { - // if (licensePolicyMaybe && licensePolicyMaybe.success) { - // licensePolicy = licensePolicyMaybe - // } else { - // logger.error('Was unable to fetch license policy, bailing') - // process.exitCode = 1 - // return { - // ok: false, - // scan: undefined, - // licensePolicy: undefined, - // securityPolicy: undefined - // } - // } - // } - let securityPolicy: undefined | SocketSdkReturnType<'getOrgSecurityPolicy'> = undefined - if (includeSecurityPolicy) { - if (securityPolicyMaybe && securityPolicyMaybe.success) { - securityPolicy = securityPolicyMaybe - } else { - logger.error('Was unable to fetch security policy, bailing') - process.exitCode = 1 - return { - ok: false, - scan: undefined, - // licensePolicy: undefined, - securityPolicy: undefined - } + if (securityPolicyMaybe && securityPolicyMaybe.success) { + securityPolicy = securityPolicyMaybe + } else { + logger.error('Was unable to fetch security policy, bailing') + process.exitCode = 1 + return { + ok: false, + scan: undefined, + securityPolicy: undefined } } return { ok: true, scan, - // licensePolicy, securityPolicy } } diff --git a/src/commands/scan/generate-report.test.ts b/src/commands/scan/generate-report.test.ts index a01c14d22..39ee4fbcc 100644 --- a/src/commands/scan/generate-report.test.ts +++ b/src/commands/scan/generate-report.test.ts @@ -7,7 +7,7 @@ import type { components } from '@socketsecurity/sdk/types/api' describe('generate-report', () => { it('should accept empty args', () => { - const result = generateReport([], undefined, undefined, { + const result = generateReport([], undefined, { orgSlug: 'fakeorg', scanId: 'scan-ai-dee', fold: 'none', @@ -33,7 +33,6 @@ describe('generate-report', () => { it('should return a healthy report without alerts when there are no violations', () => { const result = generateReport( getSimpleCleanScan(), - undefined, { success: true, data: { @@ -72,7 +71,6 @@ describe('generate-report', () => { it('should return a sick report with alert when an alert violates at error', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -136,7 +134,6 @@ describe('generate-report', () => { it('should return a healthy report with alert when an alert violates at warn', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -200,7 +197,6 @@ describe('generate-report', () => { it('should return a healthy report without alerts when an alert violates at monitor', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -239,7 +235,6 @@ describe('generate-report', () => { it('should return a healthy report without alerts when an alert violates at ignore', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -278,7 +273,6 @@ describe('generate-report', () => { it('should return a healthy report without alerts when an alert violates at defer', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -317,7 +311,6 @@ describe('generate-report', () => { it('should return a healthy report without alerts when an alert has no policy value', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -354,7 +347,6 @@ describe('generate-report', () => { it('should return a healthy report without alerts when an alert has no policy entry', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -391,7 +383,6 @@ describe('generate-report', () => { it('should return a healthy report without alerts when there are no violations', () => { const result = generateReport( getSimpleCleanScan(), - undefined, { success: true, data: { @@ -430,7 +421,6 @@ describe('generate-report', () => { it('should return a sick report with alert when an alert violates at error', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -494,7 +484,6 @@ describe('generate-report', () => { it('should return a healthy report with alert when an alert violates at warn', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -558,7 +547,6 @@ describe('generate-report', () => { it('should return a healthy report with alert when an alert violates at monitor', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -622,7 +610,6 @@ describe('generate-report', () => { it('should return a healthy report with alert when an alert violates at ignore', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -686,7 +673,6 @@ describe('generate-report', () => { it('should return a healthy report without alerts when an alert violates at defer', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -725,7 +711,6 @@ describe('generate-report', () => { it('should return a healthy report without alerts when an alert has no policy value', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -762,7 +747,6 @@ describe('generate-report', () => { it('should return a healthy report without alerts when an alert has no policy entry', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -800,7 +784,6 @@ describe('generate-report', () => { it('should not fold anything when fold=none', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -862,7 +845,6 @@ describe('generate-report', () => { it('should fold the file locations when fold=file', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -914,7 +896,6 @@ describe('generate-report', () => { it('should fold the files up when fold=version', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { @@ -964,7 +945,6 @@ describe('generate-report', () => { it('should fold the versions up when fold=pkg', () => { const result = generateReport( getScanWithEnvVars(), - undefined, { success: true, data: { diff --git a/src/commands/scan/generate-report.ts b/src/commands/scan/generate-report.ts index 170660012..b77ab9df7 100644 --- a/src/commands/scan/generate-report.ts +++ b/src/commands/scan/generate-report.ts @@ -31,7 +31,6 @@ export type ReportLeafNode = { export function generateReport( scan: Array, - _licensePolicy: undefined | SocketSdkReturnType<'getOrgSecurityPolicy'>, securityPolicy: undefined | SocketSdkReturnType<'getOrgSecurityPolicy'>, { fold, @@ -72,6 +71,14 @@ export function generateReport( // - monitor/ignore: no action // - defer: unknown (no action) + // Note: the server will emit alerts for license policy violations but + // those are only included if you set the flag when requesting the scan + // data. The alerts map to a single security policy key that determines + // what to do with any violation, regardless of the concrete license. + // That rule is called "License Policy Violation". + // The license policy part is implicitly handled here. Either they are + // included and may show up, or they are not and won't show up. + const violations = new Map() let healthy = true diff --git a/src/commands/scan/handle-scan-report.ts b/src/commands/scan/handle-scan-report.ts index a9bf29224..d3b0ce364 100644 --- a/src/commands/scan/handle-scan-report.ts +++ b/src/commands/scan/handle-scan-report.ts @@ -5,7 +5,6 @@ export async function handleScanReport({ filePath, fold, includeLicensePolicy, - includeSecurityPolicy, orgSlug, outputKind, reportLevel, @@ -15,28 +14,16 @@ export async function handleScanReport({ orgSlug: string scanId: string includeLicensePolicy: boolean - includeSecurityPolicy: boolean outputKind: 'json' | 'markdown' | 'text' filePath: string fold: 'pkg' | 'version' | 'file' | 'none' reportLevel: 'defer' | 'ignore' | 'monitor' | 'warn' | 'error' short: boolean }): Promise { - if (!includeLicensePolicy && !includeSecurityPolicy) { - process.exitCode = 1 - return // caller should assert - } - - const { - // licensePolicy, - ok, - scan, - securityPolicy - } = await fetchReportData( + const { ok, scan, securityPolicy } = await fetchReportData( orgSlug, scanId, - // includeLicensePolicy - includeSecurityPolicy + includeLicensePolicy ) if (!ok) { return @@ -47,7 +34,6 @@ export async function handleScanReport({ fold, scanId: scanId, includeLicensePolicy, - includeSecurityPolicy, orgSlug, outputKind, reportLevel, diff --git a/src/commands/scan/output-scan-report.test.ts b/src/commands/scan/output-scan-report.test.ts index 2dd991798..31719d103 100644 --- a/src/commands/scan/output-scan-report.test.ts +++ b/src/commands/scan/output-scan-report.test.ts @@ -68,11 +68,11 @@ describe('output-scan-report', () => { "# Scan Policy Report This report tells you whether the results of a Socket scan results violate the - security or license policy set by your organization. + security policy set by your organization. ## Health status - The scan *PASSES* all requirements set by your security and license policy. + The scan *PASSES* all requirements set by your security policy. ## Settings @@ -82,6 +82,7 @@ describe('output-scan-report', () => { - Scan ID: scan-ai-dee - Alert folding: none - Minimal policy level for alert to be included in report: warn + - Include license alerts: no ## Alerts @@ -95,7 +96,7 @@ describe('output-scan-report', () => { "# Scan Policy Report This report tells you whether the results of a Socket scan results violate the - security or license policy set by your organization. + security policy set by your organization. ## Health status @@ -109,6 +110,7 @@ describe('output-scan-report', () => { - Scan ID: scan-ai-dee - Alert folding: none - Minimal policy level for alert to be included in report: warn + - Include license alerts: no ## Alerts diff --git a/src/commands/scan/output-scan-report.ts b/src/commands/scan/output-scan-report.ts index 0e0c1d96c..5533f36c0 100644 --- a/src/commands/scan/output-scan-report.ts +++ b/src/commands/scan/output-scan-report.ts @@ -14,13 +14,11 @@ import type { components } from '@socketsecurity/sdk/types/api' export async function outputScanReport( scan: Array, - // licensePolicy: undefined | SocketSdkReturnType<'getOrgSecurityPolicy'>, securityPolicy: undefined | SocketSdkReturnType<'getOrgSecurityPolicy'>, { filePath, fold, includeLicensePolicy, - includeSecurityPolicy, orgSlug, outputKind, reportLevel, @@ -30,7 +28,6 @@ export async function outputScanReport( orgSlug: string scanId: string includeLicensePolicy: boolean - includeSecurityPolicy: boolean outputKind: 'json' | 'markdown' | 'text' filePath: string fold: 'pkg' | 'version' | 'file' | 'none' @@ -38,25 +35,15 @@ export async function outputScanReport( short: boolean } ): Promise { - if (!includeLicensePolicy && !includeSecurityPolicy) { - process.exitCode = 1 - return // caller should assert - } - - const scanReport = generateReport( - scan, - undefined, // licensePolicy, - securityPolicy, - { - orgSlug, - scanId, - fold, - reportLevel, - short, - // Lazily access constants.spinner. - spinner: constants.spinner - } - ) + const scanReport = generateReport(scan, securityPolicy, { + orgSlug, + scanId, + fold, + reportLevel, + short, + // Lazily access constants.spinner. + spinner: constants.spinner + }) if (!scanReport.healthy) { process.exitCode = 1 @@ -68,7 +55,7 @@ export async function outputScanReport( ) { const json = short ? JSON.stringify(scanReport) - : toJsonReport(scanReport as ScanReport) + : toJsonReport(scanReport as ScanReport, includeLicensePolicy) if (filePath && filePath !== '-') { logger.log('Writing json report to', filePath) @@ -82,7 +69,7 @@ export async function outputScanReport( if (outputKind === 'markdown' || (filePath && filePath.endsWith('.md'))) { const md = short ? `healthy = ${scanReport.healthy}` - : toMarkdownReport(scanReport as ScanReport) + : toMarkdownReport(scanReport as ScanReport, includeLicensePolicy) if (filePath && filePath !== '-') { logger.log('Writing markdown report to', filePath) @@ -100,11 +87,15 @@ export async function outputScanReport( } } -export function toJsonReport(report: ScanReport): string { +export function toJsonReport( + report: ScanReport, + includeLicensePolicy: boolean +): string { const obj = mapToObject(report.alerts) const json = JSON.stringify( { + includeLicensePolicy, ...report, alerts: obj }, @@ -115,7 +106,10 @@ export function toJsonReport(report: ScanReport): string { return json } -export function toMarkdownReport(report: ScanReport): string { +export function toMarkdownReport( + report: ScanReport, + includeLicensePolicy: boolean +): string { const flatData = Array.from(walkNestedMap(report.alerts)).map( ({ keys, value }: { keys: string[]; value: ReportLeafNode }) => { const { manifest, policy, type, url } = value @@ -135,13 +129,13 @@ export function toMarkdownReport(report: ScanReport): string { # Scan Policy Report This report tells you whether the results of a Socket scan results violate the -security or license policy set by your organization. +security${includeLicensePolicy ? ' or license' : ''} policy set by your organization. ## Health status ${ report.healthy - ? 'The scan *PASSES* all requirements set by your security and license policy.' + ? `The scan *PASSES* all requirements set by your security${includeLicensePolicy ? ' and license' : ''} policy.` : 'The scan *VIOLATES* one or more policies set to the "error" level.' } @@ -153,6 +147,7 @@ Configuration used to generate this report: - Scan ID: ${report.scanId} - Alert folding: ${report.options.fold === 'none' ? 'none' : `up to ${report.options.fold}`} - Minimal policy level for alert to be included in report: ${report.options.reportLevel === 'defer' ? 'everything' : report.options.reportLevel} +- Include license alerts: ${includeLicensePolicy ? 'yes' : 'no'} ## Alerts