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

# Import SBOMs

> Learn more about software transparency and the role of importing SBOMs in 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>;
};

SBOMs from vendors describe the components, licenses, and related metadata inside software you procure. Import them into Endor Labs so you can store, search, and analyze that composition next to the applications your organization builds.

Endor Labs' SBOM Hub is a central location to store, search, and monitor SBOMs from vendors. When you import a file, Endor Labs ingests, parses, and analyzes it and keeps versions so you can see how vendor composition changes over time. For SBOM program design and day-to-day operations, see [Key questions for your SBOM program](https://www.endorlabs.com/blog/sbom-vex-security-program-operations).

You can use [finding policies](/platform-administration/policies/finding-policies) to identify vulnerabilities, unmaintained open source software, license risks, and outdated dependencies in the SBOMs provided to you by your third-party software vendors.

## Import an SBOM to Endor Labs

Import your project's SBOM into the Endor Labs application to discover vulnerabilities and view findings.

You can use the following methods to import SBOMs:

* [**Import SBOMs through the Endor Labs UI**](#import-sboms-through-the-endor-labs-ui) to upload your SBOMs and access vulnerability and dependency insights.
* [**Import SBOMs through the Endor Labs CLI**](#import-sboms-through-the-endor-labs-cli) to ingest SBOMs and access vulnerability and dependency insights directly from your command line.

### Import SBOMs through the Endor Labs UI

To import SBOMs through the Endor Labs UI and view vulnerability and dependency insights:

1. Select **SBOM Hub** from the left sidebar.
2. Select **Import SBOM** in the top right-hand corner.
3. Choose **Upload File** and select the type of SBOM you would like to upload, either in XML or json format.
   * Use **CycloneDX** if your vendor has provided you with a [CycloneDX format SBOM](https://cyclonedx.org/).
   * Use **SPDX** if your vendor has provided you with a [SPDX format SBOM](https://spdx.dev/).
4. Select **Browse** to upload your SBOM from your workstation or drag the SBOM into the Endor Labs user interface.

Once you have imported your SBOM to Endor Labs, Endor Labs will schedule a scan in the background for the SBOM within the next few hours.

### Import SBOMs through the Endor Labs CLI

Import an SBOM using the CLI to trigger an instant scan and immediately view vulnerabilities and dependency insights with the following command:

<Tabs>
  <Tab title="CycloneDX Format">
    ```bash theme={null}
    endorctl sbom import --sbom-file-path=/path/to/your/sbom.json
    ```
  </Tab>

  <Tab title="SPDX Format">
    ```bash theme={null}
    endorctl sbom import --format=spdx --sbom-file-path=/path/to/your/sbom.json
    ```
  </Tab>
</Tabs>

See the [SBOM import command for endorctl](/developers-api/cli/commands/sbom/import) for more information.

## Manage SBOMs

You can manage SBOMs by deleting unwanted files and editing tags for consistent search and filtering.

### Delete an SBOM

1. Select **SBOM Hub** from the left sidebar.
2. Select one or more SBOMs to remove.
3. Select the vertical three dots on the row, then select **Delete SBOM**.

### Edit tags for an SBOM

Tags are keywords you attach to SBOMs to group and filter them, for example, by vendor or data classification. Tags can have a maximum of 63 characters and can contain letters A-Z, numbers (0-9), or any of (=@\_.-) special characters.

To edit tags for SBOMs:

1. Select **SBOM Hub** from the left sidebar.
2. Select one or more SBOMs.
3. Click **Edit Tags** in the top right-hand corner.
4. Add, change, or remove tags, then save.

#### Tagging strategies for SBOMs

To improve your team's ability to search and manage SBOMs, you can tag them as they are received. Tagging SBOMs helps your team understand the applications, vendors, and their importance to your business.

<YamlTable>
  {`


    - Use_Case: Data Classification
    Rationale: Understand the kind of data a vendor or vendor application handles for you.
    Example_Tags: \`Classification_Restricted\`, \`Classification_HighlySensitive\`, \`Classification_Public\`
    - Use_Case: Vendor Name
    Rationale: Some SBOMs may lack vendor information. Be sure to label your SBOMs with vendor names for better vendor management.
    Example_Tags: \`Vendor_RedHat\`
    - Use_Case: Vendor Criticality
    Rationale: Tag your SBOMs according to your internal vendor tier strategy or if the vendor is considered critical. This will streamline regular SBOM reviews.
    Example_Tags: \`Critical_Vendor\`, \`Tier1_Vendor\`


    `}
</YamlTable>
