> ## 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.

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.endorlabs.com/feedback

```json
{
  "path": "/integrations/data-exporters/export-to-ghas/index",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# Export findings to GitHub Advanced Security

> Learn how to export findings to GitHub Advanced Security.

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 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>;
    return processText(text);
  };
  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}>{processBadges(row[col])}</td>)}
          </tr>)}
      </tbody>
    </table>;
};

You can export the findings generated by Endor Labs to GitHub Advanced Security so that you can view the findings in the GitHub. Endor Labs exports the findings in the SARIF format and uploads them to GitHub. You can view the findings under **Security** > **Vulnerability Alerts** > **Code Scanning** in GitHub.

<Warning>
  GitHub has multiple limitations for SARIF files, so you may not be able to experience the full benefits on Endor Labs. For example, GitHub limits the number of results in a SARIF file. It allows a maximum of 25000 results per file but displays the first 5000 results ranked by severity. Refer to [GitHub SARIF support for code scanning](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning#validating-your-sarif-file) for the complete list of SARIF file limitations in GitHub Advanced Security.
</Warning>

## Prerequisites

Ensure that you meet the following prerequisites before exporting findings to GitHub Advanced Security:

* Endor Labs GitHub App (Pro) installed in your GitHub repository. See [Deploy Endor Labs GitHub App (Pro)](/setup-deployment/scm-integrations/github-app-pro) for more information.
* Turn on code scanning in your GitHub repository. Refer to [Enabling code scanning](https://docs.github.com/en/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning) for more information.
* Download and install endorctl. See [Install endorctl](/introduction/getting-started) for more information.

## Create a GHAS SARIF exporter

GHAS SARIF exporter allows you to export the findings generated by Endor Labs in the SARIF format. See [Understanding SARIF files](/scan/sca/scanning-strategies#understand-sarif-files) for more information on the SARIF format and Endor-specific extensions.

You can create a GHAS SARIF exporter using the Endor Labs API.

Run the following command to create a GHAS SARIF exporter.

```bash expandable theme={null}
endorctl api create -n <namespace> -r Exporter -d '{
  "meta": {
    "name": "<exporter-name>"
  },
  "tenant_meta": {
    "namespace": "<namespace>"
  },
  "spec": {
    "exporter_type": "EXPORTER_TYPE_GHAS",
    "message_type_configs": [
      {
        "message_type": "MESSAGE_TYPE_FINDING",
        "message_export_format": "MESSAGE_EXPORT_FORMAT_SARIF"
      }
    ]
  },
  "propagate": true
}'
```

For example, to create a GHAS SARIF exporter named `ghas-exporter` in the namespace `doe.deer`, run the following command.

```bash expandable theme={null}
endorctl api create -n doe.deer -r Exporter -d '{
  "meta": {
    "name": "ghas-exporter"
  },
  "tenant_meta": {
    "namespace": "doe.deer"
  },
  "spec": {
    "exporter_type": "EXPORTER_TYPE_GHAS",
    "message_type_configs": [
      {
        "message_type": "MESSAGE_TYPE_FINDING",
        "message_export_format": "MESSAGE_EXPORT_FORMAT_SARIF"
      }
    ]
  },
  "propagate": true
}'
```

## Configure scan profile and project to use the GHAS SARIF exporter

You can configure the scan profile to use the GHAS SARIF exporter and associate it with your project. You can also set the scan profile as the default scan profile so that all the projects in the namespace use the scan profile by default. See [Scan profiles](/scan/scan-profiles) for more information.

### Configure the scan profile

Ensure that you select the GHAS SARIF exporter in the **Export** section of the scan profile.

1. Select **Settings** from the left sidebar.
2. Select **Scan Profiles**.
3. Select the scan profile you want to configure and click **Edit Scan Profile**.
4. Select the GHAS SARIF exporter under **Exporters** and click **Save Scan Profile**.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/TVudXwCdR2gZhdvv/images/integrations/data-exporters/scan-profile-ghas-exporter.webp?fit=max&auto=format&n=TVudXwCdR2gZhdvv&q=85&s=1d09dc118cabbf5b15eb82b63ef317a4" alt="Scan profile" width="1292" height="380" data-path="images/integrations/data-exporters/scan-profile-ghas-exporter.webp" />

### Configure the project to use the scan profile

Ensure that you choose the scan profile with the GHAS SARIF exporter for the project.

1. Go to the **Projects** page and select the project you want to configure.
2. Select **Settings** and select the scan profile you want to use under **Scan Profile**.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/dHzwUrp_QbpzV9uv/images/integrations/data-exporters/scan-profile-for-project.webp?fit=max&auto=format&n=dHzwUrp_QbpzV9uv&q=85&s=b16268dea0b24fb6145095fc600a97c8" alt="Scan profile for project" width="1934" height="606" data-path="images/integrations/data-exporters/scan-profile-for-project.webp" />

## Scan projects to use the GHAS SARIF exporter

After the configuration is complete, your subsequent scans will export the findings in the SARIF format and upload them to GitHub. You can use the rescan ability to scan the project immediately instead of waiting for the next scheduled scan. See [Rescan projects](/setup-deployment/scm-integrations/github-app-pro/re-scan-projects) for more information.

If you have enabled pull request scans in your GitHub App, the GHAS SARIF exporter exports the findings for each pull request.

## View findings in GitHub

1. Navigate to your GitHub repository.

2. Select **Security**.

3. Select **Code scanning** under **Vulnerability Alerts**.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/dHzwUrp_QbpzV9uv/images/integrations/data-exporters/view-findings-in-ghas.webp?fit=max&auto=format&n=dHzwUrp_QbpzV9uv&q=85&s=79556a3c87dec9f8f0bc9affa163304e" alt="View findings in GitHub" width="2318" height="1338" data-path="images/integrations/data-exporters/view-findings-in-ghas.webp" />

   You can use the search bar to filter the findings. You can also view findings for a specific branch and other filter criteria. You can also view the findings specific to a pull request if you have enabled pull request scans. You can filter the findings by the pull request number and view findings associated with the pull request. You can select a finding and view the commit history behind the finding.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/dHzwUrp_QbpzV9uv/images/integrations/data-exporters/filter-findings-in-ghas.webp?fit=max&auto=format&n=dHzwUrp_QbpzV9uv&q=85&s=df2c1de9f2411d25913841c8cac89258" alt="Filter findings in GitHub" width="1616" height="862" data-path="images/integrations/data-exporters/filter-findings-in-ghas.webp" />

4. Select **Campaigns** to view and create security campaigns that coordinate remediation efforts across multiple repositories. See [GitHub security campaign](/best-practices/github-security-campaign) for more information.

### Filter findings by tags in GitHub

After you export findings to GHAS, Endor Labs includes finding tags and categories as searchable tags in the SARIF output. These tags appear in the GitHub code scanning interface, and you can filter and identify specific types of findings.

Endor Labs exports the following types of tags to GHAS:

* **Finding tags**: System-defined attributes such as `REACHABLE_FUNCTION`, `FIX_AVAILABLE`, `EXPLOITED, DIRECT`, `TRANSITIVE`, and others. See [Finding tags](/developers-api/rest-api/using-the-rest-api/data-model/resource-kinds#finding-tags) for the complete list.
* **Finding categories**: Categories such as `SCA`, `SAST`, `VULNERABILITY`, `SECRETS`, `CONTAINER`, `CICD`, `GHACTIONS`, `LICENSE_RISK`, `MALWARE`, `OPERATIONAL`, `SCPM`, `SECURITY`, `SUPPLY_CHAIN`, and `AI_MODELS`. See [Finding categories](/developers-api/rest-api/using-the-rest-api/data-model/resource-kinds#finding-categories) for the complete list.

You can use the search bar to filter findings by tags. Use the tag: prefix followed by the tag name to search for specific Endor Labs tags.

<YamlTable>
  {`


    - Available_Filter: \`REACHABLE_FUNCTION\`
    Description: Show findings with reachable vulnerable functions.
    - Available_Filter: \`FIX_AVAILABLE\`
    Description: Show findings where a fix is available.
    - Available_Filter: \`EXPLOITED\`
    Description: Show findings for actively exploited vulnerabilities (KEV).
    - Available_Filter: \`DIRECT\`
    Description: Show findings in direct dependencies.
    - Available_Filter: \`TRANSITIVE\`
    Description: Show findings in transitive dependencies.
    - Available_Filter: \`CI_BLOCKER\`
    Description: Show findings marked as blockers by action policies.
    - Available_Filter: \`SCA\`
    Description: Show Software Composition Analysis findings.
    - Available_Filter: \`SAST\`
    Description: Show SAST findings.
    - Available_Filter: \`SECRETS\`
    Description: Show exposed secrets findings.
    - Available_Filter: \`VULNERABILITY\`
    Description: Show vulnerability findings.
    - Available_Filter: \`CONTAINER\`
    Description: Show container findings.
    - Available_Filter: \`CICD\`
    Description: Show CI/CD pipeline findings.
    - Available_Filter: \`GHACTIONS\`
    Description: Show GitHub Actions findings.

    `}
