diff --git a/app/assets/images/vulnerability/secureflag-logo.svg b/app/assets/images/vulnerability/secureflag-logo.svg
new file mode 100644
index 0000000000000000000000000000000000000000..621c56b90433cbcb08779b0c360bd5a277efb7cb
--- /dev/null
+++ b/app/assets/images/vulnerability/secureflag-logo.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com">
+  <defs>
+    <linearGradient id="paint1_linear_117_388" x1="8.32922" y1="0.701083" x2="25.6103" y2="8.8381"
+      gradientUnits="userSpaceOnUse"
+      gradientTransform="matrix(5.965987, 0, 0, 5.965987, -1.949537, -0.014549)">
+      <stop stop-color="#005AEC" />
+      <stop offset="1" stop-color="#12DFE7" />
+    </linearGradient>
+    <linearGradient id="paint2_linear_117_388" x1="3.30485" y1="11.2131" x2="20.4972" y2="19.4227"
+      gradientUnits="userSpaceOnUse"
+      gradientTransform="matrix(5.965987, 0, 0, 5.965987, -1.949537, -0.014549)">
+      <stop stop-color="#005AEC" />
+      <stop offset="1" stop-color="#12DFE7" />
+    </linearGradient>
+  </defs>
+  <g style="" transform="matrix(3.098345, 0, 0, 3.098345, 46.765705, 8.335629)"
+    bx:origin="0.503455 0.502894">
+    <path d="M 65.436 0.003 L 65.436 26.662 L 87.144 12.772 L 65.436 0.003 Z"
+      fill="url(#paint1_linear_117_388)" style="" />
+    <path
+      d="M 108.686 77.337 L 87.143 65.001 L 87.143 38.543 L 65.434 51.815 L 43.562 38.543 L 43.809 64.746 L 22.512 77.592 L 0.393 64.321 L 0.393 116.301 L 65.352 155.095 L 129.901 116.301 L 129.901 64.406 L 108.686 77.337 Z M 65.434 103.2 L 43.562 90.609 L 43.562 114.344 L 22.923 102.945 L 43.562 90.609 L 65.434 77.848 C 65.434 77.848 85.663 90.694 86.156 90.609 C 86.65 90.523 65.434 103.2 65.434 103.2 Z M 86.156 114.344 L 86.156 90.609 L 106.795 102.945 L 86.156 114.344 Z"
+      fill="url(#paint2_linear_117_388)" style="" />
+  </g>
+</svg>
\ No newline at end of file
diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js
index 3bf0401ef5e4170416473cbee91b35e0048dede6..1b86d7d0a2bd8f304940512294fb2181ee037c17 100644
--- a/app/assets/javascripts/security_configuration/components/constants.js
+++ b/app/assets/javascripts/security_configuration/components/constants.js
@@ -17,6 +17,7 @@ import {
 
 import kontraLogo from 'images/vulnerability/kontra-logo.svg';
 import scwLogo from 'images/vulnerability/scw-logo.svg';
+import secureflagLogo from 'images/vulnerability/secureflag-logo.svg';
 import configureSastMutation from '../graphql/configure_sast.mutation.graphql';
 import configureSastIacMutation from '../graphql/configure_iac.mutation.graphql';
 import configureSecretDetectionMutation from '../graphql/configure_secret_detection.mutation.graphql';
@@ -313,6 +314,9 @@ export const TEMP_PROVIDER_LOGOS = {
   [__('Secure Code Warrior')]: {
     svg: scwLogo,
   },
+  SecureFlag: {
+    svg: secureflagLogo,
+  },
 };
 
 // Use the `url` field from the GraphQL query once this issue is resolved
@@ -320,4 +324,5 @@ export const TEMP_PROVIDER_LOGOS = {
 export const TEMP_PROVIDER_URLS = {
   Kontra: 'https://application.security/',
   [__('Secure Code Warrior')]: 'https://www.securecodewarrior.com/',
+  SecureFlag: 'https://www.secureflag.com/',
 };
diff --git a/db/post_migrate/20230328030101_add_secureflag_training_provider.rb b/db/post_migrate/20230328030101_add_secureflag_training_provider.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4b32570ea56d2414792af323232db64c0ef01d14
--- /dev/null
+++ b/db/post_migrate/20230328030101_add_secureflag_training_provider.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class AddSecureflagTrainingProvider < Gitlab::Database::Migration[2.1]
+  disable_ddl_transaction!
+
+  restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+  SECUREFLAG_DATA = {
+    name: 'SecureFlag',
+    description: "Get remediation advice with example code and recommended hands-on labs in a fully
+    interactive virtualised environment.",
+    url: "https://knowledge-base-api.secureflag.com/gitlab"
+  }
+
+  class TrainingProvider < MigrationRecord
+    self.table_name = 'security_training_providers'
+  end
+
+  def up
+    current_time = Time.current
+    timestamps = { created_at: current_time, updated_at: current_time }
+
+    TrainingProvider.reset_column_information
+    TrainingProvider.upsert(SECUREFLAG_DATA.merge(timestamps))
+  end
+
+  def down
+    TrainingProvider.reset_column_information
+    TrainingProvider.find_by(name: SECUREFLAG_DATA[:name])&.destroy
+  end
+end
diff --git a/db/schema_migrations/20230328030101 b/db/schema_migrations/20230328030101
new file mode 100644
index 0000000000000000000000000000000000000000..0b50a16a5142629ed100a67bd3c9a8a87aaaca21
--- /dev/null
+++ b/db/schema_migrations/20230328030101
@@ -0,0 +1 @@
+eb05e37733efa95de5067d328a8e3dbe2fe696c95658bad5362893c04c8b89b6
\ No newline at end of file
diff --git a/ee/app/finders/security/training_providers/secure_flag_url_finder.rb b/ee/app/finders/security/training_providers/secure_flag_url_finder.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f0bd9204ce1e392f4c5b8cc623e2552736c64a59
--- /dev/null
+++ b/ee/app/finders/security/training_providers/secure_flag_url_finder.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Security
+  module TrainingProviders
+    class SecureFlagUrlFinder < BaseUrlFinder
+      self.reactive_cache_key = ->(finder) { finder.full_url }
+      self.reactive_cache_worker_finder = ->(id, *_args) { from_cache(id) }
+
+      ALLOWED_IDENTIFIER_LIST = %w[cwe].freeze
+
+      def calculate_reactive_cache(full_url)
+        response = Gitlab::HTTP.try_get(full_url)
+        parsed_response = response&.parsed_response
+
+        { url: parsed_response["link"] } if parsed_response
+      end
+
+      def full_url
+        cwe = identifier.split('-').last[/\d+/]
+        Gitlab::Utils.append_path(provider.url, "?cwe=#{cwe}#{language_param}")
+      end
+
+      def language_param
+        "&language=#{@language}" if @language
+      end
+
+      def allowed_identifier_list
+        ALLOWED_IDENTIFIER_LIST
+      end
+    end
+  end
+end
diff --git a/ee/spec/finders/security/training_providers/secure_flag_url_finder_spec.rb b/ee/spec/finders/security/training_providers/secure_flag_url_finder_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..befb1a114077a7b34e44bc2675d8871ffbad3a9b
--- /dev/null
+++ b/ee/spec/finders/security/training_providers/secure_flag_url_finder_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Security::TrainingProviders::SecureFlagUrlFinder, feature_category: :vulnerability_management do
+  include ReactiveCachingHelpers
+
+  let_it_be(:provider_name) { 'SecureFlag' }
+  let_it_be(:provider) { create(:security_training_provider, name: provider_name) }
+  let_it_be(:identifier) { create(:vulnerabilities_identifier, external_type: 'cwe', external_id: 2, name: "cwe-2") }
+  let_it_be(:dummy_url) { 'http://test.host/test' }
+  let_it_be(:identifier_external_id) do
+    "[#{identifier.external_type}]-[#{identifier.external_id}]-[#{identifier.name}]"
+  end
+
+  let(:finder) { described_class.new(identifier.project, provider, identifier_external_id) }
+
+  describe '#calculate_reactive_cache' do
+    context 'when response is nil' do
+      let_it_be(:finder) { described_class.new(identifier.project, provider, identifier.external_id) }
+
+      before do
+        synchronous_reactive_cache(finder)
+        allow(Gitlab::HTTP).to receive(:try_get).and_return(nil)
+      end
+
+      it 'returns nil' do
+        expect(finder.calculate_reactive_cache(dummy_url)).to be_nil
+      end
+    end
+
+    context 'when response is not nil' do
+      let_it_be(:response) { { 'link' => dummy_url } }
+
+      before do
+        synchronous_reactive_cache(finder)
+        allow(Gitlab::HTTP).to receive_message_chain(:try_get, :parsed_response).and_return(response)
+      end
+
+      it 'returns content url hash' do
+        expect(finder.calculate_reactive_cache(dummy_url)).to eq({ url: dummy_url })
+      end
+    end
+
+    context "when external_type is not present in allowed list" do
+      let_it_be(:identifier) do
+        create(:vulnerabilities_identifier, external_type: 'invalid type', external_id: "A1", name: "A1. Injection")
+      end
+
+      let_it_be(:identifier_external_id) do
+        "[#{identifier.external_type}]-[#{identifier.external_id}]-[#{identifier.name}]"
+      end
+
+      it 'returns nil' do
+        expect(finder.execute).to be_nil
+      end
+    end
+  end
+
+  describe '#full_url' do
+    context "when external_type is present in allowed list" do
+      it 'returns full url path' do
+        expect(finder.full_url).to eq('example.com/?cwe=2')
+      end
+
+      context "when identifier contains CWE-{number} format" do
+        let_it_be(:identifier) { create(:vulnerabilities_identifier, external_type: 'cwe', external_id: "CWE-2") }
+
+        it 'returns full url path with proper mapping key' do
+          expect(finder.full_url).to eq('example.com/?cwe=2')
+        end
+      end
+
+      context "when a language is provided" do
+        let_it_be(:language) { 'ruby' }
+
+        it 'returns full url path with the language parameter mapped' do
+          expect(described_class.new(identifier.project,
+            provider,
+            identifier_external_id,
+            language).full_url).to eq("example.com/?cwe=2&language=#{language}")
+        end
+      end
+    end
+
+    describe '#allowed_identifier_list' do
+      it 'returns allowed identifiers' do
+        expect(finder.allowed_identifier_list).to match_array(['cwe'])
+      end
+    end
+  end
+end
diff --git a/ee/spec/frontend/vulnerabilities/mock_data.js b/ee/spec/frontend/vulnerabilities/mock_data.js
index d2cd68cfd8e089bd11be453d6aea3a622685ebb1..ac4a88d2877608545c94d7f9034d2c338aa86195 100644
--- a/ee/spec/frontend/vulnerabilities/mock_data.js
+++ b/ee/spec/frontend/vulnerabilities/mock_data.js
@@ -63,6 +63,13 @@ const createSecurityTrainingUrls = ({ urlOverrides = {}, urls } = {}) =>
       identifier: testIdentifierName,
       ...urlOverrides.second,
     },
+    {
+      name: testProviderName[2],
+      url: testTrainingUrls[2],
+      status: SECURITY_TRAINING_URL_STATUS_COMPLETED,
+      identifier: testIdentifierName,
+      ...urlOverrides.third,
+    },
   ];
 
 export const getSecurityTrainingProjectData = (urlOverrides = {}) => ({
diff --git a/ee/spec/frontend/vulnerabilities/vulnerability_training_spec.js b/ee/spec/frontend/vulnerabilities/vulnerability_training_spec.js
index 0c3846814a802c613cbb0ddea6dc97e07904415d..f10411d790c56c01a1ef9bd2ba20a2dc85e43167 100644
--- a/ee/spec/frontend/vulnerabilities/vulnerability_training_spec.js
+++ b/ee/spec/frontend/vulnerabilities/vulnerability_training_spec.js
@@ -46,6 +46,9 @@ const TEMP_PROVIDER_LOGOS = {
   'Secure Code Warrior': {
     svg: '<svg>Secure Code Warrior</svg>',
   },
+  SecureFlag: {
+    svg: '<svg>SecureFlag</svg>',
+  },
 };
 
 jest.mock('~/security_configuration/components/constants', () => {
@@ -61,6 +64,9 @@ jest.mock('~/security_configuration/components/constants', () => {
       'Secure Code Warrior': {
         svg: '<svg>Secure Code Warrior</svg>',
       },
+      SecureFlag: {
+        svg: '<svg>SecureFlag</svg>',
+      },
     },
   };
 });
@@ -428,7 +434,7 @@ describe('VulnerabilityTraining component', () => {
       expect(trackingSpy).toHaveBeenCalledTimes(1);
     });
 
-    it.each([0, 1])('tracks when training link %s gets clicked', async (index) => {
+    it.each([0, 1, 2])('tracks when training link %s gets clicked', async (index) => {
       createApolloProvider();
       createComponent();
       await waitForQueryToBeLoaded();
diff --git a/lib/gitlab/database_importers/security/training_providers/importer.rb b/lib/gitlab/database_importers/security/training_providers/importer.rb
index aa6a9f29c6d7840ff97084829075ab536ce3e491..87bef6400fac017a51e70eb0fac6e6665ae86171 100644
--- a/lib/gitlab/database_importers/security/training_providers/importer.rb
+++ b/lib/gitlab/database_importers/security/training_providers/importer.rb
@@ -20,6 +20,13 @@ module Importer
             url: "https://integration-api.securecodewarrior.com/api/v1/trial"
           }.freeze
 
+          SECUREFLAG_DATA = {
+            name: 'SecureFlag',
+            description: "Get remediation advice with example code and recommended hands-on labs in a fully
+                          interactive virtualised environment.",
+            url: "https://knowledge-base-api.secureflag.com/gitlab"
+          }.freeze
+
           module Security
             class TrainingProvider < ApplicationRecord
               self.table_name = 'security_training_providers'
@@ -31,7 +38,7 @@ def self.upsert_providers
             timestamps = { created_at: current_time, updated_at: current_time }
 
             Security::TrainingProvider.upsert_all(
-              [KONTRA_DATA.merge(timestamps), SCW_DATA.merge(timestamps)],
+              [KONTRA_DATA.merge(timestamps), SCW_DATA.merge(timestamps), SECUREFLAG_DATA.merge(timestamps)],
               unique_by: :index_security_training_providers_on_unique_name
             )
           end
diff --git a/spec/frontend/security_configuration/mock_data.js b/spec/frontend/security_configuration/mock_data.js
index 3d4f01d0da1cf14803f281fa174775c78c41fd49..df10d33e2f09224736e55fc0695e42daaf11919e 100644
--- a/spec/frontend/security_configuration/mock_data.js
+++ b/spec/frontend/security_configuration/mock_data.js
@@ -9,10 +9,11 @@ import { REPORT_TYPE_SAST } from '~/vue_shared/security_reports/constants';
 
 export const testProjectPath = 'foo/bar';
 export const testProviderIds = [101, 102, 103];
-export const testProviderName = ['Kontra', 'Secure Code Warrior', 'Other Vendor'];
+export const testProviderName = ['Kontra', 'Secure Code Warrior', 'SecureFlag'];
 export const testTrainingUrls = [
   'https://www.vendornameone.com/url',
   'https://www.vendornametwo.com/url',
+  'https://www.vendornamethree.com/url',
 ];
 
 const createSecurityTrainingProviders = ({ providerOverrides = {} }) => [
diff --git a/spec/migrations/20230328030101_add_secureflag_training_provider_spec.rb b/spec/migrations/20230328030101_add_secureflag_training_provider_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..774ea89937a529d03200024994392f0f1d988c44
--- /dev/null
+++ b/spec/migrations/20230328030101_add_secureflag_training_provider_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe AddSecureflagTrainingProvider, :migration, feature_category: :vulnerability_management do
+  include MigrationHelpers::WorkItemTypesHelper
+
+  let!(:security_training_providers) { table(:security_training_providers) }
+
+  it 'adds additional provider' do
+    # Need to delete all as security training providers are seeded before entire test suite
+    security_training_providers.delete_all
+
+    reversible_migration do |migration|
+      migration.before -> {
+        expect(security_training_providers.count).to eq(0)
+      }
+
+      migration.after -> {
+        expect(security_training_providers.count).to eq(1)
+      }
+    end
+  end
+end
diff --git a/spec/support/finder_collection_allowlist.yml b/spec/support/finder_collection_allowlist.yml
index 0e94c5e348e3d5e4d660ee7644b8597661f4c5ed..8fcb4ee7b9c2a6550da399f8a6ed597893a684d2 100644
--- a/spec/support/finder_collection_allowlist.yml
+++ b/spec/support/finder_collection_allowlist.yml
@@ -63,6 +63,7 @@
 - Security::TrainingUrlsFinder
 - Security::TrainingProviders::KontraUrlFinder
 - Security::TrainingProviders::SecureCodeWarriorUrlFinder
+- Security::TrainingProviders::SecureFlagUrlFinder
 - SentryIssueFinder
 - ServerlessDomainFinder
 - TagsFinder
diff --git a/spec/support/shared_examples/security_training_providers_importer.rb b/spec/support/shared_examples/security_training_providers_importer.rb
index 69d92964270b516381f13676b8fe175e9d7cf5b0..81b3d22ab234a3ab0eb3404db951a675daf0c578 100644
--- a/spec/support/shared_examples/security_training_providers_importer.rb
+++ b/spec/support/shared_examples/security_training_providers_importer.rb
@@ -8,7 +8,7 @@
   end
 
   it 'upserts security training providers' do
-    expect { 2.times { subject } }.to change { security_training_providers.count }.from(0).to(2)
-    expect(security_training_providers.all.map(&:name)).to match_array(['Kontra', 'Secure Code Warrior'])
+    expect { 3.times { subject } }.to change { security_training_providers.count }.from(0).to(3)
+    expect(security_training_providers.all.map(&:name)).to match_array(['Kontra', 'Secure Code Warrior', 'SecureFlag'])
   end
 end