-
-
Notifications
You must be signed in to change notification settings - Fork 62
fix: lazy PKG_NATIVE_CACHE_PATH eval + integrity check for cached .node files #228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2187,7 +2187,10 @@ function payloadFileSync(pointer) { | |||||||||||||||||||||||||||||||
| // - Windows: C:\Users\John\.cache | ||||||||||||||||||||||||||||||||
| // Custom example: /opt/myapp/cache or C:\myapp\cache | ||||||||||||||||||||||||||||||||
| // Native addons will be extracted to: <PKG_NATIVE_CACHE_BASE>/pkg/<hash> | ||||||||||||||||||||||||||||||||
| const PKG_NATIVE_CACHE_BASE = | ||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||
| // Note: We capture the initial value as the default, but re-read process.env inside | ||||||||||||||||||||||||||||||||
| // process.dlopen so that runtime changes to PKG_NATIVE_CACHE_PATH take effect. | ||||||||||||||||||||||||||||||||
| const PKG_NATIVE_CACHE_DEFAULT = | ||||||||||||||||||||||||||||||||
|
Comment on lines
2189
to
+2193
|
||||||||||||||||||||||||||||||||
| process.env.PKG_NATIVE_CACHE_PATH || path.join(homedir(), '.cache'); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| function revertMakingLong(f) { | ||||||||||||||||||||||||||||||||
|
|
@@ -2209,7 +2212,9 @@ function payloadFileSync(pointer) { | |||||||||||||||||||||||||||||||
| // the hash is needed to be sure we reload the module in case it changes | ||||||||||||||||||||||||||||||||
| const hash = createHash('sha256').update(moduleContent).digest('hex'); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const tmpFolder = path.join(PKG_NATIVE_CACHE_BASE, 'pkg', hash); | ||||||||||||||||||||||||||||||||
| // Re-read PKG_NATIVE_CACHE_PATH at each call so runtime changes take effect | ||||||||||||||||||||||||||||||||
| const cacheBase = process.env.PKG_NATIVE_CACHE_PATH || PKG_NATIVE_CACHE_DEFAULT; | ||||||||||||||||||||||||||||||||
| const tmpFolder = path.join(cacheBase, 'pkg', hash); | ||||||||||||||||||||||||||||||||
|
Comment on lines
+2215
to
+2217
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| fs.mkdirSync(tmpFolder, { recursive: true }); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
@@ -2237,7 +2242,17 @@ function payloadFileSync(pointer) { | |||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||
| const tmpModulePath = path.join(tmpFolder, moduleBaseName); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (!fs.existsSync(tmpModulePath)) { | ||||||||||||||||||||||||||||||||
| if (fs.existsSync(tmpModulePath)) { | ||||||||||||||||||||||||||||||||
| // Verify cached file integrity against snapshot content. | ||||||||||||||||||||||||||||||||
| // The folder name encodes the expected hash, but the file inside could | ||||||||||||||||||||||||||||||||
| // have been replaced (e.g. by a local user to inject malicious code). | ||||||||||||||||||||||||||||||||
| const cachedContent = fs.readFileSync(tmpModulePath); | ||||||||||||||||||||||||||||||||
| const cachedHash = createHash('sha256').update(cachedContent).digest('hex'); | ||||||||||||||||||||||||||||||||
| if (cachedHash !== hash) { | ||||||||||||||||||||||||||||||||
| // Cached file was tampered with or corrupted — re-extract from snapshot | ||||||||||||||||||||||||||||||||
|
Comment on lines
+2249
to
+2252
|
||||||||||||||||||||||||||||||||
| const cachedContent = fs.readFileSync(tmpModulePath); | |
| const cachedHash = createHash('sha256').update(cachedContent).digest('hex'); | |
| if (cachedHash !== hash) { | |
| // Cached file was tampered with or corrupted — re-extract from snapshot | |
| try { | |
| const cachedContent = fs.readFileSync(tmpModulePath); | |
| const cachedHash = createHash('sha256') | |
| .update(cachedContent) | |
| .digest('hex'); | |
| if (cachedHash !== hash) { | |
| // Cached file was tampered with or corrupted — re-extract from snapshot | |
| fs.copyFileSync(modulePath, tmpModulePath); | |
| } | |
| } catch (_) { | |
| // Treat read/hash failures as a cache miss and restore from snapshot. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security concern: Re-reading
process.env.PKG_NATIVE_CACHE_PATHat eachdlopencall means any code (including a compromised dependency) that runs before a native addonrequire()can redirect where addons are extracted to. This turns an immutable, bootstrap-time config into a mutable runtime setting, opening the door to arbitrary directory creation and file writes at attacker-chosen paths.The current behavior — capturing the value once before any user code runs — is the safer design. If runtime override is truly needed, consider a freeze-after-first-use pattern instead.