From 6b010c0a475f500c7234d5aa6488a453231e9fcf Mon Sep 17 00:00:00 2001 From: Furkan Ayhan Date: Wed, 22 Oct 2025 11:13:07 +0200 Subject: [PATCH 1/2] Require API authentication for GET CI Lint endpoint Add authentication check to the `GET :id/ci/lint` endpoint to ensure requests are made with proper API authentication when a user session exists. This prevents inconsistent behavior when the endpoint is accessed through different authentication methods. The check only applies when there is an authenticated user session but no API token present, requiring explicit API authentication for such requests. Changelog: changed --- lib/api/lint.rb | 2 ++ spec/requests/api/lint_spec.rb | 63 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 241967e6c47a77..50daeec8d69ecb 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -34,6 +34,8 @@ class Lint < ::API::Base end get ':id/ci/lint', urgency: :low do + unauthorized!("This endpoint requires an API authentication") if current_user && !::Current.token_info + authorize_read_code! not_found! 'Repository' if user_project.empty_repo? diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb index eef7f5a14a4e8e..066cf56fd0972f 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -270,6 +270,19 @@ end end + context 'when authenticated user has no API token' do + before do + allow(::Current).to receive(:token_info).and_return(nil) + end + + it 'returns unauthorized error' do + ci_lint + + expect(response).to have_gitlab_http_status(:unauthorized) + expect(json_response['message']).to include('This endpoint requires an API authentication') + end + end + context 'when including a component' do let_it_be(:component_project_files) do { @@ -578,6 +591,56 @@ end end end + + context 'when the project is public' do + let_it_be(:project) { create(:project, :repository, :public) } + + context 'with valid .gitlab-ci.yml content' do + let_it_be(:yaml_content) do + { test: { stage: 'test', script: 'echo 1' } }.to_yaml + end + + before_all do + project.repository.create_file( + project.creator, + '.gitlab-ci.yml', + yaml_content, + message: 'Automatically created .gitlab-ci.yml', + branch_name: 'master' + ) + end + + context 'when unauthenticated' do + let_it_be(:api_user) { nil } + + it 'passes validation' do + ci_lint + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['valid']).to eq(true) + expect(json_response['warnings']).to eq([]) + expect(json_response['errors']).to eq([]) + end + end + + context 'when authenticated as project developer' do + let_it_be(:api_user) { create(:user) } + + before do + project.add_developer(api_user) + end + + it 'passes validation' do + ci_lint + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['valid']).to eq(true) + expect(json_response['warnings']).to eq([]) + expect(json_response['errors']).to eq([]) + end + end + end + end end describe 'POST /projects/:id/ci/lint' do -- GitLab From db272cf0fc004511b9fb842ae63d5e9d66dd51d9 Mon Sep 17 00:00:00 2001 From: Furkan Ayhan Date: Wed, 22 Oct 2025 13:21:36 +0200 Subject: [PATCH 2/2] Add feature flag --- .../ci_require_api_token_for_ci_lint.yml | 10 ++++ lib/api/lint.rb | 6 +- spec/requests/api/lint_spec.rb | 57 +++++++++++++------ 3 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 config/feature_flags/gitlab_com_derisk/ci_require_api_token_for_ci_lint.yml diff --git a/config/feature_flags/gitlab_com_derisk/ci_require_api_token_for_ci_lint.yml b/config/feature_flags/gitlab_com_derisk/ci_require_api_token_for_ci_lint.yml new file mode 100644 index 00000000000000..304208909da025 --- /dev/null +++ b/config/feature_flags/gitlab_com_derisk/ci_require_api_token_for_ci_lint.yml @@ -0,0 +1,10 @@ +--- +name: ci_require_api_token_for_ci_lint +description: Require API authentication for GET CI Lint endpoint +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/209764 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/209764 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/578076 +milestone: '18.6' +group: group::pipeline authoring +type: gitlab_com_derisk +default_enabled: false diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 50daeec8d69ecb..92751568685d01 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -34,12 +34,14 @@ class Lint < ::API::Base end get ':id/ci/lint', urgency: :low do - unauthorized!("This endpoint requires an API authentication") if current_user && !::Current.token_info - authorize_read_code! not_found! 'Repository' if user_project.empty_repo? + if Feature.enabled?(:ci_require_api_token_for_ci_lint, user_project) && current_user && !::Current.token_info + unauthorized!("This endpoint requires an API authentication") + end + content_ref = params[:content_ref] || params[:sha] || user_project.repository.root_ref_sha dry_run_ref = params[:dry_run_ref] || params[:ref] || user_project.default_branch diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb index 066cf56fd0972f..45b7cfc9cd25a5 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -268,18 +268,26 @@ it_behaves_like 'valid config with warnings' end - end - context 'when authenticated user has no API token' do - before do - allow(::Current).to receive(:token_info).and_return(nil) - end + context 'when authenticated user has no API token' do + before do + allow(::Current).to receive(:token_info).and_return(nil) + end - it 'returns unauthorized error' do - ci_lint + it 'returns unauthorized error' do + ci_lint + + expect(response).to have_gitlab_http_status(:unauthorized) + expect(json_response['message']).to include('This endpoint requires an API authentication') + end - expect(response).to have_gitlab_http_status(:unauthorized) - expect(json_response['message']).to include('This endpoint requires an API authentication') + context 'when the FF ci_require_api_token_for_ci_lint is disabled' do + before do + stub_feature_flags(ci_require_api_token_for_ci_lint: false) + end + + it_behaves_like 'valid config without warnings' + end end end @@ -610,9 +618,7 @@ ) end - context 'when unauthenticated' do - let_it_be(:api_user) { nil } - + shared_examples 'passing validation' do it 'passes validation' do ci_lint @@ -623,6 +629,20 @@ end end + context 'when unauthenticated' do + let_it_be(:api_user) { nil } + + it_behaves_like 'passing validation' + + context 'when the FF ci_require_api_token_for_ci_lint is disabled' do + before do + stub_feature_flags(ci_require_api_token_for_ci_lint: false) + end + + it_behaves_like 'passing validation' + end + end + context 'when authenticated as project developer' do let_it_be(:api_user) { create(:user) } @@ -630,13 +650,14 @@ project.add_developer(api_user) end - it 'passes validation' do - ci_lint + it_behaves_like 'passing validation' - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['valid']).to eq(true) - expect(json_response['warnings']).to eq([]) - expect(json_response['errors']).to eq([]) + context 'when the FF ci_require_api_token_for_ci_lint is disabled' do + before do + stub_feature_flags(ci_require_api_token_for_ci_lint: false) + end + + it_behaves_like 'passing validation' end end end -- GitLab