This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/main by this push:
     new 4317afe  Perform some validation of a user controlled URL
4317afe is described below

commit 4317afe39c1db75bf1485b10b81bd0d5fe83368f
Author: Sean B. Palmer <[email protected]>
AuthorDate: Mon Oct 27 20:41:18 2025 +0000

    Perform some validation of a user controlled URL
---
 atr/routes/resolve.py |  8 ++++++++
 atr/web.py            | 24 +++++++++++++++++++++++-
 2 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/atr/routes/resolve.py b/atr/routes/resolve.py
index 023926b..e982e40 100644
--- a/atr/routes/resolve.py
+++ b/atr/routes/resolve.py
@@ -16,6 +16,7 @@
 # under the License.
 
 
+import asfquart.base as base
 import quart
 import werkzeug.wrappers.response as response
 
@@ -29,6 +30,7 @@ import atr.storage as storage
 import atr.tabulate as tabulate
 import atr.template as template
 import atr.util as util
+import atr.web as web
 
 
 class ResolveVoteForm(forms.Typed):
@@ -189,7 +191,13 @@ async def tabulated_selected_post(session: 
route.CommitterSession, project_name:
     fetch_error = None
     if await hidden_form.validate_on_submit():
         # TODO: Just pass the thread_id itself instead?
+        # TODO: The hidden field is user controlled data, so we should HMAC it
+        # Ideally there would be a concept of authenticated hidden fields
+        # Perhaps all hidden fields should be authenticated
+        # We should also still validate all HMACed fields
         archive_url = hidden_form.hidden_field.data or ""
+        if not web.valid_url(archive_url, "lists.apache.org"):
+            raise base.ASFQuartException("Invalid vote thread URL", 
errorcode=400)
         thread_id = archive_url.split("/")[-1]
         if thread_id:
             try:
diff --git a/atr/web.py b/atr/web.py
index a71f7fe..32ae7e7 100644
--- a/atr/web.py
+++ b/atr/web.py
@@ -17,13 +17,13 @@
 
 from __future__ import annotations
 
+import urllib.parse
 from typing import TYPE_CHECKING, Any, Protocol, TypeVar
 
 import asfquart.base as base
 import asfquart.session as session
 import quart
 import werkzeug.datastructures.headers
-import werkzeug.http
 
 import atr.config as config
 import atr.db as db
@@ -242,3 +242,25 @@ async def redirect[R](
     elif error is not None:
         await quart.flash(error, "error")
     return quart.redirect(util.as_url(route, **kwargs))
+
+
+def valid_url(
+    url: str,
+    host: str,
+    scheme: str = "https",
+    allow_params: bool = False,
+    allow_query: bool = False,
+    allow_fragment: bool = False,
+) -> bool:
+    parsed = urllib.parse.urlparse(url)
+    if parsed.scheme != scheme:
+        return False
+    if parsed.netloc != host:
+        return False
+    if (not allow_params) and parsed.params:
+        return False
+    if (not allow_query) and parsed.query:
+        return False
+    if (not allow_fragment) and parsed.fragment:
+        return False
+    return True


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to