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]