Commit 1e198653 authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot

Merge branch 'security-protected-branch-name-escaped-fix' into 'master'

Unescape and sanitize protected branch name on create and update

See merge request gitlab-org/security/gitlab!1953
parents b7dcacfd d195dd1c
......@@ -13,5 +13,23 @@ module ProtectedBranches
def after_execute(*)
# overridden in EE::ProtectedBranches module
end
def filtered_params
return unless params
params[:name] = sanitize_branch_name(params[:name]) if params[:name].present?
params
end
private
def sanitize_branch_name(name)
name = CGI.unescapeHTML(name)
name = Sanitize.fragment(name)
# Sanitize.fragment escapes HTML chars, so unescape again to allow names
# like `feature->master`
CGI.unescapeHTML(name)
end
end
end
......@@ -21,7 +21,7 @@ module ProtectedBranches
end
def protected_branch
@protected_branch ||= project.protected_branches.new(params)
@protected_branch ||= project.protected_branches.new(filtered_params)
end
end
end
......
......@@ -8,7 +8,7 @@ module ProtectedBranches
old_merge_access_levels = protected_branch.merge_access_levels.map(&:clone)
old_push_access_levels = protected_branch.push_access_levels.map(&:clone)
if protected_branch.update(params)
if protected_branch.update(filtered_params)
after_execute(protected_branch: protected_branch, old_merge_access_levels: old_merge_access_levels, old_push_access_levels: old_push_access_levels)
end
......
......@@ -118,12 +118,12 @@ RSpec.describe 'Protected Branches', :js do
it "allows creating explicit protected branches" do
visit project_protected_branches_path(project)
set_defaults
set_protected_branch_name('some-branch')
set_protected_branch_name('some->branch')
click_on "Protect"
within(".protected-branches-list") { expect(page).to have_content('some-branch') }
within(".protected-branches-list") { expect(page).to have_content('some->branch') }
expect(ProtectedBranch.count).to eq(1)
expect(ProtectedBranch.last.name).to eq('some-branch')
expect(ProtectedBranch.last.name).to eq('some->branch')
end
it "displays the last commit on the matching branch if it exists" do
......
......@@ -7,13 +7,15 @@ RSpec.describe ProtectedBranches::CreateService do
let(:user) { project.owner }
let(:params) do
{
name: 'master',
name: name,
merge_access_levels_attributes: [{ access_level: Gitlab::Access::MAINTAINER }],
push_access_levels_attributes: [{ access_level: Gitlab::Access::MAINTAINER }]
}
end
describe '#execute' do
let(:name) { 'master' }
subject(:service) { described_class.new(project, user, params) }
it 'creates a new protected branch' do
......@@ -22,6 +24,41 @@ RSpec.describe ProtectedBranches::CreateService do
expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
end
context 'when name has escaped HTML' do
let(:name) { 'feature->test' }
it 'creates the new protected branch matching the unescaped version' do
expect { service.execute }.to change(ProtectedBranch, :count).by(1)
expect(project.protected_branches.last.name).to eq('feature->test')
end
context 'and name contains HTML tags' do
let(:name) { '<b>master</b>' }
it 'creates the new protected branch with sanitized name' do
expect { service.execute }.to change(ProtectedBranch, :count).by(1)
expect(project.protected_branches.last.name).to eq('master')
end
context 'and contains unsafe HTML' do
let(:name) { '<script>alert('foo');</script>' }
it 'does not create the new protected branch' do
expect { service.execute }.not_to change(ProtectedBranch, :count)
end
end
end
context 'when name contains unescaped HTML tags' do
let(:name) { '<b>master</b>' }
it 'creates the new protected branch with sanitized name' do
expect { service.execute }.to change(ProtectedBranch, :count).by(1)
expect(project.protected_branches.last.name).to eq('master')
end
end
end
context 'when user does not have permission' do
let(:user) { create(:user) }
......
......@@ -6,17 +6,50 @@ RSpec.describe ProtectedBranches::UpdateService do
let(:protected_branch) { create(:protected_branch) }
let(:project) { protected_branch.project }
let(:user) { project.owner }
let(:params) { { name: 'new protected branch name' } }
let(:params) { { name: new_name } }
describe '#execute' do
let(:new_name) { 'new protected branch name' }
let(:result) { service.execute(protected_branch) }
subject(:service) { described_class.new(project, user, params) }
it 'updates a protected branch' do
result = service.execute(protected_branch)
expect(result.reload.name).to eq(params[:name])
end
context 'when name has escaped HTML' do
let(:new_name) { 'feature-&gt;test' }
it 'updates protected branch name with unescaped HTML' do
expect(result.reload.name).to eq('feature->test')
end
context 'and name contains HTML tags' do
let(:new_name) { '&lt;b&gt;master&lt;/b&gt;' }
it 'updates protected branch name with sanitized name' do
expect(result.reload.name).to eq('master')
end
context 'and contains unsafe HTML' do
let(:new_name) { '&lt;script&gt;alert(&#39;foo&#39;);&lt;/script&gt;' }
it 'does not update the protected branch' do
expect(result.reload.name).to eq(protected_branch.name)
end
end
end
end
context 'when name contains unescaped HTML tags' do
let(:new_name) { '<b>master</b>' }
it 'updates protected branch name with sanitized name' do
expect(result.reload.name).to eq('master')
end
end
context 'without admin_project permissions' do
let(:user) { create(:user) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment