> ## 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": "/secure-ai-coding/ai-model-discovery/ai-model-findings/index",
  "feedback": "Description of the issue"
}
```

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

</AgentInstructions>

# AI model findings

> Find and manage priority issues related to AI models.

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

Endor Labs can detect AI models and list them as dependencies when you run a scan with the `--ai-models` flag. You can view the detected AI models in the **Inventory** > **AI Models** section of the [Endor Labs user interface](#view-ai-models-in-your-namespace).

You can define custom policies to flag the usage of specific AI providers, specific AI models, or models with low-quality scores so that their usage raises findings as part of your scan. Endor Labs provides [AI model policy templates](/secure-ai-coding/ai-model-discovery/ai-model-policies) that you can use to create finding policies that are tailored to your organization's needs. You can [view these findings](#view-ai-model-findings-in-your-namespace) in **Code Dependencies** > **AI Models** on the **Findings** page.

Run the following command to detect AI models in your repository.

```bash theme={null}
endorctl scan --ai-models
```

When you run a scan with the `--ai-models` option, Endor Labs downloads Opengrep and runs Opengrep to detect AI models.

Endor Labs detects AI models using pattern matching and can use LLM processing to improve detection accuracy. LLM processing is disabled by default.

See [Supported AI model providers](#ai-model-detection) for the list of external AI models detected by Endor Labs. Only Hugging Face models are scored, as they are open source and provide extensive public metadata. Models from all other providers are detected but not scored due to limited metadata.

## Enable LLM processing for AI model detection

To enable LLM processing in Endor Labs:

1. Select **Settings** from the left sidebar.
2. Select **System settings** > **Data privacy**.
3. Turn on **Code Segment Embeddings and LLM Processing**.

See [Configure system settings](/platform-administration/configure-system-settings) for more information.

<Note>
  **Privacy**

  When you enable LLM processing, Endor Labs uses a private and isolated Azure OpenAI Service deployment, which is not accessible from the public Internet and cannot be used for LLM training.
</Note>

To generate AI model findings:

1. Configure [finding policy](/secure-ai-coding/ai-model-discovery/ai-model-policies) to detect AI models with low scores and enforce organizational restrictions on specific AI models or model providers.

2. [View AI Model findings](#view-ai-model-findings-in-your-namespace).

3. To disable AI model discovery, set `ENDOR_SCAN_AI_MODELS=false` in your [scan profile](/scan/scan-profiles/configure-scanprofile-ui#configure-general-scan-profile-settings).

## AI model detection

The following table lists the AI model providers currently supported by Endor Labs for model detection. For each provider, the table includes supported programming languages, if model scoring is available, and a reference link to the provider's API documentation.

<YamlTable>
  {`


    - AI_model: HuggingFace
    Supported_languages: Python
    Endor_score: -yes-
    Reference: <https://huggingface.co/docs>

    - AI_model: OpenAI
    Supported_languages: Python, JavaScript, Java (beta), Go (beta), C#
    Endor_score: -no-
    Reference: <https://platform.openai.com/docs/libraries>

    - AI_model: Anthropic
    Supported_languages: Python, TypeScript, JavaScript, Java (alpha), Go (alpha)
    Endor_score: -no-
    Reference: <https://docs.anthropic.com/en/api/client-sdks>

    - AI_model: Google
    Supported_languages: Python, JavaScript, TypeScript, Go
    Endor_score: -no-
    Reference: <https://ai.google.dev/gemini-api/docs/sdks>

    - AI_model: AWS
    Supported_languages: Python, JavaScript, Java, Go, C#, PHP, Ruby
    Endor_score: -no-
    Reference: <https://docs.aws.amazon.com/bedrock/latest/APIReference/welcome.html#sdk>

    - AI_model: Perplexity
    Supported_languages: Python
    Endor_score: -no-
    Reference: <https://docs.perplexity.ai/api-reference/chat-completions-post>

    - AI_model: DeepSeek
    Supported_languages: Python, JavaScript, Go, PHP, Ruby
    Endor_score: -no-
    Reference: <https://api-docs.deepseek.com/api/deepseek-api>

    - AI_model: Azure OpenAI
    Supported_languages: C#, Go, Java, Python
    Endor_score: -no-
    Reference: <https://learn.microsoft.com/en-us/azure/ai-foundry/>


    `}
</YamlTable>

## AI model discovery through monitoring scans

By default, AI models are discovered during SCA scans run through GitHub App, Bitbucket App, Azure DevOps App, and GitLab App. You can view the reported AI models under **Inventory** > **AI Models** in the left sidebar.

To disable AI model discovery, set `ENDOR_SCAN_AI_MODELS=false` as an additional environment variable in the [scan profile](/scan/scan-profiles/configure-scanprofile-ui#configure-general-scan-profile-settings) and assign the scan profile to the project.

## Detect AI models

Configure finding policies and perform an endorctl scan to detect AI models in your repositories and review the findings.

1. Configure [finding policy](/secure-ai-coding/ai-model-discovery/ai-model-policies) to detect AI models with low scores and enforce organizational restrictions on specific AI models or model providers.

2. Run an endorctl scan with the following command.

   ```bash theme={null}
   endorctl scan --ai-models --dependencies
   ```

## View AI models in your namespace

To view all AI models that are used in your namespace:

1. Select **Inventory** from the left sidebar, then **AI Models**.
   <img src="https://mintcdn.com/endorlabs-b4795f4f/dHzwUrp_QbpzV9uv/images/introduction/endor-labs-ui/endor-labs-ui/ai-inventory-ui.webp?fit=max&auto=format&n=dHzwUrp_QbpzV9uv&q=85&s=c18a2f893f54fbc19ef903f262132e75" alt="AI Models under Inventory" width="1698" height="1408" data-path="images/introduction/endor-labs-ui/endor-labs-ui/ai-inventory-ui.webp" />
2. Use the search bar to look for any specific models.
3. Select a model, and click to see its details.
4. You can also navigate to **Findings** and choose **AI Models** to view findings.
   <img src="https://mintcdn.com/endorlabs-b4795f4f/266r_aE9BWR2B51C/images/secure-ai-coding/ai-model-discovery/aimodel-global-finding.webp?fit=max&auto=format&n=266r_aE9BWR2B51C&q=85&s=f3ad0267f2952bdb603c153aee50c7d6" alt="AI model findings" width="1971" height="1133" data-path="images/secure-ai-coding/ai-model-discovery/aimodel-global-finding.webp" />

## View AI models in a project

To view AI models that are used in a specific project:

1. Select **Projects** from the left sidebar and select a project.
2. Select **Inventory** and click **AI Models** under **Dependencies** to view findings.
   <img src="https://mintcdn.com/endorlabs-b4795f4f/266r_aE9BWR2B51C/images/secure-ai-coding/ai-model-discovery/aimodel-dependency.webp?fit=max&auto=format&n=266r_aE9BWR2B51C&q=85&s=e5914873ecd1cc7076aed053a13cf988" alt="AI model dependencies" width="1920" height="918" data-path="images/secure-ai-coding/ai-model-discovery/aimodel-dependency.webp" />

## View AI model findings in your namespace

To view all AI model findings in your namespace:

1. Select **Findings** from the left sidebar.
2. Select **AI Models** from the **Findings** page.
   <img src="https://mintcdn.com/endorlabs-b4795f4f/266r_aE9BWR2B51C/images/secure-ai-coding/ai-model-discovery/aimodel-global-finding.webp?fit=max&auto=format&n=266r_aE9BWR2B51C&q=85&s=f3ad0267f2952bdb603c153aee50c7d6" alt="AI model findings" width="1971" height="1133" data-path="images/secure-ai-coding/ai-model-discovery/aimodel-global-finding.webp" />
