diff --git a/doc/api/cli.md b/doc/api/cli.md
index 977a0fd1f50915..78994f428bba3c 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -90,6 +90,13 @@ added: v8.5.0
 
 Enable experimental ES module support and caching modules.
 
+### `--experimental-policy`
+<!-- YAML
+added: TODO
+-->
+
+Use the specified file as a security policy.
+
 ### `--experimental-repl-await`
 <!-- YAML
 added: v10.0.0
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 610060a15106cd..bb903ed0ee8c15 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1380,6 +1380,39 @@ An attempt was made to open an IPC communication channel with a synchronously
 forked Node.js process. See the documentation for the [`child_process`][] module
 for more information.
 
+<a id="ERR_MANIFEST_ASSERT_INTEGRITY"></a>
+### ERR_MANIFEST_ASSERT_INTEGRITY
+
+An attempt was made to load a resource, but the resource did not match the
+integrity defined by the policy manifest. See the documentation for [policy]
+manifests for more information.
+
+<a id="ERR_MANIFEST_INTEGRITY_MISMATCH"></a>
+### ERR_MANIFEST_INTEGRITY_MISMATCH
+
+An attempt was made to load a policy manifest, but the manifest had multiple
+entries for a resource which did not match each other. Update the manifest
+entries to match in order to resolve this error. See the documentation for
+[policy] manifests for more information.
+
+<a id="ERR_MANIFEST_PARSE_POLICY"></a>
+### ERR_MANIFEST_PARSE_POLICY
+
+An attempt was made to load a policy manifest, but the manifest was unable to
+be parsed. See the documentation for [policy] manifests for more information.
+
+<a id="ERR_MANIFEST_TDZ"></a>
+### ERR_MANIFEST_TDZ
+
+An attempt was made to read from a policy manifest, but the manifest
+initialization has not yet taken place. This is likely a bug in Node.js.
+
+<a id="ERR_MANIFEST_UNKNOWN_ONERROR"></a>
+### ERR_MANIFEST_UNKNOWN_ONERROR
+
+A policy manifest was loaded, but had an unknown value for its "onerror"
+behavior. See the documentation for [policy] manifests for more information.
+
 <a id="ERR_MEMORY_ALLOCATION_FAILED"></a>
 ### ERR_MEMORY_ALLOCATION_FAILED
 
@@ -1590,6 +1623,13 @@ An attempt was made to operate on an already closed socket.
 
 A call was made and the UDP subsystem was not running.
 
+<a id="ERR_SRI_PARSE"></a>
+### ERR_SRI_PARSE
+
+A string was provided for a Subresource Integrity check, but was unable to be
+parsed. Check the format of integrity attributes by looking at the
+[Subresource Integrity specification][].
+
 <a id="ERR_STREAM_CANNOT_PIPE"></a>
 ### ERR_STREAM_CANNOT_PIPE
 
@@ -2229,7 +2269,9 @@ such as `process.stdout.on('data')`.
 [domains]: domain.html
 [event emitter-based]: events.html#events_class_eventemitter
 [file descriptors]: https://en.wikipedia.org/wiki/File_descriptor
+[policy]: policy.html
 [stream-based]: stream.html
 [syscall]: http://man7.org/linux/man-pages/man2/syscalls.2.html
+[Subresource Integrity specification]: https://www.w3.org/TR/SRI/#the-integrity-attribute
 [try-catch]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
 [vm]: vm.html
diff --git a/doc/api/index.md b/doc/api/index.md
index 23fe9bfa039ab0..a1bc34e81ffb5e 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -39,6 +39,7 @@
 * [OS](os.html)
 * [Path](path.html)
 * [Performance Hooks](perf_hooks.html)
+* [Policies](policy.html)
 * [Process](process.html)
 * [Punycode](punycode.html)
 * [Query Strings](querystring.html)
