light of the limitations listed below.
   </para>
 
+  <warning>
+   <para>
+    Restoring a dump causes the destination to execute arbitrary code of the
+    source superusers' choice.  Partial dumps and partial restores do not limit
+    that.  If the source superusers are not trusted, the dumped SQL statements
+    must be inspected before restoring.  Non-plain-text dumps can be inspected
+    by using <application>pg_restore</application>'s <option>--file</option>
+    option.  Note that the client running the dump and restore need not trust
+    the source or destination superusers.
+   </para>
+  </warning>
+
  </refsect1>
 
  <refsect1 id="pg-dump-options">
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--restrict-key=<replaceable class="parameter">restrict_key</replaceable></option></term>
+      <listitem>
+       <para>
+        Use the provided string as the <application>psql</application>
+        <command>\restrict</command> key in the dump output.  This can only be
+        specified for plain-text dumps, i.e., when <option>--format</option> is
+        set to <literal>plain</literal> or the <option>--format</option> option
+        is omitted.  If no restrict key is specified,
+        <application>pg_dump</application> will generate a random one as
+        needed.  Keys may contain only alphanumeric characters.
+       </para>
+       <para>
+        This option is primarily intended for testing purposes and other
+        scenarios that require repeatable output (e.g., comparing dump files).
+        It is not recommended for general use, as a malicious server with
+        advance knowledge of the key may be able to inject arbitrary code that
+        will be executed on the machine that runs
+        <application>psql</application> with the dump output.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--rows-per-insert=<replaceable class="parameter">nrows</replaceable></option></term>
       <listitem>
 
   linkend="libpq-pgpass"/> for more information.
   </para>
 
+  <warning>
+   <para>
+    Restoring a dump causes the destination to execute arbitrary code of the
+    source superusers' choice.  Partial dumps and partial restores do not limit
+    that.  If the source superusers are not trusted, the dumped SQL statements
+    must be inspected before restoring.  Note that the client running the dump
+    and restore need not trust the source or destination superusers.
+   </para>
+  </warning>
+
  </refsect1>
 
  <refsect1>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--restrict-key=<replaceable class="parameter">restrict_key</replaceable></option></term>
+      <listitem>
+       <para>
+        Use the provided string as the <application>psql</application>
+        <command>\restrict</command> key in the dump output.  If no restrict
+        key is specified, <application>pg_dumpall</application> will generate a
+        random one as needed.  Keys may contain only alphanumeric characters.
+       </para>
+       <para>
+        This option is primarily intended for testing purposes and other
+        scenarios that require repeatable output (e.g., comparing dump files).
+        It is not recommended for general use, as a malicious server with
+        advance knowledge of the key may be able to inject arbitrary code that
+        will be executed on the machine that runs
+        <application>psql</application> with the dump output.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--rows-per-insert=<replaceable class="parameter">nrows</replaceable></option></term>
       <listitem>
 
    <application>pg_restore</application> will not be able to load the data
    using <command>COPY</command> statements.
   </para>
+
+  <warning>
+   <para>
+    Restoring a dump causes the destination to execute arbitrary code of the
+    source superusers' choice.  Partial dumps and partial restores do not limit
+    that.  If the source superusers are not trusted, the dumped SQL statements
+    must be inspected before restoring.  Non-plain-text dumps can be inspected
+    by using <application>pg_restore</application>'s <option>--file</option>
+    option.  Note that the client running the dump and restore need not trust
+    the source or destination superusers.
+   </para>
+  </warning>
  </refsect1>
 
  <refsect1 id="app-pgrestore-options">
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--restrict-key=<replaceable class="parameter">restrict_key</replaceable></option></term>
+      <listitem>
+       <para>
+        Use the provided string as the <application>psql</application>
+        <command>\restrict</command> key in the dump output.  This can only be
+        specified for SQL script output, i.e., when the <option>--file</option>
+        option is used.  If no restrict key is specified,
+        <application>pg_restore</application> will generate a random one as
+        needed.  Keys may contain only alphanumeric characters.
+       </para>
+       <para>
+        This option is primarily intended for testing purposes and other
+        scenarios that require repeatable output (e.g., comparing dump files).
+        It is not recommended for general use, as a malicious server with
+        advance knowledge of the key may be able to inject arbitrary code that
+        will be executed on the machine that runs
+        <application>psql</application> with the dump output.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
        <term><option>--section=<replaceable class="parameter">sectionname</replaceable></option></term>
        <listitem>
 
    pg_upgrade supports upgrades from 9.2.X and later to the current
    major release of <productname>PostgreSQL</productname>, including snapshot and beta releases.
   </para>
