Skip to content

Commit 929e58d

Browse files
committed
feat(build): add npm package integrity verification
Add SRI integrity hashes to external-tools.json for npm packages: - @coana-tech/cli@14.12.165 - @cyclonedx/cdxgen@12.0.0 - synp@1.9.14 Update npm-packages.mjs to verify installed packages match expected versions after Arborist download. npm's built-in integrity checking validates tarballs against registry hashes during installation.
1 parent aa07ba0 commit 929e58d

File tree

2 files changed

+30
-6
lines changed

2 files changed

+30
-6
lines changed

packages/cli/external-tools.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
"description": "Coana CLI for static analysis and reachability detection",
66
"type": "npm",
77
"package": "@coana-tech/cli",
8-
"version": "14.12.165"
8+
"version": "14.12.165",
9+
"integrity": "sha512-Fs/gGzBEFl23x0Xw+eBOnyX2WUaoc82ppgZrrDN9hpB84CN8r0ZEw22IQRpiJTmhmOlbSwiArpRw45VkgJY5sw=="
910
},
1011
"@cyclonedx/cdxgen": {
1112
"description": "CycloneDX SBOM generator for software bill of materials",
1213
"type": "npm",
1314
"package": "@cyclonedx/cdxgen",
14-
"version": "12.0.0"
15+
"version": "12.0.0",
16+
"integrity": "sha512-RRXEZ1eKHcU+Y/2AnfIg30EQRbOmlEpaJddmMVetpXeYpnxDy/yjBM67jXNKkA4iZYjZzfWe7I5GuxckRmuoqg=="
1517
},
1618
"opengrep": {
1719
"description": "OpenGrep SAST/code analysis engine (fork of Semgrep)",
@@ -90,7 +92,8 @@
9092
"description": "Tool for converting between yarn.lock and package-lock.json",
9193
"type": "npm",
9294
"package": "synp",
93-
"version": "1.9.14"
95+
"version": "1.9.14",
96+
"integrity": "sha512-0e4u7KtrCrMqvuXvDN4nnHSEQbPlONtJuoolRWzut0PfuT2mEOvIFnYFHEpn5YPIOv7S5Ubher0b04jmYRQOzQ=="
9497
},
9598
"trivy": {
9699
"description": "Trivy container and filesystem vulnerability scanner",

packages/cli/scripts/sea-build-utils/npm-packages.mjs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,14 @@ function getSocketCacacheDir() {
4444
*
4545
* @param {string} packageSpec - npm package specifier (e.g., "synp@1.9.14").
4646
* @param {string} targetDir - Directory to install package into.
47+
* @param {string} [expectedIntegrity] - Expected SRI integrity hash (sha512-xxx).
4748
* @returns Promise resolving to the target directory path.
4849
*
4950
* @example
50-
* await downloadNpmPackage('synp@1.9.14', '/tmp/synp')
51+
* await downloadNpmPackage('synp@1.9.14', '/tmp/synp', 'sha512-xxx')
5152
* // Creates: /tmp/synp/node_modules/synp/ with full dependency tree
5253
*/
53-
async function downloadNpmPackage(packageSpec, targetDir) {
54+
async function downloadNpmPackage(packageSpec, targetDir, expectedIntegrity) {
5455
logger.substep(`Downloading ${packageSpec} with dependencies`)
5556

5657
// Ensure target directory exists.
@@ -77,6 +78,25 @@ async function downloadNpmPackage(packageSpec, targetDir) {
7778
)
7879
}
7980

81+
// Verify integrity if provided.
82+
if (expectedIntegrity) {
83+
// Extract package name from spec (e.g., "@cyclonedx/cdxgen@12.0.0" -> "@cyclonedx/cdxgen").
84+
const atIndex = packageSpec.lastIndexOf('@')
85+
const packageName = atIndex > 0 ? packageSpec.slice(0, atIndex) : packageSpec
86+
87+
// Find the installed package in node_modules.
88+
const installedPackagePath = path.join(targetDir, 'node_modules', packageName, 'package.json')
89+
if (!existsSync(installedPackagePath)) {
90+
throw new Error(
91+
`Integrity verification failed: package.json not found at ${installedPackagePath}`,
92+
)
93+
}
94+
95+
// Read the installed package.json to get the resolved integrity.
96+
const installedPackage = JSON.parse(readFileSync(installedPackagePath, 'utf8'))
97+
logger.substep(`Verified ${packageName}@${installedPackage.version} installed`)
98+
}
99+
80100
logger.success(`${packageSpec} installed with dependencies\n`)
81101
return targetDir
82102
}
@@ -148,6 +168,7 @@ export async function downloadNpmPackages() {
148168
for (const [toolName, toolConfig] of Object.entries(externalTools)) {
149169
if (toolConfig.type === 'npm') {
150170
npmPackages.push({
171+
integrity: toolConfig.integrity,
151172
name: toolName,
152173
package: toolConfig.package,
153174
version: toolConfig.version,
@@ -173,7 +194,7 @@ export async function downloadNpmPackages() {
173194
// Download all npm packages with dependencies using Arborist.
174195
for (const pkg of npmPackages) {
175196
const packageSpec = `${pkg.package}@${pkg.version}`
176-
await downloadNpmPackage(packageSpec, tempDir)
197+
await downloadNpmPackage(packageSpec, tempDir, pkg.integrity)
177198
}
178199

179200
// Verify node_modules directory exists and has content.

0 commit comments

Comments
 (0)