Skip to content

feat: allow enhanced scan to run without impacting builds #6376

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

53 changes: 30 additions & 23 deletions packages/build/src/log/messages/core_steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,19 @@ export const logSecretsScanSuccessMessage = function (logs, msg) {
log(logs, msg, { color: THEME.highlightWords })
}

export const logSecretsScanFailBuildMessage = function ({ logs, scanResults, groupedResults }) {
export const logSecretsScanFailBuildMessage = function ({
logs,
scanResults,
groupedResults,
enhancedScanShouldRunInActiveMode,
}) {
const { secretMatches, enhancedSecretMatches } = groupedResults
const secretMatchesKeys = Object.keys(secretMatches)
const enhancedSecretMatchesKeys = Object.keys(enhancedSecretMatches)

logErrorSubHeader(
logs,
`Scanning complete. ${scanResults.scannedFilesCount} file(s) scanned. Secrets scanning found ${secretMatchesKeys.length} instance(s) of secrets${enhancedSecretMatchesKeys.length > 0 ? ` and ${enhancedSecretMatchesKeys.length} instance(s) of likely secrets` : ''} in build output or repo code.\n`,
`Scanning complete. ${scanResults.scannedFilesCount} file(s) scanned. Secrets scanning found ${secretMatchesKeys.length} instance(s) of secrets${enhancedSecretMatchesKeys.length > 0 && enhancedScanShouldRunInActiveMode ? ` and ${enhancedSecretMatchesKeys.length} instance(s) of likely secrets` : ''} in build output or repo code.\n`,
)

// Explicit secret matches
Expand All @@ -162,28 +167,30 @@ export const logSecretsScanFailBuildMessage = function ({ logs, scanResults, gro
)
}

// Likely secret matches from enhanced scan
enhancedSecretMatchesKeys.forEach((key, index) => {
logError(logs, `${index === 0 && secretMatchesKeys.length ? '\n' : ''}"${key}***" detected as a likely secret:`)

enhancedSecretMatches[key]
.sort((a, b) => {
return a.file > b.file ? 0 : 1
})
.forEach(({ lineNumber, file }) => {
logError(logs, `found value at line ${lineNumber} in ${file}`, { indent: true })
})
})
if (enhancedScanShouldRunInActiveMode) {
// Likely secret matches from enhanced scan
enhancedSecretMatchesKeys.forEach((key, index) => {
logError(logs, `${index === 0 && secretMatchesKeys.length ? '\n' : ''}"${key}***" detected as a likely secret:`)

enhancedSecretMatches[key]
.sort((a, b) => {
return a.file > b.file ? 0 : 1
})
.forEach(({ lineNumber, file }) => {
logError(logs, `found value at line ${lineNumber} in ${file}`, { indent: true })
})
})

if (enhancedSecretMatchesKeys.length) {
logError(
logs,
`\nTo prevent exposing secrets, the build will fail until these likely secret values are not found in build output or repo files.`,
)
logError(
logs,
`\nIf these are expected, use ENHANCED_SECRETS_SCAN_OMIT_VALUES, or ENHANCED_SECRETS_SCAN_ENABLED to prevent detecting.`,
)
if (enhancedSecretMatchesKeys.length) {
logError(
logs,
`\nTo prevent exposing secrets, the build will fail until these likely secret values are not found in build output or repo files.`,
)
logError(
logs,
`\nIf these are expected, use ENHANCED_SECRETS_SCAN_OMIT_VALUES, or ENHANCED_SECRETS_SCAN_ENABLED to prevent detecting.`,
)
}
}

logError(
Expand Down
33 changes: 25 additions & 8 deletions packages/build/src/plugins_core/secrets_scanning/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const coreStep: CoreStepFunction = async function ({
netlifyConfig,
explicitSecretKeys,
enhancedSecretScan,
featureFlags,
systemLog,
deployId,
api,
Expand All @@ -38,6 +39,9 @@ const coreStep: CoreStepFunction = async function ({

const passedSecretKeys = (explicitSecretKeys || '').split(',')
const envVars = netlifyConfig.build.environment as Record<string, unknown>
// When the flag is disabled, we may still run the scan if a secrets scan would otherwise take place anyway
// In this case, we hide any output to the user and simply gather the information in our logs
const enhancedScanShouldRunInActiveMode = featureFlags?.enhanced_secret_scan_impacts_builds ?? false

systemLog?.({ passedSecretKeys, buildDir })

Expand All @@ -54,15 +58,17 @@ const coreStep: CoreStepFunction = async function ({
log(logs, `SECRETS_SCAN_OMIT_PATHS override option set to: ${envVars['SECRETS_SCAN_OMIT_PATHS']}\n`)
}
const enhancedScanningEnabledInEnv = isEnhancedSecretsScanningEnabled(envVars)
if (enhancedSecretScan && !enhancedScanningEnabledInEnv) {
const enhancedScanConfigured = enhancedSecretScan && enhancedScanningEnabledInEnv
if (enhancedSecretScan && enhancedScanShouldRunInActiveMode && !enhancedScanningEnabledInEnv) {
logSecretsScanSkipMessage(
logs,
'Enhanced secrets detection disabled via ENHANCED_SECRETS_SCAN_ENABLED flag set to false.',
)
}

if (
enhancedSecretScan &&
enhancedScanningEnabledInEnv &&
enhancedScanShouldRunInActiveMode &&
enhancedScanConfigured &&
envVars['ENHANCED_SECRETS_SCAN_OMIT_VALUES'] !== undefined
) {
log(
Expand All @@ -73,7 +79,11 @@ const coreStep: CoreStepFunction = async function ({

const keysToSearchFor = getSecretKeysToScanFor(envVars, passedSecretKeys)

if (keysToSearchFor.length === 0 && !enhancedSecretScan) {
// In passive mode, only run the enhanced scan if we have explicit secret keys
const enhancedScanShouldRun = enhancedScanShouldRunInActiveMode
? enhancedScanConfigured
: enhancedScanConfigured && keysToSearchFor.length > 0
if (keysToSearchFor.length === 0 && !enhancedScanShouldRun) {
logSecretsScanSkipMessage(
logs,
'Secrets scanning skipped because no env vars marked as secret are set to non-empty/non-trivial values or they are all omitted with SECRETS_SCAN_OMIT_KEYS env var setting.',
Expand Down Expand Up @@ -107,7 +117,7 @@ const coreStep: CoreStepFunction = async function ({
keys: keysToSearchFor,
base: buildDir as string,
filePaths,
enhancedScanning: enhancedSecretScan && enhancedScanningEnabledInEnv,
enhancedScanning: enhancedScanShouldRun,
omitValuesFromEnhancedScan: getOmitValuesFromEnhancedScanForEnhancedScanFromEnv(envVars),
})

Expand All @@ -122,7 +132,8 @@ const coreStep: CoreStepFunction = async function ({
secretsFilesCount: scanResults.scannedFilesCount,
keysToSearchFor,
enhancedPrefixMatches: enhancedSecretMatches.length ? enhancedSecretMatches.map((match) => match.key) : [],
enhancedScanning: enhancedSecretScan && enhancedScanningEnabledInEnv,
enhancedScanning: enhancedScanShouldRun,
enhancedScanActiveMode: enhancedScanShouldRunInActiveMode,
}

systemLog?.(attributesForLogsAndSpan)
Expand All @@ -135,12 +146,17 @@ const coreStep: CoreStepFunction = async function ({
const secretScanResult: SecretScanResult = {
scannedFilesCount: scanResults?.scannedFilesCount ?? 0,
secretsScanMatches: secretMatches ?? [],
enhancedSecretsScanMatches: enhancedSecretMatches ?? [],
enhancedSecretsScanMatches:
enhancedScanShouldRunInActiveMode && enhancedSecretMatches ? enhancedSecretMatches : [],
}
reportValidations({ api, secretScanResult, deployId, systemLog })
}

if (!scanResults || scanResults.matches.length === 0) {
if (
!scanResults ||
scanResults.matches.length === 0 ||
(!enhancedScanShouldRunInActiveMode && !secretMatches?.length)
) {
logSecretsScanSuccessMessage(
logs,
`Secrets scanning complete. ${scanResults?.scannedFilesCount} file(s) scanned. No secrets detected in build output or repo code!`,
Expand All @@ -154,6 +170,7 @@ const coreStep: CoreStepFunction = async function ({
logs,
scanResults,
groupedResults: groupScanResultsByKeyAndScanType(scanResults),
enhancedScanShouldRunInActiveMode,
})

const error = new Error(`Secrets scanning found secrets in build.`)
Expand Down
Loading
Loading