From cc9db4cf6f4da59640ca277e028481d7a787dad6 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 20 May 2026 11:44:08 -0400 Subject: [PATCH 1/6] Bug 2035372 --- Bugzilla/Bug.pm | 23 +++++++++++++++++-- Bugzilla/Constants.pm | 6 +++++ Bugzilla/Template.pm | 5 ---- Bugzilla/WebService/Constants.pm | 7 +++--- .../bug_modal/activity_stream.html.tmpl | 6 ++++- .../en/default/bug_modal/edit.html.tmpl | 17 ++++++++++---- extensions/BugModal/web/bug_modal.js | 19 --------------- .../hook/bug/comments-aftercomments.html.tmpl | 8 ++++--- qa/t/webservice_bug_create.t | 16 ++++++++++++- template/en/default/bug/edit.html.tmpl | 23 ++++++++----------- .../en/default/global/user-error.html.tmpl | 6 +++++ 11 files changed, 84 insertions(+), 52 deletions(-) diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index 9a84fb4467..e6e7f29d56 100644 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -1605,14 +1605,33 @@ sub _check_bug_file_loc { my ($invocant, $url) = @_; return '' if !defined $url; $url = trim($url); + return '' if $url eq ''; # On bug entry, if bug_file_loc is "http://", the default, use an # empty value instead. However, on bug editing people can set that # back if they *really* want to. if (!ref $invocant && $url eq 'http://') { - $url = ''; + return ''; } - return $url; + + # Allowlist scheme check — mirrors the is_safe_url template helper so + # what passes here is exactly what renders as a clickable link. + my $safe_url_regexp = SAFE_URL_REGEXP(); + return $url if $url =~ /^$safe_url_regexp$/; + + # Colon-free relative path / local reference (matches is_safe_url + # second branch in Bugzilla/Template.pm). + return $url if $url =~ /^[^\s<>\":]+[\w\/]$/i; + + # Allow an already-stored unsafe value to pass through unchanged so that + # edits to other fields on the bug are not blocked. Unsafe values are + # rendered as plain text by templates; a DB cleanup script will scrub them. + if (ref $invocant) { + my $current = $invocant->{bug_file_loc} // ''; + return $url if $url eq $current; + } + + ThrowUserError('bug_file_loc_invalid', {url => $url}); } sub _check_bug_status { diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm index 6155d985ff..c4c86a09ac 100644 --- a/Bugzilla/Constants.pm +++ b/Bugzilla/Constants.pm @@ -171,6 +171,7 @@ use Memoize; MAX_STS_AGE SAFE_PROTOCOLS + SAFE_URL_REGEXP LEGAL_CONTENT_TYPES MIN_SMALLINT @@ -490,6 +491,11 @@ use constant SAFE_PROTOCOLS => ( 'telnet', 'wais' ); +sub SAFE_URL_REGEXP { + my $safe_protocols = join('|', SAFE_PROTOCOLS); + return qr/($safe_protocols):[^:\s<>\"][^\s<>\"]+[\w\/]/i; +} + # Valid MIME types for attachments. use constant LEGAL_CONTENT_TYPES => ( 'application', 'audio', 'font', 'image', 'message', diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm index 4e7afb4d5e..f51496bce9 100644 --- a/Bugzilla/Template.pm +++ b/Bugzilla/Template.pm @@ -52,11 +52,6 @@ use constant FORMAT_2_SIZE => [19, 55]; our %SHARED_PROVIDERS; our $COLOR_QUOTES = 1; -# Pseudo-constant. -sub SAFE_URL_REGEXP { - my $safe_protocols = join('|', SAFE_PROTOCOLS); - return qr/($safe_protocols):[^:\s<>\"][^\s<>\"]+[\w\/]/i; -} sub is_safe_url { my $url = shift; diff --git a/Bugzilla/WebService/Constants.pm b/Bugzilla/WebService/Constants.pm index 10c42df9d1..349b2d83dc 100644 --- a/Bugzilla/WebService/Constants.pm +++ b/Bugzilla/WebService/Constants.pm @@ -126,9 +126,10 @@ use constant WS_ERROR_CODE => { comment_tag_too_long => 127, comment_tag_too_short => 128, - # See Also errors - bug_url_invalid => 112, - bug_url_too_long => 112, + # Bug Url errors + bug_url_invalid => 112, + bug_url_too_long => 112, + bug_file_loc_invalid => 112, # Insidergroup Errors user_not_insider => 113, diff --git a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl index aae369b866..5325324f6e 100644 --- a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl @@ -440,9 +440,13 @@ CASE 'bug_file_loc'; %] + [% IF is_safe_url(value) %] [% value FILTER truncate(40) FILTER html %] + [% ELSE %] + [% value FILTER truncate(40) FILTER html %] + [% END %] [% CASE 'see_also'; diff --git a/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl index f61783f92f..b88f906a6a 100755 --- a/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl @@ -1530,10 +1530,17 @@ END; %] [% END %] diff --git a/extensions/BugModal/web/bug_modal.js b/extensions/BugModal/web/bug_modal.js index bdc0416846..99d62b3bb3 100644 --- a/extensions/BugModal/web/bug_modal.js +++ b/extensions/BugModal/web/bug_modal.js @@ -280,25 +280,6 @@ $(function() { $('#attachments tr.attach-obsolete').toggle(); }); - // URL --> unsafe warning - $('.bug-url') - .click(function(event) { - var that = $(this); - event.stopPropagation(); - if (!that.data('safe')) { - event.preventDefault(); - if (confirm('This is considered an unsafe URL and could possibly be harmful. ' + - 'The full URL is:\n\n' + that.attr('href') + '\n\nContinue?')) - { - try { - window.open(that.attr('href')); - } catch(ex) { - alert('Malformed URL'); - } - } - } - }); - // top btn $('#top-btn') .click(function(event) { diff --git a/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl b/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl index 89e3fc57cf..0517c5fe55 100644 --- a/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl +++ b/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl @@ -137,10 +137,12 @@ [% ELSIF change.buglist %] [% value FILTER bug_list_link FILTER js %] [% ELSIF change.fieldname == 'bug_file_loc' %] - [%~%] + [%~ IF is_safe_url(value) ~%] + [%~%] [%~%][% value FILTER ih_short_value FILTER html FILTER js %] + [%~ ELSE ~%] + [%~%][% value FILTER ih_short_value FILTER html FILTER js %] + [%~ END ~%] [% ELSIF change.fieldname == 'see_also' %] [% FOREACH see_also = value %] [% IF see_also.bug_id %] diff --git a/qa/t/webservice_bug_create.t b/qa/t/webservice_bug_create.t index 4aa5ae1750..58a4c5d9be 100644 --- a/qa/t/webservice_bug_create.t +++ b/qa/t/webservice_bug_create.t @@ -13,7 +13,7 @@ use strict; use warnings; use lib qw(lib ../../lib ../../local/lib/perl5); use Storable qw(dclone); -use Test::More tests => 305; +use Test::More tests => 323; use QA::Util; use QA::Tests qw(create_bug_fields PRIVATE_BUG_USER); @@ -143,6 +143,20 @@ my $fields = { invalid => {faultstring => 'you are not allowed to.+comments.+private', value => 1,} }, + url => { + javascript => { + faultstring => 'is not a valid URL for the URL field', + value => 'javascript:alert(document.domain)//', + }, + data => { + faultstring => 'is not a valid URL for the URL field', + value => 'data:text/html,', + }, + vbscript => { + faultstring => 'is not a valid URL for the URL field', + value => 'vbscript:msgbox(1)', + }, + }, }; $jsonrpc_get->bz_call_fail( diff --git a/template/en/default/bug/edit.html.tmpl b/template/en/default/bug/edit.html.tmpl index db15c5f863..9c09bb6b2d 100644 --- a/template/en/default/bug/edit.html.tmpl +++ b/template/en/default/bug/edit.html.tmpl @@ -627,19 +627,16 @@ [% IF bug.check_can_change_field("bug_file_loc", 0, 1) %] - - [% bug.bug_file_loc FILTER truncate(40) FILTER html %] + [% IF is_safe_url(bug.bug_file_loc) %] + + [%~ bug.bug_file_loc FILTER truncate(40) FILTER html ~%] + + [% ELSE %] + + [%~ bug.bug_file_loc FILTER truncate(40) FILTER html ~%] + + [% END %] (edit) [% END %] diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl index 81878be0c1..bc8f138738 100644 --- a/template/en/default/global/user-error.html.tmpl +++ b/template/en/default/global/user-error.html.tmpl @@ -312,6 +312,12 @@ [% type FILTER html %] [% END %]. + [% ELSIF error == "bug_file_loc_invalid" %] + [% title = "Invalid URL" %] + [% url FILTER html %] is not a valid URL for the URL field. + Only safe protocols (http, https, ftp, and similar) are allowed. + Protocols such as "javascript:", "data:", and "vbscript:" are not permitted. + [% ELSIF error == "bug_url_invalid" %] [% title = "Invalid $terms.Bug URL" %] [% url FILTER html %] is not a valid URL to [% terms.abug %]. From c7937644042a67716fc4e30416af655b15fa6ee9 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 20 May 2026 12:40:34 -0400 Subject: [PATCH 2/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../en/default/hook/bug/comments-aftercomments.html.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl b/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl index 0517c5fe55..50efe33d1b 100644 --- a/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl +++ b/extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl @@ -141,7 +141,7 @@ [%~%] [%~%][% value FILTER ih_short_value FILTER html FILTER js %] [%~ ELSE ~%] - [%~%][% value FILTER ih_short_value FILTER html FILTER js %] + [%~%][% value FILTER ih_short_value FILTER html FILTER js %] [%~ END ~%] [% ELSIF change.fieldname == 'see_also' %] [% FOREACH see_also = value %] From 0795f3b78081c001808d86ce4332499168ec206a Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 20 May 2026 12:40:43 -0400 Subject: [PATCH 3/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../template/en/default/bug_modal/activity_stream.html.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl index 5325324f6e..e92999c06c 100644 --- a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl @@ -445,7 +445,7 @@ class="bug-url" data-safe="1" >[% value FILTER truncate(40) FILTER html %] [% ELSE %] - [% value FILTER truncate(40) FILTER html %] + [% value FILTER truncate(40) FILTER html %] [% END %] [% From fc6053f6e378cfb664c6910541d14ca7f56027fe Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 20 May 2026 12:41:16 -0400 Subject: [PATCH 4/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Bugzilla/Bug.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index e6e7f29d56..9145b73f1b 100644 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -1608,8 +1608,9 @@ sub _check_bug_file_loc { return '' if $url eq ''; # On bug entry, if bug_file_loc is "http://", the default, use an - # empty value instead. However, on bug editing people can set that - # back if they *really* want to. + # empty value instead. On bug editing, "http://" is not generally + # accepted by the safe URL validation below; it is only allowed if it + # is already stored on the bug and remains unchanged. if (!ref $invocant && $url eq 'http://') { return ''; } From 33633e651f572995f8da8106e913969b99942ce2 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 20 May 2026 12:41:42 -0400 Subject: [PATCH 5/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../BugModal/template/en/default/bug_modal/edit.html.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl index b88f906a6a..02b25ac646 100755 --- a/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl @@ -1533,11 +1533,11 @@ [% IF is_safe_url(bug.bug_file_loc) %] + class="bug-url"> [%~ link_text FILTER html ~%] [% ELSE %] - [%~ link_text FILTER html ~%] From 7dc800608c1c704f90b641f8d7bad028a990e533 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 20 May 2026 12:46:31 -0400 Subject: [PATCH 6/6] Copilot suggested fixes --- .../template/en/default/bug_modal/activity_stream.html.tmpl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl index e92999c06c..c7413b062f 100644 --- a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl +++ b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl @@ -442,10 +442,9 @@ %] [% IF is_safe_url(value) %] [% value FILTER truncate(40) FILTER html %] + class="bug-url">[% value FILTER truncate(40) FILTER html %] [% ELSE %] - [% value FILTER truncate(40) FILTER html %] + [% value FILTER truncate(40) FILTER html %] [% END %] [%