Skip to content

Commit aa07ba0

Browse files
committed
feat(build): inline all external tool checksums at build time
Add build-time inlined checksums for all security tools: - SFW (Socket Firewall) - OpenGrep - Trivy - TruffleHog Each tool now has a dedicated checksum getter module with: - get*Checksums(): Returns checksum map from inlined env var - require*Checksum(): Hard error in prod if checksum missing This ensures production builds have integrity verification for all external tool downloads, while allowing dev mode flexibility.
1 parent ea9d5e1 commit aa07ba0

File tree

6 files changed

+266
-1
lines changed

6 files changed

+266
-1
lines changed

packages/cli/scripts/environment-variables.mjs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,14 @@ export class EnvironmentVariables {
108108
publishedBuild ? '' : ':dev'
109109
}`
110110

111-
// Get checksums for tools that have them.
111+
// Get checksums for all external tools that have them.
112+
// All GitHub-released tools have checksums for integrity verification.
113+
const opengrepChecksums = externalTools.opengrep?.checksums || {}
112114
const pythonChecksums = externalTools.python?.checksums || {}
115+
const sfwChecksums = externalTools.sfw?.checksums || {}
113116
const socketPatchChecksums = externalTools['socket-patch']?.checksums || {}
117+
const trivyChecksums = externalTools.trivy?.checksums || {}
118+
const trufflehogChecksums = externalTools.trufflehog?.checksums || {}
114119

115120
// Return all environment variables with raw values.
116121
return {
@@ -119,19 +124,23 @@ export class EnvironmentVariables {
119124
INLINED_SOCKET_CLI_CYCLONEDX_CDXGEN_VERSION: cdxgenVersion,
120125
INLINED_SOCKET_CLI_HOMEPAGE: packageJson.homepage,
121126
INLINED_SOCKET_CLI_NAME: packageJson.name,
127+
INLINED_SOCKET_CLI_OPENGREP_CHECKSUMS: JSON.stringify(opengrepChecksums),
122128
INLINED_SOCKET_CLI_OPENGREP_VERSION: opengrepVersion,
123129
INLINED_SOCKET_CLI_PUBLISHED_BUILD: publishedBuild ? '1' : '',
124130
INLINED_SOCKET_CLI_PYCLI_VERSION: pyCliVersion,
125131
INLINED_SOCKET_CLI_PYTHON_BUILD_TAG: pythonBuildTag,
126132
INLINED_SOCKET_CLI_PYTHON_CHECKSUMS: JSON.stringify(pythonChecksums),
127133
INLINED_SOCKET_CLI_PYTHON_VERSION: pythonVersion,
128134
INLINED_SOCKET_CLI_SENTRY_BUILD: sentryBuild ? '1' : '',
135+
INLINED_SOCKET_CLI_SFW_CHECKSUMS: JSON.stringify(sfwChecksums),
129136
INLINED_SOCKET_CLI_SFW_NPM_VERSION: sfwNpmVersion,
130137
INLINED_SOCKET_CLI_SFW_VERSION: sfwVersion,
131138
INLINED_SOCKET_CLI_SOCKET_PATCH_CHECKSUMS: JSON.stringify(socketPatchChecksums),
132139
INLINED_SOCKET_CLI_SOCKET_PATCH_VERSION: socketPatchVersion,
133140
INLINED_SOCKET_CLI_SYNP_VERSION: synpVersion,
141+
INLINED_SOCKET_CLI_TRIVY_CHECKSUMS: JSON.stringify(trivyChecksums),
134142
INLINED_SOCKET_CLI_TRIVY_VERSION: trivyVersion,
143+
INLINED_SOCKET_CLI_TRUFFLEHOG_CHECKSUMS: JSON.stringify(trufflehogChecksums),
135144
INLINED_SOCKET_CLI_TRUFFLEHOG_VERSION: trufflehogVersion,
136145
INLINED_SOCKET_CLI_VERSION: socketPackageJson.version,
137146
INLINED_SOCKET_CLI_VERSION_HASH: versionHash,
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* OpenGrep SHA-256 checksums getter function.
3+
* Uses direct process.env access so esbuild define can inline values.
4+
* IMPORTANT: esbuild's define plugin can only replace direct process.env['KEY'] references.
5+
* If we imported from env modules, esbuild couldn't inline the values at build time.
6+
* This is critical for embedding checksums into the binary for integrity verification.
7+
*/
8+
9+
import process from 'node:process'
10+
11+
import type { OpengrepChecksums } from '../types.mjs'
12+
13+
/**
14+
* Get OpenGrep checksums from inlined environment variable.
15+
* Returns a map of asset filename to SHA-256 hex checksum.
16+
*
17+
* @throws Error if checksums are missing in production builds.
18+
*/
19+
export function getOpengrepChecksums(): OpengrepChecksums {
20+
const checksums = process.env['INLINED_SOCKET_CLI_OPENGREP_CHECKSUMS']
21+
if (!checksums) {
22+
// In development mode (not inlined), return empty object.
23+
// Build validation will catch missing checksums at build time.
24+
return {}
25+
}
26+
try {
27+
return JSON.parse(checksums) as OpengrepChecksums
28+
} catch {
29+
throw new Error(
30+
'Failed to parse OpenGrep checksums. This indicates a build configuration error.',
31+
)
32+
}
33+
}
34+
35+
/**
36+
* Lookup an OpenGrep checksum by asset name.
37+
* In production builds (checksums inlined), throws a hard error if asset is missing.
38+
* In dev mode (checksums not inlined), returns undefined to allow development.
39+
*
40+
* @param assetName - The asset filename to look up.
41+
* @returns The SHA-256 hex checksum, or undefined in dev mode.
42+
* @throws Error if checksum is not found in production builds.
43+
*/
44+
export function requireOpengrepChecksum(assetName: string): string | undefined {
45+
const checksums = getOpengrepChecksums()
46+
47+
// In dev mode, checksums are not inlined so the object is empty.
48+
// Allow downloads without verification during development.
49+
if (Object.keys(checksums).length === 0) {
50+
return undefined
51+
}
52+
53+
// In production mode, checksums are inlined.
54+
// Require checksum for every asset - missing checksum is a HARD ERROR.
55+
const sha256 = checksums[assetName]
56+
if (!sha256) {
57+
throw new Error(
58+
`Missing SHA-256 checksum for OpenGrep asset: ${assetName}. ` +
59+
'This is a security requirement. Please update external-tools.json with the correct checksum.',
60+
)
61+
}
62+
return sha256
63+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* SFW SHA-256 checksums getter function.
3+
* Uses direct process.env access so esbuild define can inline values.
4+
* IMPORTANT: esbuild's define plugin can only replace direct process.env['KEY'] references.
5+
* If we imported from env modules, esbuild couldn't inline the values at build time.
6+
* This is critical for embedding checksums into the binary for integrity verification.
7+
*/
8+
9+
import process from 'node:process'
10+
11+
import type { SfwChecksums } from '../types.mjs'
12+
13+
/**
14+
* Get SFW checksums from inlined environment variable.
15+
* Returns a map of asset filename to SHA-256 hex checksum.
16+
*
17+
* @throws Error if checksums are missing in production builds.
18+
*/
19+
export function getSfwChecksums(): SfwChecksums {
20+
const checksums = process.env['INLINED_SOCKET_CLI_SFW_CHECKSUMS']
21+
if (!checksums) {
22+
// In development mode (not inlined), return empty object.
23+
// Build validation will catch missing checksums at build time.
24+
return {}
25+
}
26+
try {
27+
return JSON.parse(checksums) as SfwChecksums
28+
} catch {
29+
throw new Error(
30+
'Failed to parse SFW checksums. This indicates a build configuration error.',
31+
)
32+
}
33+
}
34+
35+
/**
36+
* Lookup an SFW checksum by asset name.
37+
* In production builds (checksums inlined), throws a hard error if asset is missing.
38+
* In dev mode (checksums not inlined), returns undefined to allow development.
39+
*
40+
* @param assetName - The asset filename to look up.
41+
* @returns The SHA-256 hex checksum, or undefined in dev mode.
42+
* @throws Error if checksum is not found in production builds.
43+
*/
44+
export function requireSfwChecksum(assetName: string): string | undefined {
45+
const checksums = getSfwChecksums()
46+
47+
// In dev mode, checksums are not inlined so the object is empty.
48+
// Allow downloads without verification during development.
49+
if (Object.keys(checksums).length === 0) {
50+
return undefined
51+
}
52+
53+
// In production mode, checksums are inlined.
54+
// Require checksum for every asset - missing checksum is a HARD ERROR.
55+
const sha256 = checksums[assetName]
56+
if (!sha256) {
57+
throw new Error(
58+
`Missing SHA-256 checksum for SFW asset: ${assetName}. ` +
59+
'This is a security requirement. Please update external-tools.json with the correct checksum.',
60+
)
61+
}
62+
return sha256
63+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* Trivy SHA-256 checksums getter function.
3+
* Uses direct process.env access so esbuild define can inline values.
4+
* IMPORTANT: esbuild's define plugin can only replace direct process.env['KEY'] references.
5+
* If we imported from env modules, esbuild couldn't inline the values at build time.
6+
* This is critical for embedding checksums into the binary for integrity verification.
7+
*/
8+
9+
import process from 'node:process'
10+
11+
import type { TrivyChecksums } from '../types.mjs'
12+
13+
/**
14+
* Get Trivy checksums from inlined environment variable.
15+
* Returns a map of asset filename to SHA-256 hex checksum.
16+
*
17+
* @throws Error if checksums are missing in production builds.
18+
*/
19+
export function getTrivyChecksums(): TrivyChecksums {
20+
const checksums = process.env['INLINED_SOCKET_CLI_TRIVY_CHECKSUMS']
21+
if (!checksums) {
22+
// In development mode (not inlined), return empty object.
23+
// Build validation will catch missing checksums at build time.
24+
return {}
25+
}
26+
try {
27+
return JSON.parse(checksums) as TrivyChecksums
28+
} catch {
29+
throw new Error(
30+
'Failed to parse Trivy checksums. This indicates a build configuration error.',
31+
)
32+
}
33+
}
34+
35+
/**
36+
* Lookup a Trivy checksum by asset name.
37+
* In production builds (checksums inlined), throws a hard error if asset is missing.
38+
* In dev mode (checksums not inlined), returns undefined to allow development.
39+
*
40+
* @param assetName - The asset filename to look up.
41+
* @returns The SHA-256 hex checksum, or undefined in dev mode.
42+
* @throws Error if checksum is not found in production builds.
43+
*/
44+
export function requireTrivyChecksum(assetName: string): string | undefined {
45+
const checksums = getTrivyChecksums()
46+
47+
// In dev mode, checksums are not inlined so the object is empty.
48+
// Allow downloads without verification during development.
49+
if (Object.keys(checksums).length === 0) {
50+
return undefined
51+
}
52+
53+
// In production mode, checksums are inlined.
54+
// Require checksum for every asset - missing checksum is a HARD ERROR.
55+
const sha256 = checksums[assetName]
56+
if (!sha256) {
57+
throw new Error(
58+
`Missing SHA-256 checksum for Trivy asset: ${assetName}. ` +
59+
'This is a security requirement. Please update external-tools.json with the correct checksum.',
60+
)
61+
}
62+
return sha256
63+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* TruffleHog SHA-256 checksums getter function.
3+
* Uses direct process.env access so esbuild define can inline values.
4+
* IMPORTANT: esbuild's define plugin can only replace direct process.env['KEY'] references.
5+
* If we imported from env modules, esbuild couldn't inline the values at build time.
6+
* This is critical for embedding checksums into the binary for integrity verification.
7+
*/
8+
9+
import process from 'node:process'
10+
11+
import type { TrufflehogChecksums } from '../types.mjs'
12+
13+
/**
14+
* Get TruffleHog checksums from inlined environment variable.
15+
* Returns a map of asset filename to SHA-256 hex checksum.
16+
*
17+
* @throws Error if checksums are missing in production builds.
18+
*/
19+
export function getTrufflehogChecksums(): TrufflehogChecksums {
20+
const checksums = process.env['INLINED_SOCKET_CLI_TRUFFLEHOG_CHECKSUMS']
21+
if (!checksums) {
22+
// In development mode (not inlined), return empty object.
23+
// Build validation will catch missing checksums at build time.
24+
return {}
25+
}
26+
try {
27+
return JSON.parse(checksums) as TrufflehogChecksums
28+
} catch {
29+
throw new Error(
30+
'Failed to parse TruffleHog checksums. This indicates a build configuration error.',
31+
)
32+
}
33+
}
34+
35+
/**
36+
* Lookup a TruffleHog checksum by asset name.
37+
* In production builds (checksums inlined), throws a hard error if asset is missing.
38+
* In dev mode (checksums not inlined), returns undefined to allow development.
39+
*
40+
* @param assetName - The asset filename to look up.
41+
* @returns The SHA-256 hex checksum, or undefined in dev mode.
42+
* @throws Error if checksum is not found in production builds.
43+
*/
44+
export function requireTrufflehogChecksum(assetName: string): string | undefined {
45+
const checksums = getTrufflehogChecksums()
46+
47+
// In dev mode, checksums are not inlined so the object is empty.
48+
// Allow downloads without verification during development.
49+
if (Object.keys(checksums).length === 0) {
50+
return undefined
51+
}
52+
53+
// In production mode, checksums are inlined.
54+
// Require checksum for every asset - missing checksum is a HARD ERROR.
55+
const sha256 = checksums[assetName]
56+
if (!sha256) {
57+
throw new Error(
58+
`Missing SHA-256 checksum for TruffleHog asset: ${assetName}. ` +
59+
'This is a security requirement. Please update external-tools.json with the correct checksum.',
60+
)
61+
}
62+
return sha256
63+
}

packages/cli/src/types.mts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ export type BaseFetchOptions = {
99

1010
// Checksum types for external tool integrity verification.
1111
// Maps asset filename to SHA-256 hex checksum.
12+
export type OpengrepChecksums = Record<string, string>
1213
export type PythonChecksums = Record<string, string>
14+
export type SfwChecksums = Record<string, string>
1315
export type SocketPatchChecksums = Record<string, string>
16+
export type TrivyChecksums = Record<string, string>
17+
export type TrufflehogChecksums = Record<string, string>
1418

1519
// CResult is akin to the "Result" or "Outcome" or "Either" pattern.
1620
// Main difference might be that it's less strict about the error side of

0 commit comments

Comments
 (0)