Skip to content

Using Policies

This guide will help you understand what a LavaMoat Policy is and how to use it.

What’s a Policy?

A Policy is an object that describes which resources any given dependency can access. These “resources” include globals, builtins (e.g., node:fs), native modules, and other packages. All direct and transitive dependencies in your application’s dependency tree are subject to a Policy.

The behavior of your application is not restricted by the Policy. A Policy only applies to direct and transitive dependencies of your application.

Policies are fundamental to LavaMoat’s operation.

Policy Files: policy.json and policy-override.json

When getting stared with LavaMoat, you’ll first generate a Policy file using the LavaMoat CLI. To do this, LavaMoat crawls your application’s dependency tree and determines which resources each dependency is currently using. In other words, auto-generating a Policy takes a snapshot of the permissions at a point in time. LavaMoat writes this policy to policy.json.

At some point, you’ll want to add, update, or remove dependencies. When you do, you’ll want to have LavaMoat re-generate policy.json—another snapshot.

You should manually review policy.json at time of creation and whenever changed; it may surface dubious permissions. For example, the policy may show that package purporting to format strings is sneakily using fs.writeFile() for evil!

Do not edit policy.json by hand. Instead, you can revoke permission to fs.writeFile() by creating a policy-override.json file (details below).

Due to the dynamic nature of JavaScript, automatic Policy generation is imperfect. If a package lacks permission to a resource that it should have, you may manually grant it the necessary permissions by editing policy-override.json.

In summary, the Policy files are:

  • policy.json - automatically generated by LavaMoat, reflecting the resources accessed by packages in your dependency tree
  • policy-override.json - manually created by you, to expand or limit access to resources

Policy, By Example

Say we generate a Policy file for an application with the following dependency tree, where app.js is the entry point:

app.js
└─┬ some-package
└─┬ entropoetry
└── bn.js

When generating a Policy from this application, LavaMoat will write the following to ./lavamoat/node/policy.json:

policy.json
{
"resources": {
"some-package": {
"globals": {
"Buffer.from": true
},
"packages": {
"some-package>entropoetry": true
}
},
"some-package>entropoetry": {
"builtin": {
"assert": true,
"buffer.Buffer": true,
"zlib": true
},
"globals": {
"console": true,
"process.exitCode": "write"
},
"packages": {
"some-package>entropoetry>bn.js": true
}
},
"some-package>entropoetry>bn.js": {
"builtin": {
"buffer.Buffer": true
},
"globals": {
"Buffer": true
}
}
}
}

Fields of policy.json

This section is an overview of the fields of policy.json.

resources

resources contains all importable packages in your dependency graph.

The keys of resources are the canonical names of packages as determined by LavaMoat. In order to identify a package, LavaMoat cannot not use the package name as published, but instead generates a unique name by tracing the package’s shortest route through its dependents within the dependency graph. This prevents a malicious package which shares the same name as a published package from hijacking permissions.

packages and builtin

packages contains all packages accessible by the dependency. In this example, some-package has access to entropoetry. This means that some-package can, for example, require('entropoetry').

Likewise, entropoetry has assert in its builtin property. This means entropoetry can require('assert') - a built-in module of Node.js.

globals

globals contains all platform APIs and global variables accessible by the dependency. In this example, entropoetry can access to the global console, but it can write to process.exitCode. A value of true grants read-only access to a global; write access can be granted by setting the value to write.

Since granting write access to globals is potentially dangerous, write access is represented by a string to make it stand out when inspecting the policy file.

native

Not shown in the example above, native is a boolean flag that indicates a package is allowed to load a native Node.js module. Any auto-generated occurrence of native should be reviewed carefully—a native module is a powerful resource!

Limiting Property Access in Powerful Objects

Sometimes a package needs a very specific functionality from a globally-available reference or a builtin. Yet, there’s a meaningful difference between granting a package access to window.location.hash vs. the entire window.location object; likewise fs.readFile vs. the entire fs.

You may have noticed the “dot notation” in keys within globals and builtins—this limits access to a subset of fields within a resource. This is how a policy can effectively limit field access in powerful objects.

In the case of global resources, the "Buffer.from": true in the example Policy means the Buffer global object will exist in the package’s scope, but will only contain the from property and no other fields.

The "buffer.Buffer": true in the builtin property means that a package accessing buffer via const buffer = require('buffer') it will receive an object with only one property: the Buffer constructor.

LavaMoat can often generate precise lists of properties used by the package. If needed, you may restrict these further by creating a policy-override.json file.

Overriding Auto-Generated Policies

The behavior of a Policy at runtime depends on the auto-generated policy.json, and an optional policy-override.json. In this section, we will guide you through working with policy-override.json.

A policy-override.json has the same structure as a policy.json. At runtime, LavaMoat will merge policy-override.json into policy.json, overwriting the latter with the former.

Example: Granting Access to Resources

JavaScript is dynamic enough that certain constructs cannot be inferred statically. Because of this, sometimes permissions may be missing from the auto-generated policy.json file. If such missing permissions are identified, it’s best to double-check they are needed before adding them policy-override.json.

In the following example, we will grant entropoetry access to the global URL class and expand access to the entire buffer builtin (Node.js).

./lavamoat/node/policy-override.json
{
"resources": {
"some-package>entropoetry": {
"globals": {
"URL": true
}
},
"some-package>entropoetry>bn.js": {
"builtin": {
"buffer": true
}
}
}
}

The above policy override will be merged at runtime with the auto-generated policy.json file; in addition to the permissions granted above, entropoetry will retain read access to the the global console object and write access to the global process.exitCode.

Example: Revoking Access to Resources

If LavaMoat’s static analysis believes that a package refers to a certain resource, but your project does not actually use this resource, it’s reasonable to deny the dependency access to this resource. You can achieve that with the following example.

In this example, we’ll:

  • Forbid requiring of assert by entropoetry
  • Deny access to console with the exception of console.log
./lavamoat/node/policy-override.json
{
"resources": {
"some-package>entropoetry": {
"builtin": {
"assert": false
},
"globals": {
"console": false,
"console.log": true
}
}
}
}

The auto-generated policy.json grants entropoetry full access to the global console object. For this reason, we need to use a two-step process:

  1. Revoke access to console using console: false
  2. Explicitly grant access to the console.log function

Policies Are Not Deny-Lists

In other wordfs, explicitly allowing access to an entire item and then attempting to narrow it down by denying specific fields is unsupported. If you want to allow allow access to a.b, you might try this:

Busted Example
"globals": {
"a": true,
"a.b": false
}

It does not work. a.b will remain accessible because a was declared as accessible in its entirety.

To properly deny access to a.b—while allowing access to other fields of a—you’d need an allow-list style policy like this:

Correct Example
"globals": {
"a.c": true,
"a.d": true
}

More specifically, the deepest chain of properties needs to point to an allowed reference and skipping all-but-necessary access on higher levels is possible, so the following will work, and allow a.z and a.b.c—but not a.b.y:

Deep Property Example
"globals": {
"a": true,
"a.b": false,
"a.b.c": true
}

For more examples, see lavamoat-core’s globals.spec.js.

You shouldn’t often need this level of complexity! LavaMoat will not burden you by generating negative entries such as this.