From 943af561491996f68f982836b797984e920a9486 Mon Sep 17 00:00:00 2001 From: Robert Waite Date: Mon, 24 Feb 2025 13:16:30 -0800 Subject: [PATCH 1/7] src: add WDAC integration (Windows) Add calls to Windows Defender Application Control to enforce integrity of .js, .json, .node files. add typings. fix version number add integrity checks to esm loader fix esm code integrity tests only load code integrity module on Windows address typings feedback fix readability and formatting fix naming conventions fix error in merge --- doc/api/code_integrity.md | 133 +++++++++ doc/api/errors.md | 16 ++ doc/api/index.md | 1 + doc/api/wdac-manifest.xml | 7 + lib/internal/code_integrity.js | 44 +++ lib/internal/errors.js | 4 + lib/internal/main/eval_string.js | 14 + lib/internal/modules/cjs/loader.js | 20 ++ lib/internal/modules/esm/load.js | 21 +- node.gyp | 9 + src/node_binding.cc | 9 +- src/node_code_integrity.cc | 295 ++++++++++++++++++++ src/node_code_integrity.h | 90 ++++++ src/node_external_reference.h | 9 +- test/fixtures/code_integrity_test.js | 1 + test/fixtures/code_integrity_test.json | 1 + test/fixtures/code_integrity_test.node | 1 + test/fixtures/code_integrity_test2.js | 1 + test/parallel/test-bootstrap-modules.js | 4 + test/parallel/test-code-integrity.js | 101 +++++++ typings/globals.d.ts | 2 + typings/internalBinding/code_integrity.d.ts | 6 + 22 files changed, 786 insertions(+), 3 deletions(-) create mode 100644 doc/api/code_integrity.md create mode 100644 doc/api/wdac-manifest.xml create mode 100644 lib/internal/code_integrity.js create mode 100644 src/node_code_integrity.cc create mode 100644 src/node_code_integrity.h create mode 100644 test/fixtures/code_integrity_test.js create mode 100644 test/fixtures/code_integrity_test.json create mode 100644 test/fixtures/code_integrity_test.node create mode 100644 test/fixtures/code_integrity_test2.js create mode 100644 test/parallel/test-code-integrity.js create mode 100644 typings/internalBinding/code_integrity.d.ts diff --git a/doc/api/code_integrity.md b/doc/api/code_integrity.md new file mode 100644 index 00000000000000..589996dbfa56b6 --- /dev/null +++ b/doc/api/code_integrity.md @@ -0,0 +1,133 @@ +# 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 ccf8d16655cec4..a523da3da0017d 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -794,6 +794,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/index.md b/doc/api/index.md index 7b4144639a07be..f31f753d8fb12d 100644 --- a/doc/api/index.md +++ b/doc/api/index.md @@ -19,6 +19,7 @@ * [C++ embedder API](embedding.md) * [Child processes](child_process.md) * [Cluster](cluster.md) +* [Code integrity](code_integrity.md) * [Command-line options](cli.md) * [Console](console.md) * [Crypto](crypto.md) diff --git a/doc/api/wdac-manifest.xml b/doc/api/wdac-manifest.xml new file mode 100644 index 00000000000000..264de029012bf7 --- /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..e1ce4620fc6a44 --- /dev/null +++ b/lib/internal/code_integrity.js @@ -0,0 +1,44 @@ +// 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 { + isFileTrustedBySystemCodeIntegrityPolicy, + isInteractiveModeDisabled, + isSystemEnforcingCodeIntegrity, +} = internalBinding('code_integrity'); + +let isCodeIntegrityEnforced; +let alreadyQueriedSystemCodeEnforcmentMode = false; + +function isAllowedToExecuteFile(filepath) { + 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); +} + +module.exports = { + isAllowedToExecuteFile, + isFileTrustedBySystemCodeIntegrityPolicy, + isInteractiveModeDisabled, + isSystemEnforcingCodeIntegrity, +}; diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 87200b40d45bdc..6f9e53ac738dfc 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..6350b86d6bca71 100644 --- a/lib/internal/main/eval_string.js +++ b/lib/internal/main/eval_string.js @@ -23,10 +23,24 @@ const { const { addBuiltinLibsToObject } = require('internal/modules/helpers'); const { getOptionValue } = require('internal/options'); +const { + codes: { + ERR_CODE_INTEGRITY_BLOCKED, + }, +} = require('internal/errors'); + prepareMainThreadExecution(); addBuiltinLibsToObject(globalThis, ''); markBootstrapComplete(); +const isWindows = require('internal/util'); +if (isWindows) { + const ci = require('internal/code_integrity'); + if (ci.isInteractiveModeDisabled()) { + throw new ERR_CODE_INTEGRITY_BLOCKED('"eval"'); + } +} + const code = getOptionValue('--eval'); const print = getOptionValue('--print'); diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 6437b3ab218a71..9f7ced5a81359e 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,11 @@ const onRequire = getLazy(() => tracingChannel('module.require')); const relativeResolveCache = { __proto__: null }; +let ci; +if (isWindows) { + ci = require('internal/code_integrity'); +} + let requireDepth = 0; let isPreloading = false; let statCache = null; @@ -1217,6 +1223,13 @@ 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:')) { + + if (isWindows) { + 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 +1260,13 @@ Module._load = function(request, parent, isMain) { cachedModule[kModuleCircularVisited] = true; } + if (isWindows) { + 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) { diff --git a/lib/internal/modules/esm/load.js b/lib/internal/modules/esm/load.js index 9661bc716bfa76..7594956b723780 100644 --- a/lib/internal/modules/esm/load.js +++ b/lib/internal/modules/esm/load.js @@ -4,6 +4,7 @@ const { RegExpPrototypeExec, } = primordials; const { + isWindows, kEmptyObject, } = require('internal/util'); @@ -13,8 +14,9 @@ const { readFileSync } = require('fs'); const { Buffer: { from: BufferFrom } } = require('buffer'); -const { URL } = require('internal/url'); +const { URL, fileURLToPath } = require('internal/url'); const { + ERR_CODE_INTEGRITY_VIOLATION, ERR_INVALID_URL, ERR_UNKNOWN_MODULE_FORMAT, ERR_UNSUPPORTED_ESM_URL_SCHEME, @@ -24,6 +26,11 @@ const { dataURLProcessor, } = require('internal/data_url'); +let ci; +if (isWindows) { + ci = require('internal/code_integrity'); +} + /** * @param {URL} url URL to the module * @param {ESModuleContext} context used to decorate error messages @@ -34,6 +41,12 @@ async function getSource(url, context) { const responseURL = href; let source; if (protocol === 'file:') { + if (isWindows) { + const isAllowedToExecute = ci.isAllowedToExecuteFile(fileURLToPath(url)); + if (!isAllowedToExecute) { + throw new ERR_CODE_INTEGRITY_VIOLATION(url); + } + } const { readFile: readFileAsync } = require('internal/fs/promises').exports; source = await readFileAsync(url); } else if (protocol === 'data:') { @@ -59,6 +72,12 @@ function getSourceSync(url, context) { const responseURL = href; let source; if (protocol === 'file:') { + if (isWindows) { + const isAllowedToExecute = ci.isAllowedToExecuteFile(fileURLToPath(url)); + if (!isAllowedToExecute) { + throw new ERR_CODE_INTEGRITY_VIOLATION(url); + } + } source = readFileSync(url); } else if (protocol === 'data:') { const result = dataURLProcessor(url); diff --git a/node.gyp b/node.gyp index 59303d9ab7c1d1..653d04360e8624 100644 --- a/node.gyp +++ b/node.gyp @@ -230,6 +230,7 @@ 'src/node_blob.h', 'src/node_buffer.h', 'src/node_builtins.h', + 'src/node_code_integrity.h', 'src/node_config_file.h', 'src/node_constants.h', 'src/node_context_data.h', @@ -452,6 +453,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 367a5bcd402b53..5a30a130022725 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -97,6 +97,12 @@ 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) \ @@ -104,7 +110,8 @@ NODE_BUILTIN_PROFILER_BINDINGS(V) \ NODE_BUILTIN_DEBUG_BINDINGS(V) \ NODE_BUILTIN_QUIC_BINDINGS(V) \ - NODE_BUILTIN_SQLITE_BINDINGS(V) + NODE_BUILTIN_SQLITE_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..9fa975152ca358 --- /dev/null +++ b/src/node_code_integrity.cc @@ -0,0 +1,295 @@ +#ifdef _WIN32 + +#include "node_code_integrity.h" +#include "env-inl.h" +#include "node.h" +#include "node_errors.h" +#include "node_external_reference.h" +#include "v8.h" +#include "util.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; + +// WldpCanExecuteFile queries system code integrity policy +// to determine if the contents of a file are allowed to be executed. +pfnWldpCanExecuteFile WldpCanExecuteFile; + +// WldpGetApplicationSettingBoolean queries system code integrity policy +// for an arbitrary flag. NodeJS uses the "Node.js EnforceCodeIntegrity" +// flag to determine if NodeJS should be calling WldpCanExecuteFile +// on files intended for execution +// NodeJS also uses the "Node.js DisableInteractiveMode" flag to determine +// if it should restrict interactive code execution. More details +// on how to configure these flags can be found in doc/api/code_integrity.md +pfnWldpGetApplicationSettingBoolean WldpGetApplicationSettingBoolean; + +// WldpQuerySecurityPolicy performs similar functionality to +// WldpGetApplicationSettingBoolean, except for legacy Windows systems. +// WldpGetApplicationSettingBoolean was introduced Win10 2023H2, +// and is the modern API. However, to support more Node users, +// we also fall back to WldpQuerySecurityPolicy, +// which is available on Windows systems back to Win10 RS2 +pfnWldpQuerySecurityPolicy WldpQuerySecurityPolicy; +} // namespace per_process + +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"; + +// InitWldp loads WLDP.dll (the Windows code integrity for interpreters DLL) +// and the relevant function pointers +void InitWldp(Environment* env) { + if (per_process::isWldpInitialized) { + return; + } + + HMODULE wldp_module = + LoadLibraryExA("wldp.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + + if (wldp_module == nullptr) { + // Wldp is included on all Windows systems that are supported by Node's + // If Wldp is unable to be loaded, something is very wrong with + // the system state + THROW_ERR_INVALID_STATE(env, "WLDP.DLL does not exist"); + return; + } + + 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; +} + +// IsFileTrustedBySystemCodeIntegrityPolicy +// Queries operating system to determine if the contents of a file are +// allowed to be executed according to system code integrity policy. +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]); + CHECK_NOT_NULL(*path); + + HANDLE hFile = CreateFileA(*path, + GENERIC_READ, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + if (hFile == INVALID_HANDLE_VALUE || hFile == nullptr) { + return args.GetReturnValue().SetFalse(); + } + + 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)) { + // The failure cases from WldpCanExecuteFile are generally + // not recoverable. Inspection of the Windows event logs is necessary. + // The secure failure mode is not executing the file + args.GetReturnValue().SetFalse(); + return; + } + + bool isFileTrusted = (result == WLDP_EXECUTION_POLICY_ALLOWED); + args.GetReturnValue().Set(isFileTrusted); +} + +// IsInteractiveModeDisabled +// Queries operating system code integrity policy to determine if +// the policy is requesting NodeJS to disable interactive mode. +static void IsInteractiveModeDisabled( + 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 isInteractiveModeDisabled; + HRESULT hr = per_process::WldpGetApplicationSettingBoolean( + NODEJS, + DISABLE_INTERPRETIVE_MODE_SETTING_NAME, + &isInteractiveModeDisabled); + + if (SUCCEEDED(hr)) { + args.GetReturnValue().Set(isInteractiveModeDisabled); + 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().SetFalse(); + 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 isInteractiveModeDisabled = 0; + HRESULT hr = per_process::WldpQuerySecurityPolicy( + &providerName, + &keyName, + &valueName, + &valueType, + &isInteractiveModeDisabled, + &valueSize); + + if (FAILED(hr)) { + args.GetReturnValue().SetFalse(); + return; + } + + args.GetReturnValue().Set( + Boolean::New( + env->isolate(), + static_cast(isInteractiveModeDisabled))); + } +} + +// IsSystemEnforcingCodeIntegrity +// Queries the operating system to determine if NodeJS should be enforcing +// integrity checks by calling WldpCanExecuteFile +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 isCodeIntegrityEnforced; + HRESULT hr = per_process::WldpGetApplicationSettingBoolean( + NODEJS, ENFORCE_CODE_INTEGRITY_SETTING_NAME, &isCodeIntegrityEnforced); + + if (SUCCEEDED(hr)) { + args.GetReturnValue().Set(isCodeIntegrityEnforced); + 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().SetFalse(); + 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 isCodeIntegrityEnforced = 0; + HRESULT hr = per_process::WldpQuerySecurityPolicy( + &providerName, + &keyName, + &valueName, + &valueType, + &isCodeIntegrityEnforced, + &valueSize); + + if (FAILED(hr)) { + args.GetReturnValue().SetFalse(); + return; + } + + args.GetReturnValue().Set( + Boolean::New( + env->isolate(), + static_cast(isCodeIntegrityEnforced))); + } +} + +void Initialize(Local target, + Local unused, + Local context, + void* priv) { + SetMethod(context, + target, + "isFileTrustedBySystemCodeIntegrityPolicy", + IsFileTrustedBySystemCodeIntegrityPolicy); + + SetMethod(context, + target, + "isInteractiveModeDisabled", + IsInteractiveModeDisabled); + + SetMethod(context, + target, + "isSystemEnforcingCodeIntegrity", + IsSystemEnforcingCodeIntegrity); +} + +void RegisterExternalReferences(ExternalReferenceRegistry* registry) { + registry->Register(IsFileTrustedBySystemCodeIntegrityPolicy); + registry->Register(IsInteractiveModeDisabled); + registry->Register(IsSystemEnforcingCodeIntegrity); +} + +} // namespace code_integrity +} // namespace node + +NODE_BINDING_CONTEXT_AWARE_INTERNAL(code_integrity, + node::code_integrity::Initialize) +NODE_BINDING_EXTERNAL_REFERENCE( + code_integrity, node::code_integrity::RegisterExternalReferences) +#endif // _WIN32 diff --git a/src/node_code_integrity.h b/src/node_code_integrity.h new file mode 100644 index 00000000000000..001bc8611e59bd --- /dev/null +++ b/src/node_code_integrity.h @@ -0,0 +1,90 @@ +// Windows API documentation for WLDP can be found at +// https://learn.microsoft.com/en-us/windows/win32/api/wldp/ +#ifdef _WIN32 + +#ifndef SRC_NODE_CODE_INTEGRITY_H_ +#define SRC_NODE_CODE_INTEGRITY_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include + +#define WLDP_HOST_OTHER \ + {0x626cbec3, 0xe1fa, 0x4227, {0x98, 0x0, 0xed, 0x21, 0x2, 0x74, 0xcf, 0x7c}}; + +// +// Enumeration types for WldpCanExecuteFile +// +typedef enum WLDP_EXECUTION_POLICY { + WLDP_EXECUTION_POLICY_BLOCKED, + WLDP_EXECUTION_POLICY_ALLOWED, + WLDP_EXECUTION_POLICY_REQUIRE_SANDBOX, +} WLDP_EXECUTION_POLICY; + +typedef enum WLDP_EXECUTION_EVALUATION_OPTIONS { + WLDP_EXECUTION_EVALUATION_OPTION_NONE = 0x0, + WLDP_EXECUTION_EVALUATION_OPTION_EXECUTE_IN_INTERACTIVE_SESSION = 0x1, +} WLDP_EXECUTION_EVALUATION_OPTIONS; + +typedef HRESULT(WINAPI* pfnWldpCanExecuteFile)( + _In_ REFGUID host, + _In_ WLDP_EXECUTION_EVALUATION_OPTIONS options, + _In_ HANDLE contentFileHandle, + _In_opt_ PCWSTR auditInfo, + _Out_ WLDP_EXECUTION_POLICY* result); + +typedef HRESULT(WINAPI* pfnWldpCanExecuteBuffer)( + _In_ REFGUID host, + _In_ WLDP_EXECUTION_EVALUATION_OPTIONS options, + _In_reads_(bufferSize) const BYTE* buffer, + _In_ ULONG bufferSize, + _In_opt_ PCWSTR auditInfo, + _Out_ WLDP_EXECUTION_POLICY* result); + +typedef HRESULT(WINAPI* pfnWldpGetApplicationSettingBoolean)( + _In_ PCWSTR id, _In_ PCWSTR setting, _Out_ bool* result); + +typedef enum WLDP_SECURE_SETTING_VALUE_TYPE { + WLDP_SECURE_SETTING_VALUE_TYPE_BOOLEAN = 0, + WLDP_SECURE_SETTING_VALUE_TYPE_ULONG, + WLDP_SECURE_SETTING_VALUE_TYPE_BINARY, + WLDP_SECURE_SETTING_VALUE_TYPE_STRING +} WLDP_SECURE_SETTING_VALUE_TYPE, + *PWLDP_SECURE_SETTING_VALUE_TYPE; + +/* from winternl.h */ +#if !defined(__UNICODE_STRING_DEFINED) && defined(__MINGW32__) +#define __UNICODE_STRING_DEFINED +#endif +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING, *PUNICODE_STRING; + +typedef const UNICODE_STRING* PCUNICODE_STRING; + +typedef HRESULT(WINAPI* pfnWldpQuerySecurityPolicy)( + _In_ const UNICODE_STRING* providerName, + _In_ const UNICODE_STRING* keyName, + _In_ const UNICODE_STRING* valueName, + _Out_ PWLDP_SECURE_SETTING_VALUE_TYPE valueType, + _Out_writes_bytes_opt_(*valueSize) PVOID valueAddress, + _Inout_ PULONG valueSize); + +#ifndef DECLARE_CONST_UNICODE_STRING +#define DECLARE_CONST_UNICODE_STRING(_var, _string) \ + const WCHAR _var##_buffer[] = _string; \ + const UNICODE_STRING _var = { \ + sizeof(_string) - sizeof(WCHAR), sizeof(_string), (PWCH)_var##_buffer} +#endif + +#ifndef E_NOTFOUND +#define E_NOTFOUND 0x80070490 +#endif + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // _WIN32 + +#endif // SRC_NODE_CODE_INTEGRITY_H_ diff --git a/src/node_external_reference.h b/src/node_external_reference.h index 5981e9db9c3bc4..f6c03523419a2f 100644 --- a/src/node_external_reference.h +++ b/src/node_external_reference.h @@ -241,12 +241,19 @@ class ExternalReferenceRegistry { #define EXTERNAL_REFERENCE_BINDING_LIST_QUIC(V) #endif +#define EXTERNAL_REFERENCE_BINDING_LIST_OS_SPECIFIC(V) + +#ifdef _WIN32 +#define EXTERNAL_REFERENCE_BINDING_LIST_OS_SPECIFIC(V) V(code_integrity) +#endif + #define EXTERNAL_REFERENCE_BINDING_LIST(V) \ EXTERNAL_REFERENCE_BINDING_LIST_BASE(V) \ EXTERNAL_REFERENCE_BINDING_LIST_INSPECTOR(V) \ EXTERNAL_REFERENCE_BINDING_LIST_I18N(V) \ EXTERNAL_REFERENCE_BINDING_LIST_CRYPTO(V) \ - EXTERNAL_REFERENCE_BINDING_LIST_QUIC(V) + EXTERNAL_REFERENCE_BINDING_LIST_QUIC(V) \ + EXTERNAL_REFERENCE_BINDING_LIST_OS_SPECIFIC(V) } // namespace node diff --git a/test/fixtures/code_integrity_test.js b/test/fixtures/code_integrity_test.js new file mode 100644 index 00000000000000..839ca115b48d19 --- /dev/null +++ b/test/fixtures/code_integrity_test.js @@ -0,0 +1 @@ +1 + 1; diff --git a/test/fixtures/code_integrity_test.json b/test/fixtures/code_integrity_test.json new file mode 100644 index 00000000000000..0967ef424bce67 --- /dev/null +++ b/test/fixtures/code_integrity_test.json @@ -0,0 +1 @@ +{} diff --git a/test/fixtures/code_integrity_test.node b/test/fixtures/code_integrity_test.node new file mode 100644 index 00000000000000..af84f6510f0d90 --- /dev/null +++ b/test/fixtures/code_integrity_test.node @@ -0,0 +1 @@ +exports.file1 = 'file1.node'; diff --git a/test/fixtures/code_integrity_test2.js b/test/fixtures/code_integrity_test2.js new file mode 100644 index 00000000000000..c1c6922d1dfea3 --- /dev/null +++ b/test/fixtures/code_integrity_test2.js @@ -0,0 +1 @@ +return true; diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index d340f98b83a3d6..8c9dfaa19a620b 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -106,6 +106,10 @@ expected.beforePreExec = new Set([ 'NativeModule internal/events/abort_listener', 'NativeModule internal/modules/typescript', ]); +if (common.isWindows) { + expected.beforePreExec.add('NativeModule internal/code_integrity'); + expected.beforePreExec.add('Internal Binding code_integrity'); +} expected.atRunTime = new Set([ 'Internal Binding worker', diff --git a/test/parallel/test-code-integrity.js b/test/parallel/test-code-integrity.js new file mode 100644 index 00000000000000..b005b74da4a3dc --- /dev/null +++ b/test/parallel/test-code-integrity.js @@ -0,0 +1,101 @@ +// Flags: --expose-internals + +'use strict'; + +const common = require('../common'); +const assert = require('node:assert'); +const { describe, it } = require('node:test'); +const ci = require('internal/code_integrity'); + +// This functionality is currently only on Windows +if (!common.isWindows) { + common.skip('Windows specific test.'); +} + +describe('cjs loader code integrity integration tests', () => { + it('should throw an error if a .js file does not pass code integrity policy', + (t) => { + t.mock.method(ci, ci.isAllowedToExecuteFile.name, () => { return false; }); + + assert.throws( + () => { + require('../fixtures/code_integrity_test.js'); + }, + { + code: 'ERR_CODE_INTEGRITY_VIOLATION', + }, + ); + } + ); + it('should NOT throw an error if a .js file passes code integrity policy', + (t) => { + t.mock.method(ci, ci.isAllowedToExecuteFile.name, () => { return true; }); + + assert.ok( + require('../fixtures/code_integrity_test.js') + ); + } + ); + it('should throw an error if a .json file does not pass code integrity policy', + (t) => { + t.mock.method(ci, ci.isAllowedToExecuteFile.name, () => { return false; }); + + assert.throws( + () => { + require('../fixtures/code_integrity_test.json'); + }, + { + code: 'ERR_CODE_INTEGRITY_VIOLATION', + }, + ); + } + ); + it('should NOT throw an error if a .json file passes code integrity policy', + (t) => { + t.mock.method(ci, ci.isAllowedToExecuteFile.name, () => { return true; }); + + assert.ok( + require('../fixtures/code_integrity_test.json') + ); + } + ); + it('should throw an error if a .node file does not pass code integrity policy', + (t) => { + t.mock.method(ci, ci.isAllowedToExecuteFile.name, () => { return false; }); + + assert.throws( + () => { + require('../fixtures/code_integrity_test.node'); + }, + { + code: 'ERR_CODE_INTEGRITY_VIOLATION', + }, + ); + } + ); +}); + +describe('esm loader code integrity integration tests', async () => { + it('should NOT throw an error if a file passes code integrity policy', + async (t) => { + t.mock.method(ci, ci.isAllowedToExecuteFile.name, () => { return true; }); + + // This should import without throwing ERR_CODE_INTEGRITY_VIOLATION + await import('../fixtures/code_integrity_test.js'); + } + ); + + it('should throw an error if a file does not pass code integrity policy', + async (t) => { + t.mock.method(ci, ci.isAllowedToExecuteFile.name, () => { return false; }); + try { + await import('../fixtures/code_integrity_test2.js'); + } catch (e) { + assert.strictEqual(e.code, 'ERR_CODE_INTEGRITY_VIOLATION'); + return; + } + + assert.fail('No exception thrown'); + } + ); +}); diff --git a/typings/globals.d.ts b/typings/globals.d.ts index 3730a56da2987c..7b995ed2679c3a 100644 --- a/typings/globals.d.ts +++ b/typings/globals.d.ts @@ -1,5 +1,6 @@ import { AsyncWrapBinding } from './internalBinding/async_wrap'; import { BlobBinding } from './internalBinding/blob'; +import { CodeIntegrityBinding } from './internalBinding/code_integrity'; import { ConfigBinding } from './internalBinding/config'; import { ConstantsBinding } from './internalBinding/constants'; import { DebugBinding } from './internalBinding/debug'; @@ -25,6 +26,7 @@ import { ZlibBinding } from './internalBinding/zlib'; interface InternalBindingMap { async_wrap: AsyncWrapBinding; blob: BlobBinding; + code_integrity: CodeIntegrityBinding; config: ConfigBinding; constants: ConstantsBinding; debug: DebugBinding; diff --git a/typings/internalBinding/code_integrity.d.ts b/typings/internalBinding/code_integrity.d.ts new file mode 100644 index 00000000000000..6ed628180c815d --- /dev/null +++ b/typings/internalBinding/code_integrity.d.ts @@ -0,0 +1,6 @@ +export interface CodeIntegrityBinding { + isAllowedToExecuteFile(filePath: string) : boolean; + isFileTrustedBySystemCodeIntegrityPolicy(filePath: string) : boolean; + isInteractiveModeDisabled() : boolean; + isSystemEnforcingCodeIntegrity() : boolean; +} From 712bc176ebe04888dd60facfa390e4dd8f057f3a Mon Sep 17 00:00:00 2001 From: Robert Waite Date: Thu, 8 May 2025 08:31:18 -0700 Subject: [PATCH 2/7] fix formatting --- src/node_code_integrity.cc | 51 ++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/src/node_code_integrity.cc b/src/node_code_integrity.cc index 9fa975152ca358..273ba89c060b41 100644 --- a/src/node_code_integrity.cc +++ b/src/node_code_integrity.cc @@ -5,8 +5,8 @@ #include "node.h" #include "node_errors.h" #include "node_external_reference.h" -#include "v8.h" #include "util.h" +#include "v8.h" namespace node { @@ -134,8 +134,8 @@ static void IsFileTrustedBySystemCodeIntegrityPolicy( // IsInteractiveModeDisabled // Queries operating system code integrity policy to determine if // the policy is requesting NodeJS to disable interactive mode. -static void IsInteractiveModeDisabled( - const FunctionCallbackInfo& args) { +static void IsInteractiveModeDisabled(const FunctionCallbackInfo& args) +{ CHECK_EQ(args.Length(), 0); Environment* env = Environment::GetCurrent(args); @@ -176,23 +176,21 @@ static void IsInteractiveModeDisabled( WLDP_SECURE_SETTING_VALUE_TYPE_BOOLEAN; ULONG valueSize = sizeof(int); int isInteractiveModeDisabled = 0; - HRESULT hr = per_process::WldpQuerySecurityPolicy( - &providerName, - &keyName, - &valueName, - &valueType, - &isInteractiveModeDisabled, - &valueSize); + HRESULT hr = + per_process::WldpQuerySecurityPolicy(&providerName, + &keyName, + &valueName, + &valueType, + &isInteractiveModeDisabled, + &valueSize); if (FAILED(hr)) { args.GetReturnValue().SetFalse(); return; } - args.GetReturnValue().Set( - Boolean::New( - env->isolate(), - static_cast(isInteractiveModeDisabled))); + args.GetReturnValue().Set(Boolean::New( + env->isolate(), static_cast(isInteractiveModeDisabled))); } } @@ -239,23 +237,20 @@ static void IsSystemEnforcingCodeIntegrity( WLDP_SECURE_SETTING_VALUE_TYPE_BOOLEAN; ULONG valueSize = sizeof(int); int isCodeIntegrityEnforced = 0; - HRESULT hr = per_process::WldpQuerySecurityPolicy( - &providerName, - &keyName, - &valueName, - &valueType, - &isCodeIntegrityEnforced, - &valueSize); + HRESULT hr = per_process::WldpQuerySecurityPolicy(&providerName, + &keyName, + &valueName, + &valueType, + &isCodeIntegrityEnforced, + &valueSize); if (FAILED(hr)) { args.GetReturnValue().SetFalse(); return; } - args.GetReturnValue().Set( - Boolean::New( - env->isolate(), - static_cast(isCodeIntegrityEnforced))); + args.GetReturnValue().Set(Boolean::New( + env->isolate(), static_cast(isCodeIntegrityEnforced))); } } @@ -268,10 +263,8 @@ void Initialize(Local target, "isFileTrustedBySystemCodeIntegrityPolicy", IsFileTrustedBySystemCodeIntegrityPolicy); - SetMethod(context, - target, - "isInteractiveModeDisabled", - IsInteractiveModeDisabled); + SetMethod( + context, target, "isInteractiveModeDisabled", IsInteractiveModeDisabled); SetMethod(context, target, From befbaae1ad66f489522c8d4c096f48ea83b919d4 Mon Sep 17 00:00:00 2001 From: Robert Waite Date: Thu, 8 May 2025 12:42:27 -0700 Subject: [PATCH 3/7] properly load isWindows --- lib/internal/main/eval_string.js | 2 +- src/node_code_integrity.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/internal/main/eval_string.js b/lib/internal/main/eval_string.js index 6350b86d6bca71..dd37c20d4665f7 100644 --- a/lib/internal/main/eval_string.js +++ b/lib/internal/main/eval_string.js @@ -33,7 +33,7 @@ prepareMainThreadExecution(); addBuiltinLibsToObject(globalThis, ''); markBootstrapComplete(); -const isWindows = require('internal/util'); +const { isWindows } = require('internal/util'); if (isWindows) { const ci = require('internal/code_integrity'); if (ci.isInteractiveModeDisabled()) { diff --git a/src/node_code_integrity.cc b/src/node_code_integrity.cc index 273ba89c060b41..2695892b04155f 100644 --- a/src/node_code_integrity.cc +++ b/src/node_code_integrity.cc @@ -134,7 +134,7 @@ static void IsFileTrustedBySystemCodeIntegrityPolicy( // IsInteractiveModeDisabled // Queries operating system code integrity policy to determine if // the policy is requesting NodeJS to disable interactive mode. -static void IsInteractiveModeDisabled(const FunctionCallbackInfo& args) +static void IsInteractiveModeDisabled(const FunctionCallbackInfo& args) { CHECK_EQ(args.Length(), 0); @@ -176,7 +176,7 @@ static void IsInteractiveModeDisabled(const FunctionCallbackInfo& args) WLDP_SECURE_SETTING_VALUE_TYPE_BOOLEAN; ULONG valueSize = sizeof(int); int isInteractiveModeDisabled = 0; - HRESULT hr = + HRESULT hr = per_process::WldpQuerySecurityPolicy(&providerName, &keyName, &valueName, From 2fd8fdcb5c63ed3efa8fd9bc620ea72b000d13b9 Mon Sep 17 00:00:00 2001 From: Robert Waite Date: Tue, 13 May 2025 10:04:42 -0700 Subject: [PATCH 4/7] fix formatting --- src/node_code_integrity.cc | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/node_code_integrity.cc b/src/node_code_integrity.cc index 2695892b04155f..1ff5c1e66be6a9 100644 --- a/src/node_code_integrity.cc +++ b/src/node_code_integrity.cc @@ -134,8 +134,7 @@ static void IsFileTrustedBySystemCodeIntegrityPolicy( // IsInteractiveModeDisabled // Queries operating system code integrity policy to determine if // the policy is requesting NodeJS to disable interactive mode. -static void IsInteractiveModeDisabled(const FunctionCallbackInfo& args) -{ +static void IsInteractiveModeDisabled(const FunctionCallbackInfo& args) { CHECK_EQ(args.Length(), 0); Environment* env = Environment::GetCurrent(args); @@ -177,12 +176,12 @@ static void IsInteractiveModeDisabled(const FunctionCallbackInfo& args) ULONG valueSize = sizeof(int); int isInteractiveModeDisabled = 0; HRESULT hr = - per_process::WldpQuerySecurityPolicy(&providerName, - &keyName, - &valueName, - &valueType, - &isInteractiveModeDisabled, - &valueSize); + per_process::WldpQuerySecurityPolicy(&providerName, + &keyName, + &valueName, + &valueType, + &isInteractiveModeDisabled, + &valueSize); if (FAILED(hr)) { args.GetReturnValue().SetFalse(); @@ -190,7 +189,7 @@ static void IsInteractiveModeDisabled(const FunctionCallbackInfo& args) } args.GetReturnValue().Set(Boolean::New( - env->isolate(), static_cast(isInteractiveModeDisabled))); + env->isolate(), static_cast(isInteractiveModeDisabled))); } } @@ -250,7 +249,7 @@ static void IsSystemEnforcingCodeIntegrity( } args.GetReturnValue().Set(Boolean::New( - env->isolate(), static_cast(isCodeIntegrityEnforced))); + env->isolate(), static_cast(isCodeIntegrityEnforced))); } } @@ -264,7 +263,7 @@ void Initialize(Local target, IsFileTrustedBySystemCodeIntegrityPolicy); SetMethod( - context, target, "isInteractiveModeDisabled", IsInteractiveModeDisabled); + context, target, "isInteractiveModeDisabled", IsInteractiveModeDisabled); SetMethod(context, target, From a6fd948966aefaabfdb75408e92ec2fa652eb4e7 Mon Sep 17 00:00:00 2001 From: Robert Waite Date: Tue, 13 May 2025 17:07:37 -0700 Subject: [PATCH 5/7] remove code_integrity from builtins on non windows --- src/node_builtins.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/node_builtins.cc b/src/node_builtins.cc index 253ecaad12dace..f81961e543a678 100644 --- a/src/node_builtins.cc +++ b/src/node_builtins.cc @@ -145,6 +145,9 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const { #endif "internal/test/binding", "internal/v8_prof_polyfill", "internal/v8_prof_processor", +#if !_WIN32 + "internal/code_integrity", // Only implemented on Windows +#endif }; auto source = source_.read(); From 93555d050efc265adeec955f471e7752acc97892 Mon Sep 17 00:00:00 2001 From: Robert Waite Date: Wed, 14 May 2025 10:50:55 -0700 Subject: [PATCH 6/7] fix spacing before comment --- src/node_builtins.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node_builtins.cc b/src/node_builtins.cc index f81961e543a678..3ec58fbe3a6af7 100644 --- a/src/node_builtins.cc +++ b/src/node_builtins.cc @@ -146,7 +146,7 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const { "internal/test/binding", "internal/v8_prof_polyfill", "internal/v8_prof_processor", #if !_WIN32 - "internal/code_integrity", // Only implemented on Windows + "internal/code_integrity", // Only implemented on Windows #endif }; From de2879650fb752d2a2c86f07c1b4f2b2703c4768 Mon Sep 17 00:00:00 2001 From: Robert Waite Date: Wed, 14 May 2025 15:51:58 -0700 Subject: [PATCH 7/7] only load code integrity module on Windows --- test/parallel/test-code-integrity.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-code-integrity.js b/test/parallel/test-code-integrity.js index b005b74da4a3dc..bfc709ec55b95f 100644 --- a/test/parallel/test-code-integrity.js +++ b/test/parallel/test-code-integrity.js @@ -5,13 +5,14 @@ const common = require('../common'); const assert = require('node:assert'); const { describe, it } = require('node:test'); -const ci = require('internal/code_integrity'); // This functionality is currently only on Windows if (!common.isWindows) { common.skip('Windows specific test.'); } +const ci = require('internal/code_integrity'); + describe('cjs loader code integrity integration tests', () => { it('should throw an error if a .js file does not pass code integrity policy', (t) => {