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 treepolicy-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:
When generating a Policy from this application, LavaMoat will write the following to ./lavamoat/node/policy.json
:
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).
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
byentropoetry
- Deny access to
console
with the exception ofconsole.log
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:
- Revoke access to
console
usingconsole: false
- 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:
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:
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
:
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.