diff --git a/doc/api/cli.md b/doc/api/cli.md
index 9b45262beae077..c68181bff863cc 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -127,6 +127,15 @@ added: v12.0.0
Specify the file name of the CPU profile generated by `--cpu-prof`.
+### `--disable-proto=mode`
+
+
+Disable the `Object.prototype.__proto__` property. If `mode` is `delete`, the
+property will be removed entirely. If `mode` is `throw`, accesses to the
+property will throw an exception with the code `ERR_PROTO_ACCESS`.
+
### `--disallow-code-generation-from-strings`
+* `--disable-proto`
* `--enable-fips`
* `--enable-source-maps`
* `--experimental-import-meta-resolve`
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 2f076ae17d54e9..8f6eda57835379 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1674,6 +1674,14 @@ The `package.json` [exports][] field does not export the requested subpath.
Because exports are encapsulated, private internal modules that are not exported
cannot be imported through the package resolution, unless using an absolute URL.
+
+### `ERR_PROTO_ACCESS`
+
+Accessing `Object.prototype.__proto__` has been forbidden using
+[`--disable-proto=throw`][]. [`Object.getPrototypeOf`][] and
+[`Object.setPrototypeOf`][] should be used to get and set the prototype of an
+object.
+
### `ERR_REQUIRE_ESM`
@@ -2490,10 +2498,13 @@ This `Error` is thrown when a read is attempted on a TTY `WriteStream`,
such as `process.stdout.on('data')`.
[`'uncaughtException'`]: process.html#process_event_uncaughtexception
+[`--disable-proto=throw`]: cli.html#cli_disable_proto_mode
[`--force-fips`]: cli.html#cli_force_fips
[`Class: assert.AssertionError`]: assert.html#assert_class_assert_assertionerror
[`ERR_INVALID_ARG_TYPE`]: #ERR_INVALID_ARG_TYPE
[`EventEmitter`]: events.html#events_class_eventemitter
+[`Object.getPrototypeOf`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf
+[`Object.setPrototypeOf`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
[`REPL`]: repl.html
[`Writable`]: stream.html#stream_class_stream_writable
[`child_process`]: child_process.html
diff --git a/doc/node.1 b/doc/node.1
index 149902d3195543..430efa772339a7 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -100,6 +100,14 @@ The default is
File name of the V8 CPU profile generated with
.Fl -cpu-prof
.
+.It Fl -disable-proto Ns = Ns Ar mode
+Disable the `Object.prototype.__proto__` property. If
+.Ar mode
+is `delete`, the property will be removed entirely. If
+.Ar mode
+is `throw`, accesses to the property will throw an exception with the code
+`ERR_PROTO_ACCESS`.
+.
.It Fl -disallow-code-generation-from-strings
Make built-in language features like `eval` and `new Function` that generate
code from strings throw an exception instead. This does not affect the Node.js
diff --git a/src/api/environment.cc b/src/api/environment.cc
index 560845da65d93d..1fb97ab8d6b76d 100644
--- a/src/api/environment.cc
+++ b/src/api/environment.cc
@@ -14,6 +14,7 @@ using v8::Context;
using v8::EscapableHandleScope;
using v8::FinalizationGroup;
using v8::Function;
+using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
@@ -23,6 +24,7 @@ using v8::Null;
using v8::Object;
using v8::ObjectTemplate;
using v8::Private;
+using v8::PropertyDescriptor;
using v8::String;
using v8::Value;
@@ -417,6 +419,10 @@ Local NewContext(Isolate* isolate,
return context;
}
+void ProtoThrower(const FunctionCallbackInfo& info) {
+ THROW_ERR_PROTO_ACCESS(info.GetIsolate());
+}
+
// This runs at runtime, regardless of whether the context
// is created from a snapshot.
void InitializeContextRuntime(Local context) {
@@ -445,6 +451,32 @@ void InitializeContextRuntime(Local context) {
Local