+
+  <warning>
+   <para>
+    Upgrading a cluster causes the destination to execute arbitrary code of the
+    source superusers' choice.  Ensure that the source superusers are trusted
+    before upgrading.
+   </para>
+  </warning>
  </refsect1>
 
  <refsect1>
 
       </varlistentry>
 
 
+      <varlistentry>
+        <term><literal>\restrict <replaceable class="parameter">restrict_key</replaceable></literal></term>
+        <listitem>
+        <para>
+        Enter "restricted" mode with the provided key.  In this mode, the only
+        allowed meta-command is <command>\unrestrict</command>, to exit
+        restricted mode.  The key may contain only alphanumeric characters.
+        </para>
+        <para>
+        This command is primarily intended for use in plain-text dumps
+        generated by <application>pg_dump</application>,
+        <application>pg_dumpall</application>, and
+        <application>pg_restore</application>, but it may be useful elsewhere.
+        </para>
+        </listitem>
+      </varlistentry>
+
+
       <varlistentry>
         <term><literal>\s [ <replaceable class="parameter">filename</replaceable> ]</literal></term>
         <listitem>
       </varlistentry>
 
 
+      <varlistentry>
+        <term><literal>\unrestrict <replaceable class="parameter">restrict_key</replaceable></literal></term>
+        <listitem>
+        <para>
+        Exit "restricted" mode (i.e., where all other meta-commands are
+        blocked), provided the specified key matches the one given to
+        <command>\restrict</command> when restricted mode was entered.
+        </para>
+        <para>
+        This command is primarily intended for use in plain-text dumps
+        generated by <application>pg_dump</application>,
+        <application>pg_dumpall</application>, and
+        <application>pg_restore</application>, but it may be useful elsewhere.
+        </para>
+        </listitem>
+      </varlistentry>
+
+
       <varlistentry>
         <term><literal>\unset <replaceable class="parameter">name</replaceable></literal></term>
 
 
 #include "dumputils.h"
 #include "fe_utils/string_utils.h"
 
+static const char restrict_chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
 
 static bool parseAclItem(const char *item, const char *type,
                         const char *name, const char *subname, int remoteVersion,
 
    pg_free(mine);
 }
+
+/*
+ * Generates a valid restrict key (i.e., an alphanumeric string) for use with
+ * psql's \restrict and \unrestrict meta-commands.  For safety, the value is
+ * chosen at random.
+ */
+char *
+generate_restrict_key(void)
+{
+   uint8       buf[64];
+   char       *ret = palloc(sizeof(buf));
+
+   if (!pg_strong_random(buf, sizeof(buf)))
+       return NULL;
+
+   for (int i = 0; i < sizeof(buf) - 1; i++)
+   {
+       uint8       idx = buf[i] % strlen(restrict_chars);
+
+       ret[i] = restrict_chars[idx];
+   }
+   ret[sizeof(buf) - 1] = '\0';
+
+   return ret;
+}
+
+/*
+ * Checks that a given restrict key (intended for use with psql's \restrict and
+ * \unrestrict meta-commands) contains only alphanumeric characters.
+ */
+bool
+valid_restrict_key(const char *restrict_key)
+{
+   return restrict_key != NULL &&
+       restrict_key[0] != '\0' &&
+       strspn(restrict_key, restrict_chars) == strlen(restrict_key);
+}
 
                                   const char *type2, const char *name2,
                                   PQExpBuffer buf);
 
