> ## Documentation Index
> Fetch the complete documentation index at: https://docs.endorlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Working with finding filters

> Learn how to implement and use finding filters to search, prioritize, and manage security findings across your organization.

export const YamlTable = ({children, data: propData, content}) => {
  const KV_RE = /^([A-Za-z][A-Za-z0-9_()/#\s-]+?):\s*(.+)$/;
  const INLINE_MD_RE = /(\[([^\]]+)\]\(([^)]+)\))|(`([^`]+)`)|(\*\*([^*]+)\*\*)|(\*([^*]+)\*)/g;
  const YES_RE = /^-yes-$/i;
  const NO_RE = /^-no-$/i;
  const LIMITED_RE = /^-(limited|partial)-$/i;
  const NA_RE = /^-(na|none)-$/i;
  const NA2_RE = /^-na2-$/i;
  const SIMPLE_TAG_RE = /(<br\s*\/?>)|(<p\s*\/?>)|(-note-)|(-warning-)/gi;
  const tryParseKV = trimmed => {
    const m = KV_RE.exec(trimmed);
    return m ? {
      key: m[1],
      value: m[2].trim()
    } : null;
  };
  const registerKey = (key, seenKeys, orderedKeys) => {
    if (!seenKeys.has(key)) {
      orderedKeys.push(key);
      seenKeys.add(key);
    }
  };
  const flushEntry = (currentEntry, entries) => {
    if (Object.keys(currentEntry).length > 0) entries.push(currentEntry);
  };
  const parseDashPrefixed = (lines, entries, orderedKeys, seenKeys) => {
    let currentEntry = {};
    let inEntry = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed.startsWith('- ')) {
        if (inEntry) entries.push(currentEntry);
        currentEntry = {};
        inEntry = true;
        const kv = tryParseKV(trimmed.substring(2).trim());
        if (kv) {
          registerKey(kv.key, seenKeys, orderedKeys);
          currentEntry[kv.key] = kv.value;
        }
      } else if (inEntry && trimmed !== '') {
        const kv = tryParseKV(trimmed);
        if (kv) {
          registerKey(kv.key, seenKeys, orderedKeys);
          currentEntry[kv.key] = kv.value;
        }
      }
    }
    flushEntry(currentEntry, entries);
  };
  const parseBlankSeparated = (lines, entries, orderedKeys, seenKeys) => {
    let currentEntry = {};
    let inEntry = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed === '') {
        if (inEntry) {
          flushEntry(currentEntry, entries);
          currentEntry = {};
          inEntry = false;
        }
        continue;
      }
      const kv = tryParseKV(trimmed);
      if (!kv) continue;
      const isNewEntry = !line.startsWith(' ') && !line.startsWith('\t');
      if (isNewEntry && inEntry && Object.keys(currentEntry).length > 0) {
        entries.push(currentEntry);
        currentEntry = {};
      }
      registerKey(kv.key, seenKeys, orderedKeys);
      currentEntry[kv.key] = kv.value;
      inEntry = true;
    }
    flushEntry(currentEntry, entries);
  };
  const normalizeEntries = (entries, orderedKeys) => entries.map(entry => {
    const filled = {};
    for (const key of orderedKeys) filled[key] = entry[key] || '';
    return filled;
  });
  const parseYamlTableContent = contentStr => {
    if (!contentStr) return [];
    const entries = [];
    const orderedKeys = [];
    const seenKeys = new Set();
    const lines = contentStr.split('\n');
    if (lines.some(line => line.trim().startsWith('- '))) {
      parseDashPrefixed(lines, entries, orderedKeys, seenKeys);
    } else {
      parseBlankSeparated(lines, entries, orderedKeys, seenKeys);
    }
    return normalizeEntries(entries, orderedKeys);
  };
  const processText = text => {
    if (!text) return text;
    const parts = [];
    let keyIndex = 0;
    let lastIndex = 0;
    let match;
    while ((match = INLINE_MD_RE.exec(text)) !== null) {
      if (match.index > lastIndex) parts.push(text.slice(lastIndex, match.index));
      if (match[1]) {
        parts.push(<a key={keyIndex++} href={match[3]}>{match[2]}</a>);
      } else if (match[4]) {
        parts.push(<code key={keyIndex++}>{match[5]}</code>);
      } else if (match[6]) {
        parts.push(<strong key={keyIndex++}>{match[7]}</strong>);
      } else if (match[8]) {
        parts.push(<em key={keyIndex++}>{match[9]}</em>);
      }
      lastIndex = match.index + match[0].length;
    }
    if (lastIndex < text.length) parts.push(text.slice(lastIndex));
    if (parts.length === 0) return text;
    const keyRef = {
      current: keyIndex
    };
    return expandHtmlTags(parts, keyRef);
  };
  const processBadges = text => {
    if (!text || typeof text !== 'string') return text;
    if (YES_RE.test(text)) return <span className="yt-badge-yes" role="img" aria-label="Supported" title="Supported">✓</span>;
    if (NO_RE.test(text)) return <span className="yt-badge-no" role="img" aria-label="Not supported" title="Not supported">✗</span>;
    if (LIMITED_RE.test(text)) return <span className="yt-badge-limited" role="img" aria-label="Partially supported" title="Partially supported">◐</span>;
    if (NA_RE.test(text) || NA2_RE.test(text)) return <span className="yt-sr-only" title="Not applicable">Not applicable</span>;
    return processText(text);
  };
  const cellClassName = text => {
    if (!text || typeof text !== 'string') return undefined;
    if (NA_RE.test(text)) return 'yt-cell-na';
    if (NA2_RE.test(text)) return 'yt-cell-na2';
    return undefined;
  };
  const expandSimpleTags = (str, keyRef) => {
    const result = [];
    let last = 0;
    SIMPLE_TAG_RE.lastIndex = 0;
    let m;
    while ((m = SIMPLE_TAG_RE.exec(str)) !== null) {
      if (m.index > last) result.push(str.slice(last, m.index));
      if (m[1]) {
        result.push(<br key={keyRef.current++} />);
      } else if (m[2]) {
        result.push(<br key={keyRef.current++} />, <br key={keyRef.current++} />);
      } else if (m[3]) {
        result.push(<span key={keyRef.current++} className="yt-badge-note" style={{
          fontWeight: 600
        }}>Note: </span>);
      } else if (m[4]) {
        result.push(<span key={keyRef.current++} className="yt-badge-warning" style={{
          fontWeight: 600
        }}>Warning: </span>);
      }
      last = m.index + m[0].length;
    }
    if (last < str.length) result.push(str.slice(last));
    return result;
  };
  const expandHtmlTags = (chunks, keyRef) => {
    const out = [];
    for (const chunk of chunks) {
      if (typeof chunk === 'string') {
        out.push(...expandSimpleTags(chunk, keyRef));
      } else {
        out.push(chunk);
      }
    }
    return out;
  };
  const extractText = node => {
    if (node === null || node === undefined) return '';
    if (typeof node === 'string') return node;
    if (typeof node === 'number') return String(node);
    if (typeof node === 'boolean') return '';
    if (Array.isArray(node)) return node.map(extractText).join('');
    if (node && typeof node === 'object' && node.type) {
      const props = node.props || ({});
      if (typeof props.children === 'string') return props.children;
      if (props.children) return extractText(props.children);
      return '';
    }
    return String(node || '');
  };
  const [mounted, setMounted] = useState(false);
  useEffect(() => {
    setMounted(true);
  }, []);
  const data = useMemo(() => {
    if (propData) return propData;
    if (content && typeof content === 'string') return parseYamlTableContent(content);
    if (!children) return [];
    if (typeof children === 'string') return parseYamlTableContent(children);
    const childrenArray = Array.isArray(children) ? children : [children];
    return parseYamlTableContent(childrenArray.map(extractText).join('').trim());
  }, [children, propData, content]);
  const columns = useMemo(() => {
    if (!data || data.length === 0) return [];
    const firstRow = data[0];
    if (!firstRow || typeof firstRow !== 'object') return [];
    return Object.keys(firstRow);
  }, [data]);
  if (!mounted) return null;
  if (!data || data.length === 0) return null;
  const rowKey = row => columns.map(c => row[c] || '').join('|');
  return <table>
      <thead>
        <tr>
          {columns.map(col => <th key={col}>{col.replaceAll('_', ' ')}</th>)}
        </tr>
      </thead>
      <tbody>
        {data.map(row => <tr key={rowKey(row)}>
            {columns.map(col => <td key={col} className={cellClassName(row[col])}>{processBadges(row[col])}</td>)}
          </tr>)}
      </tbody>
    </table>;
};

Filters enable targeted queries on findings based on attributes such as severity, category, reachability, ecosystem, and policy status.

This guide explains how finding filters work, how to apply and combine them effectively, and provides practical examples to support triage, audit, and reporting workflows across your finding inventory.

## How filters work

Each filter consists of three parts.

* **Key**: The attribute you want to filter (for example, `Severity`, `Category`, and `Ecosystems`).
* **Operator**: The comparison logic (for example, `equals`, `in`, and `contains`).
* **Value**: The target value to evaluate (for example, `Critical` and `Vulnerability`).

Finding filters use standard comparison operators to evaluate criteria. See [Filter operators](/developers-api/rest-api/using-the-rest-api/filters#operators) for detailed information about available operators and their usage when using the API.

## Filter implementation techniques

You can use the following filter types to manage your findings effectively.

* [Basic filters](#search-for-findings-using-basic-filters): Use the filter bar in the Endor Labs user interface to quickly segment findings by common attributes.
* [Advanced filters](#search-for-findings-using-advanced-filters): Use advanced filters to create powerful queries that drill deeper into the dataset to fetch results with a specific context.
* [Saved filters](#saved-filters): Save your custom filters for reuse across projects.

### Search for findings using basic filters

Use the following basic filters to search for information in your findings.

* **Finding Level**: Limit results by finding severity level.
* **Dismissed**: Include or exclude dismissed findings. See [Filter dismissed findings](/inventory-insights/findings/dismiss-findings#filter-dismissed-findings) to learn more about filtering dismissed findings.
* **Category**: Choose from CI/CD, Malware, license risks, operational risks, RSPM, GitHub Actions, SAST, AI models, containers, secrets, security, supply chain, or vulnerability and view related findings.
* **Projects**: Narrow findings by one or more project names.
* **Custom Tags**: Narrow down the list based on custom tags.
* **Attributes**: Narrow down the list based on a range of factors such as:
  * if a patch is available to fix the findings
  * if the vulnerable function is reachable
  * if the dependency is reachable
  * if the dependency originates from a current repository or a current tenant
  * if the dependency is a test dependency
  * if the dependency's discovery type is manifest, phantom, or segment match
  * if the finding originates from itself, direct, or a transitive dependency
  * if the SAST finding is generated by the AI SAST detection agent
  * if AI SAST analysis has classified the SAST finding as a true positive or false positive
  * filter the findings by the **Exploited** tag from **CISA KEV**
  * filter the findings by the **Warn** or **Break the Build** options set in the [action policy](/platform-administration/policies/action-policies#create-an-action-policy-from-template)
* **EPSS Probability**: Choose the Exploit Prediction Scoring System (EPSS) score range.
* **Ecosystems**: Filter by language or ecosystem.
* **Location**: Narrow findings by where they occur (for example, path or location in the repository).
* **Confidence**: Narrow findings by detection confidence.
* **SAST Languages**: Narrow SAST findings by programming language.
* **Container Layers**: Narrow container findings by image layer.
* **Remediation**: Narrow vulnerability findings by fix status.
  * **Endor Patch Available**: Filters findings where an Endor-provided patch is available to fix the vulnerability.
  * **Recommended Upgrade Available**: Filters findings where a recommended version upgrade is available.
* **All Time**: Choose a time range.

### Search for findings using advanced filters

For complex queries, use the advanced filter syntax to combine multiple attributes and apply logical operators. Toggle the **Advanced** option in the filter bar to enter API-style filter expressions directly in the Endor Labs application.

<Tip>
  Search using the advanced filters applies to all the branches of a repository. You can retrieve results from any branch by specifying the relevant context ID or type. See [View findings associated with a project](/inventory-insights/findings#view-findings-associated-with-a-project) for an example of scoping findings to a specific branch.
</Tip>

The **Advanced Filters** use the `GetFinding` [API call](/api-reference/findingservice/getfinding) to fetch results. The following table lists the available attributes for finding filters.

<YamlTable>
  {`


    - Title: Severity Level
    Key: \`spec.level\`
    Description: Filters by severity level, such as Critical, High, Medium, or Low.

    - Title: Dismissed
    Key: \`spec.dismiss\`
    Description: Filters by whether the finding has been dismissed.

    - Title: Categories
    Key: \`spec.finding_categories\`
    Description: Filters by finding category, such as vulnerability, supply chain, license risk, secrets, malware, CI/CD, SAST, containers, or AI models.

    - Title: Attributes
    Key: \`spec.finding_tags\`
    Description: Filters by behavioral and contextual tags, such as fix available, reachable function, reachable dependency, direct, transitive, test, exploited, or unfixable.

    - Title: Ecosystem
    Key: \`spec.ecosystem\`
    Description: Filters by the package ecosystem where the issue was detected, such as npm, Maven, PyPI, or Go.

    - Title: Project UUID
    Key: \`spec.project_uuid\`
    Description: Filters by the project to which the finding belongs. Use to scope results to a single project.

    - Title: Finding Name
    Key: \`meta.name\`
    Description: Filters by the finding's name identifier.

    - Title: Parent UUID
    Key: \`meta.parent_uuid\`
    Description: Filters by the UUID of the parent object: a package version, repository version, or repository.

    - Title: Dependency Package Name
    Key: \`spec.target_dependency_package_name\`
    Description: Filters by the fully qualified name of the affected dependency package version, in the format \`ecosystem://package@version\`.

    - Title: Dependency Name
    Key: \`spec.target_dependency_name\`
    Description: Filters by the name of the affected dependency package, without the ecosystem prefix or version.

    - Title: Dependency Version
    Key: \`spec.target_dependency_version\`
    Description: Filters by the version of the affected dependency package.

    - Title: EPSS Score
    Key: \`spec.finding_metadata.vulnerability.spec.epss_score.probability_score\`
    Description: Filters by EPSS probability score, expressed as a decimal between 0 and 1 (for example, \`0.1\` equals 10%).

    - Title: Endor Patch Available
    Key: \`spec.fixing_patch.endor_patch_available\`
    Description: Filters by whether an Endor Labs-provided patch is available to fix the vulnerability.

    - Title: Approximation
    Key: \`spec.approximation\`
    Description: Filters by whether the affected dependency was discovered through approximate resolution rather than full manifest parsing.

    - Title: Context ID
    Key: \`context.id\`
    Description: Filters by the scan context identifier, such as a branch or tag. Use to retrieve findings from a specific branch or repository version.

    - Title: Last Processed
    Key: \`spec.last_processed\`
    Description: Filters by the timestamp of when the finding was last processed by the system.


    `}
