LavaMoat Node.js Runtime
@lavamoat/node (npm, GitHub) provides a runtime environment for LavaMoat-protected Node.js applications.
What Is the LavaMoat Node.js Runtime?
Section titled “What Is the LavaMoat Node.js Runtime?”@lavamoat/node differs from the standard Node.js runtime in that it:
- Prevents tampering with the execution environment via SES’ hardening facilities. This completely mitigates prototype-pollution attacks, among other concerns.
- Isolates each package’s execution via SES’ Compartments. Packages share no references whatsoever unless explicitly allowed by Policy. This is possible because SES itself—not Node.js—is responsible for loading and executing modules.
- Enforces a user-provided Policy. The Policy controls which globals, Node.js APIs and other packages any given package can access. The Policy provides granular control over whether a global (or even a property thereof) is exposed, read-only, or writable.
Installation
Section titled “Installation”npm install @lavamoat/nodeyarn add @lavamoat/nodepnpm add @lavamoat/nodeGetting Started
Section titled “Getting Started”The first step is to generate a Policy for your application.
Generating a Policy
Section titled “Generating a Policy”@lavamoat/node generates a Policy from your application’s entry point by crawling all reachable modules, detecting globals, Node.js builtins, and references to other packages1.
Creating a Policy differs slightly depending on whether you intend to run your own application2 or a third-party tool under LavaMoat.
For Your Own Application
Section titled “For Your Own Application”To create a Policy for your own application:
Execute the following command from your project’s root directory:
npx lavamoat generate <app.js>yarn exec lavamoat generate <app.js>pnpm exec lavamoat generate <app.js>…where
<app.js>is the entry point of your application.To create a Policy for a third-party tool:
Execute the following command from your project’s root directory:
npx lavamoat generate --bin <executable>yarn exec lavamoat generate --bin <executable>pnpm exec lavamoat generate --bin <executable>…where
<executable>is the tool’s executable3 (e.g.,eslint).
In either case, you should see some output like the following:
info › Generating LavaMoat Policy from ./dist/index.js…info › Crawling dependency graph…info › ✓ Found 1437 packages in ~0.61sinfo › ✓ Inspected 160 (~26.47 modules/s)warning › source-map-support is not a builtin module, but was otherwise not accessible from typescript. This may be due to an implicit dependency.warning › Package cosmiconfig>import-fresh contains potential SES violations at the following location:warning › ▶ /path/to/node_modules/import-fresh/index.js:33:63 — dynamic requireswarning › Package typescript contains potential SES violations at the following location:warning › ▶ /path/to/node_modules/typescript/lib/typescript.js:8397:27 — dynamic requiresinfo › ✓ Policy for ./dist/index.js generated in ~6.82sinfo › ✓ Wrote Policy to ./lavamoat/node/policy.jsonThe Policy file will be written to ./lavamoat/node/policy.json, which is the default location4.
The above example output shows warnings. Warnings indicate potential runtime failures, and generally shouldn’t be ignored. Check out the Troubleshooting Guide for more information. If you see warnings, it’s likely you’ll need to create a Policy Override file to explicitly grant access to the resources your application needs.
Once we have a Policy and/or Policy Override file, we can now move on to actually running the application.
Running Your Application
Section titled “Running Your Application”To execute your application under LavaMoat:
npx lavamoat <app.js> -- --flag1 --flag2yarn exec lavamoat <app.js> -- --flag1 --flag2pnpm exec lavamoat <app.js> -- --flag1 --flag2…where <app.js> is the entry point of your application and --flag1 --flag2 are any flags your application needs5.
With any luck, you should see your application running as expected. If you’re seeing unexpected failures, then there’s more work to do; read about creating a Policy Override and our troubleshooting guide for more information.
Basic Usage
Section titled “Basic Usage”@lavamoat/node’s two main modes of operation are Policy generation and execution:
generateGenerate a Policy for the application.
runRun the application with the given Policy.
When no command is provided,
runis@lavamoat/node’s default command.
Common Flags
Section titled “Common Flags”Both commands (run and generate) support the following flags.
Custom Policy & Policy Override Paths
Section titled “Custom Policy & Policy Override Paths”When using LavaMoat to run multiple applications or tools, you’ll want to keep separate Policy and Policy Override files for each application or tool; this is done by providing the following flags.
--policy/-pPath to Policy file; in the case of Policy generation, this is the path to write, and in the case of execution, this is the path to read.
The default location for the Policy file is
./lavamoat/node/policy.json.--policy-override/-oPath to Policy override file; similar to
--policy.The default location for the Policy Override file is
./lavamoat/node/policy-override.json.
Using a Third-Party Application
Section titled “Using a Third-Party Application”--binSpecify a third-party executable as the entry point.
This is a boolean flag; if provided, the positional argument is assumed to be an executable in
node_modules/.bin/and@lavamoat/nodewill find it for you6.If
--binwas provided togenerate,--binmust also be provided torun.@lavamoat/nodewill refuse to execute applications which containroot.usePolicyin a trusted context. The converse is also true; if--binwasn’t provided togenerate,--bincannot be provided torun.If either case is detected,
@lavamoat/nodewill throw aTrustMismatchErrorand fail.
Custom Project Root
Section titled “Custom Project Root”--root/--project-rootPath to the root of the project; used to determine relative paths.
Output Flags
Section titled “Output Flags”--verboseEmit verbose output.
--quietEmit no output whatsoever.
--help/-hShow help and exit.
--version/-vShow version and exit.
generate Command Flags
Section titled “generate Command Flags”These flags are available when using the generate command.
Disable Warnings
Section titled “Disable Warnings”--no-warningsDisable warnings during Policy generation.
By default, warnings are enabled when generating Policy.
Exclude Development Dependencies
Section titled “Exclude Development Dependencies”--prod-onlyExclude development dependencies (
devDependencies) from the Policy if the entry point accesses them.By default, development dependencies are included in the Policy if the entry point accesses them.
When using
@lavamoat/nodeto run a third-party tool, you will typically want to avoid this flag.
Writing to Standard Output
Section titled “Writing to Standard Output”--no-writeWrite Policy to standard output instead of a file.
Treating Warnings as Errors
Section titled “Treating Warnings as Errors”--warnings-as-errorsTreat warnings as errors. Any warnings will cause the process to exit with code
1.This can be useful in certain situations, such as running in a CI environment.
Limiting Concurrency
Section titled “Limiting Concurrency”If Policy generation is too taxing on your system (hello, CI runners!), you can tradeoff speed for memory by limiting the number of worker threads used.
--concurrency <number>Limit the number of worker threads to use for Policy generation.
By default, Policy generation uses n - 1 worker threads.
Compacting Overrides
Section titled “Compacting Overrides”You may see a notice during Policy generation that your Policy override file contains redundant entries; --compact-overrides is the solution. This will help reduce the size of your Policy Override file, and thus its maintenance burden.
--compact-overridesRemove redundant information from your Policy override file and overwrite it.
Entries which would have been created by analysis alone (i.e., disregarding Policy Overrides entirely) will be removed from your Policy Override file.
run Command Flags
Section titled “run Command Flags”These flags are available when using the run command.
Enable Warnings
Section titled “Enable Warnings”--warningsEnable warnings during execution.
By default, warnings are disabled during execution, as they may interfere with the application’s output.
Scuttle Globals
Section titled “Scuttle Globals”Scuttling provides an additional layer of security, but is not compatible with all applications.
--scuttleIf provided, the
--scuttleflag will scuttle theglobalThisobject. As another layer of security, this attempts to destroy any global property which is not explicitly allowed by Policy.Scuttling is applied to the global object (
globalThis) after its copy was made, so all of the code running under LavaMoat should never reach the real scuttled properties and instead use the copies.If code attempts direct access to a global prop outside of LavaMoat’s sandbox, an exception will be thrown.
Scuttling protections come in handy when a Policy allows access to an object which transitively exposes the real
globalThis.
Footnotes
Section titled “Footnotes”-
Because JavaScript is such a dynamic language, automatic Policy generation using static analysis is considered best-effort. For example, dynamic imports will be unknown until runtime. Additionally, a trusted a package may obfuscate access to APIs and/or globals, and
@lavamoat/nodewill not detect such cases. ↩ -
If your project is a library, the library itself won’t benefit from LavaMoat, since libraries are not “runnable.” However, you can still benefit by using
@lavamoat/nodeto run third-party tools. ↩ -
Only project-local JavaScript executables, as installed by a package manager, are supported. You cannot use a globally-installed executable, nor something like
/bin/cat! Read more about the--binflag. ↩ -
The location of Policy and Policy Override files are configurable via command-line flag ↩
-
Flags to
lavamoatmust be provided before the--separator. ↩ -
If your package manager does not install executables into
node_modules/.bin/, you can provide an absolute or relative filepath to the executable instead. ↩