Blob Blame History Raw
From e63bdcdb56539bed3e4789721a764f719be2d663 Mon Sep 17 00:00:00 2001
From: Sebastian Riedel <sri@cpan.org>
Date: Tue, 23 Jul 2019 13:48:16 +0200
Subject: [PATCH 1/3] Abstract out markdown handling into a new class

---
 lib/OpenQA/Markdown.pm                      | 52 +++++++++++++++++++++
 lib/OpenQA/Schema/Result/Comments.pm        | 32 +------------
 lib/OpenQA/Schema/Result/JobGroupParents.pm |  9 ++--
 lib/OpenQA/Schema/Result/JobGroups.pm       | 11 ++---
 t/16-markdown.t                             | 42 +++++++++++++++++
 5 files changed, 104 insertions(+), 42 deletions(-)
 create mode 100644 lib/OpenQA/Markdown.pm
 create mode 100644 t/16-markdown.t

diff --git a/lib/OpenQA/Markdown.pm b/lib/OpenQA/Markdown.pm
new file mode 100644
index 000000000..b63806a2c
--- /dev/null
+++ b/lib/OpenQA/Markdown.pm
@@ -0,0 +1,52 @@
+# Copyright (C) 2019 SUSE Linux Products GmbH
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, see <http://www.gnu.org/licenses/>.
+package OpenQA::Markdown;
+use Mojo::Base -strict;
+
+require Text::Markdown;
+our @ISA = qw(Text::Markdown);
+
+use Exporter 'import';
+use Regexp::Common 'URI';
+use OpenQA::Utils 'bugref_to_href';
+
+our @EXPORT_OK = qw(markdown_to_html);
+
+sub markdown_to_html {
+    my $text = shift;
+    my $m    = __PACKAGE__->new;
+    my $html = $m->markdown($text);
+    return $html;
+}
+
+# TODO: Kill it with fire
+sub _DoAutoLinks {
+    my ($self, $text) = @_;
+
+    # auto-replace bugrefs with 'a href...'
+    $text = bugref_to_href($text);
+
+    # auto-replace every http(s) reference which is not already either html
+    # 'a href...' or markdown link '[link](url)' or enclosed by Text::Markdown
+    # URL markers '<>'
+    $text =~ s@(?<!['"(<>])($RE{URI})@<$1>@gi;
+
+    # For tests make sure that references into test modules and needling steps also work
+    $text =~ s{\b(t#([\w/]+))}{<a href="/tests/$2">$1</a>}gi;
+
+    return $self->SUPER::_DoAutoLinks($text);
+}
+
+1;
diff --git a/lib/OpenQA/Schema/Result/Comments.pm b/lib/OpenQA/Schema/Result/Comments.pm
index ca09bf5e2..48288a6ca 100644
--- a/lib/OpenQA/Schema/Result/Comments.pm
+++ b/lib/OpenQA/Schema/Result/Comments.pm
@@ -21,6 +21,7 @@ use warnings;
 use base 'DBIx::Class::Core';
 
 use OpenQA::Utils qw(find_bugref find_bugrefs);
+use OpenQA::Markdown qw(markdown_to_html);
 
 __PACKAGE__->load_components(qw(Core));
 __PACKAGE__->load_components(qw(InflateColumn::DateTime Timestamps));
@@ -144,12 +145,7 @@ sub tag {
     return $+{build}, $+{type}, $+{description}, $+{version};
 }
 
-sub rendered_markdown {
-    my ($self) = @_;
-
-    my $m = CommentsMarkdownParser->new;
-    return Mojo::ByteStream->new($m->markdown($self->text));
-}
+sub rendered_markdown { Mojo::ByteStream->new(markdown_to_html(shift->text)) }
 
 sub hash {
     my ($self) = @_;
@@ -174,28 +170,4 @@ sub extended_hash {
     };
 }
 
-package CommentsMarkdownParser;
-require Text::Markdown;
-our @ISA = qw(Text::Markdown);
-use Regexp::Common 'URI';
-use OpenQA::Utils 'bugref_to_href';
-
-sub _DoAutoLinks {
-    my ($self, $text) = @_;
-
-    # auto-replace bugrefs with 'a href...'
-    $text = bugref_to_href($text);
-
-    # auto-replace every http(s) reference which is not already either html
-    # 'a href...' or markdown link '[link](url)' or enclosed by Text::Markdown
-    # URL markers '<>'
-    $text =~ s@(?<!['"(<>])($RE{URI})@<$1>@gi;
-
-    # For tests make sure that references into test modules and needling steps also work
-    $text =~ s{\b(t#([\w/]+))}{<a href="/tests/$2">$1</a>}gi;
-
-    $text =~ s{(http://\S*\.gif$)}{<img src="$1"/>}gi;
-    $self->SUPER::_DoAutoLinks($text);
-}
-
 1;
diff --git a/lib/OpenQA/Schema/Result/JobGroupParents.pm b/lib/OpenQA/Schema/Result/JobGroupParents.pm
index 4ee040678..18656e3b2 100644
--- a/lib/OpenQA/Schema/Result/JobGroupParents.pm
+++ b/lib/OpenQA/Schema/Result/JobGroupParents.pm
@@ -21,6 +21,7 @@ use warnings;
 
 use base 'DBIx::Class::Core';
 
+use OpenQA::Markdown 'markdown_to_html';
 use OpenQA::Schema::JobGroupDefaults;
 use OpenQA::Utils 'parse_tags_from_comments';
 use Class::Method::Modifiers;
@@ -157,11 +158,9 @@ sub jobs {
 }
 
 sub rendered_description {
-    my ($self) = @_;
-
-    return unless $self->description;
-    my $m = CommentsMarkdownParser->new;
-    return Mojo::ByteStream->new($m->markdown($self->description));
+    my $self = shift;
+    return undef unless my $desc = $self->description;
+    return Mojo::ByteStream->new(markdown_to_html($desc));
 }
 
 sub tags {
diff --git a/lib/OpenQA/Schema/Result/JobGroups.pm b/lib/OpenQA/Schema/Result/JobGroups.pm
index 1e5073386..4ba138ea7 100644
--- a/lib/OpenQA/Schema/Result/JobGroups.pm
+++ b/lib/OpenQA/Schema/Result/JobGroups.pm
@@ -20,6 +20,7 @@ use warnings;
 
 use base 'DBIx::Class::Core';
 
+use OpenQA::Markdown 'markdown_to_html';
 use OpenQA::Schema::JobGroupDefaults;
 use Class::Method::Modifiers;
 use OpenQA::Utils qw(log_debug parse_tags_from_comments);
@@ -153,13 +154,9 @@ around 'carry_over_bugrefs' => sub {
 };
 
 sub rendered_description {
-    my ($self) = @_;
-
-    if ($self->description) {
-        my $m = CommentsMarkdownParser->new;
-        return Mojo::ByteStream->new($m->markdown($self->description));
-    }
-    return;
+    my $self = shift;
+    return undef unless my $desc = $self->description;
+    return Mojo::ByteStream->new(markdown_to_html($desc));
 }
 
 sub full_name {
diff --git a/t/16-markdown.t b/t/16-markdown.t
new file mode 100644
index 000000000..99b1b8db8
--- /dev/null
+++ b/t/16-markdown.t
@@ -0,0 +1,42 @@
+#!/usr/bin/env perl -w
+
+# Copyright (C) 2016 SUSE LLC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Mojo::Base -strict;
+
+use Test::More;
+use OpenQA::Markdown 'markdown_to_html';
+
+subtest 'standard markdown' => sub {
+    is markdown_to_html('Test'),                        "<p>Test</p>\n",                               'HTML rendered';
+    is markdown_to_html("Test\n123\n\n456 789 tset\n"), qq{<p>Test\n123</p>\n\n<p>456 789 tset</p>\n}, 'HTML rendered';
+    is markdown_to_html('*Test*'),                      "<p><em>Test</em></p>\n",                      'HTML rendered';
+    is markdown_to_html('[Test](http://test.com)'), qq{<p><a href="http://test.com">Test</a></p>\n}, 'HTML rendered';
+};
+
+subtest 'bugrefs' => sub {
+    is markdown_to_html('boo#123'),
+      qq{<p><a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a></p>\n}, 'bugref expanded';
+};
+
+subtest 'openQA additions' => sub {
+    is markdown_to_html('https://example.com'),
+      qq{<p><a href="https://example.com">https://example.com</a></p>\n}, 'URL turned into a link';
+    is markdown_to_html('t#123'), qq{<p><a href="/tests/123">t#123</a></p>\n}, 'testref expanded';
+};
+
+done_testing;

From cb3b0fce08be64503362271fa31d12621c8ff806 Mon Sep 17 00:00:00 2001
From: Sebastian Riedel <sri@cpan.org>
Date: Thu, 25 Jul 2019 17:49:57 +0200
Subject: [PATCH 2/3] Scrub dangerous HTML from comments

---
 cpanfile                      |  1 +
 docker/travis_test/Dockerfile |  1 +
 lib/OpenQA/Markdown.pm        | 53 +++++++++++++++++++++----------
 openQA.spec                   |  2 +-
 t/16-markdown.t               | 60 +++++++++++++++++++++++++++++++----
 5 files changed, 92 insertions(+), 25 deletions(-)

diff --git a/cpanfile b/cpanfile
index 2596a6980..8159d6554 100644
--- a/cpanfile
+++ b/cpanfile
@@ -77,6 +77,7 @@ requires 'Sub::Install';
 requires 'Sub::Name';
 requires 'Text::Diff';
 requires 'Text::Markdown';
+requires 'HTML::Restrict';
 requires 'CommonMark';
 requires 'Time::HiRes';
 requires 'Time::ParseDate';
diff --git a/docker/travis_test/Dockerfile b/docker/travis_test/Dockerfile
index 8f11a62f6..1e14c0679 100644
--- a/docker/travis_test/Dockerfile
+++ b/docker/travis_test/Dockerfile
@@ -122,6 +122,7 @@ RUN zypper in -y -C \
        'perl(Test::Warnings)' \
        'perl(Text::Diff)' \
        'perl(Text::Markdown)' \
+       'perl(HTML::Restrict)' \
        'perl(CommonMark)' \
        'perl(Time::ParseDate)' \
        'perl(XSLoader) >= 0.24' \
diff --git a/lib/OpenQA/Markdown.pm b/lib/OpenQA/Markdown.pm
index b63806a2c..ddbcf0913 100644
--- a/lib/OpenQA/Markdown.pm
+++ b/lib/OpenQA/Markdown.pm
@@ -15,38 +15,57 @@
 package OpenQA::Markdown;
 use Mojo::Base -strict;
 
-require Text::Markdown;
-our @ISA = qw(Text::Markdown);
-
 use Exporter 'import';
 use Regexp::Common 'URI';
 use OpenQA::Utils 'bugref_to_href';
+use Text::Markdown;
+use HTML::Restrict;
 
 our @EXPORT_OK = qw(markdown_to_html);
 
+# Limit tags to a safe subset
+my $RULES = {
+    a          => [qw(href)],
+    blockquote => [],
+    code       => [],
+    em         => [],
+    img        => [qw(src alt)],
+    h1         => [],
+    h2         => [],
+    h3         => [],
+    h4         => [],
+    h5         => [],
+    h6         => [],
+    hr         => [],
+    li         => [],
+    ol         => [],
+    p          => [],
+    strong     => [],
+    ul         => []};
+
+# Only allow "href=/...", "href=http://..." and "href=https://..."
+my $SCHEMES = [undef, 'http', 'https'];
+
+my $RESTRICT = HTML::Restrict->new(rules => $RULES, uri_schemes => $SCHEMES);
+my $MARKDOWN = Text::Markdown->new;
+
 sub markdown_to_html {
     my $text = shift;
-    my $m    = __PACKAGE__->new;
-    my $html = $m->markdown($text);
-    return $html;
-}
-
-# TODO: Kill it with fire
-sub _DoAutoLinks {
-    my ($self, $text) = @_;
 
-    # auto-replace bugrefs with 'a href...'
+    # Replace bugrefs with links
     $text = bugref_to_href($text);
 
-    # auto-replace every http(s) reference which is not already either html
-    # 'a href...' or markdown link '[link](url)' or enclosed by Text::Markdown
-    # URL markers '<>'
+    # Turn all remaining URLs into links
     $text =~ s@(?<!['"(<>])($RE{URI})@<$1>@gi;
 
-    # For tests make sure that references into test modules and needling steps also work
+    # Turn references to test modules and needling steps into links
     $text =~ s{\b(t#([\w/]+))}{<a href="/tests/$2">$1</a>}gi;
 
-    return $self->SUPER::_DoAutoLinks($text);
+    # Markdown -> HTML
+    my $html = $MARKDOWN->markdown($text);
+
+    # Unsafe -> safe
+    return $RESTRICT->process($html);
 }
 
 1;
diff --git a/openQA.spec b/openQA.spec
index 4a92f2766..1f61418e2 100644
--- a/openQA.spec
+++ b/openQA.spec
@@ -45,7 +45,7 @@
 %else
 %define python_scripts_requires %{nil}
 %endif
-%define t_requires perl(DBD::Pg) perl(DBIx::Class) perl(Config::IniFiles) perl(SQL::Translator) perl(Date::Format) perl(File::Copy::Recursive) perl(DateTime::Format::Pg) perl(Net::OpenID::Consumer) perl(Mojolicious::Plugin::RenderFile) perl(Mojolicious::Plugin::AssetPack) perl(aliased) perl(Config::Tiny) perl(DBIx::Class::DeploymentHandler) perl(DBIx::Class::DynamicDefault) perl(DBIx::Class::Schema::Config) perl(DBIx::Class::Storage::Statistics) perl(IO::Socket::SSL) perl(Data::Dump) perl(DBIx::Class::OptimisticLocking) perl(Module::Pluggable) perl(Text::Diff) perl(Text::Markdown) perl(CommonMark) perl(JSON::Validator) perl(YAML::XS) perl(IPC::Run) perl(Archive::Extract) perl(CSS::Minifier::XS) perl(JavaScript::Minifier::XS) perl(Time::ParseDate) perl(Sort::Versions) perl(Mojo::RabbitMQ::Client) perl(BSD::Resource) perl(Cpanel::JSON::XS) perl(Pod::POM) perl(Mojo::IOLoop::ReadWriteProcess) perl(Minion) perl(Mojo::Pg) perl(Mojo::SQLite) perl(Minion::Backend::SQLite) %python_scripts_requires
+%define t_requires perl(DBD::Pg) perl(DBIx::Class) perl(Config::IniFiles) perl(SQL::Translator) perl(Date::Format) perl(File::Copy::Recursive) perl(DateTime::Format::Pg) perl(Net::OpenID::Consumer) perl(Mojolicious::Plugin::RenderFile) perl(Mojolicious::Plugin::AssetPack) perl(aliased) perl(Config::Tiny) perl(DBIx::Class::DeploymentHandler) perl(DBIx::Class::DynamicDefault) perl(DBIx::Class::Schema::Config) perl(DBIx::Class::Storage::Statistics) perl(IO::Socket::SSL) perl(Data::Dump) perl(DBIx::Class::OptimisticLocking) perl(Module::Pluggable) perl(Text::Diff) perl(Text::Markdown) perl(HTML::Restrict) perl(CommonMark) perl(JSON::Validator) perl(YAML::XS) perl(IPC::Run) perl(Archive::Extract) perl(CSS::Minifier::XS) perl(JavaScript::Minifier::XS) perl(Time::ParseDate) perl(Sort::Versions) perl(Mojo::RabbitMQ::Client) perl(BSD::Resource) perl(Cpanel::JSON::XS) perl(Pod::POM) perl(Mojo::IOLoop::ReadWriteProcess) perl(Minion) perl(Mojo::Pg) perl(Mojo::SQLite) perl(Minion::Backend::SQLite) %python_scripts_requires
 Name:           openQA
 Version:        4.6
 Release:        0
diff --git a/t/16-markdown.t b/t/16-markdown.t
index 99b1b8db8..d6839cd7a 100644
--- a/t/16-markdown.t
+++ b/t/16-markdown.t
@@ -22,21 +22,67 @@ use Test::More;
 use OpenQA::Markdown 'markdown_to_html';
 
 subtest 'standard markdown' => sub {
-    is markdown_to_html('Test'),                        "<p>Test</p>\n",                               'HTML rendered';
-    is markdown_to_html("Test\n123\n\n456 789 tset\n"), qq{<p>Test\n123</p>\n\n<p>456 789 tset</p>\n}, 'HTML rendered';
-    is markdown_to_html('*Test*'),                      "<p><em>Test</em></p>\n",                      'HTML rendered';
-    is markdown_to_html('[Test](http://test.com)'), qq{<p><a href="http://test.com">Test</a></p>\n}, 'HTML rendered';
+    is markdown_to_html('Test'),                        '<p>Test</p>',                               'HTML rendered';
+    is markdown_to_html('# Test'),                      '<h1>Test</h1>',                             'HTML rendered';
+    is markdown_to_html('## Test'),                     '<h2>Test</h2>',                             'HTML rendered';
+    is markdown_to_html('### Test'),                    '<h3>Test</h3>',                             'HTML rendered';
+    is markdown_to_html('#### Test'),                   '<h4>Test</h4>',                             'HTML rendered';
+    is markdown_to_html('##### Test'),                  '<h5>Test</h5>',                             'HTML rendered';
+    is markdown_to_html('###### Test'),                 '<h6>Test</h6>',                             'HTML rendered';
+    is markdown_to_html("Test\n123\n\n456 789 tset\n"), qq{<p>Test\n123</p>\n\n<p>456 789 tset</p>}, 'HTML rendered';
+    is markdown_to_html('*Test*'),                      '<p><em>Test</em></p>',                      'HTML rendered';
+    is markdown_to_html('**Test**'),                    '<p><strong>Test</strong></p>',              'HTML rendered';
+    is markdown_to_html("1. a\n2. b\n3. c\n"), qq{<ol>\n<li>a</li>\n<li>b</li>\n<li>c</li>\n</ol>}, 'HTML rendered';
+    is markdown_to_html("* a\n* b\n* c\n"),    qq{<ul>\n<li>a</li>\n<li>b</li>\n<li>c</li>\n</ul>}, 'HTML rendered';
+    is markdown_to_html('[Test](http://test.com)'),  qq{<p><a href="http://test.com">Test</a></p>},     'HTML rendered';
+    is markdown_to_html('[Test](/test.html)'),       qq{<p><a href="/test.html">Test</a></p>},          'HTML rendered';
+    is markdown_to_html('![Test](http://test.com)'), qq{<p><img src="http://test.com" alt="Test"></p>}, 'HTML rendered';
+    is markdown_to_html('Test `123` 123'),           '<p>Test <code>123</code> 123</p>',                'HTML rendered';
+    is markdown_to_html("> test\n> 123"), "<blockquote>\n  <p>test\n  123</p>\n</blockquote>", 'HTML rendered';
+    is markdown_to_html('---'), '<hr>', 'HTML rendered';
 };
 
 subtest 'bugrefs' => sub {
     is markdown_to_html('boo#123'),
-      qq{<p><a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a></p>\n}, 'bugref expanded';
+      qq{<p><a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a></p>}, 'bugref expanded';
+    is markdown_to_html('testing boo#123 123'),
+      qq{<p>testing <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a> 123</p>},
+      'bugref expanded';
+    is markdown_to_html('testing boo#123 123 boo#321'),
+      qq{<p>testing <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a> 123}
+      . qq{ <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=321">boo#321</a></p>},
+      'bugref expanded';
+    is markdown_to_html("testing boo#123 \n123\n boo#321"),
+      qq{<p>testing <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a> \n123\n}
+      . qq{ <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=321">boo#321</a></p>},
+      'bugref expanded';
+    is markdown_to_html("boo\ntesting boo#123 123\n123"),
+      qq{<p>boo\ntesting <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a> 123\n123</p>},
+      'bugref expanded';
 };
 
 subtest 'openQA additions' => sub {
     is markdown_to_html('https://example.com'),
-      qq{<p><a href="https://example.com">https://example.com</a></p>\n}, 'URL turned into a link';
-    is markdown_to_html('t#123'), qq{<p><a href="/tests/123">t#123</a></p>\n}, 'testref expanded';
+      qq{<p><a href="https://example.com">https://example.com</a></p>}, 'URL turned into a link';
+    is markdown_to_html('testing https://example.com 123'),
+      qq{<p>testing <a href="https://example.com">https://example.com</a> 123</p>}, 'URL turned into a link';
+    is markdown_to_html("t\ntesting https://example.com 123\n123"),
+      qq{<p>t\ntesting <a href="https://example.com">https://example.com</a> 123\n123</p>}, 'URL turned into a link';
+    is markdown_to_html('t#123'),             qq{<p><a href="/tests/123">t#123</a></p>},             'testref expanded';
+    is markdown_to_html('testing t#123 123'), qq{<p>testing <a href="/tests/123">t#123</a> 123</p>}, 'testref expanded';
+    is markdown_to_html("t\ntesting t#123 123\n123"), qq{<p>t\ntesting <a href="/tests/123">t#123</a> 123\n123</p>},
+      'testref expanded';
+};
+
+subtest 'unsafe HTML filtered out' => sub {
+    is markdown_to_html('Test <script>alert("boom!");</script> 123'), '<p>Test  123</p>', 'unsafe HTML filtered';
+    is markdown_to_html('<font>Test</font>'),                         '<p>Test</p>',      'unsafe HTML filtered';
+    is markdown_to_html('Test [Boom!](javascript:alert("boom!")) 123'), '<p>Test <a>Boom!</a> 123</p>',
+      'unsafe HTML filtered';
+    is markdown_to_html('<a href="/" onclick="someFunction()">Totally safe</a>'),
+      '<p><a href="/">Totally safe</a></p>', 'unsafe HTML filtered';
+    is markdown_to_html(qq{> hello <a name="n"\n> href="javascript:alert('boom!')">*you*</a>}),
+      qq{<blockquote>\n  <p>hello <a><em>you</em></a></p>\n</blockquote>}, 'unsafe HTML filtered';
 };
 
 done_testing;

From 044392c9c4ee12cdeddb7c9c2a301ddd9ac11c4e Mon Sep 17 00:00:00 2001
From: Sebastian Riedel <sri@cpan.org>
Date: Fri, 26 Jul 2019 17:31:15 +0200
Subject: [PATCH 3/3] Replace Text::Markdown and HTML::Restrict with CommonMark
 for much better comment rendering speed and security

---
 cpanfile                      |   2 -
 docker/travis_test/Dockerfile |   2 -
 lib/OpenQA/Markdown.pm        |  69 ++++++++---------
 lib/OpenQA/Utils.pm           |   1 +
 openQA.spec                   |   2 +-
 t/16-markdown.t               | 142 +++++++++++++++++++++++++---------
 t/api/10-jobgroups.t          |   2 +-
 t/fixtures/01-jobs.pl         |   2 +-
 t/ui/15-comments.t            |  49 ++++++------
 9 files changed, 166 insertions(+), 105 deletions(-)

diff --git a/cpanfile b/cpanfile
index 8159d6554..5dad876f9 100644
--- a/cpanfile
+++ b/cpanfile
@@ -76,8 +76,6 @@ requires 'Scalar::Util';
 requires 'Sub::Install';
 requires 'Sub::Name';
 requires 'Text::Diff';
-requires 'Text::Markdown';
-requires 'HTML::Restrict';
 requires 'CommonMark';
 requires 'Time::HiRes';
 requires 'Time::ParseDate';
diff --git a/docker/travis_test/Dockerfile b/docker/travis_test/Dockerfile
index 1e14c0679..e2a6d53a2 100644
--- a/docker/travis_test/Dockerfile
+++ b/docker/travis_test/Dockerfile
@@ -121,8 +121,6 @@ RUN zypper in -y -C \
        'perl(Socket::MsgHdr)' \
        'perl(Test::Warnings)' \
        'perl(Text::Diff)' \
-       'perl(Text::Markdown)' \
-       'perl(HTML::Restrict)' \
        'perl(CommonMark)' \
        'perl(Time::ParseDate)' \
        'perl(XSLoader) >= 0.24' \
diff --git a/lib/OpenQA/Markdown.pm b/lib/OpenQA/Markdown.pm
index ddbcf0913..cf6745ce0 100644
--- a/lib/OpenQA/Markdown.pm
+++ b/lib/OpenQA/Markdown.pm
@@ -17,55 +17,54 @@ use Mojo::Base -strict;
 
 use Exporter 'import';
 use Regexp::Common 'URI';
-use OpenQA::Utils 'bugref_to_href';
-use Text::Markdown;
-use HTML::Restrict;
+use OpenQA::Utils qw(bugref_regex bugurl);
+use CommonMark;
 
-our @EXPORT_OK = qw(markdown_to_html);
+our @EXPORT_OK = qw(bugref_to_markdown is_light_color markdown_to_html);
 
-# Limit tags to a safe subset
-my $RULES = {
-    a          => [qw(href)],
-    blockquote => [],
-    code       => [],
-    em         => [],
-    img        => [qw(src alt)],
-    h1         => [],
-    h2         => [],
-    h3         => [],
-    h4         => [],
-    h5         => [],
-    h6         => [],
-    hr         => [],
-    li         => [],
-    ol         => [],
-    p          => [],
-    strong     => [],
-    ul         => []};
+my $RE = bugref_regex;
 
-# Only allow "href=/...", "href=http://..." and "href=https://..."
-my $SCHEMES = [undef, 'http', 'https'];
+sub bugref_to_markdown {
+    my $text = shift;
+    $text =~ s/$RE/"[$+{match}](" . bugurl($+{match}) . ')'/geio;
+    return $text;
+}
 
-my $RESTRICT = HTML::Restrict->new(rules => $RULES, uri_schemes => $SCHEMES);
-my $MARKDOWN = Text::Markdown->new;
+sub is_light_color {
+    my $color = shift;
+    return undef unless $color =~ m/^#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})$/;
+    my ($red, $green, $blue) = ($1, $2, $3);
+    my $sum = hex($red) + hex($green) + hex($blue);
+    return $sum > 380;
+}
 
 sub markdown_to_html {
     my $text = shift;
 
-    # Replace bugrefs with links
-    $text = bugref_to_href($text);
+    $text = bugref_to_markdown($text);
 
     # Turn all remaining URLs into links
-    $text =~ s@(?<!['"(<>])($RE{URI})@<$1>@gi;
+    $text =~ s/(?<!['"(<>])($RE{URI})/<$1>/gio;
 
     # Turn references to test modules and needling steps into links
-    $text =~ s{\b(t#([\w/]+))}{<a href="/tests/$2">$1</a>}gi;
+    $text =~ s!\b(t#([\w/]+))![$1](/tests/$2)!gi;
 
-    # Markdown -> HTML
-    my $html = $MARKDOWN->markdown($text);
+    my $html = CommonMark->markdown_to_html($text);
+
+    # Custom markup "{{color:#ff0000|Some text}}"
+    $html =~ s/(\{\{([^|]+?)\|(.*?)\}\})/_custom($1, $2, $3)/ge;
+
+    return $html;
+}
 
-    # Unsafe -> safe
-    return $RESTRICT->process($html);
+sub _custom {
+    my ($full, $rules, $text) = @_;
+    if ($rules =~ /^color:(#[a-fA-F0-9]{6})$/) {
+        my $color = $1;
+        my $bg    = is_light_color($color) ? 'black' : 'white';
+        return qq{<span style="color:$color;background-color:$bg">$text</span>};
+    }
+    return $full;
 }
 
 1;
diff --git a/lib/OpenQA/Utils.pm b/lib/OpenQA/Utils.pm
index d1ac1fa2f..0dc27766f 100644
--- a/lib/OpenQA/Utils.pm
+++ b/lib/OpenQA/Utils.pm
@@ -63,6 +63,7 @@ our @EXPORT  = qw(
   &parse_assets_from_settings
   &find_bugref
   &find_bugrefs
+  bugref_regex
   &bugurl
   &bugref_to_href
   &href_to_bugref
diff --git a/openQA.spec b/openQA.spec
index 1f61418e2..5984ed027 100644
--- a/openQA.spec
+++ b/openQA.spec
@@ -45,7 +45,7 @@
 %else
 %define python_scripts_requires %{nil}
 %endif
-%define t_requires perl(DBD::Pg) perl(DBIx::Class) perl(Config::IniFiles) perl(SQL::Translator) perl(Date::Format) perl(File::Copy::Recursive) perl(DateTime::Format::Pg) perl(Net::OpenID::Consumer) perl(Mojolicious::Plugin::RenderFile) perl(Mojolicious::Plugin::AssetPack) perl(aliased) perl(Config::Tiny) perl(DBIx::Class::DeploymentHandler) perl(DBIx::Class::DynamicDefault) perl(DBIx::Class::Schema::Config) perl(DBIx::Class::Storage::Statistics) perl(IO::Socket::SSL) perl(Data::Dump) perl(DBIx::Class::OptimisticLocking) perl(Module::Pluggable) perl(Text::Diff) perl(Text::Markdown) perl(HTML::Restrict) perl(CommonMark) perl(JSON::Validator) perl(YAML::XS) perl(IPC::Run) perl(Archive::Extract) perl(CSS::Minifier::XS) perl(JavaScript::Minifier::XS) perl(Time::ParseDate) perl(Sort::Versions) perl(Mojo::RabbitMQ::Client) perl(BSD::Resource) perl(Cpanel::JSON::XS) perl(Pod::POM) perl(Mojo::IOLoop::ReadWriteProcess) perl(Minion) perl(Mojo::Pg) perl(Mojo::SQLite) perl(Minion::Backend::SQLite) %python_scripts_requires
+%define t_requires perl(DBD::Pg) perl(DBIx::Class) perl(Config::IniFiles) perl(SQL::Translator) perl(Date::Format) perl(File::Copy::Recursive) perl(DateTime::Format::Pg) perl(Net::OpenID::Consumer) perl(Mojolicious::Plugin::RenderFile) perl(Mojolicious::Plugin::AssetPack) perl(aliased) perl(Config::Tiny) perl(DBIx::Class::DeploymentHandler) perl(DBIx::Class::DynamicDefault) perl(DBIx::Class::Schema::Config) perl(DBIx::Class::Storage::Statistics) perl(IO::Socket::SSL) perl(Data::Dump) perl(DBIx::Class::OptimisticLocking) perl(Module::Pluggable) perl(Text::Diff) perl(CommonMark) perl(JSON::Validator) perl(YAML::XS) perl(IPC::Run) perl(Archive::Extract) perl(CSS::Minifier::XS) perl(JavaScript::Minifier::XS) perl(Time::ParseDate) perl(Sort::Versions) perl(Mojo::RabbitMQ::Client) perl(BSD::Resource) perl(Cpanel::JSON::XS) perl(Pod::POM) perl(Mojo::IOLoop::ReadWriteProcess) perl(Minion) perl(Mojo::Pg) perl(Mojo::SQLite) perl(Minion::Backend::SQLite) %python_scripts_requires
 Name:           openQA
 Version:        4.6
 Release:        0
diff --git a/t/16-markdown.t b/t/16-markdown.t
index d6839cd7a..e0804169e 100644
--- a/t/16-markdown.t
+++ b/t/16-markdown.t
@@ -1,6 +1,6 @@
 #!/usr/bin/env perl -w
 
-# Copyright (C) 2016 SUSE LLC
+# Copyright (C) 2019 SUSE LLC
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -18,71 +18,137 @@
 
 use Mojo::Base -strict;
 
+BEGIN { unshift @INC, 'lib' }
+
 use Test::More;
-use OpenQA::Markdown 'markdown_to_html';
+use OpenQA::Markdown qw(bugref_to_markdown is_light_color markdown_to_html);
 
 subtest 'standard markdown' => sub {
-    is markdown_to_html('Test'),                        '<p>Test</p>',                               'HTML rendered';
-    is markdown_to_html('# Test'),                      '<h1>Test</h1>',                             'HTML rendered';
-    is markdown_to_html('## Test'),                     '<h2>Test</h2>',                             'HTML rendered';
-    is markdown_to_html('### Test'),                    '<h3>Test</h3>',                             'HTML rendered';
-    is markdown_to_html('#### Test'),                   '<h4>Test</h4>',                             'HTML rendered';
-    is markdown_to_html('##### Test'),                  '<h5>Test</h5>',                             'HTML rendered';
-    is markdown_to_html('###### Test'),                 '<h6>Test</h6>',                             'HTML rendered';
-    is markdown_to_html("Test\n123\n\n456 789 tset\n"), qq{<p>Test\n123</p>\n\n<p>456 789 tset</p>}, 'HTML rendered';
-    is markdown_to_html('*Test*'),                      '<p><em>Test</em></p>',                      'HTML rendered';
-    is markdown_to_html('**Test**'),                    '<p><strong>Test</strong></p>',              'HTML rendered';
-    is markdown_to_html("1. a\n2. b\n3. c\n"), qq{<ol>\n<li>a</li>\n<li>b</li>\n<li>c</li>\n</ol>}, 'HTML rendered';
-    is markdown_to_html("* a\n* b\n* c\n"),    qq{<ul>\n<li>a</li>\n<li>b</li>\n<li>c</li>\n</ul>}, 'HTML rendered';
-    is markdown_to_html('[Test](http://test.com)'),  qq{<p><a href="http://test.com">Test</a></p>},     'HTML rendered';
-    is markdown_to_html('[Test](/test.html)'),       qq{<p><a href="/test.html">Test</a></p>},          'HTML rendered';
-    is markdown_to_html('![Test](http://test.com)'), qq{<p><img src="http://test.com" alt="Test"></p>}, 'HTML rendered';
-    is markdown_to_html('Test `123` 123'),           '<p>Test <code>123</code> 123</p>',                'HTML rendered';
-    is markdown_to_html("> test\n> 123"), "<blockquote>\n  <p>test\n  123</p>\n</blockquote>", 'HTML rendered';
-    is markdown_to_html('---'), '<hr>', 'HTML rendered';
+    is markdown_to_html('Test'),                        "<p>Test</p>\n",                             'HTML rendered';
+    is markdown_to_html('# Test #'),                    "<h1>Test</h1>\n",                           'HTML rendered';
+    is markdown_to_html('# Test'),                      "<h1>Test</h1>\n",                           'HTML rendered';
+    is markdown_to_html('## Test'),                     "<h2>Test</h2>\n",                           'HTML rendered';
+    is markdown_to_html('### Test'),                    "<h3>Test</h3>\n",                           'HTML rendered';
+    is markdown_to_html('#### Test'),                   "<h4>Test</h4>\n",                           'HTML rendered';
+    is markdown_to_html('##### Test'),                  "<h5>Test</h5>\n",                           'HTML rendered';
+    is markdown_to_html('###### Test'),                 "<h6>Test</h6>\n",                           'HTML rendered';
+    is markdown_to_html("Test\n123\n\n456 789 tset\n"), qq{<p>Test\n123</p>\n<p>456 789 tset</p>\n}, 'HTML rendered';
+    is markdown_to_html('*Test*'),                      "<p><em>Test</em></p>\n",                    'HTML rendered';
+    is markdown_to_html('**Test**'),                    "<p><strong>Test</strong></p>\n",            'HTML rendered';
+    is markdown_to_html("1. a\n2. b\n3. c\n"), qq{<ol>\n<li>a</li>\n<li>b</li>\n<li>c</li>\n</ol>\n}, 'HTML rendered';
+    is markdown_to_html("* a\n* b\n* c\n"),    qq{<ul>\n<li>a</li>\n<li>b</li>\n<li>c</li>\n</ul>\n}, 'HTML rendered';
+    is markdown_to_html('[Test](http://test.com)'), qq{<p><a href="http://test.com">Test</a></p>\n}, 'HTML rendered';
+    is markdown_to_html('[Test](/test.html)'),      qq{<p><a href="/test.html">Test</a></p>\n},      'HTML rendered';
+    is markdown_to_html('![Test](http://test.com)'), qq{<p><img src="http://test.com" alt="Test" /></p>\n},
+      'HTML rendered';
+    is markdown_to_html('Test `123` 123'), "<p>Test <code>123</code> 123</p>\n",              'HTML rendered';
+    is markdown_to_html("> test\n> 123"),  "<blockquote>\n<p>test\n123</p>\n</blockquote>\n", 'HTML rendered';
+    is markdown_to_html('---'),            "<hr />\n",                                        'HTML rendered';
 };
 
 subtest 'bugrefs' => sub {
     is markdown_to_html('boo#123'),
-      qq{<p><a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a></p>}, 'bugref expanded';
+      qq{<p><a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a></p>\n}, 'bugref expanded';
     is markdown_to_html('testing boo#123 123'),
-      qq{<p>testing <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a> 123</p>},
+      qq{<p>testing <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a> 123</p>\n},
       'bugref expanded';
     is markdown_to_html('testing boo#123 123 boo#321'),
       qq{<p>testing <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a> 123}
-      . qq{ <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=321">boo#321</a></p>},
+      . qq{ <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=321">boo#321</a></p>\n},
       'bugref expanded';
-    is markdown_to_html("testing boo#123 \n123\n boo#321"),
-      qq{<p>testing <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a> \n123\n}
-      . qq{ <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=321">boo#321</a></p>},
+    is markdown_to_html("testing boo#123\n123\n boo#321"),
+      qq{<p>testing <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a>\n123\n}
+      . qq{<a href="https://bugzilla.opensuse.org/show_bug.cgi?id=321">boo#321</a></p>\n},
       'bugref expanded';
     is markdown_to_html("boo\ntesting boo#123 123\n123"),
-      qq{<p>boo\ntesting <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a> 123\n123</p>},
+      qq{<p>boo\ntesting <a href="https://bugzilla.opensuse.org/show_bug.cgi?id=123">boo#123</a> 123\n123</p>\n},
       'bugref expanded';
 };
 
 subtest 'openQA additions' => sub {
     is markdown_to_html('https://example.com'),
-      qq{<p><a href="https://example.com">https://example.com</a></p>}, 'URL turned into a link';
+      qq{<p><a href="https://example.com">https://example.com</a></p>\n}, 'URL turned into a link';
     is markdown_to_html('testing https://example.com 123'),
-      qq{<p>testing <a href="https://example.com">https://example.com</a> 123</p>}, 'URL turned into a link';
+      qq{<p>testing <a href="https://example.com">https://example.com</a> 123</p>\n}, 'URL turned into a link';
     is markdown_to_html("t\ntesting https://example.com 123\n123"),
-      qq{<p>t\ntesting <a href="https://example.com">https://example.com</a> 123\n123</p>}, 'URL turned into a link';
-    is markdown_to_html('t#123'),             qq{<p><a href="/tests/123">t#123</a></p>},             'testref expanded';
-    is markdown_to_html('testing t#123 123'), qq{<p>testing <a href="/tests/123">t#123</a> 123</p>}, 'testref expanded';
-    is markdown_to_html("t\ntesting t#123 123\n123"), qq{<p>t\ntesting <a href="/tests/123">t#123</a> 123\n123</p>},
+      qq{<p>t\ntesting <a href="https://example.com">https://example.com</a> 123\n123</p>\n}, 'URL turned into a link';
+
+    is markdown_to_html('t#123'), qq{<p><a href="/tests/123">t#123</a></p>\n}, 'testref expanded';
+    is markdown_to_html('testing t#123 123'), qq{<p>testing <a href="/tests/123">t#123</a> 123</p>\n},
+      'testref expanded';
+    is markdown_to_html("t\ntesting t#123 123\n123"), qq{<p>t\ntesting <a href="/tests/123">t#123</a> 123\n123</p>\n},
       'testref expanded';
+
+    is markdown_to_html(qq{{{color:#ffffff|"Text"}}}),
+      qq{<p><span style="color:#ffffff;background-color:black">&quot;Text&quot;</span></p>\n},
+      'White text';
+    is markdown_to_html("test {{color:#ff0000|Text}} 123"),
+      qq{<p>test <span style="color:#ff0000;background-color:white">Text</span> 123</p>\n}, 'Red text';
+    is markdown_to_html("test {{color:#FFFFFF|Text}} 123"),
+      qq{<p>test <span style="color:#FFFFFF;background-color:black">Text</span> 123</p>\n}, 'White text';
+    is markdown_to_html("test {{color:#00ff00|Some Text}} 123"),
+      qq{<p>test <span style="color:#00ff00;background-color:white">Some Text</span> 123</p>\n}, 'Green text';
+    is markdown_to_html("test {{color:#00ff00|Some Text}} 123 {{color:#0000ff|Also {w}orks}}"),
+      qq{<p>test <span style="color:#00ff00;background-color:white">Some Text</span> 123}
+      . qq{ <span style="color:#0000ff;background-color:white">Also {w}orks</span></p>\n},
+      'Green and blue text';
+    is markdown_to_html("test {{  color: #00ff00  |  Some Text  }} 123"),
+      "<p>test {{  color: #00ff00  |  Some Text  }} 123</p>\n", 'Extra whitespace is not allowed';
+    is markdown_to_html("test {{color:javascript|Text}} 123"),
+      qq{<p>test {{color:javascript|Text}} 123</p>\n}, 'Invalid custom tag';
+    is markdown_to_html(qq{test {{javascript:alert("test")|Text}} 123}),
+      qq{<p>test {{javascript:alert(&quot;test&quot;)|Text}} 123</p>\n}, 'Invalid custom tag';
 };
 
 subtest 'unsafe HTML filtered out' => sub {
-    is markdown_to_html('Test <script>alert("boom!");</script> 123'), '<p>Test  123</p>', 'unsafe HTML filtered';
-    is markdown_to_html('<font>Test</font>'),                         '<p>Test</p>',      'unsafe HTML filtered';
-    is markdown_to_html('Test [Boom!](javascript:alert("boom!")) 123'), '<p>Test <a>Boom!</a> 123</p>',
+    is markdown_to_html('Test <script>alert("boom!");</script> 123'),
+      "<p>Test <!-- raw HTML omitted -->alert(&quot;boom!&quot;);<!-- raw HTML omitted --> 123</p>\n",
+      'unsafe HTML filtered';
+    is markdown_to_html('<font>Test</font>'), "<p><!-- raw HTML omitted -->Test<!-- raw HTML omitted --></p>\n",
+      'unsafe HTML filtered';
+    is markdown_to_html('Test [Boom!](javascript:alert("boom!")) 123'), qq{<p>Test <a href="">Boom!</a> 123</p>\n},
       'unsafe HTML filtered';
     is markdown_to_html('<a href="/" onclick="someFunction()">Totally safe</a>'),
-      '<p><a href="/">Totally safe</a></p>', 'unsafe HTML filtered';
+      "<p><!-- raw HTML omitted -->Totally safe<!-- raw HTML omitted --></p>\n", 'unsafe HTML filtered';
     is markdown_to_html(qq{> hello <a name="n"\n> href="javascript:alert('boom!')">*you*</a>}),
-      qq{<blockquote>\n  <p>hello <a><em>you</em></a></p>\n</blockquote>}, 'unsafe HTML filtered';
+      qq{<blockquote>\n<p>hello <!-- raw HTML omitted --><em>you</em><!-- raw HTML omitted --></p>\n</blockquote>\n},
+      'unsafe HTML filtered';
+    is markdown_to_html('{{color:#0000ff|<a>Test</a>}}'),
+      qq{<p><span style="color:#0000ff;background-color:white">}
+      . qq{<!-- raw HTML omitted -->Test<!-- raw HTML omitted --></span></p>\n},
+      'unsafe HTML filtered';
+};
+
+subtest 'bugrefs to markdown' => sub {
+    is bugref_to_markdown('bnc#9876'), '[bnc#9876](https://bugzilla.suse.com/show_bug.cgi?id=9876)', 'right markdown';
+    is bugref_to_markdown('bsc#9876'), '[bsc#9876](https://bugzilla.suse.com/show_bug.cgi?id=9876)', 'right markdown';
+    is bugref_to_markdown('boo#9876'), '[boo#9876](https://bugzilla.opensuse.org/show_bug.cgi?id=9876)',
+      'right markdown';
+    is bugref_to_markdown('bgo#9876'), '[bgo#9876](https://bugzilla.gnome.org/show_bug.cgi?id=9876)',  'right markdown';
+    is bugref_to_markdown('brc#9876'), '[brc#9876](https://bugzilla.redhat.com/show_bug.cgi?id=9876)', 'right markdown';
+    is bugref_to_markdown('bko#9876'), '[bko#9876](https://bugzilla.kernel.org/show_bug.cgi?id=9876)', 'right markdown';
+    is bugref_to_markdown('poo#9876'), '[poo#9876](https://progress.opensuse.org/issues/9876)',        'right markdown';
+    is bugref_to_markdown('gh#foo/bar#1234'), '[gh#foo/bar#1234](https://github.com/foo/bar/issues/1234)',
+      'right markdown';
+    is bugref_to_markdown('kde#9876'), '[kde#9876](https://bugs.kde.org/show_bug.cgi?id=9876)', 'right markdown';
+    is bugref_to_markdown('fdo#9876'), '[fdo#9876](https://bugs.freedesktop.org/show_bug.cgi?id=9876)',
+      'right markdown';
+    is bugref_to_markdown('jsc#9876'), '[jsc#9876](https://jira.suse.de/browse/9876)', 'right markdown';
+    is bugref_to_markdown("boo#9876\n\ntest boo#211\n"),
+      "[boo#9876](https://bugzilla.opensuse.org/show_bug.cgi?id=9876)\n\n"
+      . "test [boo#211](https://bugzilla.opensuse.org/show_bug.cgi?id=211)\n",
+      'right markdown';
+};
+
+subtest 'color detection' => sub {
+    ok !is_light_color('#000000'), 'dark';
+    ok !is_light_color('#ff0000'), 'dark';
+    ok !is_light_color('#00ff00'), 'dark';
+    ok !is_light_color('#0000ff'), 'dark';
+    ok !is_light_color('#0000FF'), 'dark';
+    ok is_light_color('#ffffff'),  'light';
+    ok is_light_color('#FFFFFF'),  'light';
+    ok !is_light_color('test'),    'not a color at all';
 };
 
 done_testing;
diff --git a/t/api/10-jobgroups.t b/t/api/10-jobgroups.t
index f45b8fd39..6240638b0 100644
--- a/t/api/10-jobgroups.t
+++ b/t/api/10-jobgroups.t
@@ -70,7 +70,7 @@ subtest 'list job groups' => sub() {
                 keep_important_logs_in_days    => 120,
                 default_priority               => 50,
                 carry_over_bugrefs             => 1,
-                description                    => "##Test description\n\nwith bugref bsc#1234",
+                description                    => "## Test description\n\nwith bugref bsc#1234",
                 template                       => undef,
                 keep_results_in_days           => 365,
                 keep_important_results_in_days => 0,
diff --git a/t/fixtures/01-jobs.pl b/t/fixtures/01-jobs.pl
index c7b27b840..f7e07a409 100644
--- a/t/fixtures/01-jobs.pl
+++ b/t/fixtures/01-jobs.pl
@@ -32,7 +32,7 @@
         id          => 1001,
         name        => 'opensuse',
         sort_order  => 0,
-        description => "##Test description\n\nwith bugref bsc#1234",
+        description => "## Test description\n\nwith bugref bsc#1234",
     },
     JobGroups => {
         id         => 1002,
diff --git a/t/ui/15-comments.t b/t/ui/15-comments.t
index 30aa9aca4..bd841f418 100644
--- a/t/ui/15-comments.t
+++ b/t/ui/15-comments.t
@@ -194,26 +194,29 @@ subtest 'commenting in the group overview' => sub {
 };
 
 subtest 'URL auto-replace' => sub {
-    $driver->find_element_by_id('text')->send_keys('
-        foo@bar foo#bar should not be detected as bugref
-        bsc#2436346bla should not be detected, too
-        bsc#2436347bla2
-        <a href="https://openqa.example.com/foo/bar">https://openqa.example.com/foo/bar</a>: http://localhost:9562
-        https://openqa.example.com/tests/181148 (reference http://localhost/foo/bar )
-        bsc#1234 boo#2345,poo#3456 t#4567 "some quotes suff should not cause problems"
-        t#5678/modules/welcome/steps/1
-        https://progress.opensuse.org/issues/6789
-        https://bugzilla.novell.com/show_bug.cgi?id=7890
-        [bsc#1000629](https://bugzilla.suse.com/show_bug.cgi?id=1000629)
-        <a href="https://bugzilla.suse.com/show_bug.cgi?id=1000630">bsc#1000630</a>
-        bnc#1246
-        gh#os-autoinst/openQA#1234
-        https://github.com/os-autoinst/os-autoinst/pull/960
-        bgo#768954 brc#1401123
-        https://bugzilla.gnome.org/show_bug.cgi?id=690345
-        https://bugzilla.redhat.com/show_bug.cgi?id=343098
-        [bsc#1043970](https://bugzilla.suse.com/show_bug.cgi?id=1043970 "Bugref at end of title: bsc#1043760")'
-    );
+    my $build_url = $driver->get_current_url();
+    $build_url =~ s/\?.*//;
+    OpenQA::Utils::log_debug('build_url: ' . $build_url);
+    $driver->find_element_by_id('text')->send_keys(<<'EOF');
+foo@bar foo#bar should not be detected as bugref
+bsc#2436346bla should not be detected, too
+bsc#2436347bla2
+<a href="https://openqa.example.com/foo/bar">https://openqa.example.com/foo/bar</a>: http://localhost:9562
+https://openqa.example.com/tests/181148 (reference http://localhost/foo/bar )
+bsc#1234 boo#2345,poo#3456 t#4567 "some quotes suff should not cause problems"
+t#5678/modules/welcome/steps/1
+https://progress.opensuse.org/issues/6789
+https://bugzilla.novell.com/show_bug.cgi?id=7890
+[bsc#1000629](https://bugzilla.suse.com/show_bug.cgi?id=1000629)
+<a href="https://bugzilla.suse.com/show_bug.cgi?id=1000630">bsc#1000630</a>
+bnc#1246
+gh#os-autoinst/openQA#1234
+https://github.com/os-autoinst/os-autoinst/pull/960
+bgo#768954 brc#1401123
+https://bugzilla.gnome.org/show_bug.cgi?id=690345
+https://bugzilla.redhat.com/show_bug.cgi?id=343098
+[bsc#1043970](https://bugzilla.suse.com/show_bug.cgi?id=1043970 "Bugref at end of title: bsc#1043760")
+EOF
     $driver->find_element_by_id('submitComment')->click();
     wait_for_ajax;
 
@@ -226,8 +229,7 @@ subtest 'URL auto-replace' => sub {
 qr(bsc#1234 boo#2345,poo#3456 t#4567 .*poo#6789 bsc#7890 bsc#1000629 bsc#1000630 bnc#1246 gh#os-autoinst/openQA#1234 gh#os-autoinst/os-autoinst#960 bgo#768954 brc#1401123)
     );
     my @urls = $driver->find_elements('div.media-comment a', 'css');
-    is(scalar @urls, 21);
-    is((shift @urls)->get_text(), 'https://openqa.example.com/foo/bar',      "url1");
+    is(scalar @urls, 19);
     is((shift @urls)->get_text(), 'http://localhost:9562',                   "url2");
     is((shift @urls)->get_text(), 'https://openqa.example.com/tests/181148', "url3");
     is((shift @urls)->get_text(), 'http://localhost/foo/bar',                "url4");
@@ -239,7 +241,6 @@ qr(bsc#1234 boo#2345,poo#3456 t#4567 .*poo#6789 bsc#7890 bsc#1000629 bsc#1000630
     is((shift @urls)->get_text(), 'poo#6789',                                "url10");
     is((shift @urls)->get_text(), 'bsc#7890',                                "url11");
     is((shift @urls)->get_text(), 'bsc#1000629',                             "url12");
-    is((shift @urls)->get_text(), 'bsc#1000630',                             "url13");
     is((shift @urls)->get_text(), 'bnc#1246',                                "url14");
     is((shift @urls)->get_text(), 'gh#os-autoinst/openQA#1234',              "url15");
     is((shift @urls)->get_text(), 'gh#os-autoinst/os-autoinst#960',          "url16");
@@ -250,7 +251,6 @@ qr(bsc#1234 boo#2345,poo#3456 t#4567 .*poo#6789 bsc#7890 bsc#1000629 bsc#1000630
     is((shift @urls)->get_text(), 'bsc#1043970',                             "url21");
 
     my @urls2 = $driver->find_elements('div.media-comment a', 'css');
-    is((shift @urls2)->get_attribute('href'), 'https://openqa.example.com/foo/bar',                 "url1-href");
     is((shift @urls2)->get_attribute('href'), 'http://localhost:9562/',                             "url2-href");
     is((shift @urls2)->get_attribute('href'), 'https://openqa.example.com/tests/181148',            "url3-href");
     is((shift @urls2)->get_attribute('href'), 'http://localhost/foo/bar',                           "url4-href");
@@ -262,7 +262,6 @@ qr(bsc#1234 boo#2345,poo#3456 t#4567 .*poo#6789 bsc#7890 bsc#1000629 bsc#1000630
     is((shift @urls2)->get_attribute('href'), 'https://progress.opensuse.org/issues/6789',             "url10-href");
     is((shift @urls2)->get_attribute('href'), 'https://bugzilla.suse.com/show_bug.cgi?id=7890',        "url11-href");
     is((shift @urls2)->get_attribute('href'), 'https://bugzilla.suse.com/show_bug.cgi?id=1000629',     "url12-href");
-    is((shift @urls2)->get_attribute('href'), 'https://bugzilla.suse.com/show_bug.cgi?id=1000630',     "url13-href");
     is((shift @urls2)->get_attribute('href'), 'https://bugzilla.suse.com/show_bug.cgi?id=1246',        "url14-href");
     is((shift @urls2)->get_attribute('href'), 'https://github.com/os-autoinst/openQA/issues/1234',     "url15-href");
     is((shift @urls2)->get_attribute('href'), 'https://github.com/os-autoinst/os-autoinst/issues/960', "url16-href");