</YamlTable>

#### API filter use cases

The following examples demonstrate how to combine these attributes for common security and compliance workflows.

<AccordionGroup>
  <Accordion title="Prioritize critical and high severity fixable findings">
    Find critical and high-severity findings where a fix is available and the vulnerable function is reachable.

    ```bash theme={null}
    spec.level in ["FINDING_LEVEL_CRITICAL","FINDING_LEVEL_HIGH"] and spec.finding_tags contains ["FINDING_TAGS_FIX_AVAILABLE"] and spec.finding_tags contains ["FINDING_TAGS_REACHABLE_FUNCTION"]
    ```
  </Accordion>

  <Accordion title="Find high-probability exploitable vulnerabilities">
    Identify vulnerability findings with an EPSS score greater than 10% to focus on issues most likely to be exploited in the wild.

    ```bash theme={null}
    spec.finding_categories contains ["FINDING_CATEGORY_VULNERABILITY"] and spec.finding_metadata.vulnerability.spec.epss_score.probability_score > 0.1
    ```
  </Accordion>

  <Accordion title="Scope findings to a specific project">
    Retrieve all active, non-dismissed vulnerability findings for a single project.

    ```bash theme={null}
    spec.finding_categories contains ["FINDING_CATEGORY_VULNERABILITY"] and spec.project_uuid == "<project-uuid>" and spec.dismiss == false
    ```
  </Accordion>

  <Accordion title="Find vulnerabilities in a specific ecosystem">
    List all vulnerability findings from PyPI packages across the namespace.

    ```bash theme={null}
    spec.finding_categories contains ["FINDING_CATEGORY_VULNERABILITY"] and spec.ecosystem in ["ECOSYSTEM_PYPI"]
    ```
  </Accordion>

  <Accordion title="Audit dismissed findings">
    Review all dismissed findings across the namespace to verify exception policies are applied correctly.

    ```bash theme={null}
    spec.dismiss == true
    ```
  </Accordion>

  <Accordion title="Find findings from a specific branch">
    Retrieve findings scoped to a specific branch or repository version by providing the context ID.

    ```bash theme={null}
    context.id == "<context-id>"
    ```
  </Accordion>

  <Accordion title="Identify KEV-listed exploited findings with no fix">
    Find findings tied to CVEs in the CISA KEV database that have no available fix, to assess unmitigatable risk.

    ```bash theme={null}
    spec.finding_tags contains ["FINDING_TAGS_EXPLOITED"] and spec.finding_tags contains ["FINDING_TAGS_UNFIXABLE"]
    ```
  </Accordion>

  <Accordion title="Find supply chain findings for direct dependencies">
    Surface supply chain findings that affect only direct dependencies to prioritize the most actionable issues.

    ```bash theme={null}
    spec.finding_categories contains ["FINDING_CATEGORY_SUPPLY_CHAIN"] and spec.finding_tags contains ["FINDING_TAGS_DIRECT"]
    ```
  </Accordion>
