Skip to content

Commit cd41d3b

Browse files
authored
feat: introduce enhanced secret scanning (#6230)
1 parent 0bcba66 commit cd41d3b

File tree

21 files changed

+354
-130
lines changed

21 files changed

+354
-130
lines changed

packages/build/docs/flow.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ Current core steps are:
9595
[build command](https://github.com/netlify/build/blob/9b261a6182e1ba6853966bd8d0bde9064209af7d/packages/build/src/plugins_core/build_command.js#L11).
9696
- [Functions bundling](https://github.com/netlify/build/blob/9b261a6182e1ba6853966bd8d0bde9064209af7d/packages/build/src/plugins_core/functions/index.js#L142),
9797
which uses [`zip-it-and-ship-it`](https://github.com/netlify/zip-it-and-ship-it).
98-
- Secrets Scanning only runs when the account passes non-empty explicitSecretKeys.
98+
- Secrets Scanning only runs when the account passes non-empty explicitSecretKeys, or when the account has
99+
enhancedSecretScan enabled (only applies to certain plan types).
99100
- [Site deploy](https://github.com/netlify/build/blob/9b261a6182e1ba6853966bd8d0bde9064209af7d/packages/build/src/plugins_core/deploy/index.js#L66):
100101
- This sends a network request to the buildbot to initiate site deploy.
101102
- When the buildbot

packages/build/src/core/build.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ const tExecBuild = async function ({
8585
quiet,
8686
framework,
8787
explicitSecretKeys,
88+
enhancedSecretScan,
8889
edgeFunctionsBootstrapURL,
8990
eventHandlers,
9091
}) {
@@ -223,6 +224,7 @@ const tExecBuild = async function ({
223224
quiet,
224225
integrations,
225226
explicitSecretKeys,
227+
enhancedSecretScan,
226228
edgeFunctionsBootstrapURL,
227229
eventHandlers,
228230
})
@@ -284,6 +286,7 @@ export const runAndReportBuild = async function ({
284286
quiet,
285287
integrations,
286288
explicitSecretKeys,
289+
enhancedSecretScan,
287290
edgeFunctionsBootstrapURL,
288291
eventHandlers,
289292
}) {
@@ -340,6 +343,7 @@ export const runAndReportBuild = async function ({
340343
quiet,
341344
integrations,
342345
explicitSecretKeys,
346+
enhancedSecretScan,
343347
edgeFunctionsBootstrapURL,
344348
eventHandlers,
345349
})
@@ -446,6 +450,7 @@ const initAndRunBuild = async function ({
446450
quiet,
447451
integrations,
448452
explicitSecretKeys,
453+
enhancedSecretScan,
449454
edgeFunctionsBootstrapURL,
450455
eventHandlers,
451456
}) {
@@ -551,6 +556,7 @@ const initAndRunBuild = async function ({
551556
devCommand,
552557
quiet,
553558
explicitSecretKeys,
559+
enhancedSecretScan,
554560
edgeFunctionsBootstrapURL,
555561
eventHandlers,
556562
})
@@ -627,6 +633,7 @@ const runBuild = async function ({
627633
devCommand,
628634
quiet,
629635
explicitSecretKeys,
636+
enhancedSecretScan,
630637
edgeFunctionsBootstrapURL,
631638
eventHandlers,
632639
}) {
@@ -694,6 +701,7 @@ const runBuild = async function ({
694701
quiet,
695702
userNodeVersion,
696703
explicitSecretKeys,
704+
enhancedSecretScan,
697705
edgeFunctionsBootstrapURL,
698706
})
699707

packages/build/src/core/flags.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,9 @@ Default: false`,
218218
describe: 'Env var keys that are marked as secret explicitly.',
219219
hidden: true,
220220
},
221+
enhancedSecretScan: {
222+
boolean: true,
223+
hidden: true,
224+
describe: 'Scan for potential secrets in all env vars',
225+
},
221226
}

packages/build/src/log/messages/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const INTERNAL_FLAGS = [
5656
'systemLogFile',
5757
'timeline',
5858
'explicitSecretKeys',
59+
'enhancedSecretScan',
5960
'edgeFunctionsBootstrapURL',
6061
'eventHandlers',
6162
]

packages/build/src/log/messages/core_steps.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,15 +129,31 @@ export const logSecretsScanSuccessMessage = function (logs, msg) {
129129
}
130130

131131
export const logSecretsScanFailBuildMessage = function ({ logs, scanResults, groupedResults }) {
132+
const { secretMatches, enhancedSecretMatches } = groupedResults
133+
132134
logErrorSubHeader(
133135
logs,
134136
`Scanning complete. ${scanResults.scannedFilesCount} file(s) scanned. Secrets scanning found ${scanResults.matches.length} instance(s) of secrets in build output or repo code.\n`,
135137
)
136138

137-
Object.keys(groupedResults).forEach((key) => {
139+
// Explicit secret matches
140+
Object.keys(secretMatches).forEach((key) => {
138141
logError(logs, `Secret env var "${key}"'s value detected:`)
139142

140-
groupedResults[key]
143+
secretMatches[key]
144+
.sort((a, b) => {
145+
return a.file > b.file ? 0 : 1
146+
})
147+
.forEach(({ lineNumber, file }) => {
148+
logError(logs, `found value at line ${lineNumber} in ${file}`, { indent: true })
149+
})
150+
})
151+
152+
// Likely secret matches from enhanced scan
153+
Object.keys(enhancedSecretMatches).forEach((key) => {
154+
logError(logs, `Env var "${key}"'s value detected as a likely secret value:`)
155+
156+
enhancedSecretMatches[key]
141157
.sort((a, b) => {
142158
return a.file > b.file ? 0 : 1
143159
})

packages/build/src/plugins_core/secrets_scanning/index.ts

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import {
1414
ScanResults,
1515
SecretScanResult,
1616
getFilePathsToScan,
17+
getNonSecretKeysToScanFor,
1718
getSecretKeysToScanFor,
18-
groupScanResultsByKey,
19+
groupScanResultsByKeyAndScanType,
1920
isSecretsScanningEnabled,
2021
scanFilesForKeyValues,
2122
} from './utils.js'
@@ -27,6 +28,7 @@ const coreStep: CoreStepFunction = async function ({
2728
logs,
2829
netlifyConfig,
2930
explicitSecretKeys,
31+
enhancedSecretScan,
3032
systemLog,
3133
deployId,
3234
api,
@@ -51,13 +53,15 @@ const coreStep: CoreStepFunction = async function ({
5153
log(logs, `SECRETS_SCAN_OMIT_PATHS override option set to: ${envVars['SECRETS_SCAN_OMIT_PATHS']}\n`)
5254
}
5355

54-
const keysToSearchFor = getSecretKeysToScanFor(envVars, passedSecretKeys)
56+
const explicitSecretKeysToScanFor = getSecretKeysToScanFor(envVars, passedSecretKeys)
57+
const potentialSecretKeysToScanFor = enhancedSecretScan ? getNonSecretKeysToScanFor(envVars, passedSecretKeys) : []
58+
const keysToSearchFor = explicitSecretKeysToScanFor.concat(potentialSecretKeysToScanFor)
5559

5660
if (keysToSearchFor.length === 0) {
57-
logSecretsScanSkipMessage(
58-
logs,
59-
'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.',
60-
)
61+
const msg = enhancedSecretScan
62+
? 'Secrets scanning skipped because no env vars are set to non-empty/non-trivial values or they are all omitted with SECRETS_SCAN_OMIT_KEYS env var setting.'
63+
: '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.'
64+
logSecretsScanSkipMessage(logs, msg)
6165
return stepResults
6266
}
6367

@@ -75,6 +79,8 @@ const coreStep: CoreStepFunction = async function ({
7579
}
7680

7781
let scanResults: ScanResults | undefined
82+
let secretMatches: SecretScanResult['secretsScanMatches'] | undefined
83+
let enhancedSecretMatches: SecretScanResult['enhancedSecretsScanMatches'] | undefined
7884

7985
await tracer.startActiveSpan(
8086
'scanning-files',
@@ -87,9 +93,14 @@ const coreStep: CoreStepFunction = async function ({
8793
filePaths,
8894
})
8995

96+
secretMatches = scanResults.matches.filter((match) => explicitSecretKeysToScanFor.includes(match.key))
97+
enhancedSecretMatches = scanResults.matches.filter((match) => potentialSecretKeysToScanFor.includes(match.key))
98+
9099
const attributesForLogsAndSpan = {
91-
secretsScanFoundSecrets: scanResults.matches.length > 0,
92-
secretsScanMatchesCount: scanResults.matches.length,
100+
secretsScanFoundSecrets: secretMatches.length > 0,
101+
enhancedSecretsScanFoundSecrets: enhancedSecretMatches.length > 0,
102+
secretsScanMatchesCount: secretMatches.length,
103+
enhancedSecretsScanMatchesCount: enhancedSecretMatches.length,
93104
secretsFilesCount: scanResults.scannedFilesCount,
94105
keysToSearchFor,
95106
}
@@ -103,7 +114,8 @@ const coreStep: CoreStepFunction = async function ({
103114
if (deployId !== '0') {
104115
const secretScanResult: SecretScanResult = {
105116
scannedFilesCount: scanResults?.scannedFilesCount ?? 0,
106-
secretsScanMatches: scanResults?.matches ?? [],
117+
secretsScanMatches: secretMatches ?? [],
118+
enhancedSecretsScanMatches: enhancedSecretMatches ?? [],
107119
}
108120
reportValidations({ api, secretScanResult, deployId, systemLog })
109121
}
@@ -118,18 +130,27 @@ const coreStep: CoreStepFunction = async function ({
118130

119131
// at this point we have found matching secrets
120132
// Output the results and fail the build
121-
122-
logSecretsScanFailBuildMessage({ logs, scanResults, groupedResults: groupScanResultsByKey(scanResults) })
133+
logSecretsScanFailBuildMessage({
134+
logs,
135+
scanResults,
136+
groupedResults: groupScanResultsByKeyAndScanType(scanResults, potentialSecretKeysToScanFor),
137+
})
123138

124139
const error = new Error(`Secrets scanning found secrets in build.`)
125140
addErrorInfo(error, { type: 'secretScanningFoundSecrets' })
126141
throw error
127142
}
128143

129-
// We run this core step if the build was run with explicit secret keys. This
130-
// is passed from BB to build so only accounts that are allowed to have explicit
131-
// secrets and actually have them will have them.
132-
const hasExplicitSecretsKeys: CoreStepCondition = function ({ explicitSecretKeys }): boolean {
144+
// We run this core step if the build was run with explicit secret keys or if enhanced secret scanning is enabled.
145+
// This is passed from BB to build so only accounts that are allowed to have explicit
146+
// secrets and actually have them / have enhanced secret scanning enabled will have them.
147+
const hasExplicitSecretsKeysOrEnhancedScanningEnabled: CoreStepCondition = function ({
148+
explicitSecretKeys,
149+
enhancedSecretScan,
150+
}): boolean {
151+
if (enhancedSecretScan) {
152+
return true
153+
}
133154
if (typeof explicitSecretKeys !== 'string') {
134155
return false
135156
}
@@ -143,5 +164,5 @@ export const scanForSecrets: CoreStep = {
143164
coreStepId: 'secrets_scanning',
144165
coreStepName: 'Secrets scanning',
145166
coreStepDescription: () => 'Scanning for secrets in code and build output.',
146-
condition: hasExplicitSecretsKeys,
167+
condition: hasExplicitSecretsKeysOrEnhancedScanningEnabled,
147168
}

0 commit comments

Comments
 (0)