diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/constants.js b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/constants.js
index a97e763c0d1e4d1ab9b02a56c094105b113ded87..64b3aac3218013d6aa892bfc809472f33ef39254 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/constants.js
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/constants.js
@@ -118,12 +118,20 @@ export const ALL_PROTECTED_BRANCHES = {
 
 export const ANY_OPERATOR = 'ANY';
 
-export const MORE_THAN_OPERATOR = 'MORE_THAN';
+export const GREATER_THAN_OPERATOR = 'greater_than';
 
-export const NUMBER_RANGE_I18N_MAP = {
-  [ANY_OPERATOR]: s__('ApprovalRule|Any'),
-  [MORE_THAN_OPERATOR]: s__('ApprovalRule|More than'),
-};
+export const LESS_THAN_OPERATOR = 'less_than';
+
+export const VULNERABILITIES_ALLOWED_OPERATORS = [
+  { value: ANY_OPERATOR, text: s__('ApprovalRule|Any') },
+  { value: GREATER_THAN_OPERATOR, text: s__('ApprovalRule|More than') },
+];
+
+export const VULNERABILITY_AGE_OPERATORS = [
+  { value: ANY_OPERATOR, text: s__('ApprovalRule|Any') },
+  { value: GREATER_THAN_OPERATOR, text: s__('ApprovalRule|Greater than') },
+  { value: LESS_THAN_OPERATOR, text: s__('ApprovalRule|Less than') },
+];
 
 export const SCAN_RESULT_BRANCH_TYPE_OPTIONS = (nameSpaceType = NAMESPACE_TYPES.GROUP) => [
   nameSpaceType === NAMESPACE_TYPES.GROUP ? GROUP_DEFAULT_BRANCHES : PROJECT_DEFAULT_BRANCH,
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_filter_selector.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_filter_selector.vue
index 4274de622b122e943600cab4c6f6be39f52064fc..38088565d1e7d8a86971e95103fa5d8fc0550c45 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_filter_selector.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_filter_selector.vue
@@ -34,23 +34,36 @@ export default {
       required: false,
       default: () => ({}),
     },
+    shouldDisableFilter: {
+      type: Function,
+      required: false,
+      default: () => false,
+    },
     tooltipTitle: {
       type: String,
       required: false,
       default: '',
     },
+    customFilterTooltip: {
+      type: Function,
+      required: false,
+      default: () => null,
+    },
   },
   methods: {
-    filterSelected(value) {
-      return Boolean(this.selected[value]);
+    filterDisabled(value) {
+      return this.shouldDisableFilter(value) || Boolean(this.selected[value]);
     },
     selectFilter(filter) {
-      if (this.filterSelected(filter)) {
+      if (this.filterDisabled(filter)) {
         return;
       }
 
       this.$emit('select', filter);
     },
+    filterTooltip(filter) {
+      return this.customFilterTooltip(filter) || filter.tooltip;
+    },
   },
 };
 </script>
@@ -75,17 +88,17 @@ export default {
             <span
               :id="item.value"
               class="gl-pr-3"
-              :class="{ 'gl-text-gray-500': filterSelected(item.value) }"
+              :class="{ 'gl-text-gray-500': filterDisabled(item.value) }"
             >
               {{ item.text }}
             </span>
             <gl-badge
-              v-if="filterSelected(item.value)"
+              v-if="filterDisabled(item.value)"
               v-gl-tooltip.right.viewport
               class="gl-ml-auto"
               size="sm"
               variant="neutral"
-              :title="item.tooltip"
+              :title="filterTooltip(item)"
             >
               {{ $options.i18n.disabledLabel }}
             </gl-badge>
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/lib/from_yaml.js b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/lib/from_yaml.js
index a090f751d52fb6e8e9c86e0628a91443c4e6734b..7baf940d379cbd7847b984c7fd6545fafbe51a9e 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/lib/from_yaml.js
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/lib/from_yaml.js
@@ -26,6 +26,7 @@ export const fromYaml = ({ manifest, validateRuleMode = false }) => {
         'severity_levels',
         'vulnerabilities_allowed',
         'vulnerability_states',
+        'vulnerability_age',
       ];
       const actionsKeys = [
         'type',
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/number_range_select.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/number_range_select.vue
index 4e4bc2a2a2e9c01abab33828d7a637c4ffc0c755..33d01b01b5876e73ea6c406d5e837dc2b86484c7 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/number_range_select.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/number_range_select.vue
@@ -1,7 +1,8 @@
 <script>
 import { GlCollapsibleListbox, GlFormInput } from '@gitlab/ui';
 import { s__ } from '~/locale';
-import { ANY_OPERATOR, NUMBER_RANGE_I18N_MAP } from '../constants';
+import { ANY_OPERATOR } from '../constants';
+import { enforceIntValue } from '../utils';
 
 export default {
   components: {
@@ -34,16 +35,10 @@ export default {
   },
   data() {
     return {
-      operator: this.selected || this.operators[0],
+      operator: this.selected || this.operators[0]?.value,
     };
   },
   computed: {
-    listBoxItems() {
-      return this.operators.map((operator) => ({
-        value: operator,
-        text: NUMBER_RANGE_I18N_MAP[operator],
-      }));
-    },
     showNumberInput() {
       return this.operator !== ANY_OPERATOR;
     },
@@ -56,6 +51,9 @@ export default {
       this.operator = item;
       this.$emit('operator-change', item);
     },
+    onValueChange(value) {
+      this.$emit('input', enforceIntValue(value));
+    },
   },
   i18n: {
     headerText: s__('ScanResultPolicy|Choose an option'),
@@ -66,7 +64,7 @@ export default {
 <template>
   <div class="gl-display-flex gl-gap-3">
     <gl-collapsible-listbox
-      :items="listBoxItems"
+      :items="operators"
       :header-text="$options.i18n.headerText"
       :selected="operator"
       :data-testid="`${id}-operator`"
@@ -85,7 +83,7 @@ export default {
         class="gl-w-11!"
         :min="0"
         :data-testid="`${id}-input`"
-        @input="$emit('input', $event)"
+        @input="onValueChange"
       />
     </template>
   </div>
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/age_filter.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/age_filter.vue
new file mode 100644
index 0000000000000000000000000000000000000000..9ec6af9647444ec65df1f9fbb4fa0070c7495172
--- /dev/null
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/age_filter.vue
@@ -0,0 +1,92 @@
+<script>
+import { GlCollapsibleListbox } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import BaseLayoutComponent from '../base_layout/base_layout_component.vue';
+import NumberRangeSelect from '../number_range_select.vue';
+import { ANY_OPERATOR, VULNERABILITY_AGE_OPERATORS } from '../../constants';
+import { AGE, AGE_DAY, AGE_INTERVALS } from './constants';
+
+export default {
+  i18n: {
+    label: s__('ScanResultPolicy|Age is:'),
+    headerText: s__('ScanResultPolicy|Choose an option'),
+  },
+  name: 'AgeFilter',
+  components: {
+    BaseLayoutComponent,
+    NumberRangeSelect,
+    GlCollapsibleListbox,
+  },
+  props: {
+    selected: {
+      type: Object,
+      required: false,
+      default: () => ({}),
+    },
+    showRemoveButton: {
+      type: Boolean,
+      required: false,
+      default: true,
+    },
+  },
+  computed: {
+    showInterval() {
+      return this.operator !== ANY_OPERATOR;
+    },
+    value() {
+      return this.selected.value || 0;
+    },
+    operator() {
+      return this.selected.operator || ANY_OPERATOR;
+    },
+    interval() {
+      return this.selected.interval || AGE_DAY;
+    },
+  },
+  methods: {
+    remove() {
+      this.$emit('remove', AGE);
+    },
+    emitChange(data) {
+      this.$emit('input', {
+        operator: this.operator,
+        value: this.value,
+        interval: this.interval,
+        ...data,
+      });
+    },
+  },
+  VULNERABILITY_AGE_OPERATORS,
+  AGE_INTERVALS,
+};
+</script>
+
+<template>
+  <base-layout-component
+    class="gl-w-full gl-bg-white"
+    content-class="gl-bg-white gl-rounded-base gl-p-5"
+    :show-label="false"
+    :show-remove-button="showRemoveButton"
+    @remove="remove"
+  >
+    <template #selector>
+      <label class="gl-mb-0" :title="$options.i18n.label">{{ $options.i18n.label }}</label>
+      <number-range-select
+        id="vulnerability-age-select"
+        :value="value"
+        :label="$options.i18n.headerText"
+        :selected="operator"
+        :operators="$options.VULNERABILITY_AGE_OPERATORS"
+        @input="emitChange({ value: $event })"
+        @operator-change="emitChange({ operator: $event })"
+      />
+      <gl-collapsible-listbox
+        v-if="showInterval"
+        :selected="interval"
+        :header-text="$options.i18n.headerText"
+        :items="$options.AGE_INTERVALS"
+        @select="emitChange({ interval: $event })"
+      />
+    </template>
+  </base-layout-component>
+</template>
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/constants.js b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/constants.js
index b4ba1198031d53ab77df6b0ba6d8f69f37382e2e..9338468e2356b6235cbb54e25f252c359b617d54 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/constants.js
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/constants.js
@@ -2,31 +2,51 @@ import { s__ } from '~/locale';
 
 export const SEVERITY = 'severity';
 export const STATUS = 'status';
+export const AGE = 'age';
 
 export const UNKNOWN_LICENSE = {
   value: 'unknown',
   text: s__('ScanResultPolicy|Unknown'),
 };
 
+export const AGE_TOOLTIP_MAXIMUM_REACHED = 'maximumReached';
+export const AGE_TOOLTIP_NO_PREVIOUSLY_EXISTING_VULNERABILITY = 'noPreviouslyExistingVulnerability';
+
+const AGE_TOOLTIPS = {
+  [AGE_TOOLTIP_MAXIMUM_REACHED]: s__('ScanResultPolicy|Only 1 age criteria is allowed'),
+  [AGE_TOOLTIP_NO_PREVIOUSLY_EXISTING_VULNERABILITY]: s__(
+    'ScanResultPolicy|Age criteria can only be added for pre-existing vulnerabilities',
+  ),
+};
+
 export const FILTERS = [
   {
     text: s__('ScanResultPolicy|New severity'),
     value: SEVERITY,
-    tooltip: s__('ScanResultPolicy|Maximum number of severity-criteria is one'),
+    tooltip: s__('ScanResultPolicy|Only 1 severity is allowed'),
   },
   {
     text: s__('ScanResultPolicy|New status'),
     value: STATUS,
-    tooltip: s__('ScanResultPolicy|Maximum number of status-criteria is two'),
+    tooltip: s__('ScanResultPolicy|Only 2 status criteria are allowed'),
+  },
+  {
+    text: s__('ScanResultPolicy|New age'),
+    value: AGE,
+    tooltip: AGE_TOOLTIPS,
   },
 ];
 
-export const FILTERS_STATUS_INDEX = FILTERS.findIndex(({ value }) => value === STATUS);
+export const AGE_DAY = 'day';
 
-export const FILTER_POLICY_PROPERTY_MAP = {
-  [STATUS]: 'vulnerability_states',
-  [SEVERITY]: 'severity_levels',
-};
+export const AGE_INTERVALS = [
+  { value: AGE_DAY, text: s__('ApprovalRule|day(s)') },
+  { value: 'week', text: s__('ApprovalRule|week(s)') },
+  { value: 'month', text: s__('ApprovalRule|month(s)') },
+  { value: 'year', text: s__('ApprovalRule||year(s)') },
+];
+
+export const FILTERS_STATUS_INDEX = FILTERS.findIndex(({ value }) => value === STATUS);
 
 export const NEWLY_DETECTED = 'newly_detected';
 export const PREVIOUSLY_EXISTING = 'previously_existing';
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/security_scan_rule_builder.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/security_scan_rule_builder.vue
index 1570f38badef26cf5e07118e462d4bb8f0928e0c..547ea5c872fa269ccd5f6f90f9a95b4e11b6cfcf 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/security_scan_rule_builder.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/security_scan_rule_builder.vue
@@ -3,11 +3,17 @@ import { GlSprintf } from '@gitlab/ui';
 import { s__ } from '~/locale';
 import { REPORT_TYPES_DEFAULT, SEVERITY_LEVELS } from 'ee/security_dashboard/store/constants';
 import PolicyRuleMultiSelect from '../../policy_rule_multi_select.vue';
-import { ANY_OPERATOR, MORE_THAN_OPERATOR, SCAN_RESULT_BRANCH_TYPE_OPTIONS } from '../constants';
+import {
+  ANY_OPERATOR,
+  GREATER_THAN_OPERATOR,
+  VULNERABILITIES_ALLOWED_OPERATORS,
+  SCAN_RESULT_BRANCH_TYPE_OPTIONS,
+} from '../constants';
 import { enforceIntValue } from '../utils';
 import ScanFilterSelector from '../scan_filter_selector.vue';
 import { getDefaultRule, groupSelectedVulnerabilityStates } from './lib';
 import SeverityFilter from './scan_filters/severity_filter.vue';
+import AgeFilter from './scan_filters/age_filter.vue';
 import StatusFilters from './scan_filters/status_filters.vue';
 import BaseLayoutComponent from './base_layout/base_layout_component.vue';
 import PolicyRuleBranchSelection from './policy_rule_branch_selection.vue';
@@ -17,6 +23,9 @@ import {
   PREVIOUSLY_EXISTING,
   SEVERITY,
   STATUS,
+  AGE,
+  AGE_TOOLTIP_NO_PREVIOUSLY_EXISTING_VULNERABILITY,
+  AGE_TOOLTIP_MAXIMUM_REACHED,
 } from './scan_filters/constants';
 import NumberRangeSelect from './number_range_select.vue';
 import ScanTypeSelect from './base_layout/scan_type_select.vue';
@@ -25,6 +34,7 @@ export default {
   FILTERS,
   SEVERITY,
   STATUS,
+  AGE,
   NEWLY_DETECTED,
   PREVIOUSLY_EXISTING,
   scanResultRuleCopy: s__(
@@ -38,6 +48,7 @@ export default {
     ScanFilterSelector,
     ScanTypeSelect,
     SeverityFilter,
+    AgeFilter,
     StatusFilters,
     NumberRangeSelect,
   },
@@ -52,9 +63,11 @@ export default {
     const vulnerabilityStateGroups = groupSelectedVulnerabilityStates(
       this.initRule.vulnerability_states,
     );
+    const { vulnerability_age: vulnerabilityAge, severity_levels: severityLevels } = this.initRule;
 
     const filters = {
-      [SEVERITY]: this.initRule.severity_levels.length ? this.initRule.severity_levels : null,
+      [SEVERITY]: severityLevels.length ? severityLevels : null,
+      [AGE]: Object.keys(vulnerabilityAge || {}).length ? vulnerabilityAge : null,
       [NEWLY_DETECTED]: vulnerabilityStateGroups[NEWLY_DETECTED],
       [PREVIOUSLY_EXISTING]: vulnerabilityStateGroups[PREVIOUSLY_EXISTING],
     };
@@ -65,11 +78,13 @@ export default {
     };
   },
   computed: {
-    severityLevelsToAdd() {
-      return this.initRule.severity_levels;
-    },
-    vulnerabilityStates() {
-      return this.initRule.vulnerability_states;
+    severityLevelsToAdd: {
+      get() {
+        return this.initRule.severity_levels;
+      },
+      set(value) {
+        this.triggerChanged({ severity_levels: value });
+      },
     },
     branchTypes() {
       return SCAN_RESULT_BRANCH_TYPE_OPTIONS(this.namespaceType);
@@ -91,12 +106,29 @@ export default {
         return enforceIntValue(this.initRule.vulnerabilities_allowed);
       },
       set(value) {
-        this.triggerChanged({ vulnerabilities_allowed: enforceIntValue(value) });
+        this.triggerChanged({ vulnerabilities_allowed: value });
+      },
+    },
+    vulnerabilityAge: {
+      get() {
+        return this.initRule.vulnerability_age || {};
+      },
+      set(value) {
+        if (!Object.keys(value).length) {
+          this.removeFilterFromRule('vulnerability_age');
+        } else {
+          this.triggerChanged({ vulnerability_age: value });
+        }
       },
     },
     isSeverityFilterSelected() {
       return this.isFilterSelected(this.$options.SEVERITY) || this.severityLevelsToAdd.length > 0;
     },
+    isAgeFilterSelected() {
+      return (
+        this.isFilterSelected(this.$options.AGE) || Object.keys(this.vulnerabilityAge).length > 0
+      );
+    },
     isStatusFilterSelected() {
       return (
         this.isFilterSelected(this.$options.NEWLY_DETECTED) ||
@@ -104,7 +136,17 @@ export default {
       );
     },
     selectedVulnerabilitiesOperator() {
-      return this.vulnerabilitiesAllowed === 0 ? ANY_OPERATOR : MORE_THAN_OPERATOR;
+      return this.vulnerabilitiesAllowed === 0 ? ANY_OPERATOR : GREATER_THAN_OPERATOR;
+    },
+  },
+  watch: {
+    filters: {
+      handler(value) {
+        if (!value[PREVIOUSLY_EXISTING]?.length && this.isFilterSelected(AGE)) {
+          this.removeAgeFilter();
+        }
+      },
+      deep: true,
     },
   },
   methods: {
@@ -135,6 +177,10 @@ export default {
       this.filters[SEVERITY] = null;
       this.emitSeverityFilterChanges();
     },
+    removeAgeFilter() {
+      this.filters[AGE] = null;
+      this.vulnerabilityAge = {};
+    },
     removeStatusFilter(filter) {
       this.filters[filter] = null;
       this.updateCombinedFilters();
@@ -144,11 +190,22 @@ export default {
       this.filters[STATUS] =
         this.filters[NEWLY_DETECTED] && this.filters[PREVIOUSLY_EXISTING] ? [] : null;
     },
+    removeFilterFromRule(filter) {
+      const { [filter]: deletedFilter, ...otherFilters } = this.initRule;
+      this.$emit('changed', otherFilters);
+    },
     handleVulnerabilitiesAllowedOperatorChange(value) {
       if (value === ANY_OPERATOR) {
         this.vulnerabilitiesAllowed = 0;
       }
     },
+    handleVulnerabilityAgeChanges(ageValues) {
+      if (ageValues.operator === ANY_OPERATOR) {
+        this.vulnerabilityAge = {};
+        return;
+      }
+      this.vulnerabilityAge = { ...this.vulnerabilityAge, ...ageValues };
+    },
     setStatus(updatedFilters) {
       this.filters = updatedFilters;
       this.emitStatusFilterChanges();
@@ -165,11 +222,29 @@ export default {
       const states = [...(this.filters[SEVERITY] || [])];
       this.triggerChanged({ severity_levels: states });
     },
+    shouldDisableFilterSelector(filter) {
+      if (filter !== AGE) {
+        return false;
+      }
+
+      return !this.filters[PREVIOUSLY_EXISTING]?.length;
+    },
+    customFilterSelectorTooltip(filter) {
+      switch (filter.value) {
+        case AGE:
+          if (!this.filters[PREVIOUSLY_EXISTING]?.length) {
+            return filter.tooltip[AGE_TOOLTIP_NO_PREVIOUSLY_EXISTING_VULNERABILITY];
+          }
+          return filter.tooltip[AGE_TOOLTIP_MAXIMUM_REACHED];
+        default:
+          return '';
+      }
+    },
   },
   REPORT_TYPES_DEFAULT_KEYS: Object.keys(REPORT_TYPES_DEFAULT),
   REPORT_TYPES_DEFAULT,
   SEVERITY_LEVELS,
-  VULNERABILITIES_ALLOWED_OPERATORS: [ANY_OPERATOR, MORE_THAN_OPERATOR],
+  VULNERABILITIES_ALLOWED_OPERATORS,
   i18n: {
     severityLevels: s__('ScanResultPolicy|severity levels'),
     scanners: s__('ScanResultPolicy|scanners'),
@@ -243,7 +318,7 @@ export default {
           :selected="severityLevelsToAdd"
           class="gl-bg-white!"
           @remove="removeSeverityFilter"
-          @input="triggerChanged({ severity_levels: $event })"
+          @input="severityLevelsToAdd = $event"
         />
 
         <status-filters
@@ -253,10 +328,19 @@ export default {
           @input="setStatus"
         />
 
+        <age-filter
+          v-if="isAgeFilterSelected"
+          :selected="vulnerabilityAge"
+          @remove="removeAgeFilter"
+          @input="handleVulnerabilityAgeChanges"
+        />
+
         <scan-filter-selector
           class="gl-bg-white! gl-w-full"
           :filters="$options.FILTERS"
           :selected="filters"
+          :should-disable-filter="shouldDisableFilterSelector"
+          :custom-filter-tooltip="customFilterSelectorTooltip"
           @select="selectFilter"
         />
       </template>
diff --git a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_filter_selector_spec.js b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_filter_selector_spec.js
index f4c3782a3f79dbe13fe22d53bdd2cbf926abd0e6..570da13c6b85229efceccf97dcc2c428c23fcf6a 100644
--- a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_filter_selector_spec.js
+++ b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_filter_selector_spec.js
@@ -40,6 +40,18 @@ describe('ScanFilterSelector', () => {
       createComponent({ tooltipTitle });
       expect(findListbox().attributes('title')).toBe(tooltipTitle);
     });
+
+    it('can render custom filter tooltip based on callback', () => {
+      const customFilterTooltip = () => 'Custom';
+      createComponent({ filters: FILTERS, selected: { [GOOD_FILTER]: [] }, customFilterTooltip });
+      expect(findDisabledBadge().attributes('title')).toEqual('Custom');
+    });
+
+    it('can set filter disabled on callback', () => {
+      const shouldDisableFilter = () => true;
+      createComponent({ filters: FILTERS, shouldDisableFilter });
+      expect(findDisabledBadge().exists()).toBe(true);
+    });
   });
 
   describe('when filter is unselected', () => {
diff --git a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/number_range_select_spec.js b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/number_range_select_spec.js
index 8fc1f440a6b8ae9649a070614d1cc9cff91ef665..b19a8a603c20e2d3f3846fe429a75051b6d3be01 100644
--- a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/number_range_select_spec.js
+++ b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/number_range_select_spec.js
@@ -3,7 +3,8 @@ import { mountExtended } from 'helpers/vue_test_utils_helper';
 import NumberRangeSelect from 'ee/security_orchestration/components/policy_editor/scan_result_policy/number_range_select.vue';
 import {
   ANY_OPERATOR,
-  MORE_THAN_OPERATOR,
+  GREATER_THAN_OPERATOR,
+  VULNERABILITIES_ALLOWED_OPERATORS,
 } from 'ee/security_orchestration/components/policy_editor/constants';
 
 describe('NumberRangeSelect', () => {
@@ -13,7 +14,7 @@ describe('NumberRangeSelect', () => {
     id: 'test-dropdown',
     value: 0,
     label: 'Test dropdown',
-    operators: [ANY_OPERATOR, MORE_THAN_OPERATOR],
+    operators: VULNERABILITIES_ALLOWED_OPERATORS,
   };
 
   const createComponent = (propsData = {}) => {
@@ -30,9 +31,9 @@ describe('NumberRangeSelect', () => {
 
   describe('initial rendering', () => {
     it.each`
-      selected              | inputExists
-      ${ANY_OPERATOR}       | ${false}
-      ${MORE_THAN_OPERATOR} | ${true}
+      selected                 | inputExists
+      ${ANY_OPERATOR}          | ${false}
+      ${GREATER_THAN_OPERATOR} | ${true}
     `('renders input based on operator with', ({ selected, inputExists }) => {
       createComponent({ selected });
 
@@ -51,17 +52,17 @@ describe('NumberRangeSelect', () => {
         .props('items')
         .map(({ value }) => value);
 
-      expect(itemValues).toEqual([ANY_OPERATOR, MORE_THAN_OPERATOR]);
+      expect(itemValues).toEqual([ANY_OPERATOR, GREATER_THAN_OPERATOR]);
     });
 
     it('can renders only the required operators', () => {
-      createComponent({ operators: [MORE_THAN_OPERATOR] });
+      createComponent({ operators: [{ value: GREATER_THAN_OPERATOR, text: 'greater than' }] });
 
       const itemValues = findOperator()
         .props('items')
         .map(({ value }) => value);
 
-      expect(itemValues).toEqual([MORE_THAN_OPERATOR]);
+      expect(itemValues).toEqual([GREATER_THAN_OPERATOR]);
     });
   });
 
@@ -71,22 +72,22 @@ describe('NumberRangeSelect', () => {
 
       expect(wrapper.emitted('operator-change')).toBeUndefined();
 
-      findOperator().vm.$emit('select', MORE_THAN_OPERATOR);
+      findOperator().vm.$emit('select', GREATER_THAN_OPERATOR);
 
-      expect(wrapper.emitted('operator-change')).toEqual([[MORE_THAN_OPERATOR]]);
+      expect(wrapper.emitted('operator-change')).toEqual([[GREATER_THAN_OPERATOR]]);
     });
 
-    it('shows the number input when changing to MORE_THAN_OPERATOR', async () => {
+    it('shows the number input when changing to GREATER_THAN_OPERATOR', async () => {
       createComponent({ selected: ANY_OPERATOR });
 
-      await findOperator().vm.$emit('select', MORE_THAN_OPERATOR);
+      await findOperator().vm.$emit('select', GREATER_THAN_OPERATOR);
 
       expect(findInput().exists()).toBe(true);
       expect(findInput().element.value).toEqual('0');
     });
 
     it('hides the number input when changing to ANY_OPERATOR', async () => {
-      createComponent({ selected: MORE_THAN_OPERATOR, value: 2 });
+      createComponent({ selected: GREATER_THAN_OPERATOR, value: 2 });
 
       await findOperator().vm.$emit('select', ANY_OPERATOR);
 
@@ -95,10 +96,10 @@ describe('NumberRangeSelect', () => {
   });
 
   it('emits underlying input changes', async () => {
-    createComponent({ selected: MORE_THAN_OPERATOR, value: 2 });
+    createComponent({ selected: GREATER_THAN_OPERATOR, value: 2 });
 
     await findInput().vm.$emit('input', '3');
 
-    expect(wrapper.emitted('input')).toEqual([['3']]);
+    expect(wrapper.emitted('input')).toEqual([[3]]);
   });
 });
diff --git a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/age_filter_spec.js b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/age_filter_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..23fd826e6f547d6cc867338aaa6fa46ab30ebe9e
--- /dev/null
+++ b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/age_filter_spec.js
@@ -0,0 +1,99 @@
+import { GlCollapsibleListbox } from '@gitlab/ui';
+import BaseLayoutComponent from 'ee/security_orchestration/components/policy_editor/scan_result_policy/base_layout/base_layout_component.vue';
+import AgeFilter from 'ee/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/age_filter.vue';
+import NumberRangeSelect from 'ee/security_orchestration/components/policy_editor/scan_result_policy/number_range_select.vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import {
+  ANY_OPERATOR,
+  GREATER_THAN_OPERATOR,
+  LESS_THAN_OPERATOR,
+} from 'ee/security_orchestration/components/policy_editor/constants';
+import {
+  AGE,
+  AGE_INTERVALS,
+} from 'ee/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/constants';
+
+describe('AgeFilter', () => {
+  let wrapper;
+
+  const [{ value: intervalDay }, { value: intervalWeek }] = AGE_INTERVALS;
+
+  const createComponent = (props = {}) => {
+    wrapper = shallowMountExtended(AgeFilter, {
+      propsData: {
+        ...props,
+      },
+      stubs: {
+        BaseLayoutComponent,
+        GlCollapsibleListbox,
+      },
+    });
+  };
+
+  const findBaseLayoutComponent = () => wrapper.findComponent(BaseLayoutComponent);
+  const findNumberRangeSelect = () => wrapper.findComponent(NumberRangeSelect);
+  const findIntervalSelect = () => wrapper.findComponent(GlCollapsibleListbox);
+
+  it('renders operator dropdown', () => {
+    createComponent();
+
+    expect(findNumberRangeSelect().exists()).toBe(true);
+  });
+
+  it('renders initially as ANY_OPERATOR', () => {
+    createComponent();
+
+    expect(findNumberRangeSelect().props('selected')).toEqual(ANY_OPERATOR);
+  });
+
+  it.each([GREATER_THAN_OPERATOR, LESS_THAN_OPERATOR])(
+    'renders the interval listbox for %s operator',
+    (operator) => {
+      createComponent({ selected: { operator } });
+
+      expect(findIntervalSelect().exists()).toEqual(true);
+    },
+  );
+
+  it('emits change when setting an operator', async () => {
+    createComponent();
+
+    await findNumberRangeSelect().vm.$emit('operator-change', GREATER_THAN_OPERATOR);
+
+    expect(wrapper.emitted('input')).toEqual([
+      [{ interval: intervalDay, operator: GREATER_THAN_OPERATOR, value: 0 }],
+    ]);
+  });
+
+  it('emits change when setting a value', async () => {
+    createComponent({ selected: { operator: GREATER_THAN_OPERATOR } });
+
+    await findNumberRangeSelect().vm.$emit('input', 2);
+
+    expect(wrapper.emitted('input')).toEqual([
+      [{ interval: intervalDay, operator: GREATER_THAN_OPERATOR, value: 2 }],
+    ]);
+  });
+
+  it('emits change when setting an interval', async () => {
+    createComponent({ selected: { operator: GREATER_THAN_OPERATOR } });
+
+    await findIntervalSelect().vm.$emit('select', intervalWeek);
+
+    expect(wrapper.emitted('input')).toEqual([
+      [{ interval: intervalWeek, operator: GREATER_THAN_OPERATOR, value: 0 }],
+    ]);
+  });
+
+  describe('remove', () => {
+    it('should emit remove event', async () => {
+      createComponent({
+        selected: { operator: GREATER_THAN_OPERATOR, value: 1, interval: intervalDay },
+      });
+
+      await findBaseLayoutComponent().vm.$emit('remove');
+
+      expect(wrapper.emitted('remove')).toEqual([[AGE]]);
+    });
+  });
+});
diff --git a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/security_scan_rule_builder_spec.js b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/security_scan_rule_builder_spec.js
index 3c75d31c261684340fa1fdc586070ba8e5c0591e..041205a944877e8981cb938742aa6eb5e3315899 100644
--- a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/security_scan_rule_builder_spec.js
+++ b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/security_scan_rule_builder_spec.js
@@ -1,4 +1,5 @@
 import { nextTick } from 'vue';
+import { GlBadge } from '@gitlab/ui';
 import { mountExtended } from 'helpers/vue_test_utils_helper';
 import Api from 'ee/api';
 import SecurityScanRuleBuilder from 'ee/security_orchestration/components/policy_editor/scan_result_policy/security_scan_rule_builder.vue';
@@ -6,6 +7,7 @@ import PolicyRuleMultiSelect from 'ee/security_orchestration/components/policy_r
 import PolicyRuleBranchSelection from 'ee/security_orchestration/components/policy_editor/scan_result_policy/policy_rule_branch_selection.vue';
 import SeverityFilter from 'ee/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/severity_filter.vue';
 import StatusFilter from 'ee/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/status_filter.vue';
+import AgeFilter from 'ee/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/age_filter.vue';
 import ScanTypeSelect from 'ee/security_orchestration/components/policy_editor/scan_result_policy/base_layout/scan_type_select.vue';
 import ScanFilterSelector from 'ee/security_orchestration/components/policy_editor/scan_filter_selector.vue';
 import { NAMESPACE_TYPES } from 'ee/security_orchestration/constants';
@@ -19,10 +21,13 @@ import {
   STATUS,
   NEWLY_DETECTED,
   PREVIOUSLY_EXISTING,
+  AGE,
+  AGE_DAY,
 } from 'ee/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/constants';
 import {
   ANY_OPERATOR,
-  MORE_THAN_OPERATOR,
+  GREATER_THAN_OPERATOR,
+  LESS_THAN_OPERATOR,
 } from 'ee/security_orchestration/components/policy_editor/constants';
 
 describe('SecurityScanRuleBuilder', () => {
@@ -36,7 +41,8 @@ describe('SecurityScanRuleBuilder', () => {
     scanners: ['dast'],
     vulnerabilities_allowed: 1,
     severity_levels: ['high'],
-    vulnerability_states: ['newly_detected'],
+    vulnerability_states: ['detected'],
+    vulnerability_age: { interval: AGE_DAY, value: 1, operator: LESS_THAN_OPERATOR },
   };
 
   const factory = (propsData = {}, provide = {}) => {
@@ -69,6 +75,8 @@ describe('SecurityScanRuleBuilder', () => {
   const findAllStatusFilters = () => wrapper.findAllComponents(StatusFilter);
   const findSeverityFilter = () => wrapper.findComponent(SeverityFilter);
   const findScanTypeSelect = () => wrapper.findComponent(ScanTypeSelect);
+  const findAgeFilter = () => wrapper.findComponent(AgeFilter);
+  const findScanFilterSelectorBadge = () => findScanFilterSelector().findComponent(GlBadge);
 
   beforeEach(() => {
     jest
@@ -119,11 +127,11 @@ describe('SecurityScanRuleBuilder', () => {
   });
 
   describe('vulnerabilities allowed', () => {
-    it('renders MORE_THAN_OPERATOR when initial vulnerabilities_allowed are not zero', async () => {
+    it('renders GREATER_THAN_OPERATOR when initial vulnerabilities_allowed are not zero', async () => {
       factory({ initRule: { ...UPDATED_RULE, vulnerabilities_allowed: 1 } });
       await nextTick();
       expect(findVulnAllowed().exists()).toBe(true);
-      expect(findVulnAllowedOperator().props('selected')).toEqual(MORE_THAN_OPERATOR);
+      expect(findVulnAllowedOperator().props('selected')).toEqual(GREATER_THAN_OPERATOR);
     });
 
     describe('when editing vulnerabilities allowed', () => {
@@ -139,7 +147,7 @@ describe('SecurityScanRuleBuilder', () => {
       `(
         'triggers a changed event (by $currentComponent) with the updated rule',
         async ({ currentComponent, newValue, expected }) => {
-          findVulnAllowedOperator().vm.$emit('select', MORE_THAN_OPERATOR);
+          findVulnAllowedOperator().vm.$emit('select', GREATER_THAN_OPERATOR);
           await nextTick();
           currentComponent().vm.$emit('input', newValue);
           await nextTick();
@@ -149,7 +157,7 @@ describe('SecurityScanRuleBuilder', () => {
       );
 
       it('resets vulnerabilities_allowed to 0 after changing to ANY_OPERATOR', async () => {
-        findVulnAllowedOperator().vm.$emit('select', MORE_THAN_OPERATOR);
+        findVulnAllowedOperator().vm.$emit('select', GREATER_THAN_OPERATOR);
         await nextTick();
         findVulnAllowed().vm.$emit('input', 1);
         await nextTick();
@@ -165,20 +173,26 @@ describe('SecurityScanRuleBuilder', () => {
   });
 
   it.each`
-    currentComponent  | selectedFilter
-    ${findSeverities} | ${SEVERITY}
-    ${findVulnStates} | ${STATUS}
-  `('select different filters', async ({ currentComponent, selectedFilter }) => {
-    factory();
-    await findScanFilterSelector().vm.$emit('select', selectedFilter);
-
-    expect(currentComponent().exists()).toBe(true);
-  });
+    currentComponent  | selectedFilter | existingFilters                           | expectedExists
+    ${findSeverities} | ${SEVERITY}    | ${{}}                                     | ${true}
+    ${findVulnStates} | ${STATUS}      | ${{}}                                     | ${true}
+    ${findAgeFilter}  | ${AGE}         | ${{}}                                     | ${false}
+    ${findAgeFilter}  | ${AGE}         | ${{ vulnerability_states: ['detected'] }} | ${true}
+  `(
+    'select $selectedFilter filter',
+    async ({ currentComponent, selectedFilter, existingFilters, expectedExists }) => {
+      factory({ initRule: { ...securityScanBuildRule(), ...existingFilters } });
+      await findScanFilterSelector().vm.$emit('select', selectedFilter);
+
+      expect(currentComponent().exists()).toBe(expectedExists);
+    },
+  );
 
   it('selects the correct filters', () => {
     factory({ initRule: UPDATED_RULE });
     expect(findScanFilterSelector().props('selected')).toEqual({
-      newly_detected: ['new_needs_triage', 'new_dismissed'],
+      age: { operator: 'less_than', value: 1, interval: 'day' },
+      previously_existing: ['detected'],
       severity: ['high'],
       status: null,
     });
@@ -195,17 +209,19 @@ describe('SecurityScanRuleBuilder', () => {
     expect(statusFilters.at(0).props('filter')).toEqual(NEWLY_DETECTED);
     expect(statusFilters.at(1).props('filter')).toEqual(PREVIOUSLY_EXISTING);
     expect(findScanFilterSelector().props('selected')).toEqual({
-      newly_detected: ['new_needs_triage', 'new_dismissed'],
-      previously_existing: [],
+      age: { operator: 'less_than', value: 1, interval: 'day' },
+      previously_existing: ['detected'],
+      newly_detected: [],
       severity: ['high'],
       status: [],
     });
 
-    await statusFilters.at(1).vm.$emit('remove', PREVIOUSLY_EXISTING);
+    await statusFilters.at(1).vm.$emit('remove', NEWLY_DETECTED);
 
     expect(findScanFilterSelector().props('selected')).toEqual({
-      newly_detected: ['new_needs_triage', 'new_dismissed'],
-      previously_existing: null,
+      age: { operator: 'less_than', value: 1, interval: 'day' },
+      newly_detected: null,
+      previously_existing: ['detected'],
       severity: ['high'],
       status: null,
     });
@@ -216,28 +232,71 @@ describe('SecurityScanRuleBuilder', () => {
 
     expect(findSeverities().exists()).toBe(true);
     expect(findVulnStates().exists()).toBe(true);
+    expect(findAgeFilter().exists()).toBe(true);
   });
 
   it.each`
-    currentComponent      | selectedFilter
-    ${findSeverityFilter} | ${SEVERITY}
-    ${findStatusFilter}   | ${NEWLY_DETECTED}
-    ${findStatusFilter}   | ${PREVIOUSLY_EXISTING}
-  `('removes existing filters', async ({ currentComponent, selectedFilter }) => {
-    factory();
-    await findScanFilterSelector().vm.$emit('select', selectedFilter);
-    expect(currentComponent().exists()).toBe(true);
-
-    await currentComponent().vm.$emit('remove', selectedFilter);
-
-    expect(currentComponent().exists()).toBe(false);
-    expect(wrapper.emitted('changed')).toHaveLength(1);
+    currentComponent      | selectedFilter         | existingFilters
+    ${findSeverityFilter} | ${SEVERITY}            | ${{}}
+    ${findStatusFilter}   | ${NEWLY_DETECTED}      | ${{}}
+    ${findStatusFilter}   | ${PREVIOUSLY_EXISTING} | ${{}}
+    ${findAgeFilter}      | ${AGE}                 | ${{ vulnerability_states: ['detected'] }}
+  `(
+    'removes existing $selectedFilter filter',
+    async ({ currentComponent, selectedFilter, existingFilters }) => {
+      factory({ initRule: { ...securityScanBuildRule(), ...existingFilters } });
+      await findScanFilterSelector().vm.$emit('select', selectedFilter);
+      expect(currentComponent().exists()).toBe(true);
+
+      await currentComponent().vm.$emit('remove', selectedFilter);
+
+      expect(currentComponent().exists()).toBe(false);
+      expect(wrapper.emitted('changed')).toHaveLength(1);
+    },
+  );
+
+  it('handles age filter specific behavior in combination previously existing filter', async () => {
+    factory({ initRule: securityScanBuildRule() });
+
+    expect(findScanFilterSelectorBadge().attributes('title')).toEqual(
+      'Age criteria can only be added for pre-existing vulnerabilities',
+    );
+
+    await findScanFilterSelector().vm.$emit('select', PREVIOUSLY_EXISTING);
+    await findStatusFilter().vm.$emit('input', ['detected']);
+
+    expect(findScanFilterSelectorBadge().exists()).toBe(false);
+
+    await findScanFilterSelector().vm.$emit('select', AGE);
+
+    expect(findScanFilterSelectorBadge().attributes('title')).toEqual(
+      'Only 1 age criteria is allowed',
+    );
+
+    await findAgeFilter().vm.$emit('input', {
+      operator: GREATER_THAN_OPERATOR,
+      value: 1,
+      interval: AGE_DAY,
+    });
+    expect(wrapper.emitted('changed')).toHaveLength(2);
+
+    await findStatusFilter().vm.$emit('remove', PREVIOUSLY_EXISTING);
+
+    expect(findAgeFilter().exists()).toBe(false);
+    expect(findStatusFilter().exists()).toBe(false);
+    expect(wrapper.emitted('changed')).toHaveLength(4);
   });
 
+  const updatedRuleWithoutFilter = (filter) => {
+    const { [filter]: deletedFilter, ...rule } = UPDATED_RULE;
+    return rule;
+  };
+
   it.each`
-    currentComponent      | selectedFilter    | emittedPayload
-    ${findSeverityFilter} | ${SEVERITY}       | ${{ ...UPDATED_RULE, severity_levels: [] }}
-    ${findStatusFilter}   | ${NEWLY_DETECTED} | ${{ ...UPDATED_RULE, vulnerability_states: [] }}
+    currentComponent      | selectedFilter         | emittedPayload
+    ${findSeverityFilter} | ${SEVERITY}            | ${{ ...UPDATED_RULE, severity_levels: [] }}
+    ${findStatusFilter}   | ${PREVIOUSLY_EXISTING} | ${{ ...UPDATED_RULE, vulnerability_states: [] }}
+    ${findAgeFilter}      | ${AGE}                 | ${updatedRuleWithoutFilter('vulnerability_age')}
   `(
     'removes existing filters for saved policies',
     ({ currentComponent, selectedFilter, emittedPayload }) => {
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9b76cc2dee43c76855a9b2bec212707b9c63a956..60d737857141d346ab11f3876df46fe1e3bc38f7 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5863,6 +5863,9 @@ msgstr ""
 msgid "ApprovalRule|Examples: QA, Security."
 msgstr ""
 
+msgid "ApprovalRule|Greater than"
+msgstr ""
+
 msgid "ApprovalRule|Improve your organization's code review with required approvals."
 msgstr ""
 
@@ -5872,6 +5875,9 @@ msgstr ""
 msgid "ApprovalRule|Learn more about merge request approval rules."
 msgstr ""
 
+msgid "ApprovalRule|Less than"
+msgstr ""
+
 msgid "ApprovalRule|More than"
 msgstr ""
 
@@ -5917,9 +5923,21 @@ msgstr ""
 msgid "ApprovalRule|all groups"
 msgstr ""
 
+msgid "ApprovalRule|day(s)"
+msgstr ""
+
+msgid "ApprovalRule|month(s)"
+msgstr ""
+
 msgid "ApprovalRule|project groups"
 msgstr ""
 
+msgid "ApprovalRule|week(s)"
+msgstr ""
+
+msgid "ApprovalRule||year(s)"
+msgstr ""
+
 msgid "ApprovalSettings|Keep approvals"
 msgstr ""
 
@@ -40926,6 +40944,12 @@ msgstr ""
 msgid "ScanResultPolicy|Add new criteria"
 msgstr ""
 
+msgid "ScanResultPolicy|Age criteria can only be added for pre-existing vulnerabilities"
+msgstr ""
+
+msgid "ScanResultPolicy|Age is:"
+msgstr ""
+
 msgid "ScanResultPolicy|Choose an option"
 msgstr ""
 
@@ -40950,10 +40974,7 @@ msgstr ""
 msgid "ScanResultPolicy|Matching"
 msgstr ""
 
-msgid "ScanResultPolicy|Maximum number of severity-criteria is one"
-msgstr ""
-
-msgid "ScanResultPolicy|Maximum number of status-criteria is two"
+msgid "ScanResultPolicy|New age"
 msgstr ""
 
 msgid "ScanResultPolicy|New severity"
@@ -40965,6 +40986,15 @@ msgstr ""
 msgid "ScanResultPolicy|Newly Detected"
 msgstr ""
 
+msgid "ScanResultPolicy|Only 1 age criteria is allowed"
+msgstr ""
+
+msgid "ScanResultPolicy|Only 1 severity is allowed"
+msgstr ""
+
+msgid "ScanResultPolicy|Only 2 status criteria are allowed"
+msgstr ""
+
 msgid "ScanResultPolicy|Pre-existing"
 msgstr ""