@@ -25,7 +25,7 @@ def create_action(action_type: GroupRecipeActionType = GroupRecipeActionType.lin
2525 return CreateGroupRecipeAction (
2626 action_type = action_type ,
2727 title = random_string (),
28- url = random_string (),
28+ url = f"https://example.com/ { random_string ()} " ,
2929 )
3030
3131
@@ -194,3 +194,40 @@ def test_group_recipe_actions_trigger_invalid_type(api_client: TestClient, uniqu
194194 )
195195
196196 assert response .status_code == 400
197+
198+
199+ @pytest .mark .parametrize (
200+ "url,should_pass" ,
201+ [
202+ ("https://example.com" , True ),
203+ ("http://example.com" , True ),
204+ ("HTTPS://EXAMPLE.COM" , True ),
205+ ("HTTP://EXAMPLE.COM" , True ),
206+ ("javascript:alert('xss')" , False ),
207+ ("JAVASCRIPT:alert('xss')" , False ),
208+ ("data:text/html,<script>alert('xss')</script>" , False ),
209+ ("file:///etc/passwd" , False ),
210+ ("ftp://example.com" , False ),
211+ ("//example.com" , False ),
212+ ("example.com" , False ),
213+ ],
214+ )
215+ def test_group_recipe_actions_url_scheme_validation (
216+ api_client : TestClient , unique_user : TestUser , url : str , should_pass : bool
217+ ):
218+ """Test that only http and https URLs are allowed to prevent XSS via javascript: URIs."""
219+ action_data = {
220+ "action_type" : "link" ,
221+ "title" : random_string (),
222+ "url" : url ,
223+ }
224+ response = api_client .post (
225+ api_routes .households_recipe_actions ,
226+ json = action_data ,
227+ headers = unique_user .token ,
228+ )
229+
230+ if should_pass :
231+ assert response .status_code == 201
232+ else :
233+ assert response .status_code == 422
0 commit comments