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

# Set up AI security code review with endorctl

> Use endorctl to run AI security code review with GitHub environment variables.

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

You can use AI security code review with endorctl and GitHub environment variables without requiring the GitHub App. This approach allows you to integrate AI security code review into your local development workflows. You can use this approach only if you have GitHub as your source control management system.

Complete the following tasks to set up AI security code review with endorctl:

* [Complete the prerequisites to use AI security code review with endorctl.](#prerequisites-to-use-ai-security-code-review-with-endorctl)
* [Set up the environment variables required to run endorctl for AI security code review.](#set-up-environment-variables)
* [Install and authenticate endorctl, build your project, and run a scan.](/introduction/getting-started) Scanning the repository creates the project in Endor Labs that you can use to configure the scan profile.
* Configure a [scan profile](/secure-ai-coding/ai-security-review/ai-security-review-settings#configure-scan-profile-for-ai-security-code-review) for AI security code review.
* Enable the [security review finding policy](/secure-ai-coding/ai-security-review/ai-security-review-settings#enable-finding-policy-for-ai-security-code-review).
* Configure an [action policy](/secure-ai-coding/ai-security-review/ai-security-review-settings#configure-action-policy-for-pull-request-comments) if you want to get comments on your GitHub pull request with the details of the AI security code review.
* [Run scans for AI security code review.](#pull-request-scan-with-ai-security-code-review)
* [View results of the AI security code review.](/secure-ai-coding/ai-security-review/ai-security-review-results)

## Prerequisites to use AI security code review with endorctl

Ensure that the following prerequisites are met before using AI security code review with endorctl:

* An active Endor Labs subscription with Endor Code Pro license.
* Access to configure scan profiles and policies
* Code Segment Embeddings and LLM Processing enabled in Data Privacy settings
* A GitHub token with appropriate permissions.

### Enable Code Segment Embeddings and LLM Processing

Perform the following steps to enable code segment embeddings and LLM processing:

1. Select **User menu** > **Settings** from the left sidebar.
2. Select **SYSTEM SETTINGS** > **Data Privacy**.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/Uc3T4mPoaFbRUPbf/images/secure-ai-coding/ai-security-review/enable-embeddings.webp?fit=max&auto=format&n=Uc3T4mPoaFbRUPbf&q=85&s=d6ce0f43e8c800359579d4d9a0a6b819" alt="Enable Code Segment Embeddings and LLM Processing" width="2278" height="594" data-path="images/secure-ai-coding/ai-security-review/enable-embeddings.webp" />
3. Select **Code Segment Embeddings and LLM Processing**.
4. Click **Save Data Privacy Settings**.

### Verify license and feature access

Perform the following steps to verify your license and feature access:

1. Select **User menu** > **Settings** from the left sidebar.
2. Select **License**.
3. Verify that you have **Security Review** in **Products** and **Features**.

## Set up environment variables

Configure the following environment variables for GitHub integration:

```bash theme={null}
# Required: SCM token with repo access
export ENDOR_SCAN_SCM_TOKEN=<your-scm-token>

# Required: Endor Labs authentication
export ENDOR_API_CREDENTIALS_KEY=<your-api-key>
export ENDOR_API_CREDENTIALS_SECRET=<your-api-secret>
export ENDOR_NAMESPACE=<your-namespace>
```

## Pull request scan with AI security code review

To scan a pull request with AI security code review, fetch the pull request branch locally and checkout the branch.

```shell theme={null}
git fetch origin pull/<PR_NUMBER>/head:pr-<PR_NUMBER>
git checkout pr-<PR_NUMBER>
```

For example, to scan pull request 12, you need to run the following commands.

```shell theme={null}
git fetch origin pull/12/head:pr-12
git checkout pr-12
```

After you have fetched and checked out the pull request branch, you can run the following command to scan the pull request with AI security code review.

```shell theme={null}
endorctl scan \
  -n <namespace> \
  --pr \
  --security-review \
  --scm-pr-id <PR_NUMBER> \
  --scm-token $ENDOR_SCAN_SCM_TOKEN \
  --enable-pr-comments
```

The following table describes the flags used in the command.

<YamlTable>
  {`


    - Flag: \`-n <namespace>\`
    Mandatory: -no-
    Description: Your Endor Labs namespace. If you do not specify a namespace, the command uses the default namespace.

    - Flag: \`--pr\`
    Mandatory: -yes-
    Description: Indicates that this is a pull request scan.

    - Flag: \`--security-review\`
    Mandatory: -yes-
    Description: Enables AI security code review.

    - Flag: \`--scm-pr-id <PR_NUMBER>\`
    Mandatory: -yes-
    Description: The GitHub pull request number that you want to scan. Note: You can continue to use \`--github-pr-id\` flag, but it will be deprecated and removed in the future.

    - Flag: \`--scm-token\`
    Mandatory: -yes-
    Description: Token for SCM authentication. Use the \`ENDOR_SCAN_SCM_TOKEN\` environment variable or pass the token with this flag.

    - Flag: \`--enable-pr-comments\`
    Mandatory: -no-
    Description: Enables comments on the GitHub pull request. Enable this flag if you want to get comments on your GitHub pull request with the details of the AI security code review. You must enable the action policy for pull request comments.


    `}
</YamlTable>
