> ## 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": "/scan/sast/run-a-sast-scan/index",
  "feedback": "Description of the issue"
}
```

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

</AgentInstructions>

# Run a SAST scan

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

Run a SAST scan with endorctl to identify security vulnerabilities and code quality issues in your source code.

Ensure that you [install endorctl](/introduction/getting-started) and configure your environment to run Endor Labs scan before you proceed to do a SAST scan.

## SAST scan

You can run a SAST scan on a project with endorctl using the following command.

```bash theme={null}
endorctl scan --sast --path=/path/to/code -n <namespace>
```

To view the findings generated by this scan in Endor Labs, see [view SAST findings](/scan/sast/viewing-sast-findings).

## SAST scan with AI analysis

Endor Labs uses AI Agent analysis to perform intelligent triage of SAST findings when you run a scan. The AI agent leverages a large language model (LLM) to examine code context, trace data flows, and evaluate security controls, automatically classifying each finding as either a `True Positive`, indicating a genuine security vulnerability, or a `False Positive`. This automated classification eliminates the need for manual review of every alert, allowing you to focus on addressing real security threats.

AI analysis does not process findings from test files such as unit tests and integration tests, or findings with low severity ratings. See [AI triage behaviour](#ai-triage-behaviour) for more information.

<Note>
  **License requirement**

  AI SAST analysis features require a Code Pro license. A standard Code license covers basic SAST scanning, but AI analysis capabilities require Code Pro.
</Note>

### AI analysis process

The AI analysis process uses a large language model (LLM) to systematically evaluate each finding through the following steps:

1. **Identify SAST rule match location** - The LLM locates the exact code line where the SAST rule was triggered and examines the matching code patterns.

2. **Trace data flow from source to sink** - The LLM follows the data flow from where it enters the application to where it is used in potentially vulnerable code to determine if user-controlled input reaches vulnerable paths.

3. **Examine function calls and security controls** - The LLM reviews function calls in the data flow path, including sanitizers, validators, and other security controls that may mitigate risks.

4. **Analyze function context and application usage** - The LLM understands the purpose of functions involved in the rule match, how they are used in the application, and the application context such as web application, test file, or code example.

5. **Classify findings as true or false positive** - The LLM evaluates all gathered information including whether inputs are user-controlled or hard-coded, presence of sanitization functions, application context, and existing security controls to classify the finding as a true positive or false positive.

AI analysis processes only new findings and existing un-analyzed findings. If some findings are not analyzed in one run, they will be analyzed in the next scan. The analysis process runs for up to 30 minutes by default.

To modify the analysis timeout duration, set the following environment variable:

```bash theme={null}
export ENDOR_SCAN_AI_SAST_ANALYSIS_TIMEOUT=10m
```

### AI-analyzed SAST scan

You can run an AI-analyzed SAST scan on a project with endorctl using the following command.

```bash theme={null}
endorctl scan --sast --path=/path/to/code -n <namespace> --ai-sast-analysis=agent-fallback
```

AI analysis starts with the fast agent mode, but automatically falls back to deep analysis mode when a true positive is detected. This provides a balance between speed and accuracy by using detailed analysis only when needed.

To view the findings generated by this scan in Endor Labs, see [AI-analyzed SAST findings](/scan/sast/viewing-sast-findings#ai-analyzed-sast-findings).

### AI triage behaviour

You can control which findings are analyzed by AI triage and manage re-analysis behavior. When running AI-analyzed SAST scans, use the `--ai-sast-rescan` option to ensure all findings are analyzed. This option removes all existing AI analyses and re-analyzes all findings from scratch. Without this option, SAST findings that have already undergone AI triage are skipped during subsequent scans.

```bash theme={null}
endorctl scan --sast --path=/path/to/code -n <namespace> --ai-sast-analysis=agent-fallback --ai-sast-rescan
```

The following types of findings are automatically excluded from AI triage. To include them, set the corresponding environment variable to `false`:

<YamlTable>
  {`


    - Finding_Type: Test file findings
    Environment_Variable: \`ENDOR_SAST_IGNORE_TEST_TRIAGE\`

    - Finding_Type: Low severity findings
    Environment_Variable: \`ENDOR_SAST_IGNORE_LOW_SEV_TRIAGE\`

    - Finding_Type: Low confidence rule findings
    Environment_Variable: \`ENDOR_SAST_IGNORE_LOW_CONF_RULE\`


    `}
</YamlTable>

You can use AI Analysis Status criteria in [finding policies](/platform-administration/policies/finding-policies/sast-policies) to filter findings by their such as true positives, false positives, or both, in your findings view. Similarly, [action policies](/platform-administration/policies/action-policies/templates#SAST) can trigger actions based on AI classification, such as send notifications only for true positives.

## SAST scan options

You can run the `endorctl scan --sast` command with the following options.

| Option                              | Description                                                                                                                                                                                                                                                                                                                                                                                                            |
| :---------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-n`, `--namespace`                 | Namespace of the project with which you are working. Mandatory.                                                                                                                                                                                                                                                                                                                                                        |
| `--include-path`                    | Limit the scan to the specified file paths or directories using Glob style expressions. For example, `--include-path="src/java/**”`, scans all the files under `src/java`, including any subdirectories, while `--include-path="src/java/*”,` only includes the files directly under `src/java`. Paths must be relative to the root of the repository. Use quotes to ensure that your shell does not expand wildcards. |
| `--exclude-path`                    | Specify one or more file paths or directories using Glob style expressions. For example, `--include-path="src/java/**”`, scans all the files under `src/java`, including any subdirectories, while `--include-path="src/java/*”,` only includes the files directly under `src/java`. Paths must be relative to the root of the repository. Use quotes to ensure that your shell does not expand wildcards.             |
| `--disable-code-snippet-storage`    | Specify the flag to disable storing the code snippet that violates the SAST policy.                                                                                                                                                                                                                                                                                                                                    |
| `--path`                            | The path to issue the scan.                                                                                                                                                                                                                                                                                                                                                                                            |
| `--ai-sast-analysis=agent-fallback` | Enable AI agent to identify and classify false positives in SAST findings. The `agent-fallback` mode starts with fast analysis and automatically falls back to deep analysis when needed.                                                                                                                                                                                                                              |
| `--ai-sast-rescan`                  | Remove all existing AI analyses and re-analyze all findings from scratch, including those that have already undergone AI triage.                                                                                                                                                                                                                                                                                       |
