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

# Trust & Compliance

> Learn about how Endor Labs handles your data and compliance

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>;
};

At Endor Labs, safeguarding your data is our priority. We believe that transparency builds trust, so we're upfront about what data we handle, how we process it, and most importantly, what we don't do!

The data that Endor Labs handles varies depending on the product you use, your deployment model, and your configured integrations. As Endor Labs continues to innovate, our data handling practices may change with the introduction of new capabilities, products, or changes to existing functionality over time.

## Deployment models

Endor Labs has two primary deployment models:

* **Hybrid scanning**: Customers can run Endor Labs CLI in their CI/CD environment. While using a hybrid scanning strategy, analysis of your software is performed inside your environment on the compute environment where a scan is running. Metadata associated with the scan types you run are then sent back to Endor Labs cloud environment to your hosted tenant and stored for analysis.
* **Cloud scanning:**: Customers can integrate and deploy Endor Labs using a cloud-based scanning solution, such as the Endor Labs GitHub App. When using this approach, Endor Labs is granted access to your source code and SCM to enable continuous scanning of changes to your software and SCM configuration. During the scan, repositories are cloned into a dedicated ephemeral container, which operates in a secure and isolated environment. Once the scan is complete, the container and all associated data are deleted. The source code is removed after each scan, and only the metadata related to the scan is retained.

## Data common across products

* **Finding data**: Endor Labs stores information required to report on findings to on their customers security or operational risks such as vulnerability or misconfiguration information.
* **Finding location**: Endor Labs stores information required to identify the location of a finding so that corrective action may take place. This includes information such as the repository name, file name, and issue location.
* **Integration information**: Endor Labs stores information required for operation of external integrations with third parties such as Jira. These include configuration and authentication information.
* **Identity information**: Endor Labs stores identity claim information required to access and use the platform, such as email address and group claims sent by an external identity provider.
* **User telemetry**: Endor Labs stores information about your usage of Endor Labs such as platform usage and scan configuration.

## Product: Endor Open Source

Endor Labs stores data about your repository, software packages, container images, and your software manifest files to report findings on known issues and re-analyze them as security intelligence feeds are continuously updated.

### [Data in SCA scans](/scan/sca)

* Analyzes repositories for manifest files and code to determine the resolved dependencies.
* Looks in dependency caches or environments to determine the dependencies resolved by a package manager.
* Assesses the source code or final artifact to create and identify a call graph for the specific version of your code in the case of SCA scanning.

### [Data in container scanning](/scan/containers)

Accesses the image on the host operating system and reviews the image digests and layers, assesses the dependencies installed at each layer and aggregates risk information based on those dependencies.

<YamlTable>
  {`

    - Data_Element: Package/Image metadata
    Examples: Package version call graph, dependency graph, package name, version, dependency metadata
    - Data_Element: Repository metadata
    Examples: Repository name, Git reference, Git SHA
    - Data_Element: Finding information
    Examples: Security and operational risk

    `}
</YamlTable>

## [Product: Endor SBOM Hub](/inventory-insights/sbom)

Leverages the raw data of the SBOMs you provide for tracking. This includes the complete SBOM and metadata about risks discovered by Endor Labs.

## Product: Endor Code

Endor Code consists of the following scans:

* Secrets
* SAST
* AI models

### [Data in secret scanning](/scan/secrets)

* Endor Labs scans an existing ref or all Git logs for potential secret leaks. If an issue is identified, it will store issue location information including the Git reference, file and line numbers from which a secret is found.
* Secret validation occurs as part of a local scan, during which the discovered secret is used to validate against an external validation endpoint or API. This occurs in the environment from which the scan for secrets is run.

### [Data in SAST scanning](/scan/sast)

During SAST scans, Endor Labs stores a snippet of vulnerable code for ease of identification of an issue.

<YamlTable>
  {`


    - Data_Element: File Location and Line numbers
    Examples: Line numbers and files where a finding is discovered
    - Data_Element: Repository Metadata
    Examples: Repository Name, Git Reference, Git SHA
    - Data_Element: Finding Information
    Examples: Information about the specific secret leak or code weakness

    `}
</YamlTable>

### [Data in AI Models](/scan/ai-models)

While scanning for AI models, Endor Labs may send snippets of code to Azure OpenAI Service to identify the model name in use.

## Endor Labs Policy and Transparency Information

For additional information, see the following relevant pages on how Endor Labs handles your data:

* [Endor Labs Sub-processors](https://www.endorlabs.com/legal/endor-labs-subprocessors)
* [Endor Labs Trust Center](https://app.drata.com/trust/9cc7b443-0c38-11ee-865f-029d78a187d9)
* [Endor Labs Privacy Policy](https://www.endorlabs.com/legal/privacy-policy)
* [Endor Labs Data Processing (DPA)](https://www.endorlabs.com/legal/endor-labs-data-processing)
* [Endor Labs Product Terms of Use](https://www.endorlabs.com/legal/terms-of-use)
