diff --git a/config/known_invalid_graphql_queries.yml b/config/known_invalid_graphql_queries.yml
index 3dc4b10a6a86c21bffeafa7d7fa4855fdabc475d..2989b3a4262623bd44c59d9ed090c7bf48b1db8f 100644
--- a/config/known_invalid_graphql_queries.yml
+++ b/config/known_invalid_graphql_queries.yml
@@ -3,3 +3,4 @@ filenames:
   - ee/app/assets/javascripts/oncall_schedules/graphql/mutations/update_oncall_schedule_rotation.mutation.graphql
   - ee/app/assets/javascripts/security_configuration/api_fuzzing/graphql/api_fuzzing_ci_configuration.query.graphql
   - ee/app/assets/javascripts/security_configuration/api_fuzzing/graphql/create_api_fuzzing_configuration.mutation.graphql
+  - ee/app/assets/javascripts/security_configuration/dast_profiles/graphql/dast_failed_site_validations.query.graphql
diff --git a/ee/app/assets/javascripts/security_configuration/dast_profiles/components/dast_failed_site_validations.vue b/ee/app/assets/javascripts/security_configuration/dast_profiles/components/dast_failed_site_validations.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2fd75ec5151679dc163064c46555b649a81242a6
--- /dev/null
+++ b/ee/app/assets/javascripts/security_configuration/dast_profiles/components/dast_failed_site_validations.vue
@@ -0,0 +1,108 @@
+<script>
+import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
+import DastSiteValidationModal from 'ee/security_configuration/dast_site_validation/components/dast_site_validation_modal.vue';
+import { DAST_SITE_VALIDATION_MODAL_ID } from 'ee/security_configuration/dast_site_validation/constants';
+import dastSiteValidationRevokeMutation from 'ee/security_configuration/dast_site_validation/graphql/dast_site_validation_revoke.mutation.graphql';
+import dastFailedSiteValidationsQuery from '../graphql/dast_failed_site_validations.query.graphql';
+
+export default {
+  name: 'DastFailedSiteValidations',
+  dastSiteValidationModalId: DAST_SITE_VALIDATION_MODAL_ID,
+  components: {
+    GlAlert,
+    GlLink,
+    GlSprintf,
+    DastSiteValidationModal,
+  },
+  props: {
+    fullPath: {
+      type: String,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      failedValidations: [],
+      validateTargetUrl: null,
+    };
+  },
+  apollo: {
+    dastFailedSiteValidations: {
+      query: dastFailedSiteValidationsQuery,
+      manual: true,
+      variables() {
+        return {
+          fullPath: this.fullPath,
+        };
+      },
+      result({
+        data: {
+          project: {
+            validations: { nodes },
+          },
+        },
+      }) {
+        this.failedValidations = nodes.map((node) => ({
+          ...node,
+          url: new URL(node.normalizedTargetUrl).href,
+        }));
+      },
+    },
+  },
+  methods: {
+    retryValidation({ url }) {
+      this.validateTargetUrl = url;
+      this.$nextTick(() => {
+        this.$refs[DAST_SITE_VALIDATION_MODAL_ID].show();
+      });
+    },
+    revokeValidation({ normalizedTargetUrl }) {
+      this.$apollo.mutate({
+        mutation: dastSiteValidationRevokeMutation,
+        variables: {
+          fullPath: this.fullPath,
+          normalizedTargetUrl,
+        },
+      });
+      this.failedValidations = this.failedValidations.filter(
+        (failedValidation) => failedValidation.normalizedTargetUrl !== normalizedTargetUrl,
+      );
+    },
+  },
+};
+</script>
+
+<template>
+  <div v-if="failedValidations.length">
+    <gl-alert
+      v-for="failedValidation in failedValidations"
+      :key="failedValidation.url"
+      variant="danger"
+      class="gl-mt-3"
+      @dismiss="revokeValidation(failedValidation)"
+    >
+      <gl-sprintf
+        :message="
+          s__(
+            'DastSiteValidation|Validation failed for %{url}. %{retryButtonStart}Retry validation%{retryButtonEnd}.',
+          )
+        "
+      >
+        <template #url>{{ failedValidation.url }}</template>
+        <template #retryButton="{ content }"
+          ><gl-link href="#" role="button" @click="retryValidation(failedValidation)">{{
+            content
+          }}</gl-link></template
+        >
+      </gl-sprintf>
+    </gl-alert>
+
+    <dast-site-validation-modal
+      v-if="validateTargetUrl"
+      :ref="$options.dastSiteValidationModalId"
+      :full-path="fullPath"
+      :target-url="validateTargetUrl"
+      @hidden="validateTargetUrl = null"
+    />
+  </div>
+</template>
diff --git a/ee/app/assets/javascripts/security_configuration/dast_profiles/components/dast_profiles.vue b/ee/app/assets/javascripts/security_configuration/dast_profiles/components/dast_profiles.vue
index 07c022e2016a5ebd9bd722ec84ae5f97a49baaf5..4a2720d69f6f9e310c90b74a87eac01a8107d59f 100644
--- a/ee/app/assets/javascripts/security_configuration/dast_profiles/components/dast_profiles.vue
+++ b/ee/app/assets/javascripts/security_configuration/dast_profiles/components/dast_profiles.vue
@@ -7,6 +7,7 @@ import { __, s__ } from '~/locale';
 import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
 import * as cacheUtils from '../graphql/cache_utils';
 import { getProfileSettings } from '../settings/profiles';