diff --git a/doc/api/policy.md b/doc/api/policy.md
new file mode 100644
index 00000000000000..ee8109efd651f0
--- /dev/null
+++ b/doc/api/policy.md
@@ -0,0 +1,104 @@
+# Policies
+
+<!--introduced_in=TODO-->
+<!-- type=misc -->
+
+> Stability: 1 - Experimental
+
+<!-- name=policy -->
+
+Node.js contains experimental support for creating policies on loading code.
+
+Policies are a security feature intended to allow guarantees
+about what code Node.js is able to load. The use of policies assumes
+safe practices for the policy files such as ensuring that policy
+files cannot be overwritten by the Node.js application by using
+file permissions.
+
+A best practice would be to ensure that the policy manifest is read only for
+the running Node.js application, and that the file cannot be changed
+by the running Node.js application in any way. A typical setup would be to
+create the policy file as a different user id than the one running Node.js
+and granting read permissions to the user id running Node.js.
+
+## Enabling
+
+<!-- type=misc -->
+
+The `--experimental-policy` flag can be used to enable features for policies
+when loading modules.
+
+Once this has been set, all modules must conform to a policy manifest file
+passed to the flag:
+
+```sh
+node --experimental-policy=policy.json app.js
+```
+
+The policy manifest will be used to enforce constraints on code loaded by
+Node.js.
+
+## Features
+
+### Error Behavior
+
+When a policy check fails, Node.js by default will throw an error.
+It is possible to change the error behavior to one of a few possibilities
+by defining an "onerror" field in a policy manifest. The following values are
+available to change the behavior:
+
+* `"exit"` - will exit the process immediately.
+    No cleanup code will be allowed to run.
+* `"log"` - will log the error at the site of the failure.
+* `"throw"` (default) - will throw a JS error at the site of the failure.
+
+```json
+{
+  "onerror": "log",
+  "resources": {
+    "./app/checked.js": {
+      "integrity": "sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0"
+    }
+  }
+}
+```
+
+### Integrity Checks
+
+Policy files must use integrity checks with Subresource Integrity strings
+compatible with the browser
+[integrity attribute](https://www.w3.org/TR/SRI/#the-integrity-attribute)
+associated with absolute URLs.
+
+When using `require()` all resources involved in loading are checked for
+integrity if a policy manifest has been specified. If a resource does not match
+the integrity listed in the manifest, an error will be thrown.
+
+An example policy file that would allow loading a file `checked.js`:
+
+```json
+{
+  "resources": {
+    "./app/checked.js": {
+      "integrity": "sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0"
+    }
+  }
+}
+```
+
+Each resource listed in the policy manifest can be of one the following
+formats to determine its location:
+
+1. A [relative url string][] to a resource from the manifest such as `./resource.js`, `../resource.js`, or `/resource.js`.
+2. A complete url string to a resource such as `file:///resource.js`.
+
+When loading resources the entire URL must match including search parameters
+and hash fragment. `./a.js?b` will not be used when attempting to load
+`./a.js` and vice versa.
+
+In order to generate integrity strings, a script such as
+`printf "sha384-$(cat checked.js | openssl dgst -sha384 -binary | base64)"`
+can be used.
+
+
+[relative url string]: https://url.spec.whatwg.org/#relative-url-with-fragment-string
diff --git a/doc/node.1 b/doc/node.1
index 1ea88e21c0a8d0..426efc047becac 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -86,6 +86,9 @@ Requires Node.js to be built with
 .It Fl -experimental-modules
 Enable experimental ES module support and caching modules.
 .
+.It Fl -experimental-policy
+Use the specified file as a security policy.
+.
 .It Fl -experimental-repl-await
 Enable experimental top-level
 .Sy await
diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js
index 14aa8d5d3203b9..044670e79ed357 100644
--- a/lib/internal/bootstrap/node.js
+++ b/lib/internal/bootstrap/node.js
@@ -178,6 +178,28 @@ function startup() {
     mainThreadSetup.setupChildProcessIpcChannel();
   }
 
+  // TODO(joyeecheung): move this down further to get better snapshotting
+  if (getOptionValue('[has_experimental_policy]')) {
+    process.emitWarning('Policies are experimental.',
+                        'ExperimentalWarning');
+    const experimentalPolicy = getOptionValue('--experimental-policy');
+    const { pathToFileURL, URL } = NativeModule.require('url');
+    // URL here as it is slightly different parsing
+    // no bare specifiers for now
+    let manifestURL;
+    if (NativeModule.require('path').isAbsolute(experimentalPolicy)) {
+      manifestURL = new URL(`file:///${experimentalPolicy}`);
+    } else {
+      const cwdURL = pathToFileURL(process.cwd());
+      cwdURL.pathname += '/';
+      manifestURL = new URL(experimentalPolicy, cwdURL);
+    }
+    const fs = NativeModule.require('fs');
+    const src = fs.readFileSync(manifestURL, 'utf8');
+    NativeModule.require('internal/process/policy')
+      .setup(src, manifestURL.href);
+  }
+
   const browserGlobals = !process._noBrowserGlobals;
   if (browserGlobals) {
     setupGlobalTimeouts();
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 700277630054a6..45701c82523981 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -818,6 +818,28 @@ E('ERR_IPC_CHANNEL_CLOSED', 'Channel closed', Error);
 E('ERR_IPC_DISCONNECTED', 'IPC channel is already disconnected', Error);
 E('ERR_IPC_ONE_PIPE', 'Child process can have only one IPC pipe', Error);
 E('ERR_IPC_SYNC_FORK', 'IPC cannot be used with synchronous forks', Error);
+E('ERR_MANIFEST_ASSERT_INTEGRITY',
+  (moduleURL, realIntegrities) => {
+    let msg = `The content of "${
+      moduleURL
+    }" does not match the expected integrity.`;
+    if (realIntegrities.size) {
+      const sri = [...realIntegrities.entries()].map(([alg, dgs]) => {
+        return `${alg}-${dgs}`;
+      }).join(' ');
+      msg += ` Integrities found are: ${sri}`;
+    } else {
+      msg += ' The resource was not found in the policy.';
+    }
+    return msg;
+  }, Error);
+E('ERR_MANIFEST_INTEGRITY_MISMATCH',
+  'Manifest resource %s has multiple entries but integrity lists do not match',
+  SyntaxError);
+E('ERR_MANIFEST_TDZ', 'Manifest initialization has not yet run', Error);
+E('ERR_MANIFEST_UNKNOWN_ONERROR',
+  'Manifest specified unknown error behavior "%s".',
+  SyntaxError);
 E('ERR_METHOD_NOT_IMPLEMENTED', 'The %s method is not implemented', Error);
 E('ERR_MISSING_ARGS',
   (...args) => {
@@ -889,6 +911,9 @@ E('ERR_SOCKET_BUFFER_SIZE',
 E('ERR_SOCKET_CANNOT_SEND', 'Unable to send data', Error);
 E('ERR_SOCKET_CLOSED', 'Socket is closed', Error);
 E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running', Error);
+E('ERR_SRI_PARSE',
+  'Subresource Integrity string %s had an unexpected at %d',
+  SyntaxError);
 E('ERR_STREAM_CANNOT_PIPE', 'Cannot pipe, not readable', Error);
 E('ERR_STREAM_DESTROYED', 'Cannot call %s after a stream was destroyed', Error);
 E('ERR_STREAM_NULL_VALUES', 'May not write null values to stream', TypeError);
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index bf6a9d4029c2a5..31897e583d4191 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -22,8 +22,8 @@
 'use strict';
 
 const { NativeModule } = require('internal/bootstrap/loaders');
-const util = require('util');
 const { pathToFileURL } = require('internal/url');
+const util = require('util');
 const vm = require('vm');
 const assert = require('assert').ok;
 const fs = require('fs');
@@ -45,6 +45,9 @@ const { getOptionValue } = require('internal/options');
 const preserveSymlinks = getOptionValue('--preserve-symlinks');
 const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
 const experimentalModules = getOptionValue('--experimental-modules');
+const manifest = getOptionValue('[has_experimental_policy]') ?
+  require('internal/process/policy').manifest :
+  null;
 
 const {
   ERR_INVALID_ARG_VALUE,
@@ -164,6 +167,11 @@ function readPackage(requestPath) {
     return false;
   }
 
+  if (manifest) {
+    const jsonURL = pathToFileURL(jsonPath);
+    manifest.assertIntegrity(jsonURL, json);
+  }
+
   try {
     return packageMainCache[requestPath] = JSON.parse(json).main;
   } catch (e) {
@@ -672,6 +680,10 @@ function normalizeReferrerURL(referrer) {
 // the file.
 // Returns exception, if any.
 Module.prototype._compile = function(content, filename) {
+  if (manifest) {
+    const moduleURL = pathToFileURL(filename);
+    manifest.assertIntegrity(moduleURL, content);
+  }
 
   content = stripShebang(content);
 
@@ -711,11 +723,14 @@ Module.prototype._compile = function(content, filename) {
   var depth = requireDepth;
   if (depth === 0) stat.cache = new Map();
   var result;
+  var exports = this.exports;
+  var thisValue = exports;
+  var module = this;
   if (inspectorWrapper) {
-    result = inspectorWrapper(compiledWrapper, this.exports, this.exports,
-                              require, this, filename, dirname);
+    result = inspectorWrapper(compiledWrapper, thisValue, exports,
+                              require, module, filename, dirname);
   } else {
-    result = compiledWrapper.call(this.exports, this.exports, require, this,
+    result = compiledWrapper.call(thisValue, exports, require, module,
                                   filename, dirname);
   }
   if (depth === 0) stat.cache = null;
@@ -732,7 +747,13 @@ Module._extensions['.js'] = function(module, filename) {
 
 // Native extension for .json
 Module._extensions['.json'] = function(module, filename) {
-  var content = fs.readFileSync(filename, 'utf8');
+  const content = fs.readFileSync(filename, 'utf8');
+
+  if (manifest) {
+    const moduleURL = pathToFileURL(filename);
+    manifest.assertIntegrity(moduleURL, content);
+  }
+
   try {
     module.exports = JSON.parse(stripBOM(content));
   } catch (err) {
@@ -744,6 +765,12 @@ Module._extensions['.json'] = function(module, filename) {
 
 // Native extension for .node
 Module._extensions['.node'] = function(module, filename) {
+  if (manifest) {
+    const content = fs.readFileSync(filename);
+    const moduleURL = pathToFileURL(filename);
+    manifest.assertIntegrity(moduleURL, content);
+  }
+  // be aware this doesn't use `content`
   return process.dlopen(module, path.toNamespacedPath(filename));
 };
 
diff --git a/lib/internal/policy/manifest.js b/lib/internal/policy/manifest.js
new file mode 100644
index 00000000000000..272abf2457ddc3
--- /dev/null
+++ b/lib/internal/policy/manifest.js
@@ -0,0 +1,130 @@
+'use strict';
+const {
+  ERR_MANIFEST_ASSERT_INTEGRITY,
+  ERR_MANIFEST_INTEGRITY_MISMATCH,
+  ERR_MANIFEST_UNKNOWN_ONERROR,
+} = require('internal/errors').codes;
+const debug = require('util').debuglog('policy');
+const SRI = require('internal/policy/sri');
+const { SafeWeakMap } = require('internal/safe_globals');
+const crypto = require('crypto');
+const { Buffer } = require('buffer');
+const { URL } = require('url');
+const { createHash, timingSafeEqual } = crypto;
+const HashUpdate = Function.call.bind(crypto.Hash.prototype.update);
+const HashDigest = Function.call.bind(crypto.Hash.prototype.digest);
+const BufferEquals = Function.call.bind(Buffer.prototype.equals);
+const BufferToString = Function.call.bind(Buffer.prototype.toString);
+const RegExpTest = Function.call.bind(RegExp.prototype.test);
+const { entries } = Object;
+const kIntegrities = new SafeWeakMap();
+const kReactions = new SafeWeakMap();
+const kRelativeURLStringPattern = /^\.{0,2}\//;
+const { shouldAbortOnUncaughtException } = internalBinding('config');
+const { abort, exit, _rawDebug } = process;
+function REACTION_THROW(error) {
+  throw error;
+}
+function REACTION_EXIT(error) {
+  REACTION_LOG(error);
+  if (shouldAbortOnUncaughtException) {
+    abort();
+  }
+  exit(1);
+}
+function REACTION_LOG(error) {
+  _rawDebug(error.stack);
+}
+class Manifest {
+  constructor(obj, manifestURL) {
+    const integrities = {
+      __proto__: null,
+    };
+    const reactions = {
+      __proto__: null,
+      integrity: REACTION_THROW,
+    };
+    if (obj.onerror) {
+      const behavior = obj.onerror;
+      if (behavior === 'throw') {
+      } else if (behavior === 'exit') {
+        reactions.integrity = REACTION_EXIT;
+      } else if (behavior === 'log') {
+        reactions.integrity = REACTION_LOG;
+      } else {
+        throw new ERR_MANIFEST_UNKNOWN_ONERROR(behavior);
+      }
+    }
+    kReactions.set(this, Object.freeze(reactions));
+    const manifestEntries = entries(obj.resources);
+    for (var i = 0; i < manifestEntries.length; i++) {
+      let url = manifestEntries[i][0];
+      const integrity = manifestEntries[i][1].integrity;
+      if (integrity != null) {
+        debug(`Manifest contains integrity for url ${url}`);
+        if (RegExpTest(kRelativeURLStringPattern, url)) {
+          url = new URL(url, manifestURL).href;
+        }
+        const sri = Object.freeze(SRI.parse(integrity));
+        if (url in integrities) {
+          const old = integrities[url];
+          let mismatch = false;
+          if (old.length !== sri.length) {
+            mismatch = true;
+          } else {
+            compare:
+            for (var sriI = 0; sriI < sri.length; sriI++) {
+              for (var oldI = 0; oldI < old.length; oldI++) {
+                if (sri[sriI].algorithm === old[oldI].algorithm &&
+                  BufferEquals(sri[sriI].value, old[oldI].value) &&
+                  sri[sriI].options === old[oldI].options) {
+                  continue compare;
+                }
+              }
+              mismatch = true;
+              break compare;
+            }
+          }
+          if (mismatch) {
+            throw new ERR_MANIFEST_INTEGRITY_MISMATCH(url);
+          }
+        }
+        integrities[url] = sri;
+      }
+    }
+    Object.freeze(integrities);
+    kIntegrities.set(this, integrities);
+    Object.freeze(this);
+  }
+  assertIntegrity(url, content) {
+    debug(`Checking integrity of ${url}`);
+    const integrities = kIntegrities.get(this);
+    const realIntegrities = new Map();
+    if (integrities && url in integrities) {
+      const integrityEntries = integrities[url];
+      // Avoid clobbered Symbol.iterator
+      for (var i = 0; i < integrityEntries.length; i++) {
+        const {
+          algorithm,
+          value: expected
+        } = integrityEntries[i];
+        const hash = createHash(algorithm);
+        HashUpdate(hash, content);
+        const digest = HashDigest(hash);
+        if (digest.length === expected.length &&
+          timingSafeEqual(digest, expected)) {
+          return true;
+        }
+        realIntegrities.set(algorithm, BufferToString(digest, 'base64'));
+      }
+    }
+    const error = new ERR_MANIFEST_ASSERT_INTEGRITY(url, realIntegrities);
+    kReactions.get(this).integrity(error);
+  }
+}
+// Lock everything down to avoid problems even if reference is leaked somehow
+Object.setPrototypeOf(Manifest, null);
+Object.setPrototypeOf(Manifest.prototype, null);
+Object.freeze(Manifest);
+Object.freeze(Manifest.prototype);
+module.exports = Object.freeze({ Manifest });
diff --git a/lib/internal/policy/sri.js b/lib/internal/policy/sri.js
new file mode 100644
index 00000000000000..fff4e066b17451
--- /dev/null
+++ b/lib/internal/policy/sri.js
@@ -0,0 +1,68 @@
+'use strict';
+// Value of https://w3c.github.io/webappsec-subresource-integrity/#the-integrity-attribute
+
+// Returns [{algorithm, value (in base64 string), options,}]
+const {
+  ERR_SRI_PARSE
+} = require('internal/errors').codes;
+const kWSP = '[\\x20\\x09]';
+const kVCHAR = '[\\x21-\\x7E]';
+const kHASH_ALGO = 'sha256|sha384|sha512';
+// Base64
+const kHASH_VALUE = '[A-Za-z0-9+/]+[=]{0,2}';
+const kHASH_EXPRESSION = `(${kHASH_ALGO})-(${kHASH_VALUE})`;
+const kOPTION_EXPRESSION = `(${kVCHAR}*)`;
+const kHASH_WITH_OPTIONS = `${kHASH_EXPRESSION}(?:[?](${kOPTION_EXPRESSION}))?`;
+const kSRIPattern = new RegExp(`(${kWSP}*)(?:${kHASH_WITH_OPTIONS})`, 'g');
+const { freeze } = Object;
+Object.seal(kSRIPattern);
+const kAllWSP = new RegExp(`^${kWSP}*$`);
+Object.seal(kAllWSP);
+const RegExpExec = Function.call.bind(RegExp.prototype.exec);
+const RegExpTest = Function.call.bind(RegExp.prototype.test);
+const StringSlice = Function.call.bind(String.prototype.slice);
+const {
+  Buffer: {
+    from: BufferFrom
+  }
+} = require('buffer');
+const { defineProperty } = Object;
+const parse = (str) => {
+  kSRIPattern.lastIndex = 0;
+  let prevIndex = 0;
+  let match = RegExpExec(kSRIPattern, str);
+  const entries = [];
+  while (match) {
+    if (match.index !== prevIndex) {
+      throw new ERR_SRI_PARSE(str, prevIndex);
+    }
+    if (entries.length > 0) {
+      if (match[1] === '') {
+        throw new ERR_SRI_PARSE(str, prevIndex);
+      }
+    }
+    // Avoid setters being fired
+    defineProperty(entries, entries.length, {
+      enumerable: true,
+      configurable: true,
+      value: freeze({
+        __proto__: null,
+        algorithm: match[2],
+        value: BufferFrom(match[3], 'base64'),
+        options: match[4] === undefined ? null : match[4],
+      })
+    });
+    prevIndex = prevIndex + match[0].length;
+    match = RegExpExec(kSRIPattern, str);
+  }
+  if (prevIndex !== str.length) {
+    if (!RegExpTest(kAllWSP, StringSlice(str, prevIndex))) {
+      throw new ERR_SRI_PARSE(str, prevIndex);
+    }
+  }
+  return entries;
+};
+
+module.exports = {
+  parse,
+};
diff --git a/lib/internal/process/policy.js b/lib/internal/process/policy.js
new file mode 100644
index 00000000000000..f5ca4eeb07a3e0
--- /dev/null
+++ b/lib/internal/process/policy.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const {
+  ERR_MANIFEST_TDZ,
+} = require('internal/errors').codes;
+const { Manifest } = require('internal/policy/manifest');
+let manifest;
+module.exports = Object.freeze({
+  __proto__: null,
+  setup(src, url) {
+    if (src === null) {
+      manifest = null;
+      return;
+    }
+    const json = JSON.parse(src, (_, o) => {
+      if (o && typeof o === 'object') {
+        Reflect.setPrototypeOf(o, null);
+        Object.freeze(o);
+      }
+      return o;
+    });
+    manifest = new Manifest(json, url);
+  },
+  get manifest() {
+    if (typeof manifest === 'undefined') {
+      throw new ERR_MANIFEST_TDZ();
+    }
+    return manifest;
+  },
+  assertIntegrity(moduleURL, content) {
+    this.manifest.matchesIntegrity(moduleURL, content);
+  }
+});
diff --git a/lib/internal/safe_globals.js b/lib/internal/safe_globals.js
index 31de4137f0ad53..109409d535495d 100644
--- a/lib/internal/safe_globals.js
+++ b/lib/internal/safe_globals.js
@@ -20,5 +20,6 @@ const makeSafe = (unsafe, safe) => {
 };
 
 exports.SafeMap = makeSafe(Map, class SafeMap extends Map {});
+exports.SafeWeakMap = makeSafe(WeakMap, class SafeWeakMap extends WeakMap {});
 exports.SafeSet = makeSafe(Set, class SafeSet extends Set {});
 exports.SafePromise = makeSafe(Promise, class SafePromise extends Promise {});
diff --git a/node.gyp b/node.gyp
index c7bc857b1530f1..1289dc3fba5517 100644
--- a/node.gyp
+++ b/node.gyp
@@ -140,6 +140,8 @@
       'lib/internal/safe_globals.js',
       'lib/internal/net.js',
       'lib/internal/options.js',
+      'lib/internal/policy/sri.js',
+      'lib/internal/policy/manifest.js',
       'lib/internal/print_help.js',
       'lib/internal/priority_queue.js',
       'lib/internal/process/esm_loader.js',
@@ -147,6 +149,7 @@
       'lib/internal/process/main_thread_only.js',
       'lib/internal/process/next_tick.js',
       'lib/internal/process/per_thread.js',
+      'lib/internal/process/policy.js',
       'lib/internal/process/promises.js',
       'lib/internal/process/stdio.js',
       'lib/internal/process/warning.js',
diff --git a/src/node_options.cc b/src/node_options.cc
index b225acb1e0b485..1f8d1db7ecc1cc 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -101,6 +101,15 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
             "experimental ES Module support and caching modules",
             &EnvironmentOptions::experimental_modules,
             kAllowedInEnvironment);
+  AddOption("[has_experimental_policy]",
+            "",
+            &EnvironmentOptions::has_experimental_policy);
+  AddOption("--experimental-policy",
+            "use the specified file as a "
+            "security policy",
+            &EnvironmentOptions::experimental_policy,
+            kAllowedInEnvironment);
+  Implies("--experimental-policy", "[has_experimental_policy]");
   AddOption("--experimental-repl-await",
             "experimental await keyword support in REPL",
             &EnvironmentOptions::experimental_repl_await,
diff --git a/src/node_options.h b/src/node_options.h
index beecc4ca84d72f..ead69eb61a7ed7 100644
--- a/src/node_options.h
+++ b/src/node_options.h
@@ -94,6 +94,8 @@ class EnvironmentOptions : public Options {
  public:
   bool abort_on_uncaught_exception = false;
   bool experimental_modules = false;
+  std::string experimental_policy;
+  bool has_experimental_policy;
   bool experimental_repl_await = false;
   bool experimental_vm_modules = false;
   bool expose_internals = false;
diff --git a/test/parallel/test-policy-integrity.js b/test/parallel/test-policy-integrity.js
new file mode 100644
index 00000000000000..5c1ea4fc4eed64
--- /dev/null
+++ b/test/parallel/test-policy-integrity.js
@@ -0,0 +1,297 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+  common.skip('missing crypto');
+
+const tmpdir = require('../common/tmpdir');
+const assert = require('assert');
+const { spawnSync } = require('child_process');
+const crypto = require('crypto');
+const fs = require('fs');
+const path = require('path');
+const { pathToFileURL } = require('url');
+
+tmpdir.refresh();
+
+function hash(algo, body) {
+  const h = crypto.createHash(algo);
+  h.update(body);
+  return h.digest('base64');
+}
+
+const policyFilepath = path.join(tmpdir.path, 'policy');
+
+const packageFilepath = path.join(tmpdir.path, 'package.json');
+const packageURL = pathToFileURL(packageFilepath);
+const packageBody = '{"main": "dep.js"}';
+const policyToPackageRelativeURLString = `./${
+  path.relative(path.dirname(policyFilepath), packageFilepath)
+}`;
+
+const parentFilepath = path.join(tmpdir.path, 'parent.js');
+const parentURL = pathToFileURL(parentFilepath);
+const parentBody = 'require(\'./dep.js\')';
+
+const depFilepath = path.join(tmpdir.path, 'dep.js');
+const depURL = pathToFileURL(depFilepath);
+const depBody = '';
+const policyToDepRelativeURLString = `./${
+  path.relative(path.dirname(policyFilepath), depFilepath)
+}`;
+
+fs.writeFileSync(parentFilepath, parentBody);
+fs.writeFileSync(depFilepath, depBody);
+
+const tmpdirURL = pathToFileURL(tmpdir.path);
+if (!tmpdirURL.pathname.endsWith('/')) {
+  tmpdirURL.pathname += '/';
+}
+function test({
+  shouldFail = false,
+  entry,
+  onerror,
+  resources = {}
+}) {
+  const manifest = {
+    onerror,
+    resources: {}
+  };
+  for (const [url, { body, match }] of Object.entries(resources)) {
+    manifest.resources[url] = {
+      integrity: `sha256-${hash('sha256', match ? body : body + '\n')}`
+    };
+    fs.writeFileSync(new URL(url, tmpdirURL.href), body);
+  }
+  fs.writeFileSync(policyFilepath, JSON.stringify(manifest, null, 2));
+  const { status } = spawnSync(process.execPath, [
+    '--experimental-policy', policyFilepath, entry
+  ]);
+  if (shouldFail) {
+    assert.notStrictEqual(status, 0);
+  } else {
+    assert.strictEqual(status, 0);
+  }
+}
+
+const { status } = spawnSync(process.execPath, [
+  '--experimental-policy', policyFilepath,
+  '--experimental-policy', policyFilepath
+], {
+  stdio: 'pipe'
+});
+assert.notStrictEqual(status, 0, 'Should not allow multiple policies');
+
+test({
+  shouldFail: true,
+  entry: parentFilepath,
+  resources: {
+  }
+});
+test({
+  shouldFail: false,
+  entry: parentFilepath,
+  onerror: 'log',
+});
+test({
+  shouldFail: true,
+  entry: parentFilepath,
+  onerror: 'exit',
+});
+test({
+  shouldFail: true,
+  entry: parentFilepath,
+  onerror: 'throw',
+});
+test({
+  shouldFail: true,
+  entry: parentFilepath,
+  onerror: 'unknown-onerror-value',
+});
+test({
+  shouldFail: true,
+  entry: path.dirname(packageFilepath),
+  resources: {
+  }
+});
+test({
+  shouldFail: true,
+  entry: path.dirname(packageFilepath),
+  resources: {
+    [depURL]: {
+      body: depBody,
+      match: true,
+    }
+  }
+});
+test({
+  shouldFail: false,
+  entry: path.dirname(packageFilepath),
+  onerror: 'log',
+  resources: {
+    [packageURL]: {
+      body: packageBody,
+      match: false,
+    },
+    [depURL]: {
+      body: depBody,
+      match: true,
+    }
+  }
+});
+test({
+  shouldFail: true,
+  entry: path.dirname(packageFilepath),
+  resources: {
+    [packageURL]: {
+      body: packageBody,
+      match: false,
+    },
+    [depURL]: {
+      body: depBody,
+      match: true,
+    }
+  }
+});
+test({
+  shouldFail: true,
+  entry: path.dirname(packageFilepath),
+  resources: {
+    [packageURL]: {
+      body: packageBody,
+      match: true,
+    },
+    [depURL]: {
+      body: depBody,
+      match: false,
+    }
+  }
+});
+test({
+  shouldFail: false,
+  entry: path.dirname(packageFilepath),
+  resources: {
+    [packageURL]: {
+      body: packageBody,
+      match: true,
+    },
+    [depURL]: {
+      body: depBody,
+      match: true,
+    }
+  }
+});
+test({
+  shouldFail: false,
+  entry: parentFilepath,
+  resources: {
+    [parentURL]: {
+      body: parentBody,
+      match: true,
+    },
+    [depURL]: {
+      body: depBody,
+      match: true,
+    }
+  }
+});
+test({
+  shouldFail: true,
+  entry: parentFilepath,
+  resources: {
+    [parentURL]: {
+      body: parentBody,
+      match: false,
+    },
+    [depURL]: {
+      body: depBody,
+      match: true,
+    }
+  }
+});
+test({
+  shouldFail: true,
+  entry: parentFilepath,
+  resources: {
+    [parentURL]: {
+      body: parentBody,
+      match: true,
+    },
+    [depURL]: {
+      body: depBody,
+      match: false,
+    }
+  }
+});
+test({
+  shouldFail: true,
+  entry: parentFilepath,
+  resources: {
+    [parentURL]: {
+      body: parentBody,
+      match: true,
+    }
+  }
+});
+test({
+  shouldFail: false,
+  entry: depFilepath,
+  resources: {
+    [depURL]: {
+      body: depBody,
+      match: true,
+    }
+  }
+});
+test({
+  shouldFail: false,
+  entry: depFilepath,
+  resources: {
+    [policyToDepRelativeURLString]: {
+      body: depBody,
+      match: true,
+    }
+  }
+});
+test({
+  shouldFail: true,
+  entry: depFilepath,
+  resources: {
+    [policyToDepRelativeURLString]: {
+      body: depBody,
+      match: false,
+    }
+  }
+});
+test({
+  shouldFail: false,
+  entry: depFilepath,
+  resources: {
+    [policyToDepRelativeURLString]: {
+      body: depBody,
+      match: true,
+    },
+    [depURL]: {
+      body: depBody,
+      match: true,
+    }
+  }
+});
+test({
+  shouldFail: true,
+  entry: depFilepath,
+  resources: {
+    [policyToPackageRelativeURLString]: {
+      body: packageBody,
+      match: true,
+    },
+    [packageURL]: {
+      body: packageBody,
+      match: true,
+    },
+    [depURL]: {
+      body: depBody,
+      match: false,
+    }
+  }
+});