+extern char *generate_restrict_key(void);
+extern bool valid_restrict_key(const char *restrict_key);
+
 #endif                         /* DUMPUTILS_H */
 
    int         enable_row_security;
    int         sequence_data;  /* dump sequence data even in schema-only mode */
    int         binary_upgrade;
+
+   char       *restrict_key;
 } RestoreOptions;
 
 typedef struct _dumpOptions
 
    int         sequence_data;  /* dump sequence data even in schema-only mode */
    int         do_nothing;
+
+   char       *restrict_key;
 } DumpOptions;
 
 /*
 
    dopt->include_everything = ropt->include_everything;
    dopt->enable_row_security = ropt->enable_row_security;
    dopt->sequence_data = ropt->sequence_data;
+   dopt->restrict_key = ropt->restrict_key ? pg_strdup(ropt->restrict_key) : NULL;
 
    return dopt;
 }
 
    ahprintf(AH, "--\n-- PostgreSQL database dump\n--\n\n");
 
+   /*
+    * If generating plain-text output, enter restricted mode to block any
+    * unexpected psql meta-commands.  A malicious source might try to inject
+    * a variety of things via bogus responses to queries.  While we cannot
+    * prevent such sources from affecting the destination at restore time, we
+    * can block psql meta-commands so that the client machine that runs psql
+    * with the dump output remains unaffected.
+    */
+   if (ropt->restrict_key)
+       ahprintf(AH, "\\restrict %s\n\n", ropt->restrict_key);
+
    if (AH->archiveRemoteVersion)
        ahprintf(AH, "-- Dumped from database version %s\n",
                 AH->archiveRemoteVersion);
 
    ahprintf(AH, "--\n-- PostgreSQL database dump complete\n--\n\n");
 
+   /*
+    * If generating plain-text output, exit restricted mode at the very end
+    * of the script. This is not pro forma; in particular, pg_dumpall
+    * requires this when transitioning from one database to another.
+    */
+   if (ropt->restrict_key)
+       ahprintf(AH, "\\unrestrict %s\n\n", ropt->restrict_key);
+
    /*
     * Clean up & we're done.
     */
    else
    {
        PQExpBufferData connectbuf;
+       RestoreOptions *ropt = AH->public.ropt;
+
+       /*
+        * We must temporarily exit restricted mode for \connect, etc.
+        * Anything added between this line and the following \restrict must
+        * be careful to avoid any possible meta-command injection vectors.
+        */
+       ahprintf(AH, "\\unrestrict %s\n", ropt->restrict_key);
 
        initPQExpBuffer(&connectbuf);
        appendPsqlMetaConnect(&connectbuf, dbname);
-       ahprintf(AH, "%s\n", connectbuf.data);
+       ahprintf(AH, "%s", connectbuf.data);
        termPQExpBuffer(&connectbuf);
+
+       ahprintf(AH, "\\restrict %s\n\n", ropt->restrict_key);
    }
 
    /*
 
        {"on-conflict-do-nothing", no_argument, &dopt.do_nothing, 1},
        {"rows-per-insert", required_argument, NULL, 10},
        {"include-foreign-data", required_argument, NULL, 11},
+       {"restrict-key", required_argument, NULL, 25},
 
        {NULL, 0, NULL, 0}
    };
                                          optarg);
                break;
 
+           case 25:
+               dopt.restrict_key = pg_strdup(optarg);
+               break;
+
            default:
                /* getopt_long already emitted a complaint */
                pg_log_error_hint("Try \"%s --help\" for more information.", progname);
 
    /* archiveFormat specific setup */
    if (archiveFormat == archNull)
