> ## 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": "/setup-deployment/ci-cd/scan-with-azuredevops/index",
  "feedback": "Description of the issue"
}
```

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

</AgentInstructions>

# Scanning in Azure Pipelines

> Learn how to implement Endor Labs in an Azure Pipeline.

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

Azure Pipelines is a continuous integration and continuous delivery (CI/CD) service available in Azure DevOps ecosystem. It facilitates continuous integration, continuous testing, and continuous deployment for seamless building, testing, and delivery of software.

You can use Azure extension from Endor Labs to include Endor Labs within your Azure pipelines or add steps in your pipeline to manually download and use Endor Labs in your runner.

## Complete the prerequisites

Ensure that you complete the following prerequisites before you proceed.

### Set up an Endor Labs tenant

You must have an Endor Labs tenant set up for your organization. You can also set up namespaces according to your requirements. See [Set up namespaces](/platform-administration/namespaces)

### Configure Endor Labs authentication

Configure an API key and secret for authentication. See [managing API keys](/platform-administration/api-keys) for more information on generating an API key for Endor Labs. Store API key and secret as environment variables, `ENDOR_API_CREDENTIALS_KEY` and `ENDOR_API_CREDENTIALS_SECRET`.

### Enable Advanced Security in Azure

To view scan results directly in Azure DevOps, enable Advanced Security in your Azure repository.

1. Log in to Azure and open **Project Settings**.
2. Navigate to **Repos > Repositories** in the left navigation panel.
3. Select your repository.
4. Enable Advanced Security.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/266r_aE9BWR2B51C/images/setup-deployment/ci-cd/EnableAdvancedSecurity.webp?fit=max&auto=format&n=266r_aE9BWR2B51C&q=85&s=d12f1944da98f61fa31caf75471889da" alt="Enable Advanced Security" width="1310" height="285" data-path="images/setup-deployment/ci-cd/EnableAdvancedSecurity.webp" />

## Integrate Endor Labs with Azure pipelines with the Azure extension

To integrate Endor Labs with Azure pipelines, you need to set up the Azure extension. After you set up the extension, you can configure your pipeline to use Endor Labs.

<Note>
  The Endor Labs Azure extension requires `code read`, `build read`, and `execute` permissions.
</Note>

### Set up the Azure extension

1. Install the Endor Labs extension from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=endorlabs.endorlabs-security-scan-task).

2. Log in Azure DevOps and select your project.

3. Select **Project Settings** from the left sidebar.

4. Select **Service Connections** under **Pipelines**.

5. Click **Create service connection**.

6. Select **Endor Labs** and click **Next**.

7. Enter `https://api.endorlabs.com` as the **Server URL**.