</YamlTable>

You can combine multiple filters to narrow down your results. For example, to find reachable vulnerabilities with available fixes:

```
tag:REACHABLE_FUNCTION tag:FIX_AVAILABLE
```

<img src="https://mintcdn.com/endorlabs-b4795f4f/dHzwUrp_QbpzV9uv/images/integrations/data-exporters/filter-findings-by-tags-in-ghas.webp?fit=max&auto=format&n=dHzwUrp_QbpzV9uv&q=85&s=356fb77cb11f791cb4cbefedf0156625" alt="Filter findings by tags in GitHub" style={{width: '80%'}} width="2304" height="1428" data-path="images/integrations/data-exporters/filter-findings-by-tags-in-ghas.webp" />

### Filter findings exported to GitHub

You can control which findings you export to GHAS by using action policies. Only findings from projects within the scope of your configured action policies reach GitHub Advanced Security.

To filter findings using action policies:

1. Create an [action policy](/platform-administration/policies/action-policies) that defines the criteria for findings you want to export, or use an existing action policy.
2. Assign specific projects to the scope of the action policy you want to use.
3. Run the following command to create a GHAS SARIF exporter that exports only findings from projects in the scope of your action policies.

   <Note>
     Use `MESSAGE_TYPE_ADMISSION_POLICY_FINDING` as the `message_type` to filter findings based on your action policies.
   </Note>

   ```bash theme={null}
   endorctl api create -n <namespace> -r Exporter -d '{
      "meta": {
        "name": "<exporter-name>"
      },
      "tenant_meta": {
        "namespace": "<namespace>"
      },
      "spec": {
        "exporter_type": "EXPORTER_TYPE_GHAS",
        "message_type_configs": [
          {
            "message_type": "MESSAGE_TYPE_ADMISSION_POLICY_FINDING",
            "message_export_format": "MESSAGE_EXPORT_FORMAT_SARIF"
          }
        ]
      },
      "propagate": true
    }'
   ```
