Disallow setting embed query param using set_query_param method
diff --git a/lib/streamlit/commands/query_params.py b/lib/streamlit/commands/query_params.py
index 7750ee8c2afe..b15e753aa1d6 100644
--- a/lib/streamlit/commands/query_params.py
+++ b/lib/streamlit/commands/query_params.py
@@ -15,10 +15,16 @@
 import urllib.parse as parse
 from typing import Any, Dict, List
 
+from streamlit import util
+from streamlit.errors import StreamlitAPIException
 from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
 from streamlit.runtime.metrics_util import gather_metrics
 from streamlit.runtime.scriptrunner import get_script_run_ctx
 
+EMBED_QUERY_PARAM = "embed"
+EMBED_OPTIONS_QUERY_PARAM = "embed_options"
+EMBED_QUERY_PARAMS_KEYS = [EMBED_QUERY_PARAM, EMBED_OPTIONS_QUERY_PARAM]
+
 
 @gather_metrics("experimental_get_query_params")
 def get_query_params() -> Dict[str, List[str]]:
@@ -50,13 +56,19 @@ def get_query_params() -> Dict[str, List[str]]:
     ctx = get_script_run_ctx()
     if ctx is None:
         return {}
-    return parse.parse_qs(ctx.query_string)
+    # Return new query params dict, but without embed, embed_options query params
+    return util.exclude_key_query_params(
+        parse.parse_qs(ctx.query_string), keys_to_exclude=EMBED_QUERY_PARAMS_KEYS
+    )
 
 
 @gather_metrics("experimental_set_query_params")
 def set_query_params(**query_params: Any) -> None:
     """Set the query parameters that are shown in the browser's URL bar.
 
+    .. warning::
+        Query param `embed` cannot be set using this method.
+
     Parameters
     ----------
     **query_params : dict
@@ -80,7 +92,49 @@ def set_query_params(**query_params: Any) -> None:
     ctx = get_script_run_ctx()
     if ctx is None:
         return
-    ctx.query_string = parse.urlencode(query_params, doseq=True)
+
     msg = ForwardMsg()
-    msg.page_info_changed.query_string = ctx.query_string
+    msg.page_info_changed.query_string = _ensure_no_embed_params(
+        query_params, ctx.query_string
+    )
     ctx.enqueue(msg)
+
+
+def _ensure_no_embed_params(
+    query_params: Dict[str, List[str]], query_string: str
+) -> str:
+    """Ensures there are no embed params set (raises StreamlitAPIException) if there is a try,
+    also makes sure old param values in query_string are preserved. Returns query_string : str."""
+    # Get query params dict without embed, embed_options params
+    query_params_without_embed = util.exclude_key_query_params(
+        query_params, keys_to_exclude=EMBED_QUERY_PARAMS_KEYS
+    )
+    if query_params != query_params_without_embed:
+        raise StreamlitAPIException(
+            "Query param embed and embed_options (case-insensitive) cannot be set using set_query_params method."
+        )
+
+    all_current_params = parse.parse_qs(query_string)
+    current_embed_params = parse.urlencode(
+        {
+            EMBED_QUERY_PARAM: [
+                param
+                for param in util.extract_key_query_params(
+                    all_current_params, param_key=EMBED_QUERY_PARAM
+                )
+            ],
+            EMBED_OPTIONS_QUERY_PARAM: [
+                param
+                for param in util.extract_key_query_params(
+                    all_current_params, param_key=EMBED_OPTIONS_QUERY_PARAM
+                )
+            ],
+        },
+        doseq=True,
+    )
+    query_string = parse.urlencode(query_params, doseq=True)
+
+    if query_string:
+        separator = "&" if current_embed_params else ""
+        return separator.join([query_string, current_embed_params])
+    return current_embed_params
diff --git a/lib/streamlit/util.py b/lib/streamlit/util.py
index b841b661b059..17ad69c2b8ef 100644
--- a/lib/streamlit/util.py
+++ b/lib/streamlit/util.py
@@ -18,7 +18,7 @@
 import hashlib
 import os
 import subprocess
-from typing import Any, Dict, Iterable, List, Mapping, TypeVar
+from typing import Any, Dict, Iterable, List, Mapping, Set, TypeVar
 
 from typing_extensions import Final
 
@@ -154,3 +154,31 @@ def calc_md5(s: str) -> str:
     h = hashlib.new("md5")
     h.update(s.encode("utf-8"))
     return h.hexdigest()
+
+
+def exclude_key_query_params(
+    query_params: Dict[str, List[str]], keys_to_exclude: List[str]
+) -> Dict[str, List[str]]:
+    """Returns new object query_params : Dict[str, List[str]], but without keys defined with keys_to_drop : List[str]."""
+    return {
+        key: value
+        for key, value in query_params.items()
+        if key.lower() not in keys_to_exclude
+    }
+
+
+def extract_key_query_params(
+    query_params: Dict[str, List[str]], param_key: str
+) -> Set[str]:
+    """Extracts key (case-insensitive) query params from Dict, and returns them as Set of str."""
+    return set(
+        [
+            item.lower()
+            for sublist in [
+                [value.lower() for value in query_params[key]]
+                for key in query_params.keys()
+                if key.lower() == param_key and query_params.get(key)
+            ]
+            for item in sublist
+        ]
+    )
diff --git a/lib/tests/streamlit/streamlit_test.py b/lib/tests/streamlit/streamlit_test.py
index 39f2c74fcbbd..95df5ca9abea 100644
--- a/lib/tests/streamlit/streamlit_test.py
+++ b/lib/tests/streamlit/streamlit_test.py
@@ -683,6 +683,19 @@ def test_st_warning_with_icon(self):
         self.assertEqual(el.alert.icon, "⚠️")
         self.assertEqual(el.alert.format, Alert.WARNING)
 
+    def test_set_query_params_sends_protobuf_message(self):
+        """Test valid st.set_query_params sends protobuf message."""
+        st.experimental_set_query_params(x="a")
+        message = self.get_message_from_queue(0)
+        self.assertEqual(message.page_info_changed.query_string, "x=a")
+
+    def test_set_query_params_exceptions(self):
+        """Test invalid st.set_query_params raises exceptions."""
+        with self.assertRaises(StreamlitAPIException):
+            st.experimental_set_query_params(embed="True")
+        with self.assertRaises(StreamlitAPIException):
+            st.experimental_set_query_params(embed_options="show_colored_line")
+
     @parameterized.expand([(st.error,), (st.warning,), (st.info,), (st.success,)])
     def test_st_alert_exceptions(self, alert_func):
         """Test that alert functions throw an exception when a non-emoji is given as an icon."""
diff --git a/lib/tests/streamlit/util_test.py b/lib/tests/streamlit/util_test.py
index 27a86b151afb..3f10a3be121c 100644
--- a/lib/tests/streamlit/util_test.py
+++ b/lib/tests/streamlit/util_test.py
@@ -14,6 +14,7 @@
 
 import random
 import unittest
+from typing import Dict, List, Set
 from unittest.mock import patch
 
 import numpy as np
@@ -123,3 +124,60 @@ def test_successful_index_(self, input, find_value, expected_index):
     def test_unsuccessful_index_(self, input, find_value):
         with self.assertRaises(ValueError):
             util.index_(input, find_value)
+
+    @parameterized.expand(
+        [
+            ({"x": ["a"]}, ["x"], {}),
+            ({"a": ["a1", "a2"], "b": ["b1", "b2"]}, ["a"], {"b": ["b1", "b2"]}),
+            ({"c": ["c1", "c2"]}, "no_existing_key", {"c": ["c1", "c2"]}),
+            (
+                {
+                    "embed": ["true"],
+                    "embed_options": ["show_padding", "show_colored_line"],
+                },
+                ["embed", "embed_options"],
+                {},
+            ),
+            (
+                {"EMBED": ["TRUE"], "EMBED_OPTIONS": ["DISABLE_SCROLLING"]},
+                ["embed", "embed_options"],
+                {},
+            ),
+        ]
+    )
+    def test_drop_key_query_params(
+        self,
+        query_params: Dict[str, List[str]],
+        keys_to_drop: List[str],
+        result: Dict[str, List[str]],
+    ):
+        self.assertDictEqual(
+            util.exclude_key_query_params(query_params, keys_to_drop), result
+        )
+
+    @parameterized.expand(
+        [
+            ({"x": ["a"]}, "x", {"a"}),
+            ({"a": ["a1"], "b": ["b1", "b2"]}, "a", {"a1"}),
+            ({"c": ["c1", "c2"]}, "no_existing_key", set()),
+            (
+                {
+                    "embed": ["true"],
+                    "embed_options": ["show_padding", "show_colored_line"],
+                },
+                "embed",
+                {"true"},
+            ),
+            (
+                {"EMBED": ["TRUE"], "EMBED_OPTIONS": ["DISABLE_SCROLLING"]},
+                "embed_options",
+                {"disable_scrolling"},
+            ),
+        ]
+    )
+    def test_extract_key_query_params(
+        self, query_params: Dict[str, List[str]], param_key: str, result: Set[str]
+    ):
+        self.assertSetEqual(
+            util.extract_key_query_params(query_params, param_key), result
+        )

<!--
Before contributing (PLEASE READ!)

⚠️ If your contribution is more than a few lines of code, then prior to starting to code on it please post in the issue saying you want to volunteer, then wait for a positive response. And if there is no issue for it yet, create it first.

This helps make sure:

  1. Two people aren't working on the same thing
  2. This is something Streamlit's maintainers believe should be implemented/fixed
  3. Any API, UI, or deeper architectural changes that need to be implemented have been fully thought through by Streamlit's maintainers
  4. Your time is well spent!

More information in our wiki: https://github.com/streamlit/streamlit/wiki/Contributing
-->

## 📚 Context

_Please describe the project or issue background here_

This is a proposal to disallow setting `embed` and `embed_options` query params using `set_query_param` method.

- What kind of change does this PR introduce?

  - [ ] Bugfix
  - [ ] Feature
  - [ ] Refactoring
  - [x] Other, please describe:
Disable setting `embed` query param, when someone embeds the Streamlit app, one might not want the embedded stuff to have enabled scrollbar and remote Streamlit code should not be able to re-enable it overriding embed params.

## 🧠 Description of Changes

- _Add bullet points summarizing your changes here_

  - [ ] This is a breaking API change
  - [ ] This is a visible (user-facing) change

**Revised:**

_Insert screenshot of your updated UI/code here_

**Current:**

_Insert screenshot of existing UI/code here_

## 🧪 Testing Done

- [ ] Screenshots included
- [ ] Added/Updated unit tests
- [ ] Added/Updated e2e tests

## 🌐 References

_Does this depend on other work, documents, or tickets?_

- **Issue**: Closes #XXXX

---

**Contribution License Agreement**

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.
