diff --git a/doc/api/code_integrity.md b/doc/api/code_integrity.md
new file mode 100644
index 00000000000000..dd2e4e30d1a202
--- /dev/null
+++ b/doc/api/code_integrity.md
@@ -0,0 +1,131 @@
+# Code Integrity
+
+
+
+> Stability: 1.1 - Active development
+
+Code integrity refers to the assurance that software code has not been
+altered or tampered with in any unauthorized way. It ensures that
+the code running on a system is exactly what was intended by the developers.
+
+Code integrity in Node.js integrates with platform features for code integrity
+policy enforcement. See platform speficic sections below for more information.
+
+The Node.js threat model considers the code that the runtime executes to be
+trusted. As such, this feature is an additional safety belt, not a strict
+security boundary.
+
+If you find a potential security vulnerability, please refer to our
+[Security Policy][].
+
+## Code Integrity on Windows
+
+Code integrity is an opt-in feature that leverages Window Defender Application Control
+to verify the code executing conforms to system policy and has not been modified since
+signing time.
+
+There are three audiences that are involved when using Node.js in an
+environment enforcing code integrity: the application developers,
+those administrating the system enforcing code integrity, and
+the end user. The following sections describe how each audience
+can interact with code integrity enforcement.
+
+### Windows Code Integrity and Application Developers
+
+Windows Defender Application Control uses digital signatures to verify
+a file's integrity. Application developers are responsible for generating and
+distributing the signature information for their Node.js application.
+Application developers are also expected to design their application
+in robust ways to avoid unintended code execution. This includes
+use of `eval` and loading modules outside of standard methods.
+
+Signature information for files which Node.js is intended to execute
+can be stored in a catalog file. Application developers can generate
+a Windows catalog file to store the hash of all files Node.js
+is expected to execute.
+
+A catalog can be generated using the `New-FileCatalog` Powershell
+cmdlet. For example
+
+```powershell
+New-FileCatalog -Version 2 -CatalogFilePath MyApplicationCatalog.cat -Path \my\application\path\
+```
+
+The `Path` argument should point to the root folder containing your application's code. If
+your application's code is fully contained in one file, `Path` can point to that single file.
+
+Be sure that the catalog is generated using the final version of the files that you intend to ship
+(i.e. after minifying).
+
+The application developer should then sign the generated catalog with their Code Signing certificate
+to ensure the catalog is not tampered with between distribution and execution.
+
+This can be done with the [Set-AuthenticodeSignature commandlet](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-authenticodesignature).
+
+### Windows Code Integrity and System Administrators
+
+This section is intended for system administrators who want to enable Node.js
+code integrity features in their environments.
+
+This section assumes familiarity with managing WDAC polcies.
+Official documentation for WDAC can be found [here](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/).
+
+Code integrity enforcement on Windows has two toggleable settings:
+`EnforceCodeIntegrity` and `DisableInteractiveMode`. These settings are configured
+by WDAC policy.
+
+`EnforceCodeIntegrity` causes Node.js to call WldpCanExecuteFile whenever a module is loaded using `require`.
+WldpCanExecuteFile verifies that the file's integrity has not been tampered with from signing time.
+The system administrator should sign and install the application's file catalog where the application
+is running, per WDAC guidance.
+
+`DisableInteractiveMode` prevents Node.js from being run in interactive mode, and also disables the `-e` and `--eval`
+command line options.
+
+#### Enabling Code Integrity Enforcement
+
+On newer Windows versions (22H2+), the preferred method of configuring application settings is done using
+`AppSettings` in your WDAC Policy.
+
+```text
+
+
+
+ True
+
+
+ True
+
+
+
+```
+
+On older Windows versions, use the `Settings` section of your WDAC Policy.
+
+```text
+
+
+
+ true
+
+
+
+
+ true
+
+
+
+```
+
+## Code Integrity on Linux
+
+Code integrity on Linux is not yet implemented. Plans for implementation will
+be made once the necessary APIs on Linux have been upstreamed. More information
+can be found here:
+
+## Code Integrity on MacOS
+
+Code integrity on MacOS is not yet implemented. Currently, there is no
+timeline for implementation.
+
+[Security Policy]: https://github.com/nodejs/node/blob/main/SECURITY.md
diff --git a/doc/api/errors.md b/doc/api/errors.md
index f721ed221eab21..081cadcba4ea18 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -789,6 +789,22 @@ changes:
There was an attempt to use a `MessagePort` instance in a closed
state, usually after `.close()` has been called.
+
+
+### `ERR_CODE_INTEGRITY_BLOCKED`
+
+> Stability: 1.1 - Active development
+
+Feature has been disabled due to OS Code Integrity policy.
+
+
+
+### `ERR_CODE_INTEGRITY_VIOLATION`
+
+> Stability: 1.1 - Active development
+
+JavaScript code intended to be executed was rejected by system code integrity policy.
+
### `ERR_CONSOLE_WRITABLE_STREAM`
diff --git a/doc/api/wdac-manifest.xml b/doc/api/wdac-manifest.xml
new file mode 100644
index 00000000000000..064aed76720d09
--- /dev/null
+++ b/doc/api/wdac-manifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/lib/internal/code_integrity.js b/lib/internal/code_integrity.js
new file mode 100644
index 00000000000000..ec14b534e4251b
--- /dev/null
+++ b/lib/internal/code_integrity.js
@@ -0,0 +1,69 @@
+// Code integrity is a security feature which prevents unsigned
+// code from executing. More information can be found in the docs
+// doc/api/code_integrity.md
+
+'use strict';
+
+const { emitWarning } = require('internal/process/warning');
+const { isWindows } = require('internal/util');
+
+let isCodeIntegrityEnforced;
+let alreadyQueriedSystemCodeEnforcmentMode = false;
+
+// Binding stub for non-Windows platforms
+let binding = {
+ isFileTrustedBySystemCodeIntegrityPolicy: () => true,
+ isInteractiveModeDisabledInternal: () => false,
+ isSystemEnforcingCodeIntegrity: () => false,
+};
+// Load the actual binding if on Windows
+if (isWindows) {
+ binding = internalBinding('code_integrity');
+}
+
+const {
+ isFileTrustedBySystemCodeIntegrityPolicy,
+ isInteractiveModeDisabledInternal,
+ isSystemEnforcingCodeIntegrity,
+} = binding;
+
+function isAllowedToExecuteFile(filepath) {
+ // At the moment code integrity is only implemented on Windows
+ if (!isWindows) {
+ return true;
+ }
+
+ if (!alreadyQueriedSystemCodeEnforcmentMode) {
+ isCodeIntegrityEnforced = isSystemEnforcingCodeIntegrity();
+
+ if (isCodeIntegrityEnforced) {
+ emitWarning(
+ 'Code integrity is being enforced by system policy.' +
+ '\nCode integrity is an experimental feature.' +
+ ' See docs for more info.',
+ 'ExperimentalWarning');
+ }
+
+ alreadyQueriedSystemCodeEnforcmentMode = true;
+ }
+
+ if (!isCodeIntegrityEnforced) {
+ return true;
+ }
+
+ return isFileTrustedBySystemCodeIntegrityPolicy(filepath);
+}
+
+function isInteractiveModeDisabled() {
+ if (!isWindows) {
+ return false;
+ }
+ return isInteractiveModeDisabledInternal();
+}
+
+module.exports = {
+ isAllowedToExecuteFile,
+ isFileTrustedBySystemCodeIntegrityPolicy,
+ isInteractiveModeDisabled,
+ isSystemEnforcingCodeIntegrity,
+};
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 47d4fbf677e5eb..8bb008a8115f49 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -1144,6 +1144,10 @@ E('ERR_CHILD_PROCESS_IPC_REQUIRED',
Error);
E('ERR_CHILD_PROCESS_STDIO_MAXBUFFER', '%s maxBuffer length exceeded',
RangeError);
+E('ERR_CODE_INTEGRITY_BLOCKED',
+ 'The feature "%s" is blocked by OS Code Integrity policy', Error);
+E('ERR_CODE_INTEGRITY_VIOLATION',
+ 'The file %s did not pass OS Code Integrity validation', Error);
E('ERR_CONSOLE_WRITABLE_STREAM',
'Console expects a writable stream instance for %s', TypeError);
E('ERR_CONTEXT_NOT_INITIALIZED', 'context used is not initialized', Error);
diff --git a/lib/internal/main/eval_string.js b/lib/internal/main/eval_string.js
index ee402f50fbdd2b..cd8f6ba65b8b28 100644
--- a/lib/internal/main/eval_string.js
+++ b/lib/internal/main/eval_string.js
@@ -23,6 +23,17 @@ const {
const { addBuiltinLibsToObject } = require('internal/modules/helpers');
const { getOptionValue } = require('internal/options');
+const {
+ codes: {
+ ERR_CODE_INTEGRITY_BLOCKED,
+ },
+} = require('internal/errors');
+
+const ci = require('internal/code_integrity');
+if (ci.isInteractiveModeDisabled()) {
+ throw new ERR_CODE_INTEGRITY_BLOCKED('"eval"');
+}
+
prepareMainThreadExecution();
addBuiltinLibsToObject(globalThis, '');
markBootstrapComplete();
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index 39eea53e429681..61ee67b1fc8336 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -181,6 +181,7 @@ const {
const {
codes: {
+ ERR_CODE_INTEGRITY_VIOLATION,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_MODULE_SPECIFIER,
@@ -216,6 +217,8 @@ const onRequire = getLazy(() => tracingChannel('module.require'));
const relativeResolveCache = { __proto__: null };
+const ci = require('internal/code_integrity');
+
let requireDepth = 0;
let isPreloading = false;
let statCache = null;
@@ -1217,6 +1220,11 @@ Module._load = function(request, parent, isMain) {
// For backwards compatibility, if the request itself starts with node:, load it before checking
// Module._cache. Otherwise, load it after the check.
if (StringPrototypeStartsWith(request, 'node:')) {
+
+ const isAllowedToExecute = ci.isAllowedToExecuteFile(filename);
+ if (!isAllowedToExecute) {
+ throw new ERR_CODE_INTEGRITY_VIOLATION(filename);
+ }
const result = loadBuiltinWithHooks(filename, url, format);
if (result) {
return result;
@@ -1247,6 +1255,11 @@ Module._load = function(request, parent, isMain) {
cachedModule[kModuleCircularVisited] = true;
}
+ const isAllowedToExecute = ci.isAllowedToExecuteFile(filename);
+ if (!isAllowedToExecute) {
+ throw new ERR_CODE_INTEGRITY_VIOLATION(filename);
+ }
+
if (BuiltinModule.canBeRequiredWithoutScheme(filename)) {
const result = loadBuiltinWithHooks(filename, url, format);
if (result) {
@@ -1878,6 +1891,7 @@ function getRequireESMError(mod, pkg, content, filename) {
* @param {string} filename The file path of the module
*/
Module._extensions['.js'] = function(module, filename) {
+
let format, pkg;
if (StringPrototypeEndsWith(filename, '.cjs')) {
format = 'commonjs';
@@ -1897,6 +1911,7 @@ Module._extensions['.js'] = function(module, filename) {
throw err;
}
module._compile(source, filename, loadedFormat);
+
};
/**
@@ -1905,6 +1920,7 @@ Module._extensions['.js'] = function(module, filename) {
* @param {string} filename The file path of the module
*/
Module._extensions['.json'] = function(module, filename) {
+
const { source: content } = loadSource(module, filename, 'json');
try {
@@ -1921,6 +1937,7 @@ Module._extensions['.json'] = function(module, filename) {
* @param {string} filename The file path of the module
*/
Module._extensions['.node'] = function(module, filename) {
+
// Be aware this doesn't use `content`
return process.dlopen(module, path.toNamespacedPath(filename));
};
diff --git a/node.gyp b/node.gyp
index ba1f50d7541cca..93f18df2158d75 100644
--- a/node.gyp
+++ b/node.gyp
@@ -447,6 +447,14 @@
}, {
'use_openssl_def%': 0,
}],
+ # Only compile node_code_integrity on Windows
+ [ 'OS=="win"', {
+ 'node_sources': [
+ '<(node_sources)',
+ 'src/node_code_integrity.cc',
+ 'src/node_code_integrity.h',
+ ],
+ }],
],
},
diff --git a/src/node_binding.cc b/src/node_binding.cc
index e93f3312cc6425..da796f77058772 100644
--- a/src/node_binding.cc
+++ b/src/node_binding.cc
@@ -99,13 +99,21 @@
V(worker) \
V(zlib)
+#define NODE_BUILTIN_OS_SPECIFIC_BINDINGS(V)
+
+#ifdef _WIN32
+#define NODE_BUILTIN_OS_SPECIFIC_BINDINGS(V) \
+ V(code_integrity)
+#endif
+
#define NODE_BUILTIN_BINDINGS(V) \
NODE_BUILTIN_STANDARD_BINDINGS(V) \
NODE_BUILTIN_OPENSSL_BINDINGS(V) \
NODE_BUILTIN_ICU_BINDINGS(V) \
NODE_BUILTIN_PROFILER_BINDINGS(V) \
NODE_BUILTIN_DEBUG_BINDINGS(V) \
- NODE_BUILTIN_QUIC_BINDINGS(V)
+ NODE_BUILTIN_QUIC_BINDINGS(V) \
+ NODE_BUILTIN_OS_SPECIFIC_BINDINGS(V)
// This is used to load built-in bindings. Instead of using
// __attribute__((constructor)), we call the _register_
diff --git a/src/node_code_integrity.cc b/src/node_code_integrity.cc
new file mode 100644
index 00000000000000..d1215ae917be00
--- /dev/null
+++ b/src/node_code_integrity.cc
@@ -0,0 +1,263 @@
+#include "node_code_integrity.h"
+#include "v8.h"
+#include "node.h"
+#include "env-inl.h"
+#include "node_external_reference.h"
+
+namespace node {
+
+using v8::Boolean;
+using v8::Context;
+using v8::FunctionCallbackInfo;
+using v8::Local;
+using v8::Object;
+using v8::Value;
+
+namespace per_process {
+ bool isWldpInitialized = false;
+ pfnWldpCanExecuteFile WldpCanExecuteFile;
+ pfnWldpGetApplicationSettingBoolean WldpGetApplicationSettingBoolean;
+ pfnWldpQuerySecurityPolicy WldpQuerySecurityPolicy;
+}
+
+namespace code_integrity {
+
+static PCWSTR NODEJS = L"Node.js";
+static PCWSTR ENFORCE_CODE_INTEGRITY_SETTING_NAME = L"EnforceCodeIntegrity";
+static PCWSTR DISABLE_INTERPRETIVE_MODE_SETTING_NAME =
+ L"DisableInteractiveMode";
+
+void InitWldp(Environment* env) {
+ if (per_process::isWldpInitialized) {
+ return;
+ }
+
+ HMODULE wldp_module = LoadLibraryExA(
+ "wldp.dll",
+ nullptr,
+ LOAD_LIBRARY_SEARCH_SYSTEM32);
+
+ if (wldp_module == nullptr) {
+ return env->ThrowError("Unable to load wldp.dll");
+ }
+
+ per_process::WldpCanExecuteFile =
+ (pfnWldpCanExecuteFile)GetProcAddress(
+ wldp_module,
+ "WldpCanExecuteFile");
+
+ per_process::WldpGetApplicationSettingBoolean =
+ (pfnWldpGetApplicationSettingBoolean)GetProcAddress(
+ wldp_module,
+ "WldpGetApplicationSettingBoolean");
+
+ per_process::WldpQuerySecurityPolicy =
+ (pfnWldpQuerySecurityPolicy)GetProcAddress(
+ wldp_module,
+ "WldpQuerySecurityPolicy");
+
+ per_process::isWldpInitialized = true;
+}
+
+static void IsFileTrustedBySystemCodeIntegrityPolicy(
+ const FunctionCallbackInfo& args) {
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsString());
+
+ Environment* env = Environment::GetCurrent(args);
+ if (!per_process::isWldpInitialized) {
+ InitWldp(env);
+ }
+
+ BufferValue path(env->isolate(), args[0]);
+ if (*path == nullptr) {
+ return env->ThrowError("path cannot be empty");
+ }
+
+ HANDLE hFile = CreateFileA(
+ *path,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ nullptr,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ nullptr);
+
+ if (hFile == INVALID_HANDLE_VALUE || hFile == nullptr) {
+ return env->ThrowError("Unable to open file");
+ }
+
+ const GUID wldp_host_other = WLDP_HOST_OTHER;
+ WLDP_EXECUTION_POLICY result;
+ HRESULT hr = per_process::WldpCanExecuteFile(
+ wldp_host_other,
+ WLDP_EXECUTION_EVALUATION_OPTION_NONE,
+ hFile,
+ NODEJS,
+ &result);
+ CloseHandle(hFile);
+
+ if (FAILED(hr)) {
+ return env->ThrowError("WldpCanExecuteFile failed");
+ }
+
+ bool isFileTrusted = (result == WLDP_EXECUTION_POLICY_ALLOWED);
+ args.GetReturnValue().Set(isFileTrusted);
+}
+
+static void IsInteractiveModeDisabledInternal(
+ const FunctionCallbackInfo& args) {
+ CHECK_EQ(args.Length(), 0);
+
+ Environment* env = Environment::GetCurrent(args);
+
+ if (!per_process::isWldpInitialized) {
+ InitWldp(env);
+ }
+
+ if (per_process::WldpGetApplicationSettingBoolean != nullptr) {
+ BOOL ret;
+ HRESULT hr = per_process::WldpGetApplicationSettingBoolean(
+ NODEJS,
+ DISABLE_INTERPRETIVE_MODE_SETTING_NAME,
+ &ret);
+
+ if (SUCCEEDED(hr)) {
+ args.GetReturnValue().Set(
+ Boolean::New(env->isolate(), ret));
+ return;
+ } else if (hr != E_NOTFOUND) {
+ // If the setting is not found, continue through to attempt
+ // WldpQuerySecurityPolicy, as the setting may be defined
+ // in the old settings format
+ args.GetReturnValue().Set(Boolean::New(env->isolate(), false));
+ return;
+ }
+ }
+
+ // WldpGetApplicationSettingBoolean is the preferred way for applications to
+ // query security policy values. However, this method only exists on Windows
+ // versions going back to circa Win10 2023H2. In order to support systems
+ // older than that (down to Win10RS2), we can use the deprecated
+ // WldpQuerySecurityPolicy
+ if (per_process::WldpQuerySecurityPolicy != nullptr) {
+ DECLARE_CONST_UNICODE_STRING(providerName, L"Node.js");
+ DECLARE_CONST_UNICODE_STRING(keyName, L"Settings");
+ DECLARE_CONST_UNICODE_STRING(valueName, L"DisableInteractiveMode");
+ WLDP_SECURE_SETTING_VALUE_TYPE valueType =
+ WLDP_SECURE_SETTING_VALUE_TYPE_BOOLEAN;
+ ULONG valueSize = sizeof(int);
+ int ret = 0;
+ HRESULT hr = per_process::WldpQuerySecurityPolicy(
+ &providerName,
+ &keyName,
+ &valueName,
+ &valueType,
+ &ret,
+ &valueSize);
+ if (FAILED(hr)) {
+ args.GetReturnValue().Set(Boolean::New(env->isolate(), false));
+ return;
+ }
+
+ args.GetReturnValue().Set(
+ Boolean::New(env->isolate(), static_cast(ret)));
+ }
+}
+
+static void IsSystemEnforcingCodeIntegrity(
+ const FunctionCallbackInfo& args) {
+ CHECK_EQ(args.Length(), 0);
+
+ Environment* env = Environment::GetCurrent(args);
+
+ if (!per_process::isWldpInitialized) {
+ InitWldp(env);
+ }
+
+ if (per_process::WldpGetApplicationSettingBoolean != nullptr) {
+ BOOL ret;
+ HRESULT hr = per_process::WldpGetApplicationSettingBoolean(
+ NODEJS,
+ ENFORCE_CODE_INTEGRITY_SETTING_NAME,
+ &ret);
+
+ if (SUCCEEDED(hr)) {
+ args.GetReturnValue().Set(
+ Boolean::New(env->isolate(), ret));
+ return;
+ } else if (hr != E_NOTFOUND) {
+ // If the setting is not found, continue through to attempt
+ // WldpQuerySecurityPolicy, as the setting may be defined
+ // in the old settings format
+ args.GetReturnValue().Set(Boolean::New(env->isolate(), false));
+ return;
+ }
+ }
+
+ // WldpGetApplicationSettingBoolean is the preferred way for applications to
+ // query security policy values. However, this method only exists on Windows
+ // versions going back to circa Win10 2023H2. In order to support systems
+ // older than that (down to Win10RS2), we can use the deprecated
+ // WldpQuerySecurityPolicy
+ if (per_process::WldpQuerySecurityPolicy != nullptr) {
+ DECLARE_CONST_UNICODE_STRING(providerName, L"Node.js");
+ DECLARE_CONST_UNICODE_STRING(keyName, L"Settings");
+ DECLARE_CONST_UNICODE_STRING(valueName, L"EnforceCodeIntegrity");
+ WLDP_SECURE_SETTING_VALUE_TYPE valueType =
+ WLDP_SECURE_SETTING_VALUE_TYPE_BOOLEAN;
+ ULONG valueSize = sizeof(int);
+ int ret = 0;
+ HRESULT hr = per_process::WldpQuerySecurityPolicy(
+ &providerName,
+ &keyName,
+ &valueName,
+ &valueType,
+ &ret,
+ &valueSize);
+ if (FAILED(hr)) {
+ args.GetReturnValue().Set(Boolean::New(env->isolate(), false));
+ return;
+ }
+
+ args.GetReturnValue().Set(
+ Boolean::New(env->isolate(), static_cast(ret)));
+ }
+}
+
+void Initialize(Local