</AccordionGroup>

### Saved filters

Saved filters are customizable filter settings that users can create and reuse across projects in Endor Labs. They improve efficiency by eliminating the need to manually recreate filters. You can save the advanced search filters that you created to fetch curated search results. Saved queries are visible in the drop-down list.

To create a saved filter:

1. Select **Findings** from the left sidebar.
2. Toggle **Advanced** in the top right corner.
3. Type or build your query in the filter bar.
4. Click **Saved Filters** in the top right corner.
5. Click **Save Current**.
6. Enter a name in the **Choose filter name** field.
7. Click **Save**.

<img src="https://mintcdn.com/endorlabs-b4795f4f/2_cgBAK0fOLHo0lP/images/inventory-insights/findings/saved-filter.webp?fit=max&auto=format&n=2_cgBAK0fOLHo0lP&q=85&s=5c417194cbe44cee81285ed17efa8d69" alt="Create saved filter" style={{width: '80%'}} width="1900" height="390" data-path="images/inventory-insights/findings/saved-filter.webp" />

#### Manage saved filters

To delete a saved filter:

1. Select **User menu** > **Settings** from the left sidebar.
2. Select **Saved Filters**.
3. Click the vertical three dots on the right side of the filter you want to delete and click **Delete**.

To edit a saved filter:

1. Select **User menu** > **Settings** from the left sidebar.
2. Select **Saved Filters**.
3. Click **Edit** next to the filter you want to edit.
4. You can update the name, query, and tags.
5. Click **Update** to save the updated changes.

<img src="https://mintcdn.com/endorlabs-b4795f4f/2_cgBAK0fOLHo0lP/images/inventory-insights/findings/update-saved-filter.webp?fit=max&auto=format&n=2_cgBAK0fOLHo0lP&q=85&s=bdaadb350ba87a94fd35aee124ba34ee" alt="Update saved filter" style={{width: '60%'}} width="1486" height="886" data-path="images/inventory-insights/findings/update-saved-filter.webp" />
