From 7fe20ad84544d74ca6549f43dffe696d3aa13d3e Mon Sep 17 00:00:00 2001 From: Chad Lavimoniere Date: Fri, 8 Aug 2025 17:18:43 -0400 Subject: [PATCH 01/34] Allow iframe embeds in markdown from allowed src domains Add admin setting to create allowlist of iframe src domains, and allow iframe embedding in Markdown from those domains, rendering the iframe elements proper on the frontend. Changelog: changed --- app/assets/javascripts/lib/dompurify.js | 69 ++- .../application_settings/general/index.js | 5 + .../admin/application_settings/iframe.js | 24 + app/controllers/concerns/preview_markdown.rb | 3 +- app/helpers/application_settings_helper.rb | 3 + app/models/application_setting.rb | 13 +- .../application_setting_implementation.rb | 8 + .../application_settings/_iframe.html.haml | 23 + .../application_settings/_kroki.html.haml | 8 +- .../application_settings/general.html.haml | 3 +- .../iframe_rendering_allowlist.yml | 13 + .../iframe_rendering_enabled.yml | 12 + ...frame_allowlist_to_application_settings.rb | 47 ++ db/schema_migrations/20250902215135 | 1 + db/structure.sql | 577 ++++++++++++++++-- doc/api/settings.md | 4 + .../cells/application_settings_analysis.md | 2 + doc/user/markdown.md | 4 + lib/api/entities/application_setting.rb | 3 + lib/api/settings.rb | 3 + lib/banzai/filter/sanitization_filter.rb | 29 + .../content_security_policy/config_loader.rb | 8 + lib/gitlab/gon_helper.rb | 3 + locale/gitlab.pot | 27 +- spec/requests/api/settings_spec.rb | 30 + 25 files changed, 862 insertions(+), 60 deletions(-) create mode 100644 app/assets/javascripts/pages/admin/application_settings/iframe.js create mode 100644 app/views/admin/application_settings/_iframe.html.haml create mode 100644 config/application_setting_columns/iframe_rendering_allowlist.yml create mode 100644 config/application_setting_columns/iframe_rendering_enabled.yml create mode 100644 db/migrate/20250902215135_add_iframe_allowlist_to_application_settings.rb create mode 100644 db/schema_migrations/20250902215135 diff --git a/app/assets/javascripts/lib/dompurify.js b/app/assets/javascripts/lib/dompurify.js index f5038c5a62ff6c..4ead5ff6badcc3 100644 --- a/app/assets/javascripts/lib/dompurify.js +++ b/app/assets/javascripts/lib/dompurify.js @@ -102,6 +102,22 @@ addHook('afterSanitizeAttributes', (node) => { } }); +// Allow restoring of src from known safe data attributes when present +addHook('afterSanitizeAttributes', (node) => { + // Matches usage from Markdown renderers that store canonical URLs + const tag = node.tagName; + if (tag !== 'IMG' && tag !== 'IFRAME') return; + + const dataset = node?.dataset || {}; + if (Object.prototype.hasOwnProperty.call(dataset, 'canonicalSrc')) { + node.setAttribute('src', dataset.canonicalSrc); + } + + if (Object.prototype.hasOwnProperty.call(dataset, 'original')) { + node.setAttribute('src', dataset.original); + } +}); + const TEMPORARY_ATTRIBUTE = 'data-temp-href-target'; addHook('beforeSanitizeAttributes', (node, _, config) => { @@ -161,6 +177,57 @@ addHook('afterSanitizeAttributes', (node, _, config) => { } }); -export const sanitize = (val, config) => dompurifySanitize(val, { ...defaultConfig, ...config }); +// Block iframes entirely when the setting is disabled +addHook('beforeSanitizeElements', (node) => { + if (node.tagName === 'IFRAME' && !window.gon?.iframe_rendering_enabled) { + node.remove(); + } +}); + +// Enforce iframe src allowlist when enabled +addHook('afterSanitizeAttributes', (node) => { + if (node.tagName === 'IFRAME') { + const url = node.getAttribute('src'); + const allowlist = window.gon?.iframe_rendering_allowlist || []; + const isAllowed = + Boolean(url) && allowlist.some((domain) => url.startsWith(`https://${domain}`)); + + if (!isAllowed) { + node.remove(); + } + } +}); + +export const sanitize = (val, config) => { + const finalConfig = { ...defaultConfig, ...config }; + + if (window.gon?.iframe_rendering_enabled) { + finalConfig.ADD_TAGS = finalConfig.ADD_TAGS || []; + if (!finalConfig.ADD_TAGS.includes('iframe')) { + finalConfig.ADD_TAGS.push('iframe'); + } + + const iframeAttrs = [ + 'src', + 'width', + 'height', + 'title', + 'frameborder', + 'allow', + 'allowfullscreen', + 'referrerpolicy', + 'style', + ]; + + finalConfig.ADD_ATTR = finalConfig.ADD_ATTR || []; + iframeAttrs.forEach((attr) => { + if (!finalConfig.ADD_ATTR.includes(attr)) { + finalConfig.ADD_ATTR.push(attr); + } + }); + } + + return dompurifySanitize(val, finalConfig); +}; export { isValidAttribute }; diff --git a/app/assets/javascripts/pages/admin/application_settings/general/index.js b/app/assets/javascripts/pages/admin/application_settings/general/index.js index c33a7237805da3..815523ddfb5779 100644 --- a/app/assets/javascripts/pages/admin/application_settings/general/index.js +++ b/app/assets/javascripts/pages/admin/application_settings/general/index.js @@ -5,6 +5,11 @@ import { initAdminDeletionProtectionSettings } from '~/admin/application_setting import initAccountAndLimitsSection from '../account_and_limits'; import initGitpod from '../gitpod'; import initSignupRestrictions from '../signup_restrictions'; +import initIframeSettings from '../iframe'; + +document.addEventListener('DOMContentLoaded', () => { + initIframeSettings(); +}); (() => { initAccountAndLimitsSection(); diff --git a/app/assets/javascripts/pages/admin/application_settings/iframe.js b/app/assets/javascripts/pages/admin/application_settings/iframe.js new file mode 100644 index 00000000000000..f78e90651e0012 --- /dev/null +++ b/app/assets/javascripts/pages/admin/application_settings/iframe.js @@ -0,0 +1,24 @@ +function toggleIframeAllowlist() { + const checkbox = document.getElementById('application_setting_iframe_rendering_enabled'); + const allowlistGroup = document.getElementById('iframe-allowlist-group'); + + if (!checkbox || !allowlistGroup) return; + + function updateVisibility() { + if (checkbox.checked) { + allowlistGroup.style.display = ''; + } else { + allowlistGroup.style.display = 'none'; + } + } + + // Set initial state + updateVisibility(); + + // Listen for changes + checkbox.addEventListener('change', updateVisibility); +} + +export default function initIframeSettings() { + toggleIframeAllowlist(); +} diff --git a/app/controllers/concerns/preview_markdown.rb b/app/controllers/concerns/preview_markdown.rb index 3d62139eccda13..6fdf613c35184e 100644 --- a/app/controllers/concerns/preview_markdown.rb +++ b/app/controllers/concerns/preview_markdown.rb @@ -73,7 +73,8 @@ def markdown_context_params ref: params[:ref], # Disable comments in markdown for IE browsers because comments in IE # could allow script execution. - allow_comments: !browser.ie? + allow_comments: !browser.ie?, + allow_iframes: Gitlab::CurrentSettings.iframe_rendering_enabled? ) end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 03fa486055bf40..489d2db9a02b3a 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -365,6 +365,9 @@ def visible_attributes :housekeeping_incremental_repack_period, :housekeeping_optimize_repository_period, :html_emails_enabled, + :iframe_rendering_enabled, + :iframe_rendering_allowlist, + :iframe_rendering_allowlist_raw, :import_sources, :inactive_resource_access_tokens_delete_after_days, :inactive_projects_delete_after_months, diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 0d0b1889808ff1..645befa1c7e568 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -88,6 +88,7 @@ def self.kroki_formats_attributes serialize :disabled_oauth_sign_in_sources, type: Array # rubocop:disable Cop/ActiveRecordSerialize serialize :domain_allowlist, type: Array # rubocop:disable Cop/ActiveRecordSerialize serialize :domain_denylist, type: Array # rubocop:disable Cop/ActiveRecordSerialize + serialize :iframe_rendering_allowlist, type: Array # rubocop:disable Cop/ActiveRecordSerialize # See https://gitlab.com/gitlab-org/gitlab/-/issues/300916 serialize :asset_proxy_allowlist, type: Array # rubocop:disable Cop/ActiveRecordSerialize @@ -1025,6 +1026,9 @@ def self.kroki_formats_attributes validates :math_rendering_limits_enabled, inclusion: { in: [true, false], message: N_('must be a boolean value') } + validates :iframe_rendering_enabled, + inclusion: { in: [true, false], message: N_('must be a boolean value') } + validates :require_admin_two_factor_authentication, inclusion: { in: [true, false], message: N_('must be a boolean value') } @@ -1317,9 +1321,14 @@ def should_prevent_visibility_restriction? end def should_reset_inactive_project_deletion_warning? - saved_change_to_inactive_projects_delete_after_months? || saved_change_to_delete_inactive_projects?(from: true, - to: false) + (previous_changes.keys & %w[delete_inactive_projects deletion_adjourned_period]).any? end + + def iframe_rendering_allowlist + super || [] + end + + public :iframe_rendering_allowlist end ApplicationSetting.prepend_mod_with('ApplicationSetting') diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index ccd5d153af689d..87269b59c7c94d 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -506,6 +506,14 @@ def search_rate_limit_allowlist_raw=(values) self.search_rate_limit_allowlist = strings_to_array(values).map(&:downcase) end + def iframe_rendering_allowlist_raw + array_to_string(iframe_rendering_allowlist) + end + + def iframe_rendering_allowlist_raw=(values) + self.iframe_rendering_allowlist = strings_to_array(values) + end + def asset_proxy_whitelist=(values) values = strings_to_array(values) if values.is_a?(String) diff --git a/app/views/admin/application_settings/_iframe.html.haml b/app/views/admin/application_settings/_iframe.html.haml new file mode 100644 index 00000000000000..853a903d2ece6d --- /dev/null +++ b/app/views/admin/application_settings/_iframe.html.haml @@ -0,0 +1,23 @@ +- expanded = integration_expanded?('iframe_') + += render ::Layouts::SettingsBlockComponent.new(_('Iframe embedding'), + id: 'js-iframe-settings', + testid: 'admin-iframe-settings', + expanded: expanded) do |c| + - c.with_description do + = _('Configure iframe embedding for Markdown documents.') + - c.with_body do + = gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-iframe-settings'), html: { class: 'fieldset-form', id: 'iframe-settings' } do |f| + = form_errors(@application_setting) if expanded + + %fieldset + .form-group + = f.gitlab_ui_checkbox_component :iframe_rendering_enabled, _('Allow iframes in Markdown'), help_text: _('Allows the use of iframes in Markdown documents for all users. This may be a security risk if you do not trust all users of your GitLab instance.') + + .form-group{ id: 'iframe-allowlist-group', style: @application_setting.iframe_rendering_enabled? ? '' : 'display: none;' } + = f.label :iframe_rendering_allowlist_raw, _('Allowed iframe sources'), class: 'label-bold' + = f.text_area :iframe_rendering_allowlist_raw, class: 'form-control gl-form-input', rows: 8 + .form-text.gl-text-subtle + = _('A list of domains that are allowed to be embedded as iframes. One domain per line.') + + = f.submit _('Save changes'), pajamas_button: true diff --git a/app/views/admin/application_settings/_kroki.html.haml b/app/views/admin/application_settings/_kroki.html.haml index 7fbd2ede999f6b..376b900e105210 100644 --- a/app/views/admin/application_settings/_kroki.html.haml +++ b/app/views/admin/application_settings/_kroki.html.haml @@ -15,12 +15,10 @@ = f.gitlab_ui_checkbox_component :kroki_enabled, _('Enable Kroki') .form-group - = f.label :kroki_url, 'Kroki URL', class: 'label-bold' - = f.text_field :kroki_url, class: 'form-control gl-form-input', placeholder: 'http://your-kroki-instance:8000' + = f.label :kroki_url, _('Kroki URL'), class: 'label-bold' + = f.text_field :kroki_url, class: 'form-control gl-form-input' .form-text.gl-text-subtle - - install_link_url = 'https://docs.kroki.io/kroki/setup/install/' - - install_link_start = ''.html_safe % { url: install_link_url } - = html_escape(_('Use the public cloud instance URL (%{kroki_public_url}) or %{install_link_start}install Kroki%{install_link_end} on your own infrastructure and use your own instance URL.')) % { kroki_public_url: 'https://kroki.io'.html_safe, install_link_start: install_link_start, install_link_end: ''.html_safe } + = _('The Kroki server that will be used to generate diagrams.') .form-group = f.label :kroki_formats, _('Additional diagram formats'), class: 'label-bold' .form-text.gl-text-subtle diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml index 1daa21c126df4b..647ad339159083 100644 --- a/app/views/admin/application_settings/general.html.haml +++ b/app/views/admin/application_settings/general.html.haml @@ -88,10 +88,11 @@ = render_if_exists 'admin/application_settings/maintenance_mode_settings_form' = render 'admin/application_settings/silent_mode_settings_form' = render 'admin/application_settings/gitpod' -= render 'admin/application_settings/kroki' = render 'admin/application_settings/mailgun' += render 'admin/application_settings/kroki' = render 'admin/application_settings/plantuml' = render 'admin/application_settings/diagramsnet' += render 'admin/application_settings/iframe' = render 'admin/application_settings/sourcegraph' -# this partial is from JiHu, see details in https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417 = render_if_exists 'admin/application_settings/dingtalk_integration' diff --git a/config/application_setting_columns/iframe_rendering_allowlist.yml b/config/application_setting_columns/iframe_rendering_allowlist.yml new file mode 100644 index 00000000000000..c2b1e2a1aa4631 --- /dev/null +++ b/config/application_setting_columns/iframe_rendering_allowlist.yml @@ -0,0 +1,13 @@ +--- +api_type: array of strings +attr: iframe_rendering_allowlist +clusterwide: true +column: iframe_rendering_allowlist +db_type: text +default: +description: List of allowed iframe `src` host[:port] entries used for Content Security + Policy and sanitization. +encrypted: false +gitlab_com_different_than_default: false +jihu: false +not_null: false diff --git a/config/application_setting_columns/iframe_rendering_enabled.yml b/config/application_setting_columns/iframe_rendering_enabled.yml new file mode 100644 index 00000000000000..cca9058240bc59 --- /dev/null +++ b/config/application_setting_columns/iframe_rendering_enabled.yml @@ -0,0 +1,12 @@ +--- +api_type: boolean +attr: iframe_rendering_enabled +clusterwide: true +column: iframe_rendering_enabled +db_type: boolean +default: 'false' +description: Allow rendering of iframes in Markdown. Disabled by default. +encrypted: false +gitlab_com_different_than_default: false +jihu: false +not_null: true diff --git a/db/migrate/20250902215135_add_iframe_allowlist_to_application_settings.rb b/db/migrate/20250902215135_add_iframe_allowlist_to_application_settings.rb new file mode 100644 index 00000000000000..5b5ba0bf71f444 --- /dev/null +++ b/db/migrate/20250902215135_add_iframe_allowlist_to_application_settings.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# See https://docs.gitlab.com/ee/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddIframeAllowlistToApplicationSettings < Gitlab::Database::Migration[2.3] + # When using the methods "add_concurrent_index" or "remove_concurrent_index" + # you must disable the use of transactions + # as these methods can not run in an existing transaction. + # When using "add_concurrent_index" or "remove_concurrent_index" methods make sure + # that either of them is the _only_ method called in the migration, + # any other changes should go in a separate migration. + # This ensures that upon failure _only_ the index creation or removing fails + # and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + disable_ddl_transaction! + # + # Configure the `gitlab_schema` to perform data manipulation (DML). + # Visit: https://docs.gitlab.com/ee/development/database/migrations_for_multiple_databases.html + # restrict_gitlab_migration gitlab_schema: :gitlab_main + + # Add dependent 'batched_background_migrations.queued_migration_version' values. + # DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS = [] + milestone '18.4' + + def up + with_lock_retries do + unless column_exists?(:application_settings, :iframe_rendering_enabled) + add_column :application_settings, :iframe_rendering_enabled, :boolean, default: false, null: false + end + + unless column_exists?(:application_settings, :iframe_rendering_allowlist) + add_column :application_settings, :iframe_rendering_allowlist, :text + end + end + + # Limit size of the allowlist text column for safety + add_text_limit :application_settings, :iframe_rendering_allowlist, 5000 + end + + def down + remove_column :application_settings, :iframe_rendering_allowlist, if_exists: true + remove_column :application_settings, :iframe_rendering_enabled, if_exists: true + end +end diff --git a/db/schema_migrations/20250902215135 b/db/schema_migrations/20250902215135 new file mode 100644 index 00000000000000..55ae61eba63cff --- /dev/null +++ b/db/schema_migrations/20250902215135 @@ -0,0 +1 @@ +f49347eef568271e12fde9a92ef90971aa7fa34dbb0ba01b404e68c295ce403f \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 853309fdc7eaf2..030510e839d2dd 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -4882,6 +4882,36 @@ RETURN NULL; END $$; +CREATE TABLE ai_code_suggestion_events ( + id bigint NOT NULL, + "timestamp" timestamp with time zone NOT NULL, + user_id bigint NOT NULL, + organization_id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + event smallint NOT NULL, + namespace_path text, + payload jsonb, + CONSTRAINT check_ba9ae3f258 CHECK ((char_length(namespace_path) <= 255)) +) +PARTITION BY RANGE ("timestamp"); + +CREATE TABLE ai_duo_chat_events ( + id bigint NOT NULL, + "timestamp" timestamp with time zone NOT NULL, + user_id bigint NOT NULL, + personal_namespace_id bigint, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + event smallint NOT NULL, + namespace_path text, + payload jsonb, + organization_id bigint, + CONSTRAINT check_628cdfbf3f CHECK ((char_length(namespace_path) <= 255)), + CONSTRAINT check_f759f45177 CHECK ((organization_id IS NOT NULL)) +) +PARTITION BY RANGE ("timestamp"); + CREATE TABLE ai_events_counts ( id bigint NOT NULL, events_date date NOT NULL, @@ -4893,6 +4923,21 @@ CREATE TABLE ai_events_counts ( ) PARTITION BY RANGE (events_date); +CREATE TABLE ai_troubleshoot_job_events ( + id bigint NOT NULL, + "timestamp" timestamp with time zone NOT NULL, + user_id bigint NOT NULL, + job_id bigint NOT NULL, + project_id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + event smallint NOT NULL, + namespace_path text, + payload jsonb, + CONSTRAINT check_29d6dbc329 CHECK ((char_length(namespace_path) <= 255)) +) +PARTITION BY RANGE ("timestamp"); + CREATE TABLE ai_usage_events ( id bigint NOT NULL, "timestamp" timestamp with time zone NOT NULL, @@ -5741,7 +5786,7 @@ PARTITION BY LIST (partition_id); CREATE TABLE p_ci_finished_build_ch_sync_events ( build_id bigint NOT NULL, - partition bigint DEFAULT 1 NOT NULL, + partition bigint DEFAULT 2 NOT NULL, build_finished_at timestamp without time zone NOT NULL, processed boolean DEFAULT false NOT NULL, project_id bigint NOT NULL @@ -5751,7 +5796,7 @@ PARTITION BY LIST (partition); CREATE TABLE p_ci_finished_pipeline_ch_sync_events ( pipeline_id bigint NOT NULL, project_namespace_id bigint NOT NULL, - partition bigint DEFAULT 1 NOT NULL, + partition bigint DEFAULT 2 NOT NULL, pipeline_finished_at timestamp without time zone NOT NULL, processed boolean DEFAULT false NOT NULL ) @@ -10780,20 +10825,6 @@ CREATE SEQUENCE ai_catalog_items_id_seq ALTER SEQUENCE ai_catalog_items_id_seq OWNED BY ai_catalog_items.id; -CREATE TABLE ai_code_suggestion_events ( - id bigint NOT NULL, - "timestamp" timestamp with time zone NOT NULL, - user_id bigint NOT NULL, - organization_id bigint NOT NULL, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - event smallint NOT NULL, - namespace_path text, - payload jsonb, - CONSTRAINT check_ba9ae3f258 CHECK ((char_length(namespace_path) <= 255)) -) -PARTITION BY RANGE ("timestamp"); - CREATE SEQUENCE ai_code_suggestion_events_id_seq START WITH 1 INCREMENT BY 1 @@ -10852,22 +10883,6 @@ CREATE SEQUENCE ai_conversation_threads_id_seq ALTER SEQUENCE ai_conversation_threads_id_seq OWNED BY ai_conversation_threads.id; -CREATE TABLE ai_duo_chat_events ( - id bigint NOT NULL, - "timestamp" timestamp with time zone NOT NULL, - user_id bigint NOT NULL, - personal_namespace_id bigint, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - event smallint NOT NULL, - namespace_path text, - payload jsonb, - organization_id bigint, - CONSTRAINT check_628cdfbf3f CHECK ((char_length(namespace_path) <= 255)), - CONSTRAINT check_f759f45177 CHECK ((organization_id IS NOT NULL)) -) -PARTITION BY RANGE ("timestamp"); - CREATE SEQUENCE ai_duo_chat_events_id_seq START WITH 1 INCREMENT BY 1 @@ -11017,21 +11032,6 @@ CREATE TABLE ai_testing_terms_acceptances ( CONSTRAINT check_5efe98894e CHECK ((char_length(user_email) <= 255)) ); -CREATE TABLE ai_troubleshoot_job_events ( - id bigint NOT NULL, - "timestamp" timestamp with time zone NOT NULL, - user_id bigint NOT NULL, - job_id bigint NOT NULL, - project_id bigint NOT NULL, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - event smallint NOT NULL, - namespace_path text, - payload jsonb, - CONSTRAINT check_29d6dbc329 CHECK ((char_length(namespace_path) <= 255)) -) -PARTITION BY RANGE ("timestamp"); - CREATE SEQUENCE ai_troubleshoot_job_events_id_seq START WITH 1 INCREMENT BY 1 @@ -12146,6 +12146,8 @@ CREATE TABLE application_settings ( lock_duo_foundational_flows_enabled boolean DEFAULT false NOT NULL, duo_sast_fp_detection_enabled boolean DEFAULT true NOT NULL, lock_duo_sast_fp_detection_enabled boolean DEFAULT false NOT NULL, + iframe_rendering_enabled boolean DEFAULT false NOT NULL, + iframe_rendering_allowlist text, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)), @@ -12188,6 +12190,7 @@ CREATE TABLE application_settings ( CONSTRAINT check_85a39b68ff CHECK ((char_length(encrypted_ci_jwt_signing_key_iv) <= 255)), CONSTRAINT check_8dca35398a CHECK ((char_length(public_runner_releases_url) <= 255)), CONSTRAINT check_8e7df605a1 CHECK ((char_length(cube_api_base_url) <= 512)), + CONSTRAINT check_987903e806 CHECK ((char_length(iframe_rendering_allowlist) <= 5000)), CONSTRAINT check_9a719834eb CHECK ((char_length(secret_detection_token_revocation_url) <= 255)), CONSTRAINT check_9c6c447a13 CHECK ((char_length(maintenance_mode_message) <= 255)), CONSTRAINT check_a5704163cc CHECK ((char_length(secret_detection_revocation_token_types_url) <= 255)), @@ -48489,6 +48492,486 @@ ALTER INDEX index_uploads_9ba88c4165_on_uploaded_by_user_id ATTACH PARTITION vul ALTER INDEX index_uploads_9ba88c4165_on_uploader_and_path ATTACH PARTITION vulnerability_remediation_uploads_uploader_path_idx; +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_16 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_17 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_18 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_19 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_20 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_21 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_22 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_23 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_24 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_25 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_26 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_27 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_28 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_29 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_30 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_31 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_16 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_17 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_18 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_19 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_20 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_21 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_22 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_23 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_24 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_25 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_26 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_27 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_28 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_29 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_30 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_31 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_00 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_01 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_02 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_03 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_04 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_05 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_06 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_07 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_08 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_09 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_10 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_11 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_12 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_13 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_14 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_15 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_16 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_16 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_17 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_17 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_18 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_18 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_19 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_19 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_20 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_20 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_21 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_21 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_22 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_22 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_23 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_23 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_24 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_24 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_25 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_25 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_26 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_26 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_27 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_27 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_28 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_28 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_29 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_29 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_30 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_30 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_31 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_31 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_32 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_32 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_33 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_33 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_34 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_34 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_35 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_35 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_36 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_36 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_37 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_37 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_38 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_38 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_39 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_39 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_40 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_40 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_41 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_41 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_42 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_42 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_43 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_43 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_44 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_44 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_45 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_45 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_46 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_46 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_47 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_47 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_48 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_48 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_49 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_49 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_50 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_50 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_51 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_51 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_52 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_52 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_53 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_53 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_54 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_54 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_55 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_55 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_56 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_56 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_57 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_57 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_58 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_58 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_59 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_59 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_60 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_60 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_61 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_61 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_62 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_62 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_63 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_63 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_00 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_01 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_02 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_03 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_04 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_05 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_06 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_07 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_08 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_09 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_10 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_11 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_12 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_13 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_14 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_15 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_16 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_16 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_17 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_17 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_18 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_18 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_19 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_19 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_20 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_20 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_21 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_21 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_22 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_22 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_23 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_23 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_24 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_24 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_25 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_25 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_26 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_26 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_27 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_27 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_28 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_28 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_29 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_29 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_30 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_30 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_31 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_31 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_00 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_01 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_02 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_03 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_04 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_05 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_06 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_07 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_08 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_09 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_10 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_11 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_12 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_13 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_14 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_15 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_16 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_16 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_17 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_17 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_18 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_18 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_19 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_19 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_20 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_20 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_21 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_21 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_22 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_22 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_23 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_23 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_24 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_24 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_25 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_25 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_26 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_26 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_27 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_27 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_28 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_28 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_29 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_29 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_30 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_30 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_31 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_31 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_32 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_32 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_33 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_33 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_34 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_34 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_35 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_35 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_36 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_36 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_37 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_37 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_38 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_38 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_39 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_39 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_40 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_40 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_41 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_41 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_42 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_42 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_43 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_43 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_44 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_44 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_45 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_45 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_46 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_46 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_47 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_47 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_48 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_48 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_49 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_49 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_50 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_50 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_51 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_51 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_52 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_52 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_53 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_53 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_54 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_54 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_55 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_55 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_56 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_56 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_57 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_57 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_58 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_58 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_59 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_59 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_60 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_60 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_61 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_61 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_62 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_62 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + +CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_63 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_63 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); + CREATE TRIGGER ai_active_context_connections_loose_fk_trigger AFTER DELETE ON ai_active_context_connections REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); CREATE TRIGGER ai_conversation_threads_loose_fk_trigger AFTER DELETE ON ai_conversation_threads REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); diff --git a/doc/api/settings.md b/doc/api/settings.md index cb87ac9bbc9729..5dea0b46e633f6 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -440,6 +440,7 @@ This heading is referenced by a script: `scripts/cells/application-settings-anal - `require_personal_access_token_expiry` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/470192) in GitLab 17.3. - `receptive_cluster_agents_enabled` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/463427) in GitLab 17.4. - `allow_all_integrations` and `allowed_integrations` [added](https://gitlab.com/gitlab-org/gitlab/-/issues/500610) in GitLab 17.6. +- `iframe_rendering_enabled`, `iframe_rendering_allowlist`, and `iframe_rendering_allowlist_raw` introduced in GitLab 18.3. {{< /history >}} @@ -835,6 +836,9 @@ to configure other related settings. These requirements are | `secret_push_protection_available` | boolean | no | Allow projects to enable secret push protection. This does not enable secret push protection. Ultimate only. | | `disable_invite_members` | boolean | no | Disable invite members functionality for group. | | `enforce_pipl_compliance` | boolean | no | Sets whether pipl compliance is enforced for the saas application or not | +| `iframe_rendering_enabled` | boolean | no | Allow rendering of iframes in Markdown. Disabled by default. | +| `iframe_rendering_allowlist` | array of strings | no | List of allowed iframe `src` host[:port] entries used for Content Security Policy and sanitization. | +| `iframe_rendering_allowlist_raw` | string | no | Raw newline- or comma-separated list of allowed iframe `src` host[:port] entries. | ### Dormant project settings diff --git a/doc/development/cells/application_settings_analysis.md b/doc/development/cells/application_settings_analysis.md index 5414b9cfeb9cca..e7210d662d1c86 100644 --- a/doc/development/cells/application_settings_analysis.md +++ b/doc/development/cells/application_settings_analysis.md @@ -237,6 +237,8 @@ title: Application Settings analysis | `html_emails_enabled` | `false` | `boolean` | `boolean` | `false` | `true` | `false` | `false`| `true` | | `id` | `false` | `bigint` | `` | `true` | `???` | `false` | `false`| `false` | | `identity_verification_settings` | `false` | `jsonb` | `` | `true` | `'{}'::jsonb` | `true` | `true`| `false` | +| `iframe_rendering_allowlist` | `false` | `text` | `array of strings` | `false` | `null` | `false` | `true`| `true` | +| `iframe_rendering_enabled` | `false` | `boolean` | `boolean` | `true` | `false` | `false` | `true`| `true` | | `import_sources` | `false` | `text` | `array of strings` | `false` | `null` | `true` | `true`| `true` | | `importers` | `false` | `jsonb` | `` | `true` | `'{}'::jsonb` | `true` | `true`| `false` | | `inactive_projects_delete_after_months` | `false` | `integer` | `` | `true` | `2` | `false` | `false`| `false` | diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 5eb0a117c7d9cc..7ae85f81271380 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -1038,6 +1038,10 @@ Watch the following video walkthrough of this feature: +{{< alert type="note" >}} +Administrators can enable rendering of iframes in Markdown and configure the allowed iframe `src` hosts at the instance level. You can manage these settings via the [Application settings API](../api/settings.md#available-settings): `iframe_rendering_enabled`, `iframe_rendering_allowlist`, and `iframe_rendering_allowlist_raw`. +{{< /alert >}} + The `items` attribute is a list of objects representing the data points. ````markdown diff --git a/lib/api/entities/application_setting.rb b/lib/api/entities/application_setting.rb index 68052f5aeb92a0..96981b4cc4c353 100644 --- a/lib/api/entities/application_setting.rb +++ b/lib/api/entities/application_setting.rb @@ -42,6 +42,9 @@ def self.exposed_attributes expose :password_authentication_enabled_for_web, as: :signin_enabled expose :allow_local_requests_from_web_hooks_and_services, as: :allow_local_requests_from_hooks_and_services expose :asset_proxy_allowlist, as: :asset_proxy_whitelist + expose :iframe_rendering_enabled + expose :iframe_rendering_allowlist + expose :iframe_rendering_allowlist_raw # This field is deprecated and always returns true expose(:housekeeping_bitmaps_enabled) { |_settings, _options| true } diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 4a337e81e68f89..d62c41b6611f44 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -71,6 +71,9 @@ def filter_attributes_using_license(attrs) optional :domain_denylist_enabled, type: Boolean, desc: 'Enable domain denylist for sign ups' optional :domain_denylist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Enter multiple entries on separate lines. Ex: domain.com, *.domain.com' optional :domain_allowlist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Enter multiple entries on separate lines. Ex: domain.com, *.domain.com' + optional :iframe_rendering_enabled, type: Boolean, desc: 'Allow rendering of iframes in Markdown.' + optional :iframe_rendering_allowlist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Allowed iframe src host[:port] entries. Enter multiple entries separated by commas or on separate lines.' + optional :iframe_rendering_allowlist_raw, type: String, desc: 'Raw newline- or comma-separated list of allowed iframe src host[:port] entries.' optional :eks_integration_enabled, type: Boolean, desc: 'Enable integration with Amazon EKS' given eks_integration_enabled: ->(val) { val } do requires :eks_account_id, type: String, desc: 'Amazon account ID for EKS integration' diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb index b64ce6520d09fb..bf6ddc972c48d0 100644 --- a/lib/banzai/filter/sanitization_filter.rb +++ b/lib/banzai/filter/sanitization_filter.rb @@ -13,6 +13,7 @@ class SanitizationFilter < Banzai::Filter::BaseSanitizationFilter def customize_allowlist(allowlist) allowlist[:allow_comments] = context[:allow_comments] + add_iframe_allowlist(allowlist) allow_table_alignment(allowlist) allow_json_table_attributes(allowlist) allow_sourcepos_and_escaped_char(allowlist) @@ -25,6 +26,16 @@ def customize_allowlist(allowlist) private + def add_iframe_allowlist(allowlist) + return unless context[:allow_iframes] + + allowlist[:elements].push('iframe') + allowlist[:attributes]['iframe'] = %w[ + src width height title frameborder allow allowfullscreen referrerpolicy style + ] + allowlist[:transformers].push(self.class.validate_iframe_rendering) + end + def allow_table_alignment(allowlist) # Allow table alignment; we allow specific text-align values in a transformer below allowlist[:attributes]['th'] = %w[style] @@ -72,6 +83,24 @@ def allow_section_footnotes(allowlist) end class << self + def validate_iframe_rendering + ->(env) do + node = env[:node] + return unless node.name == 'iframe' + + if node.has_attribute?('src') + url = node['src'] + # Only allow HTTPS URLs and check against allowlist + unless url.start_with?('https://') && + Gitlab::CurrentSettings.iframe_rendering_allowlist.any? { |domain| url.start_with?("https://#{domain}") } + node.remove + end + else + node.remove + end + end + end + def remove_unsafe_table_style ->(env) do node = env[:node] diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb index 26c844cc1fe8a0..f073434bb95640 100644 --- a/lib/gitlab/content_security_policy/config_loader.rb +++ b/lib/gitlab/content_security_policy/config_loader.rb @@ -28,6 +28,7 @@ def default_directives allow_sentry(directives) allow_framed_gitlab_paths(directives) allow_customersdot(directives) + allow_iframes(directives) csp_level_3_backport(directives) directives @@ -168,6 +169,13 @@ def allow_customersdot(directives) append_to_directive(directives, 'frame_src', customersdot_host) end + def allow_iframes(directives) + return unless Gitlab::CurrentSettings.respond_to?(:iframe_rendering_enabled?) && + Gitlab::CurrentSettings.iframe_rendering_enabled? + + append_to_directive(directives, 'frame_src', Gitlab::CurrentSettings.iframe_rendering_allowlist.join(' ')) + end + # The follow contains workarounds to patch Safari's lack of support for CSP Level 3 def csp_level_3_backport(directives) # See https://gitlab.com/gitlab-org/gitlab/-/issues/343579 diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index a49c63c6b6ac96..b17d0b06a06afe 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -22,6 +22,8 @@ def add_gon_variables gon.math_rendering_limits_enabled = Gitlab::CurrentSettings.math_rendering_limits_enabled gon.allow_immediate_namespaces_deletion = Gitlab::CurrentSettings.allow_immediate_namespaces_deletion_for_user?(current_user) + gon.iframe_rendering_enabled = Gitlab::CurrentSettings.iframe_rendering_enabled? + gon.iframe_rendering_allowlist = Gitlab::CurrentSettings.iframe_rendering_allowlist # Sentry configurations for the browser client are done # via `Gitlab::CurrentSettings` from the Admin panel: @@ -106,6 +108,7 @@ def add_gon_feature_flags push_frontend_feature_flag(:paneled_view, current_user) push_frontend_feature_flag(:archive_group) push_frontend_feature_flag(:accessible_loading_button, current_user) + push_frontend_feature_flag(:allow_iframes_in_markdown, current_user) # Expose the Project Studio user preference as if it were a feature flag push_force_frontend_feature_flag(:project_studio_enabled, Users::ProjectStudio.new(current_user).enabled?) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5909e0cd20e57f..17b53bf920c691 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2271,6 +2271,9 @@ msgstr "" msgid "A list for this status already exists on the board" msgstr "" +msgid "A list of domains that are allowed to be embedded as iframes. One domain per line." +msgstr "" + msgid "A management, operational, or technical control (that is, safeguard or countermeasure) employed by an organization that provides equivalent or comparable protection for an information system." msgstr "" @@ -7600,6 +7603,9 @@ msgstr "" msgid "Allow group owners to manage LDAP-related settings" msgstr "" +msgid "Allow iframes in Markdown" +msgstr "" + msgid "Allow job retries even if the deployment job is outdated." msgstr "" @@ -7672,6 +7678,9 @@ msgstr "" msgid "Allowed email domain restriction only permitted for top-level groups" msgstr "" +msgid "Allowed iframe sources" +msgstr "" + msgid "Allowed to create" msgstr "" @@ -7687,6 +7696,9 @@ msgstr "" msgid "Allows projects to track errors using an Opstrace integration." msgstr "" +msgid "Allows the use of iframes in Markdown documents for all users. This may be a security risk if you do not trust all users of your GitLab instance." +msgstr "" + msgid "Almost there…" msgstr "" @@ -18577,6 +18589,9 @@ msgstr "" msgid "Configure feature \"%{name}\"" msgstr "" +msgid "Configure iframe embedding for Markdown documents." +msgstr "" + msgid "Configure import sources and settings related to import and export features." msgstr "" @@ -34703,6 +34718,9 @@ msgstr "" msgid "If your HTTP repository is not publicly accessible, add your credentials." msgstr "" +msgid "Iframe embedding" +msgstr "" + msgid "Ignore" msgstr "" @@ -38416,6 +38434,9 @@ msgstr "" msgid "Kroki" msgstr "" +msgid "Kroki URL" +msgstr "" + msgid "Kubernetes" msgstr "" @@ -67094,6 +67115,9 @@ msgstr "" msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." msgstr "" +msgid "The Kroki server that will be used to generate diagrams." +msgstr "" + msgid "The Matrix access token (for example, `syt-zyx57W2v1u123ew11`)." msgstr "" @@ -72023,9 +72047,6 @@ msgstr "" msgid "Use the link below to confirm your email address." msgstr "" -msgid "Use the public cloud instance URL (%{kroki_public_url}) or %{install_link_start}install Kroki%{install_link_end} on your own infrastructure and use your own instance URL." -msgstr "" - msgid "Use the search bar on the top of this page" msgstr "" diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 57971d29256be5..f96d538b8a7180 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -105,12 +105,42 @@ expect(json_response['require_personal_access_token_expiry']).to eq(true) expect(json_response['organization_cluster_agent_authorization_enabled']).to eq(false) expect(json_response['terraform_state_encryption_enabled']).to eq(true) + expect(json_response['iframe_rendering_enabled']).to be(false) + expect(json_response['iframe_rendering_allowlist']).to eq([]) end end describe "PUT /application/settings" do let(:group) { create(:group) } + context 'iframe in markdown settings' do + it 'updates iframe_rendering_enabled and iframe_rendering_allowlist via array' do + put api('/application/settings', admin), + params: { + iframe_rendering_enabled: true, + iframe_rendering_allowlist: ['example.com', 'videos.example.com:443'] + } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['iframe_rendering_enabled']).to be(true) + expect(json_response['iframe_rendering_allowlist']).to match_array(['example.com', 'videos.example.com:443']) + expect(ApplicationSetting.current.iframe_rendering_allowlist).to match_array(['example.com', 'videos.example.com:443']) + end + + it 'allows a raw string for iframe_rendering_allowlist_raw' do + raw = "example.com\nvideos.example.com:443" + put api('/application/settings', admin), + params: { + iframe_rendering_allowlist_raw: raw + } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['iframe_rendering_allowlist']).to match_array(['example.com', 'videos.example.com:443']) + expect(json_response['iframe_rendering_allowlist_raw']).to eq(raw) + expect(ApplicationSetting.current.iframe_rendering_allowlist).to match_array(['example.com', 'videos.example.com:443']) + end + end + context "custom repository storage type set in the config" do before do # Add a possible storage to the config -- GitLab From 5345c588ba344c1988b9ab4303143429e9f638d7 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Wed, 29 Oct 2025 17:24:55 +1100 Subject: [PATCH 02/34] Update migration per docs See https://docs.gitlab.com/development/application_settings/. --- ...frame_allowlist_to_application_settings.rb | 47 ------------------- ...frame_allowlist_to_application_settings.rb | 24 ++++++++++ db/schema_migrations/20250902215135 | 1 - db/schema_migrations/20251029062200 | 1 + 4 files changed, 25 insertions(+), 48 deletions(-) delete mode 100644 db/migrate/20250902215135_add_iframe_allowlist_to_application_settings.rb create mode 100644 db/migrate/20251029062200_add_iframe_allowlist_to_application_settings.rb delete mode 100644 db/schema_migrations/20250902215135 create mode 100644 db/schema_migrations/20251029062200 diff --git a/db/migrate/20250902215135_add_iframe_allowlist_to_application_settings.rb b/db/migrate/20250902215135_add_iframe_allowlist_to_application_settings.rb deleted file mode 100644 index 5b5ba0bf71f444..00000000000000 --- a/db/migrate/20250902215135_add_iframe_allowlist_to_application_settings.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -# See https://docs.gitlab.com/ee/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class AddIframeAllowlistToApplicationSettings < Gitlab::Database::Migration[2.3] - # When using the methods "add_concurrent_index" or "remove_concurrent_index" - # you must disable the use of transactions - # as these methods can not run in an existing transaction. - # When using "add_concurrent_index" or "remove_concurrent_index" methods make sure - # that either of them is the _only_ method called in the migration, - # any other changes should go in a separate migration. - # This ensures that upon failure _only_ the index creation or removing fails - # and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - disable_ddl_transaction! - # - # Configure the `gitlab_schema` to perform data manipulation (DML). - # Visit: https://docs.gitlab.com/ee/development/database/migrations_for_multiple_databases.html - # restrict_gitlab_migration gitlab_schema: :gitlab_main - - # Add dependent 'batched_background_migrations.queued_migration_version' values. - # DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS = [] - milestone '18.4' - - def up - with_lock_retries do - unless column_exists?(:application_settings, :iframe_rendering_enabled) - add_column :application_settings, :iframe_rendering_enabled, :boolean, default: false, null: false - end - - unless column_exists?(:application_settings, :iframe_rendering_allowlist) - add_column :application_settings, :iframe_rendering_allowlist, :text - end - end - - # Limit size of the allowlist text column for safety - add_text_limit :application_settings, :iframe_rendering_allowlist, 5000 - end - - def down - remove_column :application_settings, :iframe_rendering_allowlist, if_exists: true - remove_column :application_settings, :iframe_rendering_enabled, if_exists: true - end -end diff --git a/db/migrate/20251029062200_add_iframe_allowlist_to_application_settings.rb b/db/migrate/20251029062200_add_iframe_allowlist_to_application_settings.rb new file mode 100644 index 00000000000000..eb32032929b7c4 --- /dev/null +++ b/db/migrate/20251029062200_add_iframe_allowlist_to_application_settings.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class AddIframeAllowlistToApplicationSettings < Gitlab::Database::Migration[2.3] + disable_ddl_transaction! + milestone '18.6' + + def up + with_lock_retries do + add_column :application_settings, :iframe_rendering_enabled, :boolean, default: false, null: false, + if_not_exists: true + add_column :application_settings, :iframe_rendering_allowlist, :text, if_not_exists: true + end + + # Limit size of the allowlist text column for safety + add_text_limit :application_settings, :iframe_rendering_allowlist, 5000 + end + + def down + with_lock_retries do + remove_column :application_settings, :iframe_rendering_allowlist, if_exists: true + remove_column :application_settings, :iframe_rendering_enabled, if_exists: true + end + end +end diff --git a/db/schema_migrations/20250902215135 b/db/schema_migrations/20250902215135 deleted file mode 100644 index 55ae61eba63cff..00000000000000 --- a/db/schema_migrations/20250902215135 +++ /dev/null @@ -1 +0,0 @@ -f49347eef568271e12fde9a92ef90971aa7fa34dbb0ba01b404e68c295ce403f \ No newline at end of file diff --git a/db/schema_migrations/20251029062200 b/db/schema_migrations/20251029062200 new file mode 100644 index 00000000000000..e7b11a579fe335 --- /dev/null +++ b/db/schema_migrations/20251029062200 @@ -0,0 +1 @@ +704444a7adfd231b1f5c845750a0ac2b8a6a6b57cf18d00f9f7fb6af7398b678 \ No newline at end of file -- GitLab From 0c6572f9425d3f3b68f99907f56c55e4300f0f37 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Wed, 29 Oct 2025 17:24:55 +1100 Subject: [PATCH 03/34] Reverse possibly inadvertent change Doesn't relate to this MR and I can't vouch for it! Possible merge error; leave it as-is in master. --- app/models/application_setting.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 645befa1c7e568..a9c5a425740a0e 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -1321,7 +1321,8 @@ def should_prevent_visibility_restriction? end def should_reset_inactive_project_deletion_warning? - (previous_changes.keys & %w[delete_inactive_projects deletion_adjourned_period]).any? + saved_change_to_inactive_projects_delete_after_months? || saved_change_to_delete_inactive_projects?(from: true, + to: false) end def iframe_rendering_allowlist -- GitLab From 2f8dd3d58b2c2b3071b4f978e12b388e70769049 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Wed, 29 Oct 2025 17:24:55 +1100 Subject: [PATCH 04/34] Update milestone in docs --- doc/api/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/settings.md b/doc/api/settings.md index 5dea0b46e633f6..f0200692e4eaf4 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -440,7 +440,7 @@ This heading is referenced by a script: `scripts/cells/application-settings-anal - `require_personal_access_token_expiry` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/470192) in GitLab 17.3. - `receptive_cluster_agents_enabled` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/463427) in GitLab 17.4. - `allow_all_integrations` and `allowed_integrations` [added](https://gitlab.com/gitlab-org/gitlab/-/issues/500610) in GitLab 17.6. -- `iframe_rendering_enabled`, `iframe_rendering_allowlist`, and `iframe_rendering_allowlist_raw` introduced in GitLab 18.3. +- `iframe_rendering_enabled`, `iframe_rendering_allowlist`, and `iframe_rendering_allowlist_raw` introduced in GitLab 18.6. {{< /history >}} -- GitLab From 4903bfe8234d96e72db637762c9ea1187c9c1e5b Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Wed, 29 Oct 2025 17:24:55 +1100 Subject: [PATCH 05/34] Check for forward-slash (/) following domain This prevents malicious use; e.g. "https://www.youtube.com.my.evil.site/". --- app/assets/javascripts/lib/dompurify.js | 2 +- lib/banzai/filter/sanitization_filter.rb | 26 +++++++++---------- .../content_security_policy/config_loader.rb | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/lib/dompurify.js b/app/assets/javascripts/lib/dompurify.js index 4ead5ff6badcc3..dc8cfad6c6f5f7 100644 --- a/app/assets/javascripts/lib/dompurify.js +++ b/app/assets/javascripts/lib/dompurify.js @@ -190,7 +190,7 @@ addHook('afterSanitizeAttributes', (node) => { const url = node.getAttribute('src'); const allowlist = window.gon?.iframe_rendering_allowlist || []; const isAllowed = - Boolean(url) && allowlist.some((domain) => url.startsWith(`https://${domain}`)); + Boolean(url) && allowlist.some((domain) => url.startsWith(`https://${domain}/`)); if (!isAllowed) { node.remove(); diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb index bf6ddc972c48d0..9948b7839624c2 100644 --- a/lib/banzai/filter/sanitization_filter.rb +++ b/lib/banzai/filter/sanitization_filter.rb @@ -33,7 +33,7 @@ def add_iframe_allowlist(allowlist) allowlist[:attributes]['iframe'] = %w[ src width height title frameborder allow allowfullscreen referrerpolicy style ] - allowlist[:transformers].push(self.class.validate_iframe_rendering) + allowlist[:transformers].push(self.class.method(:validate_iframe_rendering)) end def allow_table_alignment(allowlist) @@ -83,21 +83,19 @@ def allow_section_footnotes(allowlist) end class << self - def validate_iframe_rendering - ->(env) do - node = env[:node] - return unless node.name == 'iframe' - - if node.has_attribute?('src') - url = node['src'] - # Only allow HTTPS URLs and check against allowlist - unless url.start_with?('https://') && - Gitlab::CurrentSettings.iframe_rendering_allowlist.any? { |domain| url.start_with?("https://#{domain}") } - node.remove - end - else + def validate_iframe_rendering(env) + node = env[:node] + return unless node.name == 'iframe' + + if node.has_attribute?('src') + url = node['src'] + # Only allow HTTPS URLs and check against allowlist + unless url.start_with?('https://') && + Gitlab::CurrentSettings.iframe_rendering_allowlist.any? { |domain| url.start_with?("https://#{domain}/") } node.remove end + else + node.remove end end diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb index f073434bb95640..8cb7b5bf155ced 100644 --- a/lib/gitlab/content_security_policy/config_loader.rb +++ b/lib/gitlab/content_security_policy/config_loader.rb @@ -173,7 +173,7 @@ def allow_iframes(directives) return unless Gitlab::CurrentSettings.respond_to?(:iframe_rendering_enabled?) && Gitlab::CurrentSettings.iframe_rendering_enabled? - append_to_directive(directives, 'frame_src', Gitlab::CurrentSettings.iframe_rendering_allowlist.join(' ')) + append_to_directive(directives, 'frame_src', Gitlab::CurrentSettings.iframe_rendering_allowlist.map { |domain| "https://#{domain}/" }.join(' ')) end # The follow contains workarounds to patch Safari's lack of support for CSP Level 3 -- GitLab From b7c9cd2674dc5cc9a0635cd57e5c18a7b435b153 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Thu, 30 Oct 2025 11:48:09 +1100 Subject: [PATCH 06/34] Remove cruft in structure.sql --- db/structure.sql | 574 ++++------------------------------------------- 1 file changed, 47 insertions(+), 527 deletions(-) diff --git a/db/structure.sql b/db/structure.sql index 030510e839d2dd..15599dae0c33a7 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -4882,36 +4882,6 @@ RETURN NULL; END $$; -CREATE TABLE ai_code_suggestion_events ( - id bigint NOT NULL, - "timestamp" timestamp with time zone NOT NULL, - user_id bigint NOT NULL, - organization_id bigint NOT NULL, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - event smallint NOT NULL, - namespace_path text, - payload jsonb, - CONSTRAINT check_ba9ae3f258 CHECK ((char_length(namespace_path) <= 255)) -) -PARTITION BY RANGE ("timestamp"); - -CREATE TABLE ai_duo_chat_events ( - id bigint NOT NULL, - "timestamp" timestamp with time zone NOT NULL, - user_id bigint NOT NULL, - personal_namespace_id bigint, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - event smallint NOT NULL, - namespace_path text, - payload jsonb, - organization_id bigint, - CONSTRAINT check_628cdfbf3f CHECK ((char_length(namespace_path) <= 255)), - CONSTRAINT check_f759f45177 CHECK ((organization_id IS NOT NULL)) -) -PARTITION BY RANGE ("timestamp"); - CREATE TABLE ai_events_counts ( id bigint NOT NULL, events_date date NOT NULL, @@ -4923,21 +4893,6 @@ CREATE TABLE ai_events_counts ( ) PARTITION BY RANGE (events_date); -CREATE TABLE ai_troubleshoot_job_events ( - id bigint NOT NULL, - "timestamp" timestamp with time zone NOT NULL, - user_id bigint NOT NULL, - job_id bigint NOT NULL, - project_id bigint NOT NULL, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - event smallint NOT NULL, - namespace_path text, - payload jsonb, - CONSTRAINT check_29d6dbc329 CHECK ((char_length(namespace_path) <= 255)) -) -PARTITION BY RANGE ("timestamp"); - CREATE TABLE ai_usage_events ( id bigint NOT NULL, "timestamp" timestamp with time zone NOT NULL, @@ -5786,7 +5741,7 @@ PARTITION BY LIST (partition_id); CREATE TABLE p_ci_finished_build_ch_sync_events ( build_id bigint NOT NULL, - partition bigint DEFAULT 2 NOT NULL, + partition bigint DEFAULT 1 NOT NULL, build_finished_at timestamp without time zone NOT NULL, processed boolean DEFAULT false NOT NULL, project_id bigint NOT NULL @@ -5796,7 +5751,7 @@ PARTITION BY LIST (partition); CREATE TABLE p_ci_finished_pipeline_ch_sync_events ( pipeline_id bigint NOT NULL, project_namespace_id bigint NOT NULL, - partition bigint DEFAULT 2 NOT NULL, + partition bigint DEFAULT 1 NOT NULL, pipeline_finished_at timestamp without time zone NOT NULL, processed boolean DEFAULT false NOT NULL ) @@ -10825,6 +10780,20 @@ CREATE SEQUENCE ai_catalog_items_id_seq ALTER SEQUENCE ai_catalog_items_id_seq OWNED BY ai_catalog_items.id; +CREATE TABLE ai_code_suggestion_events ( + id bigint NOT NULL, + "timestamp" timestamp with time zone NOT NULL, + user_id bigint NOT NULL, + organization_id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + event smallint NOT NULL, + namespace_path text, + payload jsonb, + CONSTRAINT check_ba9ae3f258 CHECK ((char_length(namespace_path) <= 255)) +) +PARTITION BY RANGE ("timestamp"); + CREATE SEQUENCE ai_code_suggestion_events_id_seq START WITH 1 INCREMENT BY 1 @@ -10883,6 +10852,22 @@ CREATE SEQUENCE ai_conversation_threads_id_seq ALTER SEQUENCE ai_conversation_threads_id_seq OWNED BY ai_conversation_threads.id; +CREATE TABLE ai_duo_chat_events ( + id bigint NOT NULL, + "timestamp" timestamp with time zone NOT NULL, + user_id bigint NOT NULL, + personal_namespace_id bigint, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + event smallint NOT NULL, + namespace_path text, + payload jsonb, + organization_id bigint, + CONSTRAINT check_628cdfbf3f CHECK ((char_length(namespace_path) <= 255)), + CONSTRAINT check_f759f45177 CHECK ((organization_id IS NOT NULL)) +) +PARTITION BY RANGE ("timestamp"); + CREATE SEQUENCE ai_duo_chat_events_id_seq START WITH 1 INCREMENT BY 1 @@ -11032,6 +11017,21 @@ CREATE TABLE ai_testing_terms_acceptances ( CONSTRAINT check_5efe98894e CHECK ((char_length(user_email) <= 255)) ); +CREATE TABLE ai_troubleshoot_job_events ( + id bigint NOT NULL, + "timestamp" timestamp with time zone NOT NULL, + user_id bigint NOT NULL, + job_id bigint NOT NULL, + project_id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + event smallint NOT NULL, + namespace_path text, + payload jsonb, + CONSTRAINT check_29d6dbc329 CHECK ((char_length(namespace_path) <= 255)) +) +PARTITION BY RANGE ("timestamp"); + CREATE SEQUENCE ai_troubleshoot_job_events_id_seq START WITH 1 INCREMENT BY 1 @@ -48492,486 +48492,6 @@ ALTER INDEX index_uploads_9ba88c4165_on_uploaded_by_user_id ATTACH PARTITION vul ALTER INDEX index_uploads_9ba88c4165_on_uploader_and_path ATTACH PARTITION vulnerability_remediation_uploads_uploader_path_idx; -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_16 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_17 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_18 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_19 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_20 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_21 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_22 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_23 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_24 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_25 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_26 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_27 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_28 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_29 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_30 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_issue BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_issue_stage_events_31 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_16 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_17 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_18 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_19 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_20 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_21 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_22 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_23 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_24 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_25 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_26 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_27 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_28 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_29 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_30 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_analytics_cycle_analytics_merge BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.analytics_cycle_analytics_merge_request_stage_events_31 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_00 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_01 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_02 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_03 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_04 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_05 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_06 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_07 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_08 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_09 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_10 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_11 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_12 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_13 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_14 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_15 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_16 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_16 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_17 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_17 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_18 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_18 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_19 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_19 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_20 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_20 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_21 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_21 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_22 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_22 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_23 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_23 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_24 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_24 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_25 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_25 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_26 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_26 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_27 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_27 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_28 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_28 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_29 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_29 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_30 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_30 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_31 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_31 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_32 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_32 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_33 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_33 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_34 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_34 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_35 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_35 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_36 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_36 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_37 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_37 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_38 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_38 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_39 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_39 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_40 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_40 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_41 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_41 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_42 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_42 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_43 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_43 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_44 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_44 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_45 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_45 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_46 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_46 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_47 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_47 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_48 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_48 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_49 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_49 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_50 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_50 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_51 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_51 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_52 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_52 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_53 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_53 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_54 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_54 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_55 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_55 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_56 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_56 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_57 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_57 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_58 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_58 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_59 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_59 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_60 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_60 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_61 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_61 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_62 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_62 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_issue_search_data_63 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.issue_search_data_63 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_00 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_01 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_02 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_03 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_04 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_05 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_06 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_07 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_08 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_09 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_10 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_11 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_12 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_13 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_14 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_15 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_16 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_16 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_17 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_17 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_18 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_18 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_19 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_19 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_20 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_20 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_21 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_21 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_22 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_22 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_23 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_23 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_24 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_24 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_25 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_25 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_26 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_26 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_27 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_27 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_28 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_28 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_29 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_29 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_30 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_30 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_namespace_descendants_31 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.namespace_descendants_31 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_virtual_registries_packages_mav BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.virtual_registries_packages_maven_cache_entries_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_00 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_00 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_01 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_01 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_02 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_02 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_03 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_03 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_04 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_04 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_05 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_05 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_06 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_06 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_07 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_07 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_08 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_08 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_09 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_09 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_10 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_10 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_11 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_11 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_12 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_12 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_13 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_13 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_14 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_14 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_15 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_15 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_16 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_16 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_17 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_17 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_18 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_18 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_19 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_19 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_20 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_20 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_21 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_21 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_22 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_22 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_23 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_23 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_24 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_24 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_25 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_25 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_26 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_26 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_27 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_27 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_28 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_28 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_29 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_29 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_30 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_30 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_31 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_31 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_32 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_32 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_33 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_33 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_34 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_34 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_35 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_35 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_36 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_36 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_37 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_37 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_38 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_38 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_39 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_39 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_40 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_40 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_41 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_41 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_42 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_42 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_43 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_43 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_44 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_44 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_45 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_45 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_46 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_46 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_47 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_47 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_48 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_48 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_49 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_49 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_50 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_50 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_51 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_51 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_52 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_52 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_53 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_53 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_54 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_54 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_55 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_55 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_56 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_56 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_57 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_57 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_58 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_58 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_59 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_59 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_60 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_60 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_61 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_61 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_62 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_62 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - -CREATE TRIGGER gitlab_schema_write_trigger_for_work_item_descriptions_63 BEFORE INSERT OR DELETE OR UPDATE OR TRUNCATE ON gitlab_partitions_static.work_item_descriptions_63 FOR EACH STATEMENT EXECUTE FUNCTION gitlab_schema_prevent_write(); - CREATE TRIGGER ai_active_context_connections_loose_fk_trigger AFTER DELETE ON ai_active_context_connections REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); CREATE TRIGGER ai_conversation_threads_loose_fk_trigger AFTER DELETE ON ai_conversation_threads REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); -- GitLab From 3ef16a86f2b014288d926efa5cf5c7244261b413 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Thu, 30 Oct 2025 11:48:09 +1100 Subject: [PATCH 07/34] Integrate changes from !206993 --- .../behaviors/markdown/render_gfm.js | 47 +++++++++---------- .../behaviors/markdown/render_iframe.js | 43 +++++++++++++++++ lib/banzai/filter/iframe_link_filter.rb | 6 +-- 3 files changed, 66 insertions(+), 30 deletions(-) create mode 100644 app/assets/javascripts/behaviors/markdown/render_iframe.js diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js index c02950534ff2bf..b95c1b98226e59 100644 --- a/app/assets/javascripts/behaviors/markdown/render_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js @@ -3,6 +3,7 @@ import highlightCurrentUser from './highlight_current_user'; import { renderKroki } from './render_kroki'; import renderMath from './render_math'; import renderSandboxedMermaid from './render_sandboxed_mermaid'; +import renderIframe from './render_iframe'; import { renderGlql } from './render_glql'; import { renderJSONTable, renderJSONTableHTML } from './render_json_table'; import { addAriaLabels } from './accessibility'; @@ -17,43 +18,37 @@ function initPopovers(elements) { .catch(() => {}); } -// Render GitLab flavored Markdown +// Render GitLab Flavored Markdown export function renderGFM(element) { if (!element) { return; } - const [ - highlightEls, - krokiEls, - mathEls, - mermaidEls, - tableEls, - tableHTMLEls, - glqlEls, - userEls, - popoverEls, - taskListCheckboxEls, - imageEls, - ] = [ - '.js-syntax-highlight', - '.js-render-kroki[hidden]', - '.js-render-math', - '.js-render-mermaid', - '[data-canonical-lang="json"][data-lang-params~="table"]', - 'table[data-table-fields]', - '[data-canonical-lang="glql"], .language-glql', - '.gfm-project_member', + function arrayFromAll(selector) { + return Array.from(element.querySelectorAll(selector)); + } + + const highlightEls = arrayFromAll('.js-syntax-highlight'); + const krokiEls = arrayFromAll('.js-render-kroki[hidden]'); + const mathEls = arrayFromAll('.js-render-math'); + const mermaidEls = arrayFromAll('.js-render-mermaid'); + const iframeEls = arrayFromAll('.js-render-iframe'); + const tableEls = arrayFromAll('[data-canonical-lang="json"][data-lang-params~="table"]'); + const tableHTMLEls = arrayFromAll('table[data-table-fields]'); + const glqlEls = arrayFromAll('[data-canonical-lang="glql"], .language-glql'); + const userEls = arrayFromAll('.gfm-project_member'); + const popoverEls = arrayFromAll( '.gfm-issue, .gfm-work_item, .gfm-merge_request, .gfm-epic, .gfm-milestone', - '.task-list-item-checkbox', - // eslint-disable-next-line @gitlab/require-i18n-strings - 'a>img', - ].map((selector) => Array.from(element.querySelectorAll(selector))); + ); + const taskListCheckboxEls = arrayFromAll('.task-list-item-checkbox'); + // eslint-disable-next-line @gitlab/require-i18n-strings + const imageEls = arrayFromAll('a>img'); syntaxHighlight(highlightEls); renderKroki(krokiEls); renderMath(mathEls); renderSandboxedMermaid(mermaidEls); + renderIframe(iframeEls); renderJSONTable(tableEls.map((e) => e.parentNode)); renderJSONTableHTML(tableHTMLEls); highlightCurrentUser(userEls); diff --git a/app/assets/javascripts/behaviors/markdown/render_iframe.js b/app/assets/javascripts/behaviors/markdown/render_iframe.js new file mode 100644 index 00000000000000..dc1d26c9623b68 --- /dev/null +++ b/app/assets/javascripts/behaviors/markdown/render_iframe.js @@ -0,0 +1,43 @@ +import { setAttributes } from '~/lib/utils/dom_utils'; + +// https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe#sandbox +const IFRAME_SANDBOX_RESTRICTIONS = 'allow-scripts allow-popups allow-same-origin'; + +const elsProcessingMap = new WeakMap(); + +function renderIframeEl(el) { + const iframeEl = document.createElement('iframe'); + setAttributes(iframeEl, { + src: el.src, + sandbox: IFRAME_SANDBOX_RESTRICTIONS, + frameBorder: 0, + class: 'gl-inset-0 gl-h-full gl-w-full', + allowfullscreen: 'true', + referrerpolicy: 'strict-origin-when-cross-origin', + }); + + const wrapper = document.createElement('div'); + wrapper.appendChild(iframeEl); + + const container = el.closest('.media-container'); + while (container.firstChild) { + container.removeChild(container.firstChild); + } + container.appendChild(wrapper); +} + +export default function renderIframes(els) { + if (!els.length) return; + + els.forEach((el) => { + if (elsProcessingMap.has(el)) { + return; + } + + const requestId = window.requestIdleCallback(() => { + renderIframeEl(el); + }); + + elsProcessingMap.set(el, requestId); + }); +} diff --git a/lib/banzai/filter/iframe_link_filter.rb b/lib/banzai/filter/iframe_link_filter.rb index 5c355c5b502d6d..8918eb30291a7f 100644 --- a/lib/banzai/filter/iframe_link_filter.rb +++ b/lib/banzai/filter/iframe_link_filter.rb @@ -20,9 +20,7 @@ def media_type end def safe_media_ext - # TODO: will change to use the administrator defined allow list - # Gitlab::CurrentSettings.iframe_src_allowlist - ['www.youtube.com/embed'] + Gitlab::CurrentSettings.iframe_rendering_allowlist end override :has_allowed_media? @@ -34,7 +32,7 @@ def has_allowed_media?(element) return unless src.present? - src.start_with?('https://') && safe_media_ext.any? { |domain| src.start_with?("https://#{domain}") } + src.start_with?('https://') && safe_media_ext.any? { |domain| src.start_with?("https://#{domain}/") } end def extra_element_attrs(element) -- GitLab From cd35e84e3a7ebd87f2a0d0599ad281297d5b4c5e Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Thu, 30 Oct 2025 12:37:24 +1100 Subject: [PATCH 08/34] Validate and coerce the allowlist We need it to have a certain known format, since it's relied upon in many different places. Also move the '[]' fallback into ApplicationSettingsImplementation to keep the model cleaner. --- app/models/application_setting.rb | 19 +++++++++++++------ .../application_setting_implementation.rb | 14 ++++++++++++++ locale/gitlab.pot | 3 +++ spec/requests/api/settings_spec.rb | 6 +++--- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index a9c5a425740a0e..d27edfdec849c5 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -1029,6 +1029,18 @@ def self.kroki_formats_attributes validates :iframe_rendering_enabled, inclusion: { in: [true, false], message: N_('must be a boolean value') } + validates_each :iframe_rendering_allowlist, on: :update do |record, attr, value| + # Leading "http://" or "https://" and trailing "/" are removed in + # ApplicationSettingImplementation#coerce_iframe_rendering_allowlist. + # Normalising the values in here is crucial, since we rely on it to + # correctly (securely) check iframe src attributes and construct our frame-src CSP. + value&.each do |entry| + unless %r{\A[a-zA-Z0-9.-]+(?::\d+)?\z}.match?(entry) + record.errors.add(attr, format(_("'%{entry}' is not a valid domain name"), entry:)) + end + end + end + validates :require_admin_two_factor_authentication, inclusion: { in: [true, false], message: N_('must be a boolean value') } @@ -1062,6 +1074,7 @@ def self.kroki_formats_attributes before_validation :ensure_uuid! before_validation :coerce_repository_storages_weighted, if: :repository_storages_weighted_changed? before_validation :normalize_default_branch_name + before_validation :coerce_iframe_rendering_allowlist, if: :iframe_rendering_allowlist_changed? before_save :ensure_runners_registration_token before_save :ensure_health_check_access_token @@ -1324,12 +1337,6 @@ def should_reset_inactive_project_deletion_warning? saved_change_to_inactive_projects_delete_after_months? || saved_change_to_delete_inactive_projects?(from: true, to: false) end - - def iframe_rendering_allowlist - super || [] - end - - public :iframe_rendering_allowlist end ApplicationSetting.prepend_mod_with('ApplicationSetting') diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 87269b59c7c94d..6f53f5d7f17dcf 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -506,6 +506,20 @@ def search_rate_limit_allowlist_raw=(values) self.search_rate_limit_allowlist = strings_to_array(values).map(&:downcase) end + def coerce_iframe_rendering_allowlist + self.iframe_rendering_allowlist = iframe_rendering_allowlist&.map do |entry| + # We mandate https://, and always add a trailing slash; we expect the configured + # list has neither present, so we remove them if present. + entry + .sub(%r{\Ahttps?://}i, '') + .sub(%r{/+\z}, '') + end&.sort + end + + def iframe_rendering_allowlist + super || [] + end + def iframe_rendering_allowlist_raw array_to_string(iframe_rendering_allowlist) end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 17b53bf920c691..1eca466028fecf 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1686,6 +1686,9 @@ msgstr "" msgid "%{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}v*%{code_tag_end} or %{code_tag_start}*-release%{code_tag_end} are supported." msgstr "" +msgid "'%{entry}' is not a valid domain name" +msgstr "" + msgid "'%{group_name}' has been scheduled for deletion and will be deleted on %{date}." msgstr "" diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index f96d538b8a7180..70531ea595ef82 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -118,13 +118,13 @@ put api('/application/settings', admin), params: { iframe_rendering_enabled: true, - iframe_rendering_allowlist: ['example.com', 'videos.example.com:443'] + iframe_rendering_allowlist: ['example.com', 'videos.example.com:443', 'https://example.net/'] } expect(response).to have_gitlab_http_status(:ok) expect(json_response['iframe_rendering_enabled']).to be(true) - expect(json_response['iframe_rendering_allowlist']).to match_array(['example.com', 'videos.example.com:443']) - expect(ApplicationSetting.current.iframe_rendering_allowlist).to match_array(['example.com', 'videos.example.com:443']) + expect(json_response['iframe_rendering_allowlist']).to match_array(['example.com', 'videos.example.com:443', 'example.net']) + expect(ApplicationSetting.current.iframe_rendering_allowlist).to match_array(['example.com', 'videos.example.com:443', 'example.net']) end it 'allows a raw string for iframe_rendering_allowlist_raw' do -- GitLab From 6077715747394ca0208b50d6fdf23baad22e6710 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Thu, 30 Oct 2025 15:56:49 +1100 Subject: [PATCH 09/34] Remove frontend iframe permit Let's stick to one method while introducing this. --- app/assets/javascripts/lib/dompurify.js | 69 +----------------------- lib/banzai/filter/iframe_link_filter.rb | 2 + lib/banzai/filter/sanitization_filter.rb | 27 ---------- 3 files changed, 3 insertions(+), 95 deletions(-) diff --git a/app/assets/javascripts/lib/dompurify.js b/app/assets/javascripts/lib/dompurify.js index dc8cfad6c6f5f7..f5038c5a62ff6c 100644 --- a/app/assets/javascripts/lib/dompurify.js +++ b/app/assets/javascripts/lib/dompurify.js @@ -102,22 +102,6 @@ addHook('afterSanitizeAttributes', (node) => { } }); -// Allow restoring of src from known safe data attributes when present -addHook('afterSanitizeAttributes', (node) => { - // Matches usage from Markdown renderers that store canonical URLs - const tag = node.tagName; - if (tag !== 'IMG' && tag !== 'IFRAME') return; - - const dataset = node?.dataset || {}; - if (Object.prototype.hasOwnProperty.call(dataset, 'canonicalSrc')) { - node.setAttribute('src', dataset.canonicalSrc); - } - - if (Object.prototype.hasOwnProperty.call(dataset, 'original')) { - node.setAttribute('src', dataset.original); - } -}); - const TEMPORARY_ATTRIBUTE = 'data-temp-href-target'; addHook('beforeSanitizeAttributes', (node, _, config) => { @@ -177,57 +161,6 @@ addHook('afterSanitizeAttributes', (node, _, config) => { } }); -// Block iframes entirely when the setting is disabled -addHook('beforeSanitizeElements', (node) => { - if (node.tagName === 'IFRAME' && !window.gon?.iframe_rendering_enabled) { - node.remove(); - } -}); - -// Enforce iframe src allowlist when enabled -addHook('afterSanitizeAttributes', (node) => { - if (node.tagName === 'IFRAME') { - const url = node.getAttribute('src'); - const allowlist = window.gon?.iframe_rendering_allowlist || []; - const isAllowed = - Boolean(url) && allowlist.some((domain) => url.startsWith(`https://${domain}/`)); - - if (!isAllowed) { - node.remove(); - } - } -}); - -export const sanitize = (val, config) => { - const finalConfig = { ...defaultConfig, ...config }; - - if (window.gon?.iframe_rendering_enabled) { - finalConfig.ADD_TAGS = finalConfig.ADD_TAGS || []; - if (!finalConfig.ADD_TAGS.includes('iframe')) { - finalConfig.ADD_TAGS.push('iframe'); - } - - const iframeAttrs = [ - 'src', - 'width', - 'height', - 'title', - 'frameborder', - 'allow', - 'allowfullscreen', - 'referrerpolicy', - 'style', - ]; - - finalConfig.ADD_ATTR = finalConfig.ADD_ATTR || []; - iframeAttrs.forEach((attr) => { - if (!finalConfig.ADD_ATTR.includes(attr)) { - finalConfig.ADD_ATTR.push(attr); - } - }); - } - - return dompurifySanitize(val, finalConfig); -}; +export const sanitize = (val, config) => dompurifySanitize(val, { ...defaultConfig, ...config }); export { isValidAttribute }; diff --git a/lib/banzai/filter/iframe_link_filter.rb b/lib/banzai/filter/iframe_link_filter.rb index 8918eb30291a7f..c0757a56a540f2 100644 --- a/lib/banzai/filter/iframe_link_filter.rb +++ b/lib/banzai/filter/iframe_link_filter.rb @@ -25,6 +25,8 @@ def safe_media_ext override :has_allowed_media? def has_allowed_media?(element) + return unless context[:allow_iframes] + return unless context[:project]&.allow_iframes_in_markdown_feature_flag_enabled? || context[:group]&.allow_iframes_in_markdown_feature_flag_enabled? diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb index 9948b7839624c2..b64ce6520d09fb 100644 --- a/lib/banzai/filter/sanitization_filter.rb +++ b/lib/banzai/filter/sanitization_filter.rb @@ -13,7 +13,6 @@ class SanitizationFilter < Banzai::Filter::BaseSanitizationFilter def customize_allowlist(allowlist) allowlist[:allow_comments] = context[:allow_comments] - add_iframe_allowlist(allowlist) allow_table_alignment(allowlist) allow_json_table_attributes(allowlist) allow_sourcepos_and_escaped_char(allowlist) @@ -26,16 +25,6 @@ def customize_allowlist(allowlist) private - def add_iframe_allowlist(allowlist) - return unless context[:allow_iframes] - - allowlist[:elements].push('iframe') - allowlist[:attributes]['iframe'] = %w[ - src width height title frameborder allow allowfullscreen referrerpolicy style - ] - allowlist[:transformers].push(self.class.method(:validate_iframe_rendering)) - end - def allow_table_alignment(allowlist) # Allow table alignment; we allow specific text-align values in a transformer below allowlist[:attributes]['th'] = %w[style] @@ -83,22 +72,6 @@ def allow_section_footnotes(allowlist) end class << self - def validate_iframe_rendering(env) - node = env[:node] - return unless node.name == 'iframe' - - if node.has_attribute?('src') - url = node['src'] - # Only allow HTTPS URLs and check against allowlist - unless url.start_with?('https://') && - Gitlab::CurrentSettings.iframe_rendering_allowlist.any? { |domain| url.start_with?("https://#{domain}/") } - node.remove - end - else - node.remove - end - end - def remove_unsafe_table_style ->(env) do node = env[:node] -- GitLab From ec4ea61254707824e97f1279beb3f5e0a2c1d856 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Thu, 30 Oct 2025 16:22:45 +1100 Subject: [PATCH 10/34] Do the iframe settings checks once (and remove old context) --- app/controllers/concerns/preview_markdown.rb | 3 +-- lib/banzai/filter/iframe_link_filter.rb | 14 +++++++++----- .../content_security_policy/config_loader.rb | 3 +-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/controllers/concerns/preview_markdown.rb b/app/controllers/concerns/preview_markdown.rb index 6fdf613c35184e..3d62139eccda13 100644 --- a/app/controllers/concerns/preview_markdown.rb +++ b/app/controllers/concerns/preview_markdown.rb @@ -73,8 +73,7 @@ def markdown_context_params ref: params[:ref], # Disable comments in markdown for IE browsers because comments in IE # could allow script execution. - allow_comments: !browser.ie?, - allow_iframes: Gitlab::CurrentSettings.iframe_rendering_enabled? + allow_comments: !browser.ie? ) end end diff --git a/lib/banzai/filter/iframe_link_filter.rb b/lib/banzai/filter/iframe_link_filter.rb index c0757a56a540f2..b5c81fb7a25fdf 100644 --- a/lib/banzai/filter/iframe_link_filter.rb +++ b/lib/banzai/filter/iframe_link_filter.rb @@ -13,6 +13,15 @@ module Filter class IframeLinkFilter < PlayableLinkFilter extend ::Gitlab::Utils::Override + def call + return doc unless Gitlab::CurrentSettings.iframe_rendering_enabled? + + return doc unless context[:project]&.allow_iframes_in_markdown_feature_flag_enabled? || + context[:group]&.allow_iframes_in_markdown_feature_flag_enabled? + + super + end + private def media_type @@ -25,11 +34,6 @@ def safe_media_ext override :has_allowed_media? def has_allowed_media?(element) - return unless context[:allow_iframes] - - return unless context[:project]&.allow_iframes_in_markdown_feature_flag_enabled? || - context[:group]&.allow_iframes_in_markdown_feature_flag_enabled? - src = element.attr('data-canonical-src').presence || element.attr('src') return unless src.present? diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb index 8cb7b5bf155ced..4528740f1c049a 100644 --- a/lib/gitlab/content_security_policy/config_loader.rb +++ b/lib/gitlab/content_security_policy/config_loader.rb @@ -170,8 +170,7 @@ def allow_customersdot(directives) end def allow_iframes(directives) - return unless Gitlab::CurrentSettings.respond_to?(:iframe_rendering_enabled?) && - Gitlab::CurrentSettings.iframe_rendering_enabled? + return unless Gitlab::CurrentSettings.try(:iframe_rendering_enabled?) append_to_directive(directives, 'frame_src', Gitlab::CurrentSettings.iframe_rendering_allowlist.map { |domain| "https://#{domain}/" }.join(' ')) end -- GitLab From 53a9fa28e73ddeb3e59dc1175d61341b3931940b Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Thu, 30 Oct 2025 16:22:45 +1100 Subject: [PATCH 11/34] Pass context to pipeline timing check We can't pass "project" as an argument here, since the arguments to it_behaves_like are evaluated at spec eval time, rather than runtime. So we change the behaves-like group to accept the context as an argument or as a context-defined variable with `let`. --- spec/lib/banzai/filter/iframe_link_filter_spec.rb | 10 +++++++++- .../banzai/filters/filter_timeout_shared_examples.rb | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/spec/lib/banzai/filter/iframe_link_filter_spec.rb b/spec/lib/banzai/filter/iframe_link_filter_spec.rb index cb3a1f198e527d..908c126b3a7dbe 100644 --- a/spec/lib/banzai/filter/iframe_link_filter_spec.rb +++ b/spec/lib/banzai/filter/iframe_link_filter_spec.rb @@ -21,6 +21,12 @@ def link_to_image(path, height = nil, width = nil) let_it_be(:project) { create(:project, :repository) } + before do + allow(Gitlab::CurrentSettings).to receive_messages( + iframe_rendering_enabled?: true, + iframe_rendering_allowlist: ["www.youtube.com"]) + end + shared_examples 'an iframe element' do let(:image) { link_to_image(src, height, width) } @@ -138,5 +144,7 @@ def link_to_image(path, height = nil, width = nil) it_behaves_like 'an unchanged element' end - it_behaves_like 'pipeline timing check' + it_behaves_like 'pipeline timing check' do + let(:context) { { project: } } + end end diff --git a/spec/support/shared_examples/lib/banzai/filters/filter_timeout_shared_examples.rb b/spec/support/shared_examples/lib/banzai/filters/filter_timeout_shared_examples.rb index 4074745cfbc10a..b168acfbecc03a 100644 --- a/spec/support/shared_examples/lib/banzai/filters/filter_timeout_shared_examples.rb +++ b/spec/support/shared_examples/lib/banzai/filters/filter_timeout_shared_examples.rb @@ -64,13 +64,13 @@ # Usage: # # it_behaves_like 'pipeline timing check' -RSpec.shared_examples 'pipeline timing check' do |context: {}| +RSpec.shared_examples 'pipeline timing check' do |**opts| it 'checks the pipeline timing' do expect_next_instance_of(described_class) do |instance| expect(instance).to receive(:exceeded_pipeline_max?).and_return(true) end - filter = described_class.new('text', context) + filter = described_class.new('text', defined?(context) ? context : opts.fetch(:context, {})) filter.call end end -- GitLab From b6ea3696ce6b601029c67e8e7abf3a65158db533 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Thu, 30 Oct 2025 16:22:45 +1100 Subject: [PATCH 12/34] Construct HTML with Nokogiri, not by hand --- spec/lib/banzai/filter/iframe_link_filter_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/lib/banzai/filter/iframe_link_filter_spec.rb b/spec/lib/banzai/filter/iframe_link_filter_spec.rb index 908c126b3a7dbe..cad27ed2293306 100644 --- a/spec/lib/banzai/filter/iframe_link_filter_spec.rb +++ b/spec/lib/banzai/filter/iframe_link_filter_spec.rb @@ -10,13 +10,13 @@ def filter(doc, contexts = {}) end def link_to_image(path, height = nil, width = nil) - return '' if path.nil? + img = Nokogiri::HTML.fragment("").css('img').first + return img.to_html if path.nil? - attrs = %(src="#{path}") - attrs += %( width="#{width}") if width - attrs += %( height="#{height}") if height - - %() + img["src"] = path + img["width"] = width if width + img["height"] = height if height + img.to_html end let_it_be(:project) { create(:project, :repository) } -- GitLab From 679e1d8ee6478d8577449c9bc05dcd6ef61ebd50 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Fri, 31 Oct 2025 16:16:42 +1100 Subject: [PATCH 13/34] Fix spec broken by unexpected messages --- spec/lib/gitlab/content_security_policy/config_loader_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index 6f296f9e40e91b..1523f23b59a533 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -430,6 +430,7 @@ def dev_server_socket_path before do allow(Gitlab::CurrentSettings).to receive(:sentry_enabled).and_return(true) allow(Gitlab::CurrentSettings).to receive(:sentry_clientside_dsn).and_return(dsn) + allow(Gitlab::CurrentSettings).to receive(:iframe_rendering_enabled?).and_return(false) end it 'adds new sentry path to CSP' do @@ -442,6 +443,9 @@ def dev_server_socket_path allow(Gitlab::CurrentSettings).to receive(:respond_to?).with(:sentry_enabled).and_return(false) allow(Gitlab::CurrentSettings).to receive(:sentry_enabled).and_raise(NoMethodError) + allow(Gitlab::CurrentSettings).to receive(:respond_to?).with(:iframe_rendering_enabled?).and_return(false) + allow(Gitlab::CurrentSettings).to receive(:iframe_rendering_enabled?).and_raise(NoMethodError) + allow(Gitlab::CurrentSettings).to receive(:respond_to?).with(:sentry_clientside_dsn).and_return(false) allow(Gitlab::CurrentSettings).to receive(:sentry_clientside_dsn).and_raise(NoMethodError) end -- GitLab From b488fecc5089cabdcdc10ac66ca4276901bfee09 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Fri, 31 Oct 2025 16:16:42 +1100 Subject: [PATCH 14/34] Remove defensive nil guarding and coercion The underlying serializer appears to take care of this for us. --- app/models/application_setting.rb | 2 +- app/models/application_setting_implementation.rb | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index d27edfdec849c5..6db6b93ca8c519 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -1034,7 +1034,7 @@ def self.kroki_formats_attributes # ApplicationSettingImplementation#coerce_iframe_rendering_allowlist. # Normalising the values in here is crucial, since we rely on it to # correctly (securely) check iframe src attributes and construct our frame-src CSP. - value&.each do |entry| + value.each do |entry| unless %r{\A[a-zA-Z0-9.-]+(?::\d+)?\z}.match?(entry) record.errors.add(attr, format(_("'%{entry}' is not a valid domain name"), entry:)) end diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 6f53f5d7f17dcf..d52c7650aac72d 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -507,17 +507,13 @@ def search_rate_limit_allowlist_raw=(values) end def coerce_iframe_rendering_allowlist - self.iframe_rendering_allowlist = iframe_rendering_allowlist&.map do |entry| + self.iframe_rendering_allowlist = iframe_rendering_allowlist.map do |entry| # We mandate https://, and always add a trailing slash; we expect the configured # list has neither present, so we remove them if present. entry .sub(%r{\Ahttps?://}i, '') .sub(%r{/+\z}, '') - end&.sort - end - - def iframe_rendering_allowlist - super || [] + end.sort end def iframe_rendering_allowlist_raw -- GitLab From 808fbb541a4badee54a1f339d7eda4f34b0f995f Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Fri, 31 Oct 2025 16:16:42 +1100 Subject: [PATCH 15/34] Propagate width/height attributes to created iframe Note that, with the styles in place currently, this doesn't actually cause them to take effect. It's very easy to set width=1000 and break the page layout, and I assume we don't want this --- needs designer attention. --- app/assets/javascripts/behaviors/markdown/render_iframe.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/javascripts/behaviors/markdown/render_iframe.js b/app/assets/javascripts/behaviors/markdown/render_iframe.js index dc1d26c9623b68..2b20d9595309f4 100644 --- a/app/assets/javascripts/behaviors/markdown/render_iframe.js +++ b/app/assets/javascripts/behaviors/markdown/render_iframe.js @@ -16,6 +16,13 @@ function renderIframeEl(el) { referrerpolicy: 'strict-origin-when-cross-origin', }); + if (el.getAttribute('width')) { + iframeEl.setAttribute('width', el.getAttribute('width')); + } + if (el.getAttribute('height')) { + iframeEl.setAttribute('height', el.getAttribute('height')); + } + const wrapper = document.createElement('div'); wrapper.appendChild(iframeEl); -- GitLab From 9f5f621394a34d0af1ced6ebf59f11d4aa18fec3 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Fri, 31 Oct 2025 16:48:46 +1100 Subject: [PATCH 16/34] Add CSP ConfigLoader spec for frame-src --- .../config_loader_spec.rb | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index 1523f23b59a533..631f58f0e747d8 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -559,6 +559,32 @@ def dev_server_socket_path end end end + + describe 'iframes in Markdown' do + let(:iframe_rendering_allowlist) { ['www.youtube.com', 'embed.figma.com', 'www.figma.com'] } + + before do + allow(Gitlab::CurrentSettings).to receive(:iframe_rendering_enabled?).and_return(iframe_rendering_enabled?) + allow(Gitlab::CurrentSettings).to receive(:iframe_rendering_allowlist).and_return(iframe_rendering_allowlist) + end + + context 'when disabled' do + let(:iframe_rendering_enabled?) { false } + + it 'does not modify frame-src' do + expect(frame_src).not_to include('www.youtube.com') + expect(frame_src).not_to include('figma.com') + end + end + + context 'when enabled' do + let(:iframe_rendering_enabled?) { true } + + it 'adds the domains to frame-src' do + expect(frame_src.split).to include('https://www.youtube.com/', 'https://embed.figma.com/', 'https://www.figma.com/') + end + end + end end describe '#load' do -- GitLab From 88a31da363529476884b7ec62f677b3c762a74a4 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Tue, 4 Nov 2025 15:10:44 +1100 Subject: [PATCH 17/34] Clean up and match surrounding style --- app/assets/javascripts/behaviors/markdown/render_iframe.js | 3 +++ .../pages/admin/application_settings/general/index.js | 5 +---- .../javascripts/pages/admin/application_settings/iframe.js | 2 -- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/behaviors/markdown/render_iframe.js b/app/assets/javascripts/behaviors/markdown/render_iframe.js index 2b20d9595309f4..08f4e6f4524944 100644 --- a/app/assets/javascripts/behaviors/markdown/render_iframe.js +++ b/app/assets/javascripts/behaviors/markdown/render_iframe.js @@ -16,6 +16,9 @@ function renderIframeEl(el) { referrerpolicy: 'strict-origin-when-cross-origin', }); + // We propagate these attributes, but currently the gl-h-full/gl-w-full above override them, + // as they can easily overrun the container and break the layout. + // For potential later use with some frontend design help. if (el.getAttribute('width')) { iframeEl.setAttribute('width', el.getAttribute('width')); } diff --git a/app/assets/javascripts/pages/admin/application_settings/general/index.js b/app/assets/javascripts/pages/admin/application_settings/general/index.js index 815523ddfb5779..2299ba1d3ac419 100644 --- a/app/assets/javascripts/pages/admin/application_settings/general/index.js +++ b/app/assets/javascripts/pages/admin/application_settings/general/index.js @@ -7,16 +7,13 @@ import initGitpod from '../gitpod'; import initSignupRestrictions from '../signup_restrictions'; import initIframeSettings from '../iframe'; -document.addEventListener('DOMContentLoaded', () => { - initIframeSettings(); -}); - (() => { initAccountAndLimitsSection(); initGitpod(); initSignupRestrictions(); initSilentModeSettings(); initAdminDeletionProtectionSettings(); + initIframeSettings(); initSimpleApp('#js-extension-marketplace-settings-app', VscodeExtensionMarketplaceSettings); })(); diff --git a/app/assets/javascripts/pages/admin/application_settings/iframe.js b/app/assets/javascripts/pages/admin/application_settings/iframe.js index f78e90651e0012..ad09ded5a372b2 100644 --- a/app/assets/javascripts/pages/admin/application_settings/iframe.js +++ b/app/assets/javascripts/pages/admin/application_settings/iframe.js @@ -12,10 +12,8 @@ function toggleIframeAllowlist() { } } - // Set initial state updateVisibility(); - // Listen for changes checkbox.addEventListener('change', updateVisibility); } -- GitLab From 42f62e18cb2a0cdfbddab68e4f1ea012ac657de0 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Tue, 4 Nov 2025 16:06:39 +1100 Subject: [PATCH 18/34] Do not process elements in the frontend if no longer allowed --- .../javascripts/behaviors/markdown/render_iframe.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/behaviors/markdown/render_iframe.js b/app/assets/javascripts/behaviors/markdown/render_iframe.js index 08f4e6f4524944..b9ceac482bac54 100644 --- a/app/assets/javascripts/behaviors/markdown/render_iframe.js +++ b/app/assets/javascripts/behaviors/markdown/render_iframe.js @@ -6,9 +6,17 @@ const IFRAME_SANDBOX_RESTRICTIONS = 'allow-scripts allow-popups allow-same-origi const elsProcessingMap = new WeakMap(); function renderIframeEl(el) { + const url = el.src; + + const allowlist = window.gon?.iframe_rendering_allowlist ?? []; + const allowed = Boolean(url) && allowlist.some((domain) => url.startsWith(`https://${domain}/`)); + if (!allowed) { + return; + } + const iframeEl = document.createElement('iframe'); setAttributes(iframeEl, { - src: el.src, + src: url, sandbox: IFRAME_SANDBOX_RESTRICTIONS, frameBorder: 0, class: 'gl-inset-0 gl-h-full gl-w-full', @@ -37,6 +45,7 @@ function renderIframeEl(el) { } export default function renderIframes(els) { + if (!window.gon?.iframe_rendering_enabled) return; if (!els.length) return; els.forEach((el) => { -- GitLab From 78a8b63327fea4661d2b8f625ff7581c9a82c289 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Tue, 4 Nov 2025 16:46:12 +1100 Subject: [PATCH 19/34] Evaluate CSP in BaseActionController, not ConfigLoader --- app/controllers/base_action_controller.rb | 20 +++++++++-- .../content_security_policy/config_loader.rb | 7 ---- .../config_loader_spec.rb | 26 --------------- .../base_action_controller_shared_examples.rb | 33 +++++++++++++++++++ 4 files changed, 50 insertions(+), 36 deletions(-) diff --git a/app/controllers/base_action_controller.rb b/app/controllers/base_action_controller.rb index 7f46a153f42526..0304454eab9b75 100644 --- a/app/controllers/base_action_controller.rb +++ b/app/controllers/base_action_controller.rb @@ -27,9 +27,23 @@ class BaseActionController < ActionController::Base next if p.directives.blank? next unless Gitlab::CurrentSettings.snowplow_enabled? && !Gitlab::CurrentSettings.snowplow_collector_hostname.blank? - default_connect_src = p.directives['connect-src'] || p.directives['default-src'] - connect_src_values = Array.wrap(default_connect_src) | [Gitlab::CurrentSettings.snowplow_collector_hostname] - p.connect_src(*connect_src_values) + append_to_content_security_policy(p, 'connect-src', [Gitlab::CurrentSettings.snowplow_collector_hostname]) + end + + content_security_policy do |p| + next if p.directives.blank? + next unless Gitlab::CurrentSettings.try(:iframe_rendering_enabled?) + + add_frame_srcs = Gitlab::CurrentSettings.iframe_rendering_allowlist.map { |domain| "https://#{domain}/" } + + append_to_content_security_policy(p, 'frame-src', add_frame_srcs) + append_to_content_security_policy(p, 'child-src', add_frame_srcs) + end + + def append_to_content_security_policy(policy, directive, values) + existing_value = policy.directives[directive] || policy.directives['default-src'] + new_value = Array.wrap(existing_value) | values + policy.directives[directive] = new_value end end # rubocop:enable Gitlab/NamespacedClass diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb index 4528740f1c049a..26c844cc1fe8a0 100644 --- a/lib/gitlab/content_security_policy/config_loader.rb +++ b/lib/gitlab/content_security_policy/config_loader.rb @@ -28,7 +28,6 @@ def default_directives allow_sentry(directives) allow_framed_gitlab_paths(directives) allow_customersdot(directives) - allow_iframes(directives) csp_level_3_backport(directives) directives @@ -169,12 +168,6 @@ def allow_customersdot(directives) append_to_directive(directives, 'frame_src', customersdot_host) end - def allow_iframes(directives) - return unless Gitlab::CurrentSettings.try(:iframe_rendering_enabled?) - - append_to_directive(directives, 'frame_src', Gitlab::CurrentSettings.iframe_rendering_allowlist.map { |domain| "https://#{domain}/" }.join(' ')) - end - # The follow contains workarounds to patch Safari's lack of support for CSP Level 3 def csp_level_3_backport(directives) # See https://gitlab.com/gitlab-org/gitlab/-/issues/343579 diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index 631f58f0e747d8..1523f23b59a533 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -559,32 +559,6 @@ def dev_server_socket_path end end end - - describe 'iframes in Markdown' do - let(:iframe_rendering_allowlist) { ['www.youtube.com', 'embed.figma.com', 'www.figma.com'] } - - before do - allow(Gitlab::CurrentSettings).to receive(:iframe_rendering_enabled?).and_return(iframe_rendering_enabled?) - allow(Gitlab::CurrentSettings).to receive(:iframe_rendering_allowlist).and_return(iframe_rendering_allowlist) - end - - context 'when disabled' do - let(:iframe_rendering_enabled?) { false } - - it 'does not modify frame-src' do - expect(frame_src).not_to include('www.youtube.com') - expect(frame_src).not_to include('figma.com') - end - end - - context 'when enabled' do - let(:iframe_rendering_enabled?) { true } - - it 'adds the domains to frame-src' do - expect(frame_src.split).to include('https://www.youtube.com/', 'https://embed.figma.com/', 'https://www.figma.com/') - end - end - end end describe '#load' do diff --git a/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb b/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb index 1a01df0ce3aa57..883ee8985a3b5c 100644 --- a/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb +++ b/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb @@ -45,6 +45,39 @@ it_behaves_like 'snowplow is not in the CSP' end end + + context 'when configuring iframes in Markdown' do + let(:iframe_rendering_allowlist) { ['www.youtube.com', 'embed.figma.com', 'www.figma.com'] } + + before do + allow(Gitlab::CurrentSettings).to receive_messages( + iframe_rendering_enabled?: iframe_rendering_enabled?, + iframe_rendering_allowlist: iframe_rendering_allowlist) + end + + context 'when disabled' do + let(:iframe_rendering_enabled?) { false } + + it 'does not modify frame-src' do + request + + expect(response.headers['Content-Security-Policy']).not_to include('www.youtube.com') + expect(response.headers['Content-Security-Policy']).not_to include('figma.com') + end + end + + context 'when enabled' do + let(:iframe_rendering_enabled?) { true } + + it 'adds the domains to frame-src' do + request + + expect(response.headers['Content-Security-Policy']).to include('https://www.youtube.com/') + expect(response.headers['Content-Security-Policy']).to include('https://embed.figma.com/') + expect(response.headers['Content-Security-Policy']).to include('https://www.figma.com/') + end + end + end end end end -- GitLab From ddca9265cf388306c866cb525aec63a53a6e4196 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Wed, 5 Nov 2025 12:07:42 +1100 Subject: [PATCH 20/34] Rerun script/cells/application-settings-analysis.rb Omitting changes unrelated to this MR. --- doc/development/cells/application_settings_analysis.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/development/cells/application_settings_analysis.md b/doc/development/cells/application_settings_analysis.md index e7210d662d1c86..e2c7643c11847d 100644 --- a/doc/development/cells/application_settings_analysis.md +++ b/doc/development/cells/application_settings_analysis.md @@ -14,12 +14,12 @@ title: Application Settings analysis ## Statistics -- Number of attributes: 506 +- Number of attributes: 508 - Number of encrypted attributes: 42 (8.0%) -- Number of attributes documented: 295 (57.99999999999999%) +- Number of attributes documented: 297 (57.99999999999999%) - Number of attributes on GitLab.com different from the defaults: 224 (44.0%) -- Number of attributes with `clusterwide` set: 506 (100.0%) -- Number of attributes with `clusterwide: true` set: 133 (26.0%) +- Number of attributes with `clusterwide` set: 508 (100.0%) +- Number of attributes with `clusterwide: true` set: 135 (27.0%) ## Individual columns -- GitLab From c86db3f6742ee92ae4e2830999aaa8c2097ec3aa Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Wed, 5 Nov 2025 12:28:46 +1100 Subject: [PATCH 21/34] Apply suggestion from @slashmanov: compare URL origins --- .../behaviors/markdown/render_iframe.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/behaviors/markdown/render_iframe.js b/app/assets/javascripts/behaviors/markdown/render_iframe.js index b9ceac482bac54..e53030db808b64 100644 --- a/app/assets/javascripts/behaviors/markdown/render_iframe.js +++ b/app/assets/javascripts/behaviors/markdown/render_iframe.js @@ -6,17 +6,19 @@ const IFRAME_SANDBOX_RESTRICTIONS = 'allow-scripts allow-popups allow-same-origi const elsProcessingMap = new WeakMap(); function renderIframeEl(el) { - const url = el.src; + const src = el.src; + if (!src) return; + + const srcUrl = new URL(src); const allowlist = window.gon?.iframe_rendering_allowlist ?? []; - const allowed = Boolean(url) && allowlist.some((domain) => url.startsWith(`https://${domain}/`)); - if (!allowed) { - return; - } + const allowlistUrls = allowlist.map((domain) => new URL(`https://${domain}`)); + const allowed = allowlistUrls.some((allowlistUrl) => allowlistUrl.origin === srcUrl.origin); + if (!allowed) return; const iframeEl = document.createElement('iframe'); setAttributes(iframeEl, { - src: url, + src, sandbox: IFRAME_SANDBOX_RESTRICTIONS, frameBorder: 0, class: 'gl-inset-0 gl-h-full gl-w-full', -- GitLab From 87b46de332a175677ac5430b02463bfbc6737b57 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Wed, 5 Nov 2025 12:37:03 +1100 Subject: [PATCH 22/34] OK eslint, whatever you say --- app/assets/javascripts/behaviors/markdown/render_iframe.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/behaviors/markdown/render_iframe.js b/app/assets/javascripts/behaviors/markdown/render_iframe.js index e53030db808b64..8ce33d6efa42e7 100644 --- a/app/assets/javascripts/behaviors/markdown/render_iframe.js +++ b/app/assets/javascripts/behaviors/markdown/render_iframe.js @@ -6,7 +6,7 @@ const IFRAME_SANDBOX_RESTRICTIONS = 'allow-scripts allow-popups allow-same-origi const elsProcessingMap = new WeakMap(); function renderIframeEl(el) { - const src = el.src; + const {src} = el; if (!src) return; const srcUrl = new URL(src); -- GitLab From 8279ddca0548f3ca2491eebd20c399128fb243ad Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Wed, 5 Nov 2025 12:37:03 +1100 Subject: [PATCH 23/34] Make corresponding change to backend check --- lib/banzai/filter/iframe_link_filter.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/banzai/filter/iframe_link_filter.rb b/lib/banzai/filter/iframe_link_filter.rb index b5c81fb7a25fdf..1646114046c940 100644 --- a/lib/banzai/filter/iframe_link_filter.rb +++ b/lib/banzai/filter/iframe_link_filter.rb @@ -12,6 +12,7 @@ module Banzai module Filter class IframeLinkFilter < PlayableLinkFilter extend ::Gitlab::Utils::Override + include ::Gitlab::Utils::StrongMemoize def call return doc unless Gitlab::CurrentSettings.iframe_rendering_enabled? @@ -29,16 +30,19 @@ def media_type end def safe_media_ext - Gitlab::CurrentSettings.iframe_rendering_allowlist + Gitlab::CurrentSettings.iframe_rendering_allowlist.map do |domain| + Addressable::URI.parse("https://#{domain}") + end end + strong_memoize_attr :safe_media_ext override :has_allowed_media? def has_allowed_media?(element) src = element.attr('data-canonical-src').presence || element.attr('src') - return unless src.present? - src.start_with?('https://') && safe_media_ext.any? { |domain| src.start_with?("https://#{domain}/") } + uri = Addressable::URI.parse(src) + safe_media_ext.any? { |allowed_uri| allowed_uri.origin == uri.origin } end def extra_element_attrs(element) -- GitLab From b49841880bf7f0070f62a2107a25a845ceeba566 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Wed, 5 Nov 2025 19:01:52 +1100 Subject: [PATCH 24/34] OK prettier, whatever you say --- app/assets/javascripts/behaviors/markdown/render_iframe.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/behaviors/markdown/render_iframe.js b/app/assets/javascripts/behaviors/markdown/render_iframe.js index 8ce33d6efa42e7..b7ae3bebfc305e 100644 --- a/app/assets/javascripts/behaviors/markdown/render_iframe.js +++ b/app/assets/javascripts/behaviors/markdown/render_iframe.js @@ -6,7 +6,7 @@ const IFRAME_SANDBOX_RESTRICTIONS = 'allow-scripts allow-popups allow-same-origi const elsProcessingMap = new WeakMap(); function renderIframeEl(el) { - const {src} = el; + const { src } = el; if (!src) return; const srcUrl = new URL(src); -- GitLab From 953640fc909d95c1e1a85b62c0dd72f87da15af1 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Wed, 5 Nov 2025 20:08:03 +1100 Subject: [PATCH 25/34] Revert changes to spec; we moved our changes elsewhere --- spec/lib/gitlab/content_security_policy/config_loader_spec.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index 1523f23b59a533..6f296f9e40e91b 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -430,7 +430,6 @@ def dev_server_socket_path before do allow(Gitlab::CurrentSettings).to receive(:sentry_enabled).and_return(true) allow(Gitlab::CurrentSettings).to receive(:sentry_clientside_dsn).and_return(dsn) - allow(Gitlab::CurrentSettings).to receive(:iframe_rendering_enabled?).and_return(false) end it 'adds new sentry path to CSP' do @@ -443,9 +442,6 @@ def dev_server_socket_path allow(Gitlab::CurrentSettings).to receive(:respond_to?).with(:sentry_enabled).and_return(false) allow(Gitlab::CurrentSettings).to receive(:sentry_enabled).and_raise(NoMethodError) - allow(Gitlab::CurrentSettings).to receive(:respond_to?).with(:iframe_rendering_enabled?).and_return(false) - allow(Gitlab::CurrentSettings).to receive(:iframe_rendering_enabled?).and_raise(NoMethodError) - allow(Gitlab::CurrentSettings).to receive(:respond_to?).with(:sentry_clientside_dsn).and_return(false) allow(Gitlab::CurrentSettings).to receive(:sentry_clientside_dsn).and_raise(NoMethodError) end -- GitLab From ebbf8314dc38998c8d4f7fa87d7f2002dedf1371 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Wed, 5 Nov 2025 20:08:03 +1100 Subject: [PATCH 26/34] Add bad setting spec for coverage --- spec/requests/api/settings_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 70531ea595ef82..cd936f04d95164 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -124,9 +124,22 @@ expect(response).to have_gitlab_http_status(:ok) expect(json_response['iframe_rendering_enabled']).to be(true) expect(json_response['iframe_rendering_allowlist']).to match_array(['example.com', 'videos.example.com:443', 'example.net']) + expect(ApplicationSetting.current.iframe_rendering_enabled?).to be(true) expect(ApplicationSetting.current.iframe_rendering_allowlist).to match_array(['example.com', 'videos.example.com:443', 'example.net']) end + it 'denies bad allowlist entries' do + put api('/application/settings', admin), + params: { + iframe_rendering_enabled: true, + iframe_rendering_allowlist: ['gopher://gopherz.tv'] + } + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']['iframe_rendering_allowlist']).to include("'gopher://gopherz.tv' is not a valid domain name") + expect(ApplicationSetting.current.iframe_rendering_allowlist.join(',')).not_to include('gopherz') + end + it 'allows a raw string for iframe_rendering_allowlist_raw' do raw = "example.com\nvideos.example.com:443" put api('/application/settings', admin), -- GitLab From 533397e2b44431f825e8b953196d934a4847ffbd Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Thu, 6 Nov 2025 16:58:21 +1100 Subject: [PATCH 27/34] Check feature flag in frontend, too Co-authored-by: Stanislav Lashmanov --- app/assets/javascripts/behaviors/markdown/render_iframe.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/behaviors/markdown/render_iframe.js b/app/assets/javascripts/behaviors/markdown/render_iframe.js index b7ae3bebfc305e..e8c04f9cead7d6 100644 --- a/app/assets/javascripts/behaviors/markdown/render_iframe.js +++ b/app/assets/javascripts/behaviors/markdown/render_iframe.js @@ -48,6 +48,8 @@ function renderIframeEl(el) { export default function renderIframes(els) { if (!window.gon?.iframe_rendering_enabled) return; + if (!window.gon?.features.allowIframesInMarkdown) return; + if (!els.length) return; els.forEach((el) => { -- GitLab From 310ac2737adfbf9b2f35bb9c6371b8e1242b3c5d Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Thu, 6 Nov 2025 18:05:27 +1100 Subject: [PATCH 28/34] Check data-src, then src, due to lazy loading --- app/assets/javascripts/behaviors/markdown/render_iframe.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/behaviors/markdown/render_iframe.js b/app/assets/javascripts/behaviors/markdown/render_iframe.js index e8c04f9cead7d6..8c575cf3cfea54 100644 --- a/app/assets/javascripts/behaviors/markdown/render_iframe.js +++ b/app/assets/javascripts/behaviors/markdown/render_iframe.js @@ -6,7 +6,9 @@ const IFRAME_SANDBOX_RESTRICTIONS = 'allow-scripts allow-popups allow-same-origi const elsProcessingMap = new WeakMap(); function renderIframeEl(el) { - const { src } = el; + // Obtain src from data-src first, in case image lazy loading hasn't + // resolved this yet. See Banzai::Filter::ImageLazyLoadFilter. + const src = el.dataset.src || el.src; if (!src) return; const srcUrl = new URL(src); -- GitLab From c4dc8860300c3a8e6bfdb9ff120cc573c7f750e8 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Thu, 6 Nov 2025 18:05:27 +1100 Subject: [PATCH 29/34] Add feature test for iframes in Markdown --- spec/features/markdown/iframe_spec.rb | 128 ++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 spec/features/markdown/iframe_spec.rb diff --git a/spec/features/markdown/iframe_spec.rb b/spec/features/markdown/iframe_spec.rb new file mode 100644 index 00000000000000..7d3635afb4b4cd --- /dev/null +++ b/spec/features/markdown/iframe_spec.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'iframe rendering', :js, feature_category: :markdown do + let_it_be(:project) { create(:project, :public, :repository) } + + # No registration of .example domains is possible: + # https://en.wikipedia.org/wiki/.example + let(:markdown) do + <<~MARKDOWN + ![](https://iframe.example/some-video) + MARKDOWN + end + + let(:expected_selector) do + 'iframe[src="https://iframe.example/some-video"][sandbox]' + end + + let(:untouched_selector) do + 'img[data-src="https://iframe.example/some-video"], + img[src="https://iframe.example/some-video"]' + end + + context 'when feature is enabled and configured' do + before do + stub_feature_flags(allow_iframes_in_markdown: true) + stub_application_setting(iframe_rendering_enabled: true, iframe_rendering_allowlist: ['iframe.example']) + end + + context 'in an issue' do + let(:issue) { create(:issue, project: project, description: markdown) } + + it 'includes iframe embed correctly' do + visit project_issue_path(project, issue) + wait_for_requests + + expect(page).to have_css(expected_selector) + end + end + + context 'in a merge request' do + let(:merge_request) { create(:merge_request_with_diffs, source_project: project, description: markdown) } + + it 'renders diffs and includes iframe correctly' do + visit(diffs_project_merge_request_path(project, merge_request)) + + wait_for_requests + + page.within('.tab-content') do + expect(page).to have_selector('.diffs') + end + + visit(project_merge_request_path(project, merge_request)) + + wait_for_requests + + page.within('.merge-request') do + expect(page).to have_css(expected_selector) + end + end + end + + context 'in a project milestone' do + let(:milestone) { create(:project_milestone, project: project, description: markdown) } + + it 'includes iframe correctly' do + visit(project_milestone_path(project, milestone)) + + wait_for_requests + + expect(page).to have_css(expected_selector) + end + end + + context 'in a project home page' do + let!(:wiki) { create(:project_wiki, project: project) } + let!(:wiki_page) { create(:wiki_page, wiki: wiki, title: 'home', content: markdown) } + + before do + project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED) + end + + it 'includes iframe correctly' do + visit(project_path(project)) + + wait_for_all_requests + + page.within '.js-wiki-content' do + expect(page).to have_css(expected_selector) + end + end + end + + context 'in a group milestone' do + let(:group_milestone) { create(:group_milestone, description: markdown) } + + it 'includes iframe correctly' do + visit(group_milestone_path(group_milestone.group, group_milestone)) + + wait_for_requests + + expect(page).to have_css(expected_selector) + end + end + end + + context 'when feature is enabled but not configured' do + before do + stub_feature_flags(allow_iframes_in_markdown: true) + stub_application_setting(iframe_rendering_enabled: false) + end + + context 'in an issue' do + let(:issue) { create(:issue, project: project, description: markdown) } + + it 'no iframe is added, the image tag is left untouched' do + visit project_issue_path(project, issue) + wait_for_requests + + expect(page).not_to have_css(expected_selector) + # The image may be considered invisible because of the invalid target; + # the main thing is it's there, and it's not an iframe. + expect(page).to have_css(untouched_selector, visible: :all) + end + end + end +end -- GitLab From 2ea66f0bd6b34ba84e8788947106544dc4eec6e1 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Wed, 19 Nov 2025 15:05:18 +1100 Subject: [PATCH 30/34] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: Stanislav Lashmanov --- app/assets/javascripts/behaviors/markdown/render_iframe.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/assets/javascripts/behaviors/markdown/render_iframe.js b/app/assets/javascripts/behaviors/markdown/render_iframe.js index 8c575cf3cfea54..ef20e5e97ebccd 100644 --- a/app/assets/javascripts/behaviors/markdown/render_iframe.js +++ b/app/assets/javascripts/behaviors/markdown/render_iframe.js @@ -42,10 +42,7 @@ function renderIframeEl(el) { wrapper.appendChild(iframeEl); const container = el.closest('.media-container'); - while (container.firstChild) { - container.removeChild(container.firstChild); - } - container.appendChild(wrapper); + container.replaceChildren(wrapper); } export default function renderIframes(els) { -- GitLab From ed09da50059b8749e4dfb838c07b671daf4037d9 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Fri, 21 Nov 2025 13:11:00 +1100 Subject: [PATCH 31/34] Move CSP to ApplicationController, remove trailing `/` --- app/controllers/application_controller.rb | 10 +++++ app/controllers/base_action_controller.rb | 10 ----- spec/requests/application_controller_spec.rb | 39 +++++++++++++++++++ .../base_action_controller_shared_examples.rb | 33 ---------------- 4 files changed, 49 insertions(+), 43 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index a20f6ce8124376..cf4b235c6ecac5 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -31,6 +31,16 @@ class ApplicationController < BaseActionController include Gitlab::HttpRouter::RuleMetrics include CookiesHelper + content_security_policy do |p| + next if p.directives.blank? + next unless Gitlab::CurrentSettings.try(:iframe_rendering_enabled?) + + add_frame_srcs = Gitlab::CurrentSettings.iframe_rendering_allowlist.map { |domain| "https://#{domain}" } + + append_to_content_security_policy(p, 'frame-src', add_frame_srcs) + append_to_content_security_policy(p, 'child-src', add_frame_srcs) + end + around_action :set_current_ip_address before_action :authenticate_user!, except: [:route_not_found] diff --git a/app/controllers/base_action_controller.rb b/app/controllers/base_action_controller.rb index 0304454eab9b75..f3ef206c49dcc1 100644 --- a/app/controllers/base_action_controller.rb +++ b/app/controllers/base_action_controller.rb @@ -30,16 +30,6 @@ class BaseActionController < ActionController::Base append_to_content_security_policy(p, 'connect-src', [Gitlab::CurrentSettings.snowplow_collector_hostname]) end - content_security_policy do |p| - next if p.directives.blank? - next unless Gitlab::CurrentSettings.try(:iframe_rendering_enabled?) - - add_frame_srcs = Gitlab::CurrentSettings.iframe_rendering_allowlist.map { |domain| "https://#{domain}/" } - - append_to_content_security_policy(p, 'frame-src', add_frame_srcs) - append_to_content_security_policy(p, 'child-src', add_frame_srcs) - end - def append_to_content_security_policy(policy, directive, values) existing_value = policy.directives[directive] || policy.directives['default-src'] new_value = Array.wrap(existing_value) | values diff --git a/spec/requests/application_controller_spec.rb b/spec/requests/application_controller_spec.rb index bacb57e926bdf2..d157d8b7c83181 100644 --- a/spec/requests/application_controller_spec.rb +++ b/spec/requests/application_controller_spec.rb @@ -315,4 +315,43 @@ end end end + + describe 'content security policy' do + before do + sign_in(user) + end + + context 'when configuring iframes in Markdown' do + let(:iframe_rendering_allowlist) { ['www.youtube.com', 'embed.figma.com', 'www.figma.com'] } + + before do + allow(Gitlab::CurrentSettings).to receive_messages( + iframe_rendering_enabled?: iframe_rendering_enabled?, + iframe_rendering_allowlist: iframe_rendering_allowlist) + end + + context 'when disabled' do + let(:iframe_rendering_enabled?) { false } + + it 'does not modify frame-src' do + get root_path + + expect(response.headers['Content-Security-Policy']).not_to include('www.youtube.com') + expect(response.headers['Content-Security-Policy']).not_to include('figma.com') + end + end + + context 'when enabled' do + let(:iframe_rendering_enabled?) { true } + + it 'adds the domains to frame-src' do + get root_path + + expect(response.headers['Content-Security-Policy']).to include('https://www.youtube.com') + expect(response.headers['Content-Security-Policy']).to include('https://embed.figma.com') + expect(response.headers['Content-Security-Policy']).to include('https://www.figma.com') + end + end + end + end end diff --git a/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb b/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb index 883ee8985a3b5c..1a01df0ce3aa57 100644 --- a/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb +++ b/spec/support/shared_examples/controllers/base_action_controller_shared_examples.rb @@ -45,39 +45,6 @@ it_behaves_like 'snowplow is not in the CSP' end end - - context 'when configuring iframes in Markdown' do - let(:iframe_rendering_allowlist) { ['www.youtube.com', 'embed.figma.com', 'www.figma.com'] } - - before do - allow(Gitlab::CurrentSettings).to receive_messages( - iframe_rendering_enabled?: iframe_rendering_enabled?, - iframe_rendering_allowlist: iframe_rendering_allowlist) - end - - context 'when disabled' do - let(:iframe_rendering_enabled?) { false } - - it 'does not modify frame-src' do - request - - expect(response.headers['Content-Security-Policy']).not_to include('www.youtube.com') - expect(response.headers['Content-Security-Policy']).not_to include('figma.com') - end - end - - context 'when enabled' do - let(:iframe_rendering_enabled?) { true } - - it 'adds the domains to frame-src' do - request - - expect(response.headers['Content-Security-Policy']).to include('https://www.youtube.com/') - expect(response.headers['Content-Security-Policy']).to include('https://embed.figma.com/') - expect(response.headers['Content-Security-Policy']).to include('https://www.figma.com/') - end - end - end end end end -- GitLab From a2ac15eecea94cc15b0271e5850b4df5274ef75a Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Tue, 25 Nov 2025 12:14:29 +1100 Subject: [PATCH 32/34] Apply 5 suggestion(s) to 1 file(s) Co-authored-by: Alex Fracazo --- app/views/admin/application_settings/_iframe.html.haml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/admin/application_settings/_iframe.html.haml b/app/views/admin/application_settings/_iframe.html.haml index 853a903d2ece6d..7892ea21347192 100644 --- a/app/views/admin/application_settings/_iframe.html.haml +++ b/app/views/admin/application_settings/_iframe.html.haml @@ -1,23 +1,23 @@ - expanded = integration_expanded?('iframe_') -= render ::Layouts::SettingsBlockComponent.new(_('Iframe embedding'), += render ::Layouts::SettingsBlockComponent.new(_('Embedded content'), id: 'js-iframe-settings', testid: 'admin-iframe-settings', expanded: expanded) do |c| - c.with_description do - = _('Configure iframe embedding for Markdown documents.') + = _('Allow embedding of external videos and designs in Markdown.') - c.with_body do = gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-iframe-settings'), html: { class: 'fieldset-form', id: 'iframe-settings' } do |f| = form_errors(@application_setting) if expanded %fieldset .form-group - = f.gitlab_ui_checkbox_component :iframe_rendering_enabled, _('Allow iframes in Markdown'), help_text: _('Allows the use of iframes in Markdown documents for all users. This may be a security risk if you do not trust all users of your GitLab instance.') + = f.gitlab_ui_checkbox_component :iframe_rendering_enabled, _('Enable embedded content'), help_text: _('When enabled, users can embed YouTube videos, Figma designs, and other external content.') .form-group{ id: 'iframe-allowlist-group', style: @application_setting.iframe_rendering_enabled? ? '' : 'display: none;' } - = f.label :iframe_rendering_allowlist_raw, _('Allowed iframe sources'), class: 'label-bold' + = f.label :iframe_rendering_allowlist_raw, _('Allowed domains'), class: 'label-bold' = f.text_area :iframe_rendering_allowlist_raw, class: 'form-control gl-form-input', rows: 8 .form-text.gl-text-subtle - = _('A list of domains that are allowed to be embedded as iframes. One domain per line.') + = _('Enter trusted domains that users can embed content from. One domain per line.') = f.submit _('Save changes'), pajamas_button: true -- GitLab From 47a531c96543262e58ca2b5306b998007731ffda Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Tue, 25 Nov 2025 12:15:10 +1100 Subject: [PATCH 33/34] Regenerate locale/gitlab.pot --- locale/gitlab.pot | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1eca466028fecf..c19cbe6947f213 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2274,9 +2274,6 @@ msgstr "" msgid "A list for this status already exists on the board" msgstr "" -msgid "A list of domains that are allowed to be embedded as iframes. One domain per line." -msgstr "" - msgid "A management, operational, or technical control (that is, safeguard or countermeasure) employed by an organization that provides equivalent or comparable protection for an information system." msgstr "" @@ -7600,13 +7597,13 @@ msgstr "" msgid "Allow commits from members who can merge to the target branch. %{link_start}About this feature.%{link_end}" msgstr "" -msgid "Allow group and project access tokens" +msgid "Allow embedding of external videos and designs in Markdown." msgstr "" -msgid "Allow group owners to manage LDAP-related settings" +msgid "Allow group and project access tokens" msgstr "" -msgid "Allow iframes in Markdown" +msgid "Allow group owners to manage LDAP-related settings" msgstr "" msgid "Allow job retries even if the deployment job is outdated." @@ -7678,10 +7675,10 @@ msgstr "" msgid "Allowed" msgstr "" -msgid "Allowed email domain restriction only permitted for top-level groups" +msgid "Allowed domains" msgstr "" -msgid "Allowed iframe sources" +msgid "Allowed email domain restriction only permitted for top-level groups" msgstr "" msgid "Allowed to create" @@ -7699,9 +7696,6 @@ msgstr "" msgid "Allows projects to track errors using an Opstrace integration." msgstr "" -msgid "Allows the use of iframes in Markdown documents for all users. This may be a security risk if you do not trust all users of your GitLab instance." -msgstr "" - msgid "Almost there…" msgstr "" @@ -18592,9 +18586,6 @@ msgstr "" msgid "Configure feature \"%{name}\"" msgstr "" -msgid "Configure iframe embedding for Markdown documents." -msgstr "" - msgid "Configure import sources and settings related to import and export features." msgstr "" @@ -26388,6 +26379,9 @@ msgstr "" msgid "Embed" msgstr "" +msgid "Embedded content" +msgstr "" + msgid "Embedded list view" msgstr "" @@ -26517,6 +26511,9 @@ msgstr "" msgid "Enable email notification" msgstr "" +msgid "Enable embedded content" +msgstr "" + msgid "Enable feature to choose access level" msgstr "" @@ -26793,6 +26790,9 @@ msgstr "" msgid "Enter the username for password-protected Elasticsearch or OpenSearch servers." msgstr "" +msgid "Enter trusted domains that users can embed content from. One domain per line." +msgstr "" + msgid "Enter values to populate the .gitlab-ci.yml configuration file." msgstr "" @@ -34721,9 +34721,6 @@ msgstr "" msgid "If your HTTP repository is not publicly accessible, add your credentials." msgstr "" -msgid "Iframe embedding" -msgstr "" - msgid "Ignore" msgstr "" @@ -75358,6 +75355,9 @@ msgstr "" msgid "When enabled, job logs are collected by Datadog and displayed along with pipeline execution traces." msgstr "" +msgid "When enabled, users can embed YouTube videos, Figma designs, and other external content." +msgstr "" + msgid "When left blank, default value of %{max_lifetime_in_days} is applied. When set, value must be %{max_lifetime_in_days} or less. When changed, existing access tokens with an expiration date beyond the maximum allowable lifetime are revoked." msgstr "" -- GitLab From 6021af65e723c232d32c43b303f9bdb01cef85d4 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Tue, 25 Nov 2025 12:20:41 +1100 Subject: [PATCH 34/34] Update migration timestamp and milestone --- ...51125011943_add_iframe_allowlist_to_application_settings.rb} | 2 +- db/schema_migrations/20251029062200 | 1 - db/schema_migrations/20251125011943 | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) rename db/migrate/{20251029062200_add_iframe_allowlist_to_application_settings.rb => 20251125011943_add_iframe_allowlist_to_application_settings.rb} (97%) delete mode 100644 db/schema_migrations/20251029062200 create mode 100644 db/schema_migrations/20251125011943 diff --git a/db/migrate/20251029062200_add_iframe_allowlist_to_application_settings.rb b/db/migrate/20251125011943_add_iframe_allowlist_to_application_settings.rb similarity index 97% rename from db/migrate/20251029062200_add_iframe_allowlist_to_application_settings.rb rename to db/migrate/20251125011943_add_iframe_allowlist_to_application_settings.rb index eb32032929b7c4..893e45e153b463 100644 --- a/db/migrate/20251029062200_add_iframe_allowlist_to_application_settings.rb +++ b/db/migrate/20251125011943_add_iframe_allowlist_to_application_settings.rb @@ -2,7 +2,7 @@ class AddIframeAllowlistToApplicationSettings < Gitlab::Database::Migration[2.3] disable_ddl_transaction! - milestone '18.6' + milestone '18.7' def up with_lock_retries do diff --git a/db/schema_migrations/20251029062200 b/db/schema_migrations/20251029062200 deleted file mode 100644 index e7b11a579fe335..00000000000000 --- a/db/schema_migrations/20251029062200 +++ /dev/null @@ -1 +0,0 @@ -704444a7adfd231b1f5c845750a0ac2b8a6a6b57cf18d00f9f7fb6af7398b678 \ No newline at end of file diff --git a/db/schema_migrations/20251125011943 b/db/schema_migrations/20251125011943 new file mode 100644 index 00000000000000..29bbb56b819dfc --- /dev/null +++ b/db/schema_migrations/20251125011943 @@ -0,0 +1 @@ +985069c3755868f3daf5d97b07a5013997f721efc8df935f7ee9d5fb743457a3 \ No newline at end of file -- GitLab