@chargebee/chargebee-apps-shared
Advanced tools
+6
-0
| # @chargebee/chargebee-apps-shared | ||
| ## [1.0.1] - 2026-07-24 | ||
| ### Fixed | ||
| - Local modules imported via relative paths (e.g., require('./helper')) now execute within the entry handler’s sandbox, allowing them to share the same environment variables and logging configuration. | ||
| ## [1.0.0] - 2026-07-09 | ||
@@ -4,0 +10,0 @@ |
@@ -0,3 +1,9 @@ | ||
| import vm from 'vm'; | ||
| import { CBFileSystem } from '../node/file-system'; | ||
| import { AllowedModule, Manifest } from '../types/common'; | ||
| interface UserModuleCacheEntry { | ||
| exports: any; | ||
| } | ||
| type SandboxRequire = (moduleName: string) => any; | ||
| type MakeSandboxRequire = (parentDir: string) => SandboxRequire; | ||
| /** | ||
@@ -19,8 +25,12 @@ * DependencyManager handles isolated npm installations for user projects | ||
| /** | ||
| * Creates a safe require function that uses isolated node_modules | ||
| * Creates a safe require function that uses isolated node_modules. | ||
| * When vmContext is provided, relative user modules are compiled and run in that | ||
| * context so they share sandbox process/console with the entry handler. | ||
| * Allowlisted packages always load via native Node require. | ||
| * @param {string} projectPath - Path to the user project | ||
| * @param {AllowedModule[]} allowedModules - List of allowed modules | ||
| * @param {vm.Context} [vmContext] - Shared VM context for relative user modules | ||
| * @returns {Function} Safe require function | ||
| */ | ||
| createIsolatedRequire(projectPath: string, allowedModules: AllowedModule[]): (moduleName: string) => any; | ||
| createIsolatedRequire(projectPath: string, allowedModules: AllowedModule[], vmContext?: vm.Context): SandboxRequire; | ||
| /** | ||
@@ -45,9 +55,48 @@ * Creates or updates package.json for the project | ||
| /** | ||
| * Gets the relative path require. | ||
| * @param moduleName - Name of the module to require | ||
| * @param handlerPath - Path to the handler directory | ||
| * @returns | ||
| * Loads a relative user module. With a VM context, the module is compiled in the | ||
| * sandbox so it shares process/console with the entry handler; otherwise native require is used. | ||
| * @param {string} moduleName - Relative module id (./ or ../) | ||
| * @param {string} parentDir - Directory of the requiring module | ||
| * @param {string} handlerPath - Path to the handler directory (containment root) | ||
| * @param {{ vmContext: vm.Context, makeRequire: MakeSandboxRequire, cache: Map<string, UserModuleCacheEntry> }} [vmOptions] - VM load options | ||
| * @returns {any} Module exports | ||
| */ | ||
| protected getRelativePathRequire(moduleName: string, handlerPath: string): any; | ||
| protected getRelativePathRequire(moduleName: string, parentDir: string, handlerPath: string, vmOptions?: { | ||
| vmContext: vm.Context; | ||
| makeRequire: MakeSandboxRequire; | ||
| cache: Map<string, UserModuleCacheEntry>; | ||
| }): any; | ||
| /** | ||
| * Resolves a relative user module path and ensures it stays under handlerPath. | ||
| * Supports direct files, `.js` extension fallback, and directory → `index.js`. | ||
| * Containment is enforced on both lexical and canonical (realpath) paths so | ||
| * symlinks cannot escape the handler root. | ||
| * @param {string} moduleName - Relative module id | ||
| * @param {string} parentDir - Directory of the requiring module | ||
| * @param {string} handlerPath - Handler directory root | ||
| * @returns {string} Absolute path to the module file | ||
| */ | ||
| protected resolveRelativeUserModulePath(moduleName: string, parentDir: string, handlerPath: string): string; | ||
| /** | ||
| * Lexical containment check (no symlink resolution). | ||
| * @param {string} candidatePath - Absolute path to validate | ||
| * @param {string} handlerPath - Handler directory root | ||
| */ | ||
| protected assertPathInsideHandler(candidatePath: string, handlerPath: string): void; | ||
| /** | ||
| * Canonical containment check using realpath so symlink targets outside handler are rejected. | ||
| * @param {string} candidatePath - Absolute path to validate (must exist) | ||
| * @param {string} handlerPath - Handler directory root | ||
| */ | ||
| protected assertCanonicalPathInsideHandler(candidatePath: string, handlerPath: string): void; | ||
| /** | ||
| * Compiles and runs a user module inside the shared VM context (CommonJS wrapper). | ||
| * @param {string} filename - Absolute path to the module file | ||
| * @param {vm.Context} vmContext - Shared sandbox context | ||
| * @param {MakeSandboxRequire} makeRequire - Factory for nested sandbox require | ||
| * @param {Map<string, UserModuleCacheEntry>} cache - Per-execution module cache | ||
| * @returns {any} Module exports | ||
| */ | ||
| protected runUserModuleInVm(filename: string, vmContext: vm.Context, makeRequire: MakeSandboxRequire, cache: Map<string, UserModuleCacheEntry>): any; | ||
| /** | ||
| * Gets the generic dependency require. Unless there are special cases like ES modules, this should work for all dependencies. | ||
@@ -69,1 +118,2 @@ * @param moduleName - Name of the module to require | ||
| } | ||
| export {}; |
@@ -1,1 +0,3 @@ | ||
| "use strict";var __importDefault=this&&this.__importDefault||function(a){return a&&a.__esModule?a:{default:a}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.DependencyManager=void 0;const child_process_1=require("child_process"),path_1=__importDefault(require("path")),semver_1=__importDefault(require("semver")),internal_logger_1=require("../logger/internal-logger");class DependencyManager{constructor(s){this.fileSystem=s,this.installed=!1}async ensureDependencies(s,n){if(!n.dependencies||Object.keys(n.dependencies).length===0){internal_logger_1.__logger.debug("No dependencies to install");return}if(this.installed){internal_logger_1.__logger.debug("Dependencies already installed for this project");return}const e=path_1.default.join(s,"handler"),i=path_1.default.join(e,"package.json");try{this.fileSystem.existsSync(e)||this.fileSystem.mkdirSync(e,{recursive:!0}),await this.createPackageJson(i,n.dependencies),await this.installDependencies(e),this.installed=!0,internal_logger_1.__logger.info(`Installed ${Object.keys(n.dependencies).length} dependencies for project`)}catch(t){const r=t;throw internal_logger_1.__logger.error("Failed to install dependencies:",r.message),new Error(`Dependency installation failed: ${r.message}`)}}createIsolatedRequire(s,n){const e=path_1.default.resolve(s,"handler"),i=path_1.default.join(e,"node_modules");return t=>{if(t.startsWith("./"))return this.getRelativePathRequire(t,e);if(t.includes("/"))return this.getGenericDependencyRequire(t,n,i);const r=this.validateAndGetModulePath(n,i,t);try{return this.tryLoadCommonJSAlternative(t,r)}catch(o){const l=o;throw new Error(`Failed to load module "${t}": ${l.message}`)}}}async createPackageJson(s,n){const e={dependencies:{...n}},i=JSON.stringify(e,null,2);this.fileSystem.writeFileSync(s,i),internal_logger_1.__logger.debug("Created/updated package.json")}async installDependencies(s){try{(0,child_process_1.execSync)("npm install --ignore-scripts --no-audit --no-fund --loglevel=error",{cwd:s,stdio:"pipe",timeout:6e4}),internal_logger_1.__logger.debug("npm install completed successfully")}catch(n){const e=n;throw new Error(`npm install failed: ${e.message}`)}}tryLoadCommonJSAlternative(s,n){const e=require(n);if(e&&typeof e=="object"&&e.__esModule&&typeof e!="function"){internal_logger_1.__logger.debug(`ES module detected for ${s} (loaded but has named exports), looking for CommonJS alternative`);const i=path_1.default.join(n,"package.json");if(this.fileSystem.existsSync(i)){const t=JSON.parse(this.fileSystem.readFileSync(i,"utf8"));if(t.exports&&t.exports["."]){const r=t.exports["."];if(r.require){const o=path_1.default.resolve(n,r.require);return internal_logger_1.__logger.debug(`Trying CommonJS path: ${r.require}`),require(o)}if(r.default&&r.default.require){const o=path_1.default.resolve(n,r.default.require);return internal_logger_1.__logger.debug(`Trying default CommonJS path: ${r.default.require}`),require(o)}}if(t.main){const r=path_1.default.resolve(n,t.main);return internal_logger_1.__logger.debug(`Trying main field: ${t.main}`),require(r)}}return internal_logger_1.__logger.error(`No CommonJS alternative found for ${s}, using ES module as-is`),e}return e}getRelativePathRequire(s,n){const e=path_1.default.normalize(s);if(e.includes(".."))throw new Error("Path traversal not allowed in relative imports");const i=path_1.default.resolve(n,e);if(!i.startsWith(n))throw new Error("Relative import path resolves outside of handler directory");let t=i;if(!this.fileSystem.existsSync(i)){const r=i+".js";if(this.fileSystem.existsSync(r))t=r;else throw new Error(`File or module not found: ${s}`)}return require(t)}getGenericDependencyRequire(s,n,e){const i=s.split("/")[0];if(!n.find(o=>o.name===i))throw new Error(`Module "${i}" is not allowed in this environment`);const r=path_1.default.join(e,s);if(!this.fileSystem.existsSync(r))throw new Error(`Module path "${s}" is not installed. Please ensure dependencies are installed.`);return internal_logger_1.__logger.debug(`Loading CommonJS path: ${s}`),require(path_1.default.resolve(r))}validateAndGetModulePath(s,n,e){const i=path_1.default.join(n,e);if(!this.fileSystem.existsSync(i))throw new Error(`Module "${e}" is not installed. Please ensure dependencies are installed.`);const t=s.find(r=>r.name===e);if(!t)throw new Error(`Module "${e}" is not allowed in this environment`);if(t.version)try{const r=path_1.default.join(i,"package.json"),l=JSON.parse(this.fileSystem.readFileSync(r,"utf8")).version;if(!semver_1.default.satisfies(l,t.version))throw new Error(`Module "${e}" version ${l} does not satisfy allowed version constraint ${t.version}`);internal_logger_1.__logger.debug(`Module "${e}" version ${l} satisfies constraint ${t.version}`)}catch(r){const o=r;internal_logger_1.__logger.warn(`Warning: Could not verify version for module "${e}":`,o.message)}else throw new Error(`Configuration error: Allowed module "${e}" does not have a version constraint`);return path_1.default.resolve(i)}}exports.DependencyManager=DependencyManager; | ||
| "use strict";var __importDefault=this&&this.__importDefault||function(d){return d&&d.__esModule?d:{default:d}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.DependencyManager=void 0;const child_process_1=require("child_process"),path_1=__importDefault(require("path")),semver_1=__importDefault(require("semver")),vm_1=__importDefault(require("vm")),internal_logger_1=require("../logger/internal-logger");class DependencyManager{constructor(t){this.fileSystem=t,this.installed=!1}async ensureDependencies(t,s){if(!s.dependencies||Object.keys(s.dependencies).length===0){internal_logger_1.__logger.debug("No dependencies to install");return}if(this.installed){internal_logger_1.__logger.debug("Dependencies already installed for this project");return}const e=path_1.default.join(t,"handler"),r=path_1.default.join(e,"package.json");try{this.fileSystem.existsSync(e)||this.fileSystem.mkdirSync(e,{recursive:!0}),await this.createPackageJson(r,s.dependencies),await this.installDependencies(e),this.installed=!0,internal_logger_1.__logger.info(`Installed ${Object.keys(s.dependencies).length} dependencies for project`)}catch(o){const n=o;throw internal_logger_1.__logger.error("Failed to install dependencies:",n.message),new Error(`Dependency installation failed: ${n.message}`)}}createIsolatedRequire(t,s,e){const r=path_1.default.resolve(t,"handler"),o=path_1.default.join(r,"node_modules"),n=new Map,i=l=>a=>{if(a.startsWith("./")||a.startsWith("../"))return this.getRelativePathRequire(a,l,r,e?{vmContext:e,makeRequire:i,cache:n}:void 0);if(a.includes("/"))return this.getGenericDependencyRequire(a,s,o);const c=this.validateAndGetModulePath(s,o,a);try{return this.tryLoadCommonJSAlternative(a,c)}catch(u){const h=u;throw new Error(`Failed to load module "${a}": ${h.message}`)}};return i(r)}async createPackageJson(t,s){const e={dependencies:{...s}},r=JSON.stringify(e,null,2);this.fileSystem.writeFileSync(t,r),internal_logger_1.__logger.debug("Created/updated package.json")}async installDependencies(t){try{(0,child_process_1.execSync)("npm install --ignore-scripts --no-audit --no-fund --loglevel=error",{cwd:t,stdio:"pipe",timeout:6e4}),internal_logger_1.__logger.debug("npm install completed successfully")}catch(s){const e=s;throw new Error(`npm install failed: ${e.message}`)}}tryLoadCommonJSAlternative(t,s){const e=require(s);if(e&&typeof e=="object"&&e.__esModule&&typeof e!="function"){internal_logger_1.__logger.debug(`ES module detected for ${t} (loaded but has named exports), looking for CommonJS alternative`);const r=path_1.default.join(s,"package.json");if(this.fileSystem.existsSync(r)){const o=JSON.parse(this.fileSystem.readFileSync(r,"utf8"));if(o.exports&&o.exports["."]){const n=o.exports["."];if(n.require){const i=path_1.default.resolve(s,n.require);return internal_logger_1.__logger.debug(`Trying CommonJS path: ${n.require}`),require(i)}if(n.default&&n.default.require){const i=path_1.default.resolve(s,n.default.require);return internal_logger_1.__logger.debug(`Trying default CommonJS path: ${n.default.require}`),require(i)}}if(o.main){const n=path_1.default.resolve(s,o.main);return internal_logger_1.__logger.debug(`Trying main field: ${o.main}`),require(n)}}return internal_logger_1.__logger.error(`No CommonJS alternative found for ${t}, using ES module as-is`),e}return e}getRelativePathRequire(t,s,e,r){const o=this.resolveRelativeUserModulePath(t,s,e);return r?this.runUserModuleInVm(o,r.vmContext,r.makeRequire,r.cache):require(o)}resolveRelativeUserModulePath(t,s,e){const r=path_1.default.resolve(s,t);this.assertPathInsideHandler(r,e);let o=r;if(this.fileSystem.existsSync(r)){if(this.fileSystem.statSync(r).isDirectory()){const n=path_1.default.join(r,"index.js");if(this.fileSystem.existsSync(n))o=n;else throw new Error(`File or module not found: ${t}`)}}else{const n=r+".js";if(this.fileSystem.existsSync(n))o=n;else throw new Error(`File or module not found: ${t}`)}return this.assertPathInsideHandler(o,e),this.assertCanonicalPathInsideHandler(o,e),o}assertPathInsideHandler(t,s){if(t!==s&&!t.startsWith(s+path_1.default.sep))throw new Error("Relative import path resolves outside of handler directory")}assertCanonicalPathInsideHandler(t,s){let e,r;try{e=this.fileSystem.realpathSync(t),r=this.fileSystem.realpathSync(s)}catch{throw new Error("Relative import path resolves outside of handler directory")}if(e!==r&&!e.startsWith(r+path_1.default.sep))throw new Error("Relative import path resolves outside of handler directory")}runUserModuleInVm(t,s,e,r){const o=r.get(t);if(o)return o.exports;const n={exports:{}};r.set(t,n);try{const l=`(function (exports, require, module, __filename, __dirname) { | ||
| ${this.fileSystem.readFileSync(t,"utf8")} | ||
| })`,a=vm_1.default.runInContext(l,s,{filename:t,displayErrors:!0}),c=path_1.default.dirname(t),u=e(c);return a.call(n.exports,n.exports,u,n,t,c),n.exports}catch(i){throw r.delete(t),i}}getGenericDependencyRequire(t,s,e){const r=t.split("/")[0];if(!s.find(i=>i.name===r))throw new Error(`Module "${r}" is not allowed in this environment`);const n=path_1.default.join(e,t);if(!this.fileSystem.existsSync(n))throw new Error(`Module path "${t}" is not installed. Please ensure dependencies are installed.`);return internal_logger_1.__logger.debug(`Loading CommonJS path: ${t}`),require(path_1.default.resolve(n))}validateAndGetModulePath(t,s,e){const r=path_1.default.join(s,e);if(!this.fileSystem.existsSync(r))throw new Error(`Module "${e}" is not installed. Please ensure dependencies are installed.`);const o=t.find(n=>n.name===e);if(!o)throw new Error(`Module "${e}" is not allowed in this environment`);if(o.version)try{const n=path_1.default.join(r,"package.json"),l=JSON.parse(this.fileSystem.readFileSync(n,"utf8")).version;if(!semver_1.default.satisfies(l,o.version))throw new Error(`Module "${e}" version ${l} does not satisfy allowed version constraint ${o.version}`);internal_logger_1.__logger.debug(`Module "${e}" version ${l} satisfies constraint ${o.version}`)}catch(n){const i=n;internal_logger_1.__logger.warn(`Warning: Could not verify version for module "${e}":`,i.message)}else throw new Error(`Configuration error: Allowed module "${e}" does not have a version constraint`);return path_1.default.resolve(r)}}exports.DependencyManager=DependencyManager; |
@@ -34,2 +34,4 @@ import fs, { Stats } from 'fs'; | ||
| lstatSync: (path: string) => Stats; | ||
| /** Resolves all symbolic links and returns the canonical absolute path. */ | ||
| realpathSync: (path: string) => string; | ||
| /** Renames a file or directory. */ | ||
@@ -63,2 +65,3 @@ renameSync: (oldPath: string, newPath: string) => void; | ||
| lstatSync(path: string): Stats; | ||
| realpathSync(path: string): string; | ||
| renameSync(oldPath: string, newPath: string): void; | ||
@@ -65,0 +68,0 @@ cpSync(src: string, dest: string, options?: { |
@@ -1,1 +0,1 @@ | ||
| "use strict";var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.FileSystemService=void 0;const fs_1=__importDefault(require("fs"));class FileSystemService{existsSync(e){return fs_1.default.existsSync(e)}readdirSync(e,r){return fs_1.default.readdirSync(e,r)}mkdirSync(e,r){fs_1.default.mkdirSync(e,r)}unlinkSync(e){fs_1.default.unlinkSync(e)}rmdirSync(e){fs_1.default.rmdirSync(e)}statSync(e){return fs_1.default.statSync(e)}createWriteStream(e){return fs_1.default.createWriteStream(e)}readFileSync(e,r){return fs_1.default.readFileSync(e,r)}writeFileSync(e,r){fs_1.default.writeFileSync(e,r)}copyFileSync(e,r){fs_1.default.copyFileSync(e,r)}lstatSync(e){return fs_1.default.lstatSync(e)}renameSync(e,r){try{fs_1.default.renameSync(e,r)}catch(c){if(c.code==="EXDEV")try{fs_1.default.cpSync(e,r,{recursive:!0}),fs_1.default.rmSync(e,{recursive:!0,force:!0})}catch(u){try{fs_1.default.rmSync(r,{recursive:!0,force:!0})}catch{}throw u}else throw c}}cpSync(e,r,c){fs_1.default.cpSync(e,r,c)}}exports.FileSystemService=FileSystemService; | ||
| "use strict";var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.FileSystemService=void 0;const fs_1=__importDefault(require("fs"));class FileSystemService{existsSync(e){return fs_1.default.existsSync(e)}readdirSync(e,r){return fs_1.default.readdirSync(e,r)}mkdirSync(e,r){fs_1.default.mkdirSync(e,r)}unlinkSync(e){fs_1.default.unlinkSync(e)}rmdirSync(e){fs_1.default.rmdirSync(e)}statSync(e){return fs_1.default.statSync(e)}createWriteStream(e){return fs_1.default.createWriteStream(e)}readFileSync(e,r){return fs_1.default.readFileSync(e,r)}writeFileSync(e,r){fs_1.default.writeFileSync(e,r)}copyFileSync(e,r){fs_1.default.copyFileSync(e,r)}lstatSync(e){return fs_1.default.lstatSync(e)}realpathSync(e){return fs_1.default.realpathSync(e)}renameSync(e,r){try{fs_1.default.renameSync(e,r)}catch(c){if(c.code==="EXDEV")try{fs_1.default.cpSync(e,r,{recursive:!0}),fs_1.default.rmSync(e,{recursive:!0,force:!0})}catch(n){try{fs_1.default.rmSync(r,{recursive:!0,force:!0})}catch{}throw n}else throw c}}cpSync(e,r,c){fs_1.default.cpSync(e,r,c)}}exports.FileSystemService=FileSystemService; |
@@ -56,5 +56,6 @@ import { ICBLogger } from '../logger/cb-logger'; | ||
| * @param projectPath - Path to the user project | ||
| * @param vmContext - Optional shared VM context for relative user modules | ||
| * @returns A safe require function | ||
| */ | ||
| createSafeRequire(projectPath: string): (moduleName: string) => any; | ||
| createSafeRequire(projectPath: string, vmContext?: object): (moduleName: string) => any; | ||
| /** | ||
@@ -61,0 +62,0 @@ * Creates a secure sandbox environment for code execution |
+1
-1
| { | ||
| "name": "@chargebee/chargebee-apps-shared", | ||
| "version": "1.0.0", | ||
| "version": "1.0.1", | ||
| "description": "Shared interfaces and utilities for Chargebee Apps CLI", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
100451
5.03%1405
4.46%19
11.76%