+   {
        plainText = 1;
 
+       /*
+        * If you don't provide a restrict key, one will be appointed for you.
+        */
+       if (!dopt.restrict_key)
+           dopt.restrict_key = generate_restrict_key();
+       if (!dopt.restrict_key)
+           pg_fatal("could not generate restrict key");
+       if (!valid_restrict_key(dopt.restrict_key))
+           pg_fatal("invalid restrict key");
+   }
+   else if (dopt.restrict_key)
+       pg_fatal("option --restrict-key can only be used with --format=plain");
+
    /* Custom and directory formats are compressed by default, others not */
    if (compressLevel == -1)
    {
    ropt->enable_row_security = dopt.enable_row_security;
    ropt->sequence_data = dopt.sequence_data;
    ropt->binary_upgrade = dopt.binary_upgrade;
+   ropt->restrict_key = dopt.restrict_key ? pg_strdup(dopt.restrict_key) : NULL;
 
    if (compressLevel == -1)
        ropt->compression = 0;
    printf(_("  --no-unlogged-table-data     do not dump unlogged table data\n"));
    printf(_("  --on-conflict-do-nothing     add ON CONFLICT DO NOTHING to INSERT commands\n"));
    printf(_("  --quote-all-identifiers      quote all identifiers, even if not key words\n"));
+   printf(_("  --restrict-key=RESTRICT_KEY  use provided string as psql \\restrict key\n"));
    printf(_("  --rows-per-insert=NROWS      number of rows per INSERT; implies --inserts\n"));
    printf(_("  --section=SECTION            dump named section (pre-data, data, or post-data)\n"));
    printf(_("  --serializable-deferrable    wait until the dump can run without anomalies\n"));
 
 static SimpleStringList database_exclude_patterns = {NULL, NULL};
 static SimpleStringList database_exclude_names = {NULL, NULL};
 
+static char *restrict_key;
+
 #define exit_nicely(code) exit(code)
 
 int
        {"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1},
        {"on-conflict-do-nothing", no_argument, &on_conflict_do_nothing, 1},
        {"rows-per-insert", required_argument, NULL, 7},
+       {"restrict-key", required_argument, NULL, 9},
 
        {NULL, 0, NULL, 0}
    };
                appendShellString(pgdumpopts, optarg);
                break;
 
+           case 9:
+               restrict_key = pg_strdup(optarg);
+               appendPQExpBufferStr(pgdumpopts, " --restrict-key ");
+               appendShellString(pgdumpopts, optarg);
+               break;
+
            default:
                /* getopt_long already emitted a complaint */
                pg_log_error_hint("Try \"%s --help\" for more information.", progname);
    if (on_conflict_do_nothing)
        appendPQExpBufferStr(pgdumpopts, " --on-conflict-do-nothing");
 
+   /*
+    * If you don't provide a restrict key, one will be appointed for you.
+    */
+   if (!restrict_key)
+       restrict_key = generate_restrict_key();
+   if (!restrict_key)
+       pg_fatal("could not generate restrict key");
+   if (!valid_restrict_key(restrict_key))
+       pg_fatal("invalid restrict key");
+
    /*
     * If there was a database specified on the command line, use that,
     * otherwise try to connect to database "postgres", and failing that
    if (verbose)
        dumpTimestamp("Started on");
 
+   /*
+    * Enter restricted mode to block any unexpected psql meta-commands.  A
+    * malicious source might try to inject a variety of things via bogus
+    * responses to queries.  While we cannot prevent such sources from
+    * affecting the destination at restore time, we can block psql
+    * meta-commands so that the client machine that runs psql with the dump
+    * output remains unaffected.
+    */
+   fprintf(OPF, "\\restrict %s\n\n", restrict_key);
+
    /*
     * We used to emit \connect postgres here, but that served no purpose
     * other than to break things for installations without a postgres
            dumpTablespaces(conn);
    }
 
+   /*
+    * Exit restricted mode just before dumping the databases.  pg_dump will
+    * handle entering restricted mode again as appropriate.
+    */
+   fprintf(OPF, "\\unrestrict %s\n\n", restrict_key);
+
    if (!globals_only && !roles_only && !tablespaces_only)
        dumpDatabases(conn);
 
    printf(_("  --no-unlogged-table-data     do not dump unlogged table data\n"));
    printf(_("  --on-conflict-do-nothing     add ON CONFLICT DO NOTHING to INSERT commands\n"));
    printf(_("  --quote-all-identifiers      quote all identifiers, even if not key words\n"));
+   printf(_("  --restrict-key=RESTRICT_KEY  use provided string as psql \\restrict key\n"));
    printf(_("  --rows-per-insert=NROWS      number of rows per INSERT; implies --inserts\n"));
    printf(_("  --use-set-session-authorization\n"
             "                               use SET SESSION AUTHORIZATION commands instead of\n"
 
        {"no-publications", no_argument, &no_publications, 1},
        {"no-security-labels", no_argument, &no_security_labels, 1},
        {"no-subscriptions", no_argument, &no_subscriptions, 1},
+       {"restrict-key", required_argument, NULL, 6},
 
        {NULL, 0, NULL, 0}
    };
                set_dump_section(optarg, &(opts->dumpSections));
                break;
 
+           case 6:
+               opts->restrict_key = pg_strdup(optarg);
+               break;
+
            default:
                /* getopt_long already emitted a complaint */
                pg_log_error_hint("Try \"%s --help\" for more information.", progname);
            pg_log_error_hint("Try \"%s --help\" for more information.", progname);
            exit_nicely(1);
        }
+
+       if (opts->restrict_key)
+           pg_fatal("options -d/--dbname and --restrict-key cannot be used together");
+
        opts->useDB = 1;
    }
