Files
sanish chirayathandlohit-bruno 7be4a66bdb fix(node-vm): resolve npm modules from additionalContextRoots via native walk-up (#8538)
* fix: resolve npm modules from additionalContextRoots node_modules in NodeVM

When shared script folders configured via additionalContextRoots have
their own node_modules, the NodeVM runtime now searches those directories
for npm packages instead of only checking the collection and Bruno paths.

* fix(node-vm): enhance npm module resolution security by restricting access to ancestor node_modules

Added a new function to compare resolved npm module paths against allowed roots, ensuring that modules from ancestor directories outside the configured additionalContextRoots are not accessible. Updated the loadNpmModule function to utilize this security check during module resolution. Added tests to verify that ancestor node_modules cannot be accessed when not explicitly allowed.

* feat(tests): add integration tests for additionalContextRoots npm module resolution

Introduced a comprehensive set of integration tests to validate the resolution of npm modules from additionalContextRoots in the CLI. The tests ensure that shared libraries are correctly resolved, ancestor node_modules are inaccessible, and that collections can successfully utilize shared scripts. Additionally, created fixture files to support the testing environment, including workspace configurations and sample scripts for both collections A and B.

* chore(tests): update .gitignore to track specific node_modules for e2e tests

Modified .gitignore to include tracking for node_modules in test fixtures, ensuring that fake npm packages are available for end-to-end tests. Added new files for 'forbidden-lib' and 'signature-utils' to support testing scenarios.

* test(node-vm): add test for resolving npm modules via symlink in additionalContextRoots

Implemented a new test to verify that npm modules can be resolved correctly when additionalContextRoots points to a symlink. The test ensures that the symlinked library is accessible and that the context variable is set appropriately, enhancing the coverage for npm module resolution scenarios.

* feat(node-vm): enhance npm module resolution with additional context roots

Added support for resolving npm modules from additional context roots, including symlinked modules. Introduced new functions to validate module paths against allowed roots, ensuring security and proper resolution of transitive dependencies. Updated the loadNpmModule and createNpmModuleRequire functions to incorporate these changes, improving the flexibility and reliability of module loading in the NodeVM context.

* feat(node-vm): improve npm module resolution for linked packages

Enhanced the module resolution logic to allow internal relative requires within npm-linked packages, even when their physical location is outside declared allowed roots. Introduced a new function to find the closest package root, ensuring that internal dependencies can be resolved correctly. Updated tests to validate this behavior, confirming that the system handles linked packages as expected.

* feat(node-vm): support subpath imports for npm-linked packages

Enhanced the module resolution logic to allow subpath imports from npm-linked packages, ensuring that imports like `require('linked-pkg/utils')` are correctly resolved. Updated the `isModuleLinkedFromAllowedRoot` function to utilize bare-name lookup for improved accuracy. Added tests to validate this new behavior, confirming that subpath imports function as expected in the NodeVM context.

* refactor(tests): remove forbidden-lib tests and update integration scenarios

Eliminated tests related to the forbidden-lib package to streamline the test suite, ensuring that ancestor node_modules are not resolvable. Updated integration tests for additionalContextRoots to reflect changes in request counts and improved clarity in test descriptions. Adjusted workspace configurations to enhance overall test coverage and maintainability.

* refactor(cjs-loader): simplify module resolution documentation

Updated comments in the cjs-loader.js file to clarify the module resolution order, removing redundant explanations while maintaining essential details. This change enhances the readability and understanding of the module loading process in the NodeVM context.

* refactor(cjs-loader): improve comments and clarify module resolution logic

Updated comments in cjs-loader.js to enhance clarity regarding module resolution, specifically indicating fallback behavior when modules are not found in the collection. Adjusted the path used in createNpmModuleRequire for improved accuracy in module loading.

* feat(tests): add additional-context-root-lib and update workflows

Introduced the additional-context-root-lib package with its dependencies and updated relevant workflows to install this new library. Adjusted the CLI test and flaky test detector workflows to include installation steps for the additional-context-root-lib, ensuring proper setup for testing scenarios.

* refactor(tests): update npm module resolution test for additionalContextRoots

Revised the test case to ensure that requiring an npm package from a sibling additionalContextRoot fails as expected. Updated the test description for clarity and adjusted the script to reflect the intended behavior of module resolution, emphasizing the limitations of cross-root discovery in the NodeVM context.

* refactor(tests): clean up comments in node-vm sandbox tests

* refactor(tests): update fixture URLs in additional-context-roots tests

Modified the URLs in the .bru fixture files to point to the local testbench server instead of an external service. This change ensures consistent test behavior and eliminates reliance on external resources during testing.

* refactor(tests): enhance node-vm sandbox tests with symlink support

Introduced a symlink support check in the node-vm sandbox tests to conditionally skip tests on Windows where symlink creation is restricted. Refactored the test setup to utilize a new `makePkg` function for creating package directories and files, improving code readability and maintainability. Updated relevant tests to leverage this new structure, ensuring consistent behavior across different environments.

* refactor(setup): preserve specific node_modules during cleanup

Added a mechanism to preserve certain `node_modules` directories during the cleanup process in the setup script. This change ensures that fixture data remains intact for testing, specifically for directories related to additional context roots.

---------

Co-authored-by: lohit-bruno <[email protected]>
2026-07-29 20:41:36 +05:30

128 lines
4.2 KiB
JavaScript

const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const icons = {
clean: '🧹',
delete: '🗑️',
install: '📦',
build: '🔨',
success: '✅',
error: '❌',
working: '⚡'
};
// Committed fixture directories whose `node_modules` folders must survive the
// pre-install cleanup — the tests treat them as fixture data, not build output.
// `additional-context-roots/fixtures` ships a `shared-scripts/node_modules/signature-utils`
// package that exercises walk-up resolution from an additional context root.
const PRESERVED_NODE_MODULES_PREFIXES = [
path.join('tests', 'scripting', 'additional-context-roots', 'fixtures')
];
const execCommand = (command, description) => {
try {
console.log(`\n${icons.working} ${description}...`);
execSync(command, { stdio: 'inherit' });
console.log(`${icons.success} ${description} completed`);
} catch (error) {
console.error(`${icons.error} ${description} failed`);
throw error;
}
};
const glob = function (startPath, pattern) {
let results = [];
// Ensure start path exists
if (!fs.existsSync(startPath)) {
return results;
}
const files = fs.readdirSync(startPath);
for (const file of files) {
const filename = path.join(startPath, file);
const stat = fs.lstatSync(filename);
// If directory, recurse into it
if (stat.isDirectory()) {
// Skip node_modules recursion to avoid unnecessary deep scanning
if (file === 'node_modules') {
if (file === pattern) {
results.push(filename);
}
continue;
}
results = results.concat(glob(filename, pattern));
}
// If file matches pattern, add to results
if (file === pattern) {
results.push(filename);
}
}
return results;
};
function forceInstallPlatformDeps() {
// Note: make sure to hard pin deps and only add deps that have been checked
// for sec vuln already since the following will be force installed.
const deps = {
darwin: ['@lydell/[email protected]', '@lydell/[email protected]'],
win32: ['@lydell/[email protected]', '@lydell/[email protected]'],
linux: ['@lydell/[email protected]', '@lydell/[email protected]']
};
// Ignore if no deps need to be installed
if (!deps[process.platform] || (Array.isArray(deps[process.platform]) && deps[process.platform].length === 0)) return;
const toInstall = deps[process.platform];
execCommand(
`npm i --legacy-peer-deps --no-save --force ${toInstall.join(' ')}`,
'Installing platform specific dependencies'
);
}
async function setup() {
try {
// Clean up node_modules (if exists)
console.log(`\n${icons.clean} Cleaning up node_modules directories...`);
const nodeModulesPaths = glob('.', 'node_modules').filter((dir) =>
!PRESERVED_NODE_MODULES_PREFIXES.some((prefix) => dir.startsWith(prefix))
);
for (const dir of nodeModulesPaths) {
console.log(`${icons.delete} Removing ${dir}`);
fs.rmSync(dir, { recursive: true, force: true });
}
// Install dependencies
execCommand('npm i --legacy-peer-deps', 'Installing dependencies');
forceInstallPlatformDeps();
// Build packages
execCommand('npm run build:graphql-docs', 'Building graphql-docs');
execCommand('npm run build:bruno-query', 'Building bruno-query');
execCommand('npm run build:bruno-common', 'Building bruno-common');
execCommand('npm run build:bruno-converters', 'Building bruno-converters');
execCommand('npm run build:bruno-requests', 'Building bruno-requests');
execCommand('npm run build:schema-types', 'Building schema-types');
execCommand('npm run build:bruno-filestore', 'Building bruno-filestore');
execCommand('npm run build:bruno-sqlite', 'Building bruno-sqlite');
// Bundle JS sandbox libraries
execCommand('npm run sandbox:bundle-libraries --workspace=packages/bruno-js', 'Bundling JS sandbox libraries');
console.log(`\n${icons.success} Setup completed successfully!\n`);
} catch (error) {
console.error(`\n${icons.error} Setup failed:`);
console.error(error);
process.exit(1);
}
}
setup().catch((error) => {
console.error(error);
process.exit(1);
});