8. Enter the **API Key** and **API Secret** that you [created](#configure-endor-labs-authentication).

9. Enter the service connection name.
   The name you enter here is to be used inside the Azure pipeline.

10. Optionally, you can enter service management reference and description.

11. Select **Grant access permission to all pipelines** to provide access to the Endor Labs service connection to your pipelines.
    <Warning> Ensure that you select this option if you want to use Endor Labs with your pipelines. Unless you enable the service connection, Endor Labs will not be available to your pipelines.</Warning>

12. Click **Save**.

### Configure Azure pipeline to use Endor Labs

<Note>
  **Important**

  Azure Pipelines often check out commits in a detached HEAD state, which can lead to fragmented branch tracking in Endor Labs. See [Set up branch tracking in Azure Pipelines](#set-up-branch-tracking-in-azure-pipelines) to configure proper branch context.
</Note>

1. Create `azure-pipelines.yml` file in your project, if it doesn't exist and enter values according to your requirement.

2. In the `azure-pipelines.yml` file, enter the task, `EndorLabsScan@0`, with the service connection name, Endor Labs namespace, and the SARIF file name.

   For example:

   ```yaml theme={null}

    steps:
     - task: EndorLabsScan@0
       inputs:
         serviceConnectionEndpoint: 'Endor'
         namespace: 'demo'
         sarifFile: 'scanresults.sarif'
   ```

3. Enter the task, `AdvancedSecurity-Publish@1`, if you wish to publish the scan results, which you can view under the Advanced Security tab in Azure DevOps.

   ```yaml theme={null}

   steps:
     - task: AdvancedSecurity-Dependency-Scanning@1
       displayName: Publish scan dependencies to Advanced Security
       inputs:
         SarifsInputDirectory: $(Build.SourcesDirectory)\
   ```

After a successful run of the pipeline, you can [view the results in Azure](#view-scan-results-in-azure).

### Endor Labs scan parameters

You can use the following input parameters in the `EndorLabsScan@0` task.

<YamlTable>
  {`


    - Parameter: \`additionalArgs\`
    Description: Add custom arguments to the endorctl scan command.
    - Parameter: \`phantomDependencies\`
    Description: Set to \`true\` to enable phantom dependency analysis. (Default \`false\`)
    - Parameter: \`sarifFile\`
    Description: Set to a location on your hosted agent to output the findings in SARIF format.
    - Parameter: \`scanDependencies\`
    Description: Scan Git commits and generate findings for all dependencies. (Default \`true\`)
    - Parameter: \`scanPath\`
    Description: Set the path to the directory to scan. (Default \`.\`)
    - Parameter: \`scanSast\`
    Description: Set to \`true\` to enable SAST scan. (Default \`false\`)
    - Parameter: \`scanSecrets\`
    Description: Scan source code repository and generate findings for secrets. See also \`scanGitLogs\`. (Default \`false\`)
    - Parameter: \`scanGitLogs\`
    Description: Perform a more complete and detailed scan of secrets in the repository history. Requires \`scanSecrets\` to be set as \`true\`. (Default \`false\`)
    - Parameter: \`scanTools\`
    Description: Scan source code repository for CI/CD tools. (Default \`false\`)
    - Parameter: \`tags\`
    Description: Specify a list of user-defined tags to add to this scan. Tags help you search and filter scans.
    - Parameter: \`scanPackage\`
    Description: Scan a specified artifact or a package. The path to an artifact must be set with \`scanPath\`. (Default \`false\`)
    - Parameter: \`scanContainer\`
    Description: Scan a specified container image. Set the image with \`image\` and a project with \`projectName\`. (Default \`false\`)
    - Parameter: \`projectName\`
    Description: Specify a project name for a container image scan or for a package scan.
    - Parameter: \`image\`
    Description: Specify a container image to scan.
    - Parameter: \`enableDetachedRefName\`
    Description: Automatically append \`--detached-ref-name\` flag during scan to associate the commit with the actual branch name as seen in Azure DevOps. Set to \`false\` to disable this behavior and use the commit SHA instead. (Default \`true\`)


    `}
</YamlTable>

<Note>
  To enable AI SAST analysis, set the `additionalArgs` parameter to `--ai-sast-analysis=agent-fallback`.
</Note>

### Example Workflow

The following example workflow initiates a scan where all dependencies are scanned along with secrets. The findings are tagged with `Azure`. The scan generates a SARIF file and uploads to GitHub Advanced Security.

```yaml expandable theme={null}

trigger:
- none

pool:
  name: Azure Pipelines
  vmImage: "windows-latest"

steps:
- task: EndorLabsScan@0
  inputs:
    serviceConnectionEndpoint: 'endorlabs-service-connection'
    namespace: 'endor'
    sarifFile: 'scanresults.sarif'
    scanSecrets: 'true'
    tags: `Azure`

- task: AdvancedSecurity-Publish@1
  displayName: Publish 'scanresults.sarif' to Advanced Security
  inputs:
   SarifsInputDirectory: $(Build.SourcesDirectory)\
```

## View scan results in Azure

After the pipeline runs, you can view the scan results in Azure.

1. Log in to Azure and navigate to your projects.
2. Select **Repos** > **Advanced Security** to view the scan results.
   <img src="https://mintcdn.com/endorlabs-b4795f4f/266r_aE9BWR2B51C/images/setup-deployment/ci-cd/azure-advanced-security.webp?fit=max&auto=format&n=266r_aE9BWR2B51C&q=85&s=4b771250408056871dc7d235b372d435" alt="View Azure advanced security" width="3838" height="1628" data-path="images/setup-deployment/ci-cd/azure-advanced-security.webp" />
3. Click an alert to view more details.
   <img src="https://mintcdn.com/endorlabs-b4795f4f/266r_aE9BWR2B51C/images/setup-deployment/ci-cd/azure-singleissue.webp?fit=max&auto=format&n=266r_aE9BWR2B51C&q=85&s=34778034857075efa65487a78d451607" alt="View Azure alert" width="3004" height="1516" data-path="images/setup-deployment/ci-cd/azure-singleissue.webp" />
4. If you ran endorctl with `--secrets` flag, you can view if there are any secret leaks.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/W0xEjuxQ6UT-8MM3/images/setup-deployment/ci-cd/azuresecretdetected.webp?fit=max&auto=format&n=W0xEjuxQ6UT-8MM3&q=85&s=e1409f0b9c6d9ab1ec9a6b9b51f5b06b" alt="View Azure secret leak" width="2466" height="1388" data-path="images/setup-deployment/ci-cd/azuresecretdetected.webp" />

   Click the entry to view more details.

   <img src="https://mintcdn.com/endorlabs-b4795f4f/266r_aE9BWR2B51C/images/setup-deployment/ci-cd/azuresecretexpanded.webp?fit=max&auto=format&n=266r_aE9BWR2B51C&q=85&s=f52f567f1e9577de09a2b397865ef097" alt="View Azure secret leak expanded" width="3004" height="1516" data-path="images/setup-deployment/ci-cd/azuresecretexpanded.webp" />

## Download and use endorctl in Azure pipeline

You can also choose to set up your pipeline to download endorctl and scan using Endor Labs without using the Azure extension.

### Configure Endor Labs variables in the pipeline

You can manage Endor Labs variables centrally by configuring them within your Azure project. You can assign these variables to multiple pipelines.

1. Log in to Azure and select **Pipelines > Library**.
2. Click **+Variable Group** to add a new variable group for Endor Labs.
3. Enter a name for the variable group, for example, `tenant-variables`, and click **Add** under **Variables**.
4. Add the following variables.
   * `ENDOR_API_CREDENTIALS_KEY`
   * `ENDOR_API_CREDENTIALS_SECRET`
   * `NAMESPACE`
     <img src="https://mintcdn.com/endorlabs-b4795f4f/266r_aE9BWR2B51C/images/setup-deployment/ci-cd/createvariables.webp?fit=max&auto=format&n=266r_aE9BWR2B51C&q=85&s=ca2e4b1202f76209f62cd5d3cff7528d" alt="Create Variables" width="3022" height="1420" data-path="images/setup-deployment/ci-cd/createvariables.webp" />
5. Select the variable group that you created.
   <img src="https://mintcdn.com/endorlabs-b4795f4f/266r_aE9BWR2B51C/images/setup-deployment/ci-cd/variableset.webp?fit=max&auto=format&n=266r_aE9BWR2B51C&q=85&s=cb62f1a8ba99be822ca3d04c763b357b" alt="Create Variables" width="3014" height="1076" data-path="images/setup-deployment/ci-cd/variableset.webp" />
6. Click **Pipeline Permissions**.
7. Click **+** to add the pipelines in which you want to use the variable group.
   <img src="https://mintcdn.com/endorlabs-b4795f4f/266r_aE9BWR2B51C/images/setup-deployment/ci-cd/assignvariableset.webp?fit=max&auto=format&n=266r_aE9BWR2B51C&q=85&s=3d4b762525f048a0057c7b13129681ce" alt="Create Variables" width="2978" height="1512" data-path="images/setup-deployment/ci-cd/assignvariableset.webp" />

### Configure your Azure pipeline

1. Create `azure-pipelines.yml` file in your project, if it doesn't exist.
2. In the `azure-pipelines.yml` file, customize the job configuration based on your project's requirements.
3. Adjust the image field to use the necessary build tools for constructing your software packages, and align your build steps with those of your project. For example, update the node pool settings based on your operating system.

<Tabs>
  <Tab title="Windows">
    ```yaml theme={null}
    pool:
      name: Default
      vmImage: "windows-latest"
    ```
  </Tab>

  <Tab title="Ubuntu">
    ```yaml theme={null}
    pool:
      name: Default
      vmImage: "ubuntu-latest"
    ```
  </Tab>

  <Tab title="macOS">
    ```yaml theme={null}
    pool:
      name: Default
      vmImage: "macOS-latest"
    ```
  </Tab>
</Tabs>

4. Update your default branch from main if you do not use main as the default branch name.

5. Modify any dependency or artifact caches to align with the languages and caches used by your project.

6. Enter the following steps in the `azure-pipelines.yml` file to download endorctl.

<Tabs>
  <Tab title="Windows">
    ```yaml theme={null}
    - bash: |
        echo "Downloading latest version of endorctl"
        VERSION=$(curl https://api.endorlabs.com/meta/version | grep -o '"Version":"[^"]*"' | sed 's/.*"Version":"\([^"]*\)".*/\1/')
        curl https://api.endorlabs.com/download/endorlabs/"$VERSION"/binaries/endorctl_"$VERSION"_windows_amd64.exe -o endorctl.exe
        echo "$(curl -s https://api.endorlabs.com/sha/latest/endorctl_windows_amd64.exe)  endorctl" | sha256sum -c
        if [ $? -ne 0 ]; then
          echo "Integrity check failed"
          exit 1
        fi
    ```
  </Tab>

  <Tab title="Ubuntu">
    ```yaml theme={null}
    - bash: |
        echo "Downloading latest version of endorctl"
        VERSION=$(curl https://api.endorlabs.com/meta/version | grep -o '"Version":"[^"]*"' | sed 's/.*"Version":"\([^"]*\)".*/\1/')
        curl https://api.endorlabs.com/download/endorlabs/"$VERSION"/binaries/endorctl_"$VERSION"_linux_amd64 -o endorctl
        echo "$(curl -s https://api.endorlabs.com/sha/latest/endorctl_linux_amd64)  endorctl" | sha256sum -c
        if [ $? -ne 0 ]; then
          echo "Integrity check failed"
          exit 1
        fi
    ```
  </Tab>

  <Tab title="macOS">
    ```yaml theme={null}
    - bash: |
        echo "Downloading latest version of endorctl"
        VERSION=$(curl https://api.endorlabs.com/meta/version | grep -o '"Version":"[^"]*"' | sed 's/.*"Version":"\([^"]*\)".*/\1/')
        curl https://api.endorlabs.com/download/endorlabs/"$VERSION"/binaries/endorctl_"$VERSION"_macos_arm64 -o endorctl
        echo "$(curl -s https://api.endorlabs.com/sha/latest/endorctl_macos_arm64)  endorctl" | shasum -a 256 --check
        if [ $? -ne 0 ]; then
          echo "Integrity check failed"
          exit 1
        fi
    ```
  </Tab>
</Tabs>

7. Enter the steps to build your project if your project needs building and setup steps.

8. Enter the following step in the `azure-pipelines.yml` file to run endorctl scan to generate the SARIF file.

   You can run endorctl scan with [options](/developers-api/cli/commands/scan) according to your requirement, but you must include the `-s` option to generate the SARIF file.

   For example, use the `--secrets` flag to scan for secrets.

<Tabs>
  <Tab title="Windows">
    ```yaml theme={null}
    - script: |
        .\endorctl.exe scan -n $(NAMESPACE) -s scanresults.sarif
      env:
        ENDOR_API_CREDENTIALS_KEY: $(ENDOR_API_CREDENTIALS_KEY)
        ENDOR_API_CREDENTIALS_SECRET: $(ENDOR_API_CREDENTIALS_SECRET)
    ```
  </Tab>

  <Tab title="Ubuntu">
    ```yaml theme={null}
    - script: |
        ./endorctl scan -n $(NAMESPACE) -s scanresults.sarif
      env:
        ENDOR_API_CREDENTIALS_KEY: $(ENDOR_API_CREDENTIALS_KEY)
        ENDOR_API_CREDENTIALS_SECRET: $(ENDOR_API_CREDENTIALS_SECRET)
    ```
  </Tab>

  <Tab title="macOS">
    ```yaml theme={null}
    - script: |
        ./endorctl scan -n $(NAMESPACE) -s scanresults.sarif
      env:
        ENDOR_API_CREDENTIALS_KEY: $(ENDOR_API_CREDENTIALS_KEY)
        ENDOR_API_CREDENTIALS_SECRET: $(ENDOR_API_CREDENTIALS_SECRET)
    ```
  </Tab>
</Tabs>

9. Enter the following task in the `azure-pipelines.yml` to publish the scan results.

```yaml theme={null}
- task: AdvancedSecurity-Publish@1
    displayName: Publish '.\sarif\scanresults.sarif' to Advanced Security
    inputs:
      SarifsInputDirectory: $(Build.SourcesDirectory)\
```

After a successful run of the pipeline, you can [view the results in Azure](#view-scan-results-in-azure).

### Azure Pipeline Examples

<Tabs>
  <Tab title="Windows">
    ```yaml theme={null}
    trigger:
    - none

    pool:
      name: Azure Pipelines
      vmImage: "windows-latest"

    variables:
    - group: tenant-variables

    steps:
    # All steps related to building of the project should be before this step.
    # Implement and scan with Endor Labs after your build is complete.
    - bash: |
        - bash: |
            echo "Downloading latest version of endorctl"
            VERSION=$(curl https://api.endorlabs.com/meta/version | grep -o '"Version":"[^"]*"' | sed 's/.*"Version":"\([^"]*\)".*/\1/')
            curl https://api.endorlabs.com/download/endorlabs/"$VERSION"/binaries/endorctl_"$VERSION"_windows_amd64.exe -o endorctl.exe
           echo "$(curl -s https://api.endorlabs.com/sha/latest/endorctl_windows_amd64.exe)  endorctl" | sha256sum -c
            if [ $? -ne 0 ]; then
              echo "Integrity check failed"
              exit 1
            fi

      displayName: 'Downloading latest version of endorctl'
      continueOnError: false

    - script: |
        .\endorctl.exe scan -n $(NAMESPACE) -s scanresults.sarif
      displayName: 'Run a scan against the repository using your API key & secret pair'
      env:
        ENDOR_API_CREDENTIALS_KEY: $(ENDOR_API_CREDENTIALS_KEY)
        ENDOR_API_CREDENTIALS_SECRET: $(ENDOR_API_CREDENTIALS_SECRET)

    - task: AdvancedSecurity-Publish@1
      displayName: Publish '.\sarif\scanresults.sarif' to Advanced Security
      inputs:
       SarifsInputDirectory: $(Build.SourcesDirectory)\
    ```
  </Tab>

  <Tab title="Ubuntu">
    ```yaml theme={null}
    trigger:
    - none

    pool:
      name: Azure Pipelines
      vmImage: "ubuntu-latest"

    variables:
    - group: tenant-variables

    steps:
    # All steps related to building of the project should be before this step.
    # Implement and scan with Endor Labs after your build is complete.
    - bash: |
        - bash: |
            echo "Downloading latest version of endorctl"
            VERSION=$(curl https://api.endorlabs.com/meta/version | grep -o '"Version":"[^"]*"' | sed 's/.*"Version":"\([^"]*\)".*/\1/')
            curl https://api.endorlabs.com/download/endorlabs/"$VERSION"/binaries/endorctl_"$VERSION"_linux_amd64 -o endorctl
            echo "$(curl -s https://api.endorlabs.com/sha/latest/endorctl_linux_amd64)  endorctl" | sha256sum -c
            if [ $? -ne 0 ]; then
              echo "Integrity check failed"
              exit 1
            fi
            ## Modify the permissions of the binary to ensure it is executable
            chmod +x ./endorctl
            ## Create an alias of the endorctl binary to ensure it is available in other directories
            alias endorctl="$PWD/endorctl"

      displayName: 'Downloading latest version of endorctl'
      continueOnError: false

    - script: |
        ./endorctl scan -n $(NAMESPACE) -s scanresults.sarif
      displayName: 'Run a scan against the repository using your API key & secret pair'
      env:
        ENDOR_API_CREDENTIALS_KEY: $(ENDOR_API_CREDENTIALS_KEY)
        ENDOR_API_CREDENTIALS_SECRET: $(ENDOR_API_CREDENTIALS_SECRET)

    - task: AdvancedSecurity-Publish@1
      displayName: Publish '.\sarif\scanresults.sarif' to Advanced Security
      inputs:
       SarifsInputDirectory: $(Build.SourcesDirectory)/
    ```
  </Tab>

  <Tab title="macOS">
    ```yaml theme={null}
    trigger:
    - none

    pool:
      name: Azure Pipelines
      vmImage: "macos-latest"

    variables:
    - group: tenant-variables

    steps:
    # All steps related to building of the project should be before this step.
    # Implement and scan with Endor Labs after your build is complete.
    - bash: |
            echo "Downloading latest version of endorctl"
            VERSION=$(curl https://api.endorlabs.com/meta/version | grep -o '"Version":"[^"]*"' | sed 's/.*"Version":"\([^"]*\)".*/\1/')
            curl https://api.endorlabs.com/download/endorlabs/"$VERSION"/binaries/endorctl_"$VERSION"_macos_arm64 -o endorctl
            echo "$(curl -s https://api.endorlabs.com/sha/latest/endorctl_macos_arm64)  endorctl" | shasum -a 256 --check
            if [ $? -ne 0 ]; then
              echo "Integrity check failed"
              exit 1
            fi
            ## Modify the permissions of the binary to ensure it is executable
            chmod +x ./endorctl
            ## Create an alias of the endorctl binary to ensure it is available in other directories
            alias endorctl="$PWD/endorctl"
      displayName: 'Downloading latest version of endorctl'
      continueOnError: false

    - script: |
        ./endorctl scan -n $(NAMESPACE) -s scanresults.sarif
      displayName: 'Run a scan against the repository using your API key & secret pair'
      env:
        ENDOR_API_CREDENTIALS_KEY: $(ENDOR_API_CREDENTIALS_KEY)
        ENDOR_API_CREDENTIALS_SECRET: $(ENDOR_API_CREDENTIALS_SECRET)

    - task: AdvancedSecurity-Publish@1
      displayName: Publish '.\sarif\scanresults.sarif' to Advanced Security
      inputs:
       SarifsInputDirectory: $(Build.SourcesDirectory)/
    ```
  </Tab>
</Tabs>

## Set up branch tracking in Azure Pipelines

In Git, a detached HEAD state occurs when the repository checks out a specific commit instead of a branch reference. In this state, Git points the HEAD directly to a commit hash, without associating it with a named branch. As a result, actions performed, such as creating new commits or running automated scans, do not carry branch identity unless explicitly specified.

Proper branch context enables Endor Labs to:

* Associate scans with the correct branch
* Identify scans on the monitored default branch
* Track findings and display metrics accurately across branches

Without proper branch configuration, Endor Labs may create multiple branch entries for the same logical branch, leading to fragmented reporting and inaccurate metrics.

<img src="https://mintcdn.com/endorlabs-b4795f4f/266r_aE9BWR2B51C/images/setup-deployment/ci-cd/branch-fragmentation.webp?fit=max&auto=format&n=266r_aE9BWR2B51C&q=85&s=da0b5af516eb674f7a243de8d5ce3a73" alt="Project with multiple branch entries" style={{width: '80%'}} width="1268" height="382" data-path="images/setup-deployment/ci-cd/branch-fragmentation.webp" />

Azure Pipelines often check out commits by their SHA instead of the branch name, which creates a detached HEAD state.

### Automatic branch tracking

When you use the Endor Labs Azure extension, branch tracking is automated. The `enableDetachedRefName` parameter is set to `true` by default, which automatically detects the branch name from your Azure pipeline and appends the `--detached-ref-name` flag during scans. This ensures that scans display the actual branch name instead of the commit SHA.

```yaml theme={null}
steps:
  - task: EndorLabsScan@0
    inputs:
      namespace: 'demo'
      sarifFile: 'scanresults.sarif'
      serviceConnectionEndpoint: 'Endor'
```

To disable automatic branch tracking and use the commit SHA instead, explicitly set `enableDetachedRefName` to `false`.

```yaml theme={null}
steps:
  - task: EndorLabsScan@0
    inputs:
      enableDetachedRefName: false
      namespace: 'demo'
      sarifFile: 'scanresults.sarif'
      serviceConnectionEndpoint: 'Endor'
```

### Manual branch tracking with endorctl

When you use endorctl, specify the branch name using the `--detached-ref-name` flag.

Use `--detached-ref-name` only to specify the branch name for a commit in detached HEAD state. This associates the commit with the correct branch without setting it as the default branch.

```yaml theme={null}
- script: |
    BRANCH_NAME=$(Build.SourceBranchName)
    ./endorctl scan -n $(NAMESPACE) \
    --detached-ref-name="$BRANCH_NAME" \
    -s scanresults.sarif
```

Use both `--detached-ref-name` and `--as-default-branch` together when you want to associate the commit with a branch and set it as the default branch scan.

```yaml theme={null}
- script: |
    BRANCH_NAME=$(Build.SourceBranchName)
    ./endorctl scan -n $(NAMESPACE) \
    --as-default-branch \
    --detached-ref-name="$BRANCH_NAME" \
    -s scanresults.sarif
```