+   else
+   {
+       /*
+        * If you don't provide a restrict key, one will be appointed for you.
+        */
+       if (!opts->restrict_key)
+           opts->restrict_key = generate_restrict_key();
+       if (!opts->restrict_key)
+           pg_fatal("could not generate restrict key");
+       if (!valid_restrict_key(opts->restrict_key))
+           pg_fatal("invalid restrict key");
+   }
 
    if (opts->dataOnly && opts->schemaOnly)
        pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together");
    printf(_("  --no-subscriptions           do not restore subscriptions\n"));
    printf(_("  --no-table-access-method     do not restore table access methods\n"));
    printf(_("  --no-tablespaces             do not restore tablespace assignments\n"));
+   printf(_("  --restrict-key=RESTRICT_KEY  use provided string as psql \\restrict key\n"));
    printf(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
    printf(_("  --strict-names               require table and/or schema include patterns to\n"
             "                               match at least one entity each\n"));
 
 
 # This is where the actual tests are defined.
 my %tests = (
+   'restrict' => {
+       all_runs => 1,
+       regexp => qr/^\\restrict [a-zA-Z0-9]+$/m,
+   },
+
+   'unrestrict' => {
+       all_runs => 1,
+       regexp => qr/^\\unrestrict [a-zA-Z0-9]+$/m,
+   },
+
    'ALTER DEFAULT PRIVILEGES FOR ROLE regress_dump_test_role GRANT' => {
        create_order => 14,
        create_sql   => 'ALTER DEFAULT PRIVILEGES
    },
 
    'ALTER TABLE measurement PRIMARY KEY' => {
-       all_runs     => 1,
        catch_all    => 'CREATE ... commands',
        create_order => 93,
        create_sql =>
    },
 
    'ALTER INDEX ... ATTACH PARTITION (primary key)' => {
-       all_runs  => 1,
        catch_all => 'CREATE ... commands',
        regexp    => qr/^
        \QALTER INDEX dump_test.measurement_pkey ATTACH PARTITION dump_test_second_schema.measurement_y2006m2_pkey\E
            next;
        }
 
-       # Run the test listed as a like, unless it is specifically noted
-       # as an unlike (generally due to an explicit exclusion or similar).
-       if ($tests{$test}->{like}->{$test_key}
+       # Run the test if all_runs is set or if listed as a like, unless it is
+       # specifically noted as an unlike (generally due to an explicit
+       # exclusion or similar).
+       if (($tests{$test}->{like}->{$test_key} || $tests{$test}->{all_runs})
            && !defined($tests{$test}->{unlike}->{$test_key}))
        {
            if (!ok($output_file =~ $tests{$test}->{regexp},
 
 # that we need to use pg_dumpall from the new node here.
 my @dump_command = (
    'pg_dumpall', '--no-sync', '-d', $oldnode->connstr('postgres'),
+   '--restrict-key=test',
    '-f', $dump1_file);
 # --extra-float-digits is needed when upgrading from a version older than 11.
 push(@dump_command, '--extra-float-digits', '0')
 # Second dump from the upgraded instance.
 @dump_command = (
    'pg_dumpall', '--no-sync', '-d', $newnode->connstr('postgres'),
+   '--restrict-key=test',
    '-f', $dump2_file);
 # --extra-float-digits is needed when upgrading from a version older than 11.
 push(@dump_command, '--extra-float-digits', '0')
 
 static backslashResult exec_command_quit(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_reset(PsqlScanState scan_state, bool active_branch,
                                          PQExpBuffer query_buf);
+static backslashResult exec_command_restrict(PsqlScanState scan_state, bool active_branch,
+                                            const char *cmd);
 static backslashResult exec_command_s(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_set(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_setenv(PsqlScanState scan_state, bool active_branch,
 static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_timing(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_unrestrict(PsqlScanState scan_state, bool active_branch,
+                                              const char *cmd);
 static backslashResult exec_command_unset(PsqlScanState scan_state, bool active_branch,
                                          const char *cmd);
 static backslashResult exec_command_write(PsqlScanState scan_state, bool active_branch,
 static void checkWin32Codepage(void);
 #endif
 
+static bool restricted;
+static char *restrict_key;
 
 
 /*----------
    /* Parse off the command name */
    cmd = psql_scan_slash_command(scan_state);
 
-   /* And try to execute it */
-   status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf);
+   /*
+    * And try to execute it.
+    *
+    * If we are in "restricted" mode, the only allowable backslash command is
+    * \unrestrict (to exit restricted mode).
+    */
+   if (restricted && strcmp(cmd, "unrestrict") != 0)
+   {
+       pg_log_error("backslash commands are restricted; only \\unrestrict is allowed");
+       status = PSQL_CMD_ERROR;
+   }
+   else
+       status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf);
 
    if (status == PSQL_CMD_UNKNOWN)
    {
        status = exec_command_quit(scan_state, active_branch);
    else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
        status = exec_command_reset(scan_state, active_branch, query_buf);
+   else if (strcmp(cmd, "restrict") == 0)
+       status = exec_command_restrict(scan_state, active_branch, cmd);
    else if (strcmp(cmd, "s") == 0)
        status = exec_command_s(scan_state, active_branch);
    else if (strcmp(cmd, "set") == 0)
        status = exec_command_T(scan_state, active_branch);
    else if (strcmp(cmd, "timing") == 0)
        status = exec_command_timing(scan_state, active_branch);
+   else if (strcmp(cmd, "unrestrict") == 0)
+       status = exec_command_unrestrict(scan_state, active_branch, cmd);
    else if (strcmp(cmd, "unset") == 0)
        status = exec_command_unset(scan_state, active_branch, cmd);
    else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
    return PSQL_CMD_SKIP_LINE;
 }
 
+/*
+ * \restrict -- enter "restricted mode" with the provided key
+ */
+static backslashResult
+exec_command_restrict(PsqlScanState scan_state, bool active_branch,
+                     const char *cmd)
+{
+   if (active_branch)
+   {
+       char       *opt;
+
+       Assert(!restricted);
+
+       opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+       if (opt == NULL || opt[0] == '\0')
+       {
+           pg_log_error("\\%s: missing required argument", cmd);
+           return PSQL_CMD_ERROR;
+       }
+
+       restrict_key = pstrdup(opt);
+       restricted = true;
+   }
+   else
+       ignore_slash_options(scan_state);
+
+   return PSQL_CMD_SKIP_LINE;
+}
+
 /*
  * \s -- save history in a file or show it on the screen
  */
    return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
 }
 
+/*
+ * \unrestrict -- exit "restricted mode" if provided key matches
+ */
+static backslashResult
+exec_command_unrestrict(PsqlScanState scan_state, bool active_branch,
+                       const char *cmd)
+{
+   if (active_branch)
+   {
+       char       *opt;
+
+       opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+       if (opt == NULL || opt[0] == '\0')
+       {
+           pg_log_error("\\%s: missing required argument", cmd);
+           return PSQL_CMD_ERROR;
+       }
+
+       if (!restricted)
+       {
+           pg_log_error("\\%s: not currently in restricted mode", cmd);
+           return PSQL_CMD_ERROR;
+       }
+       else if (strcmp(opt, restrict_key) == 0)
+       {
+           pfree(restrict_key);
+           restricted = false;
+       }
+       else
+       {
+           pg_log_error("\\%s: wrong key", cmd);
+           return PSQL_CMD_ERROR;
+       }
+   }
+   else
+       ignore_slash_options(scan_state);
+
+   return PSQL_CMD_SKIP_LINE;
+}
+
 /*
  * \unset -- unset variable
  */
 
    HELP0("  \\gset [PREFIX]         execute query and store result in psql variables\n");
    HELP0("  \\gx [(OPTIONS)] [FILE] as \\g, but forces expanded output mode\n");
    HELP0("  \\q                     quit psql\n");
+   HELP0("  \\restrict RESTRICT_KEY\n"
+         "                         enter restricted mode with provided key\n");
+   HELP0("  \\unrestrict RESTRICT_KEY\n"
+         "                         exit restricted mode if key matches\n");
    HELP0("  \\watch [SEC]           execute query every SEC seconds\n");
    HELP0("\n");
 
 
    'client-side error commits transaction, no ON_ERROR_STOP and multiple -c switches'
 );
 
+psql_fails_like(
+   $node,
+   qq{\\restrict test
+\\! should_fail},
+   qr/backslash commands are restricted; only \\unrestrict is allowed/,
+   'meta-command in restrict mode fails');
+
 done_testing();
 
        "\\out",
        "\\password", "\\print", "\\prompt", "\\pset",
        "\\qecho", "\\quit",
-       "\\reset",
+       "\\reset", "\\restrict",
        "\\s", "\\set", "\\setenv", "\\sf", "\\sv",
        "\\t", "\\T", "\\timing",
-       "\\unset",
+       "\\unrestrict", "\\unset",
        "\\x",
        "\\warn", "\\watch", "\\write",
        "\\z",
 
 command_ok(
    [
        'pg_dumpall', '-f', $outputdir . '/primary.dump',
+       '--restrict-key=test',
        '--no-sync',  '-p', $node_primary->port,
        '--no-unlogged-table-data'    # if unlogged, standby has schema only
    ],
 command_ok(
    [
        'pg_dumpall', '-f', $outputdir . '/standby.dump',
+       '--restrict-key=test',
        '--no-sync', '-p', $node_standby_1->port
    ],
    'dump standby server');
        ('--schema', 'pg_catalog'),
        ('-f', $outputdir . '/catalogs_primary.dump'),
        '--no-sync',
+       '--restrict-key=test',
        ('-p', $node_primary->port),
        '--no-unlogged-table-data',
        'regression'
        ('--schema', 'pg_catalog'),
        ('-f', $outputdir . '/catalogs_standby.dump'),
        '--no-sync',
+       '--restrict-key=test',
        ('-p', $node_standby_1->port),
        'regression'
    ],
 
    \pset arg1 arg2
    \q
    \reset
+   \restrict test
    \s arg1
    \set arg1 arg2 arg3 arg4 arg5 arg6 arg7
    \setenv arg1 arg2
    \t arg1
    \T arg1
    \timing arg1
+   \unrestrict not_valid
    \unset arg1
    \w arg1
    \watch arg1
 
    \pset arg1 arg2
    \q
    \reset
+   \restrict test
    \s arg1
    \set arg1 arg2 arg3 arg4 arg5 arg6 arg7
    \setenv arg1 arg2
    \t arg1
    \T arg1
    \timing arg1
+   \unrestrict not_valid
    \unset arg1
    \w arg1
    \watch arg1