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

# Git-based private dependencies

> <Badge color="green">Beta</Badge> <br /> Configure Endor Labs to resolve private Git-based dependencies hosted in SCM repositories during scans.

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

Configure Endor Labs to integrate with private Git repositories hosted on GitHub, GitLab, or Bitbucket to access private dependencies during security scanning and analysis. When your projects depend on Git-based dependencies in private repositories, Endor Labs requires authentication credentials to resolve them and generate a complete Software Bill of Materials (SBOM).

This integration enables Endor Labs to:

* Resolve private Git-based dependencies during dependency resolution.
* Generate comprehensive security analysis including private dependencies.
* Maintain complete visibility into your software supply chain.

You can configure two types of private Git-based dependencies:

* **Same-namespace dependencies**: Dependencies on private repositories already covered by an SCM integration in your namespace, such as GitHub, GitLab, or Bitbucket. Endor Labs automatically reuses credentials from every SCM integration in your namespace to resolve them, even when the integration is for a different SCM platform than the project being scanned. Select the private repositories during app installation to improve dependency resolution.

  You can add repositories to your existing **GitHub Cloud App** or **GitHub Cloud App Pro** installation. See [Manage GitHub Cloud App](/setup-deployment/scm-integrations/github-app-pro/github-app/manage-github-app#add-more-github-repositories-to-scan) and [Manage GitHub Cloud App Pro](/setup-deployment/scm-integrations/github-app-pro/manage-github-app-pro#add-more-github-repositories-to-scan) to learn more.

* **Cross-organization dependencies**: Dependencies on private repositories in another project, workspace, or organization where your SCM credentials do not apply. Configure [cross-organization dependencies](#configure-cross-organization-dependencies) and supply credentials to resolve these dependencies.

## Configure cross-organization dependencies

Configure this integration to provide additional credentials for repositories not covered by your SCM integrations, such as in a different project, workspace, or organization.

Use the Git-based dependencies integration in the following scenarios:

* When authentication for private repositories is not defined in standard manifest or configuration files.
* When your existing SCM integration does not have access to the repositories on which your project depends.

To configure credentials for cross-organization dependencies:

1. Select **User menu** > **Integrations** from the left sidebar.

2. Click **Add** next to **Git-based Dependencies**.

3. Click **Create Git Configuration**.

4. Enter a name for the credential.

5. Select the **Source Code Manager Type** where your private repository is hosted.

6. Enter the organization, group, or repository URL that hosts the private dependencies in **Host URL**.

7. Enter a token with the required permissions for the target repositories in **Access token**.

   See [Supported SCM platforms and access tokens](#supported-scm-platforms-and-access-tokens) for host URL formats and required permissions for each SCM.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/dK5ks9zn8CES53i_/images/integrations/package-managers/git-based-dependency.webp?fit=max&auto=format&n=dK5ks9zn8CES53i_&q=85&s=a763d0b9ed45fc464dc593af1f394802" alt="Git based dependencies" width="941" height="545" data-path="images/integrations/package-managers/git-based-dependency.webp" />

8. Optionally, click **Advanced** and select **Propagate this configuration to all child namespaces** to apply this configuration to all child namespaces.

9. Click **Create Git Configuration**.

You can add multiple credentials for different organizations or repositories on the same or different SCM platforms.

### Supported SCM platforms and access tokens

Here are the supported SCM platforms and their corresponding URL formats. Ensure that the access tokens have the following permissions.

<YamlTable>
  {`

    - SCM: **GitHub**
    Host_URL_format: \`https://github.com/{organization}\` or \`https://github.com/{owner}/{repo}\`
    Access_Token: [Personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) with at least read access to code.
    - SCM: **GitLab**
    Host_URL_format: \`https://gitlab.com/{group}/{subgroup1}/...\`.
    Access_Token: [GitLab personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token) with at least \`read_api\` permission. If a standard GitLab personal access token is restricted or unavailable, use a [service account token](https://docs.gitlab.com/user/profile/service_accounts/#view-and-manage-personal-access-tokens-for-a-service-account).
    - SCM: **Bitbucket**
    Host_URL_format: \`https://bitbucket.org/{workspace}/{repo}\` or \`https://bitbucket.org/{workspace}/projects/{project-key}\`
    Access_Token: [Workspace access token](https://support.atlassian.com/bitbucket-cloud/docs/create-a-workspace-access-token/) or [project access token](https://support.atlassian.com/bitbucket-cloud/docs/create-a-project-access-token/) with at least \`Project: read\` permission.

    `}
</YamlTable>

### Test Git-based dependency integration

You can test the connection for a configured Git-based dependency integration to verify that Endor Labs can reach the repository.

To test the connection:

1. Select **User menu** > **Integrations** from the left sidebar.
2. Click **Manage** next to the **Git-based Dependencies** integration.
3. Click the vertical three dots on the integration you want to test and select **Test Connection**.

<Note>
  The test checks basic connectivity to the configured host. It does not verify full authentication or authorization with your Git host or across all repositories your scan may require.
</Note>

### Edit Git-based dependency integration

Edit an existing Git-based dependency integration to update the name, host URL, or access token.

To edit the integration:

1. Select **User menu** > **Integrations** from the left sidebar.
2. Click **Manage** next to the **Git-based Dependencies** integration.
3. Click the vertical three dots on the integration you want to edit and select **Edit**.
4. You can modify the name, host URL, or access token as needed.
5. Optionally, click **Advanced** and select **Propagate this configuration to all child namespaces** to apply the dependency configuration to all child namespaces.
6. Click **Save Changes**.

### Delete Git-based dependency integration

Delete a Git-based dependency integration when you no longer require that credential or access to that repository. Scans will no longer resolve dependencies from that source. To delete the integration:

1. Select **User menu** > **Integrations** from the left sidebar.
2. Click **Manage** next to the **Git-based Dependencies** integration.
3. Click the vertical three dots on the integration you want to delete and select **Delete**.
4. Click **Delete this Dependency?** to confirm the deletion when prompted.

## Configure Git-based dependency integration using API

Use the Endor Labs API to create, fetch, update, and delete SCM credentials for Git-based dependencies. Provide access tokens for the SCM platforms that host your private dependencies so Endor Labs can authenticate and resolve them during scans.

### Create an SCM credential

Run the following command to create an SCM credential resource.

Replace:

* `<namespace>` with your namespace.
* `<credential-name>` with a unique name for this credential in your namespace.
* `PLATFORM_SOURCE_SCM` with the SCM platform constant for your host: `PLATFORM_SOURCE_GITHUB`, `PLATFORM_SOURCE_GITLAB`, or `PLATFORM_SOURCE_BITBUCKET`.
* `<repository-or-org-url>` with the organization or repository URL that hosts the private dependencies.
* `<access-token>` with a valid access token for the target SCM platform.
* `<credential-description>` with a description for the credential.

You can optionally set `propagate` to `true` to ensure the credential is available in child namespaces.

```bash theme={null}
endorctl api create -r SCMCredential -n <namespace> -d '{
    "meta": {
        "name": "<credential-name>"
    },
    "spec": {
        "platform_source": "PLATFORM_SOURCE_SCM",
        "target_url": "<repository-or-org-url>",
        "access_token": "<access-token>",
        "description": "<credential-description>"
    },
    "propagate": false
}'
```

### List SCM credentials using API

Run the following command to list all SCM credentials in a namespace.

```bash theme={null}
endorctl api list -r SCMCredential -n <namespace>
```

### Fetch an SCM credential using API

Run the following command to fetch a specific SCM credential using the UUID.

```bash theme={null}
endorctl api get -r SCMCredential -n <namespace> --uuid <uuid>
```

### Update an SCM credential

Run the following command to update the access token or the host URL.

Replace:

* `<namespace>` with your namespace.
* `<uuid>` with the credential's UUID.
* `PLATFORM_SOURCE_SCM` with the SCM platform: `PLATFORM_SOURCE_GITHUB`, `PLATFORM_SOURCE_GITLAB`, or `PLATFORM_SOURCE_BITBUCKET`.
* `https://platform.org` with the configured organization or repository URL for this credential.
* `abcdef12345` with the new access token.

Set `--field-mask` to only the fields you are updating, such as `spec.access_token` to update the token or `spec.access_token,spec.target_url` when updating both the access token and host URL.

```bash theme={null}
endorctl api update -r SCMCredential -n <namespace> --uuid <uuid> \
  --field-mask spec.access_token \
  -d '{
    "spec": {
        "platform_source": "PLATFORM_SOURCE_SCM",
        "target_url": "https://platform.org",
        "access_token": "abcdef12345"
    }
}'
```

### Delete an SCM credential using API

Run the following command to delete an SCM credential using the UUID. Deleting a credential revokes access to the private repositories and can cause dependency resolution errors in subsequent scans.

```bash theme={null}
endorctl api delete -r SCMCredential -n <namespace> --uuid <uuid>
```
