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

# Scan for secrets

> Scan for secrets in your source code

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 `endorctl scan --secrets` to scan for leaked secrets in your source code. You can also scan for secrets with [monitoring scans](/setup-deployment/scm-integrations) and [CI scans](/setup-deployment/ci-cd). Ensure that you select **Secrets** as a scan type when you install the Endor Labs App for your SCM to scan for secrets during monitoring scans.

The following table lists the options available with endorctl for secrets scan.

<YamlTable>
  {`
    - Flag: \`secrets\`
    Environment_Variable: \`ENDOR_SCAN_SECRETS\`
    Description: Scan a source code repository and generate findings for leaked secrets. See also \`--git-logs\`, \`--pre-commit-checks\`, and \`--diff-scope\`.
    - Flag: \`git-logs\`
    Environment_Variable: \`ENDOR_SCAN_GIT_LOGS\`
    Description: Audit the historical Git logs of all branches in the repository. Use with \`--secrets\`.
    - Flag: \`force-rescan\`
    Environment_Variable: \`ENDOR_SCAN_FORCE_RESCAN\`
    Description: Force a full rescan of the historical Git logs for all branches. Use with \`--secrets\`.
    - Flag: \`start-commit\`
    Environment_Variable: \`ENDOR_SCAN_START_COMMIT\`
    Description: The commit to start scanning the Git logs from. Use with \`--secrets\` and \`--end-commit\`.
    - Flag: \`end-commit\`
    Environment_Variable: \`ENDOR_SCAN_END_COMMIT\`
    Description: The commit to stop scanning the Git logs at. Use with \`--secrets\` and \`--start-commit\`.
    - Flag: \`local\`
    Environment_Variable: \`ENDOR_SCAN_LOCAL\`
    Description: Scan the local filesystem without connecting to Endor Labs. Use with \`--secrets\`.
    - Flag: \`pre-commit-checks\`
    Environment_Variable: \`ENDOR_SCAN_PRE_COMMIT_CHECKS\`
    Description: Scan the staged changeset before a commit. Use with \`--secrets\`.
    - Flag: \`secret-rules-file\`
    Environment_Variable: \`ENDOR_SCAN_SECRET_RULES_FILE\`
    Description: Path to a YAML file of secret rules to use instead of the built-in rules for \`--pre-commit-checks\` or \`--local\` scans. Use with \`--secrets\`. Generate the file with \`endorctl api list -r SecretRule -o yaml --list-all\`.
    - Flag: \`diff-scope\`
    Environment_Variable: \`ENDOR_SCAN_DIFF_SCOPE\`
    Description: Limit the secrets scan to changed files only. Accepts \`local\` or \`baseline\`. Cannot be combined with \`--pre-commit-checks\`, \`--local\`, \`--git-logs\`, or \`--force-rescan\`.
    `}
</YamlTable>

## Scan methods

You can perform the following types of scans to detect secrets:

* [**Scan a specific code reference**](#scan-a-specific-code-reference): Scan for secrets only on a defined path in the context of a checked-out branch, commit SHA or tag to identify secrets and raise findings. This helps you to identify secrets that are leaked in the context of what you are working on right now.

* [**Scan complete history**](#scan-complete-history): Scan for secrets in all existing branches or tags to identify if a secret has ever been leaked in the history of the project and raise findings. This helps you to identify if any secret has ever been leaked even if it was not leaked in the context of what you are working on right now.

* [**Scan pre-commits**](#scan-pre-commits): Scan for secrets in the code before committing the code to your repository during the automated pre-commit checks. This helps you identify and remove sensitive information from your code files early in the development life cycle.

* [**Scan with custom rules offline**](#scan-with-custom-rules-offline): Include the custom secret rules from your namespace in a `--pre-commit-checks` or `--local` scan by exporting them to a file, without connecting to Endor Labs.

* [**Scan changed files only**](#scan-changed-files-only): Limit a secrets scan to the files that changed to speed up pre-commit and pre-merge workflows.

### Scan a specific code reference

By default, a secrets scan searches the files in the path where you start the scan.

Run the following command in the directory of the code reference to scan for secrets.

```bash theme={null}
endorctl scan --secrets
```

`--dependencies` runs a separate dependency scan, not a secrets scan option. To run both in one command, combine the flags: `endorctl scan --secrets --dependencies`.

### Scan complete history

You can scan the Git logs by using the complete history scan. The repository should be present in the scanned path. Endor Labs examines the entire repository history to search for secrets.

To perform a complete scan, include the `--git-logs` option in the command line.

```bash theme={null}
endorctl scan --secrets --git-logs
```

Add `--dependencies` to also run a dependency scan in the same command.

```bash theme={null}
endorctl scan --secrets --git-logs --dependencies
```

The `--git-logs` option scans the repository's Git logs using the following logic:

* Perform a full scan if it is the first time the repository's Git log history is scanned.
* Perform a full rescan if a change has been detected to any of the rules in the namespace.
* Perform an incremental scan based on the last time a scan was performed in all the other cases.

Run the following command to force a full rescan if any of the detected secrets are no longer valid, and you want to accurately reflect the state of the secrets.

```bash theme={null}
endorctl scan --secrets --force-rescan
```

You can combine `--force-rescan` with `--dependencies` in the same way.

```bash theme={null}
endorctl scan --secrets --force-rescan --dependencies
```

### Scan pre-commits

You can check for secrets before committing the code to the repository as part of pre-commit hooks.

You must [install and initialize endorctl](/developers-api/cli/install-and-configure) before scanning the pre-commits.

1. Create a `.git/hooks/pre-commit` file at the root of your Git repository to configure the pre-commit hook. It runs automatically when you make a commit and looks for secrets in your commit.

   ```bash theme={null}
   cd .git/hooks
   touch pre-commit
   ```

2. Edit the `.git/hooks/pre-commit` and include:

   ```bash theme={null}
   #!/bin/bash
   #
   # Script invoked on git commit.
   #
   if ! endorctl scan --pre-commit-checks --secrets; then
      echo "Pre-commit checks failed"
      exit 1
   fi
   echo "No secrets found: Pre-commit checks succeeded"
   ```

   `--pre-commit-checks` scans only the changes you are about to commit to the repository.

3. Set the file permissions to make it executable.

   ```bash theme={null}
   chmod +x .git/hooks/pre-commit
   ```

   <Note>
     You can't push the `.git/hooks/` folder to the Git repository because it's only recognized locally on your system. To include the pre-commit code in the Git repository, save it in a different location, like a `hooks/` directory, and then copy it into `.git/hooks/`. This way, you can push the hook code to your Git repository.
   </Note>

4. You can set up this hook on other systems in your organization by creating a script and running it on each system.

   ```bash theme={null}
   sh setup-hooks.sh
   #!/bin/sh
   # Copy all hooks to .git/hooks
   cp hooks/* .git/hooks/
   chmod +x .git/hooks/*
   ```

Here's an example output when no secrets are found.

<img src="https://mintcdn.com/endorlabs-b4795f4f/266r_aE9BWR2B51C/images/scan/secrets/secrets-not-found.webp?fit=max&auto=format&n=266r_aE9BWR2B51C&q=85&s=b9b6c3c292e6938783a0f7b27ef21ba8" alt="No secrets" width="685" height="146" data-path="images/scan/secrets/secrets-not-found.webp" />

Here's an example when secrets are detected and the commit fails.

<img src="https://mintcdn.com/endorlabs-b4795f4f/266r_aE9BWR2B51C/images/scan/secrets/secrets-found.webp?fit=max&auto=format&n=266r_aE9BWR2B51C&q=85&s=11d728e363a57dd1e18e1db463e7e199" alt="Secrets found" width="703" height="231" data-path="images/scan/secrets/secrets-found.webp" />

### Scan with custom rules offline

By default, `--pre-commit-checks` and `--local` scans use the secret rules built into the `endorctl` binary. They run fully offline but do not include the custom rules in your namespace. To use your namespace rules offline, export them to a file and pass that file to the scan.

1. Export your rules to a YAML file.

   ```bash theme={null}
   endorctl api list -r SecretRule -o yaml --list-all > secret-rules.yaml
   ```

2. Run the scan with the exported file.

   ```bash theme={null}
   endorctl scan --secrets --pre-commit-checks --secret-rules-file secret-rules.yaml
   ```

The file replaces the built-in rules for that scan. The scan stays offline and needs no API key. Re-export the file after you upgrade `endorctl`, because the scan rejects a file whose schema does not match the running binary.

### Scan changed files only

Use `--diff-scope` to limit a secrets scan to files that changed, which speeds up scans in pre-commit and pre-merge workflows. The flag requires endorctl v1.7.1040 or later and accepts two values:

* `local`: Scan files with local edits, including staged, unstaged, and untracked changes, against the current `HEAD`.
* `baseline`: Scan files that changed against the repository default branch.

Endor Labs reports only secrets on lines that the change adds or modifies. Untracked files are new, so all findings in them are reported.

Run the following command to scan only the files changed against the default branch.

```bash theme={null}
endorctl scan --secrets --diff-scope baseline
```

`--diff-scope` cannot be combined with `--pre-commit-checks`, `--local`, `--git-logs`, or `--force-rescan`, because each of those modes defines its own scan range.

## Exclude false positives from secret scans

A scan can flag a value that is safe, such as a test credential or an example token. Endor Labs offers three ways to suppress a false positive, from the narrowest to the broadest.

<YamlTable>
  {`
    - Mechanism: \`endorctl:allow\` annotation
    Scope: A single line of source
    Use_when: A specific line is safe and you control the file
    - Mechanism: Rule allowlist
    Scope: All scans, per rule
    Use_when: A pattern is consistently a false positive for a rule
    - Mechanism: Finding exception
    Scope: A single finding, after the scan
    Use_when: You want to dismiss a finding without changing code or rules
    `}
</YamlTable>

### Annotate a safe line

Add an `endorctl:allow` comment to mark a non-sensitive line, such as a test value.

```bash theme={null}
# These are test credentials, safe to commit
username = "test_user"  # endorctl:allow
password = "test_password"  # endorctl:allow
```

### Allowlist a pattern in a rule

To exclude a known false positive from every scan of a rule, add an allowlist to the rule. Allowlists match on content, path, commit, or stop words. See [Allowlists](/scan/secrets/secret-rules#allowlists).

### Dismiss a finding

To suppress an individual finding after a scan, create a finding exception. Exceptions are managed through finding policies. See [Finding policies](/platform-administration/policies/finding-policies).

## Scan for secrets using regular expression

Endor Labs scans for secrets based on regular expressions that are designed to detect the presence of a secret. It then validates the discovered secrets against external APIs to identify if they are valid. Valid secrets actively provide access to a service or an application and can be used to gain unauthorized access.

Regular expressions are customized to match specific types of secrets, such as GitHub personal access tokens, OAuth access tokens, AWS access tokens, OpenAI keys, Client IDs, Client Secrets, and more.

For example, you can describe a GitHub Personal Access Token with the following regular expression.

```bash theme={null}
github_pat_[0-9a-zA-Z_]{82}
```