+import DastFailedSiteValidations from './dast_failed_site_validations.vue';
 
 export default {
   components: {
@@ -14,6 +15,7 @@ export default {
     GlDropdownItem,
     GlTab,
     GlTabs,
+    DastFailedSiteValidations,
   },
   mixins: [glFeatureFlagsMixin()],
   props: {
@@ -223,6 +225,10 @@ export default {
 
 <template>
   <section>
+    <dast-failed-site-validations
+      v-if="glFeatures.dastFailedSiteValidations"
+      :full-path="projectFullPath"
+    />
     <header>
       <div class="gl-display-flex gl-align-items-center gl-pt-6 gl-pb-4">
         <h2 class="my-0">
diff --git a/ee/app/assets/javascripts/security_configuration/dast_profiles/graphql/dast_failed_site_validations.query.graphql b/ee/app/assets/javascripts/security_configuration/dast_profiles/graphql/dast_failed_site_validations.query.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..4421c7cf817b19d91b9d0187d757706d4c884c12
--- /dev/null
+++ b/ee/app/assets/javascripts/security_configuration/dast_profiles/graphql/dast_failed_site_validations.query.graphql
@@ -0,0 +1,9 @@
+query DastFailedSiteValidations($fullPath: ID!) {
+  project(fullPath: $fullPath) {
+    validations: dastSiteValidations(normalizedTargetUrls: $urls, status: "FAILED_VALIDATION") {
+      nodes {
+        normalizedTargetUrl
+      }
+    }
+  }
+}
diff --git a/ee/spec/frontend/security_configuration/dast_profiles/components/dast_failed_site_validations_spec.js b/ee/spec/frontend/security_configuration/dast_profiles/components/dast_failed_site_validations_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b013732b12c213b849d137b819b9da6d31efe401
--- /dev/null
+++ b/ee/spec/frontend/security_configuration/dast_profiles/components/dast_failed_site_validations_spec.js
@@ -0,0 +1,142 @@
+import { GlAlert } from '@gitlab/ui';
+import { within } from '@testing-library/dom';
+import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
+import { merge } from 'lodash';
+import VueApollo from 'vue-apollo';
+import DastFailedSiteValidations from 'ee/security_configuration/dast_profiles/components/dast_failed_site_validations.vue';
+import dastFailedSiteValidationsQuery from 'ee/security_configuration/dast_profiles/graphql/dast_failed_site_validations.query.graphql';
+import dastSiteValidationRevokeMutation from 'ee/security_configuration/dast_site_validation/graphql/dast_site_validation_revoke.mutation.graphql';
+import createApolloProvider from 'helpers/mock_apollo_helper';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { dastSiteValidationRevoke as dastSiteValidationRevokeResponse } from '../../dast_site_validation/mock_data/apollo_mock';
+import { dastSiteValidations as dastSiteValidationsResponse } from '../mocks/apollo_mock';
+import { failedSiteValidations } from '../mocks/mock_data';
+
+const TEST_PROJECT_FULL_PATH = '/namespace/project';
+const GlModal = {
+  template: '<div data-testid="validation-modal" />',
+  methods: {
+    show: () => {},
+  },
+};
+
+const localVue = createLocalVue();
+
+describe('EE - DastFailedSiteValidations', () => {
+  let wrapper;
+  let requestHandlers;
+
+  const createMockApolloProvider = (handlers) => {
+    localVue.use(VueApollo);
+    requestHandlers = handlers;
+    return createApolloProvider([
+      [dastFailedSiteValidationsQuery, requestHandlers.dastFailedSiteValidations],
+      [dastSiteValidationRevokeMutation, requestHandlers.dastSiteValidationRevoke],
+    ]);
+  };
+
+  const createComponentFactory = (mountFn = shallowMount) => (options = {}, handlers) => {
+    const defaultProps = {
+      fullPath: TEST_PROJECT_FULL_PATH,
+    };
+
+    wrapper = extendedWrapper(
+      mountFn(
+        DastFailedSiteValidations,
+        merge(
+          {
+            propsData: defaultProps,
+            localVue,
+            apolloProvider: createMockApolloProvider(handlers),
+            stubs: {
+              GlModal,
+            },
+          },
+          options,
+        ),
+      ),
+    );
+  };
+
+  const createFullComponent = createComponentFactory(mount);
+
+  const withinComponent = () => within(wrapper.element);
+  const findFirstRetryButton = () =>
+    withinComponent().getAllByRole('button', { name: /retry validation/i })[0];
+  const findFirstDismissButton = () =>
+    withinComponent().getAllByRole('button', { name: /dismiss/i })[0];
+  const findValidationModal = () => wrapper.findByTestId('validation-modal');
+
+  afterEach(() => {
+    wrapper.destroy();
+  });
+
+  describe('with failed site validations', () => {
+    beforeEach(() => {
+      createFullComponent(
+        {},
+        {
+          dastFailedSiteValidations: jest
+            .fn()
+            .mockResolvedValue(dastSiteValidationsResponse(failedSiteValidations)),
+          dastSiteValidationRevoke: jest.fn().mockResolvedValue(dastSiteValidationRevokeResponse()),
+        },
+      );
+    });
+
+    it('triggers the dastSiteValidations query', () => {
+      expect(requestHandlers.dastFailedSiteValidations).toHaveBeenCalledWith({
+        fullPath: TEST_PROJECT_FULL_PATH,
+      });
+    });
+
+    it('renders an alert for each failed validation', () => {
+      expect(wrapper.findAllComponents(GlAlert)).toHaveLength(failedSiteValidations.length);
+    });
+
+    it.each`
+      index | expectedUrl
+      ${0}  | ${'http://example.com/'}
+      ${1}  | ${'https://example.com/'}
+    `('shows parsed URL $expectedUrl in alert #$index', ({ index, expectedUrl }) => {
+      expect(wrapper.findAllComponents(GlAlert).at(index).text()).toMatchInterpolatedText(
+        `Validation failed for ${expectedUrl}. Retry validation.`,
+      );
+    });
+
+    it('shows the validation modal when clicking on a retry button', async () => {
+      expect(findValidationModal().exists()).toBe(false);
+
+      findFirstRetryButton().click();
+      await wrapper.vm.$nextTick();
+      const modal = findValidationModal();
+
+      expect(modal.exists()).toBe(true);
+      expect(modal.attributes('targetUrl')).toBe(failedSiteValidations[0].url);
+    });
+
+    it('destroys the modal after it has been hidden', async () => {
+      findFirstRetryButton().click();
+      await wrapper.vm.$nextTick();
+      const modal = findValidationModal();
+
+      expect(modal.exists()).toBe(true);
+
+      modal.vm.$emit('hidden');
+      await wrapper.vm.$nextTick();
+
+      expect(modal.exists()).toBe(false);
+    });
+
+    it('triggers the dastSiteValidationRevoke GraphQL mutation', async () => {
+      findFirstDismissButton().click();
+      await wrapper.vm.$nextTick();
+
+      expect(wrapper.findAllComponents(GlAlert)).toHaveLength(1);
+      expect(requestHandlers.dastSiteValidationRevoke).toHaveBeenCalledWith({
+        fullPath: TEST_PROJECT_FULL_PATH,
+        normalizedTargetUrl: failedSiteValidations[0].normalizedTargetUrl,
+      });
+    });
+  });
+});
diff --git a/ee/spec/frontend/security_configuration/dast_profiles/components/dast_profiles_spec.js b/ee/spec/frontend/security_configuration/dast_profiles/components/dast_profiles_spec.js
index 72c55458115c19e3f7d62e4f507d739beb8495af..6cace6a4bf66cdb1d90bb6207845f940ee661ec0 100644
--- a/ee/spec/frontend/security_configuration/dast_profiles/components/dast_profiles_spec.js
+++ b/ee/spec/frontend/security_configuration/dast_profiles/components/dast_profiles_spec.js
@@ -2,6 +2,7 @@ import { GlDropdown, GlTabs } from '@gitlab/ui';
 import { within } from '@testing-library/dom';
 import { mount, shallowMount } from '@vue/test-utils';
 import { merge } from 'lodash';
+import DastFailedSiteValidations from 'ee/security_configuration/dast_profiles/components/dast_failed_site_validations.vue';
 import DastProfiles from 'ee/security_configuration/dast_profiles/components/dast_profiles.vue';
 import setWindowLocation from 'helpers/set_window_location_helper';
 
@@ -48,6 +49,11 @@ describe('EE - DastProfiles', () => {
         {
           propsData: defaultProps,
           mocks: defaultMocks,
+          provide: {
+            glFeatures: {
+              dastFailedSiteValidations: true,
+            },
+          },
         },
         options,
       ),
@@ -73,6 +79,14 @@ describe('EE - DastProfiles', () => {
     wrapper.destroy();
   });
 
+  describe('failed validations', () => {
+    it('renders the failed site validations summary', () => {
+      createComponent();
+
+      expect(wrapper.findComponent(DastFailedSiteValidations).exists()).toBe(true);
+    });
+  });
+
   describe('header', () => {
     it('shows a heading that describes the purpose of the page', () => {
       createFullComponent();
@@ -235,4 +249,18 @@ describe('EE - DastProfiles', () => {
       expect(mutate).toHaveBeenCalledTimes(1);
     });
   });
+
+  describe('dastFailedSiteValidations feature flag disabled', () => {
+    it('does not render the failed site validations summary', () => {
+      createComponent({
+        provide: {
+          glFeatures: {
+            dastFailedSiteValidations: false,
+          },
+        },
+      });
+
+      expect(wrapper.findComponent(DastFailedSiteValidations).exists()).toBe(false);
+    });
+  });
 });
diff --git a/ee/spec/frontend/security_configuration/dast_profiles/mocks/mock_data.js b/ee/spec/frontend/security_configuration/dast_profiles/mocks/mock_data.js
index b9a28c8e530e31f9b7273093ee3b60d5b075a908..50824a6d11f055136a16d6820b6878a36979331f 100644
--- a/ee/spec/frontend/security_configuration/dast_profiles/mocks/mock_data.js
+++ b/ee/spec/frontend/security_configuration/dast_profiles/mocks/mock_data.js
@@ -103,3 +103,12 @@ export const savedScans = [
     },
   },
 ];
+
+export const failedSiteValidations = [
+  {
+    normalizedTargetUrl: 'http://example.com:80',
+  },
+  {
+    normalizedTargetUrl: 'https://example.com:443',
+  },
+];
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 58f48f98c770873a5e762c52823e87d02b60e7ee..d44ba61fafb8b90e339bfe4c85540b3e69252c15 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -9750,6 +9750,9 @@ msgstr ""
 msgid "DastSiteValidation|Validation failed"
 msgstr ""
 
+msgid "DastSiteValidation|Validation failed for %{url}. %{retryButtonStart}Retry validation%{retryButtonEnd}."
+msgstr ""
+
 msgid "DastSiteValidation|Validation succeeded. Both active and passive scans can be run against the target site."
 msgstr ""