Equixly Integration for Azure DevOps
Integrate Equixly automated API penetration testing directly into your Azure DevOps pipelines. Discover security issues in your APIs before they reach production — without leaving your CI/CD workflow.
Table of Contents
What Is Equixly?
Equixly is an AI-powered SaaS Dynamic Application Security Testing (DAST) platform that automatically runs penetration tests. Contrary to other testing platforms that primarily rely on fuzzing — a process of sending random data to APIs to detect vulnerabilities — Equixly stands out by excelling in identifying logic vulnerabilities, focusing on complex business logic security issues that are often overlooked by other tools.
Extension Tasks
This extension provides four pipeline tasks that map directly to the stages of an Equixly security scan:
1 · Equixly — Update API Specifications
Upload your OpenAPI/Swagger specification files to Equixly before triggering a scan. Supports three sourcing modes:
| Mode |
Description |
| File Path |
Read the spec from the repository checkout (relative path). Requires checkout: self. |
| URL |
Download the spec from a remote HTTPS endpoint. |
| Skip |
Bypass the upload step. The spec already stored in Equixly is used as-is. |
Parameters
| Parameter |
Required |
Default |
Description |
equixlyServiceConnection |
Yes |
— |
Equixly service connection (how to create) |
organizationId |
Yes |
— |
Your Equixly organization ID (UUID) |
specSourceType |
Yes |
skip |
How to obtain the OpenAPI spec: filePath, url, or skip |
services |
No |
{} |
JSON map of Equixly service IDs to spec locations (hidden when specSourceType is skip). Example for filePath: {"service-uuid": "docs/openapi.yaml"}. Example for url: {"service-uuid": "https://api.example.com/v1/openapi.json"} |
Output Variables
| Variable |
Description |
servicesUpdated |
Number of services successfully updated |
servicesFailed |
Number of services that failed to update |
2 · Equixly — Trigger Security Scan
Start an Equixly penetration test against the configured project. The created scan ID is published as an output variable for downstream tasks.
Parameters
| Parameter |
Required |
Default |
Description |
equixlyServiceConnection |
Yes |
— |
Equixly service connection (how to create) |
organizationId |
Yes |
— |
Your Equixly organization ID (UUID) |
projectId |
Yes |
— |
The Equixly project ID (UUID) to scan |
Output Variables
| Variable |
Description |
scanId |
The ID of the started scan (use in downstream tasks) |
3 · Equixly — Wait for Scan Completion
Poll the Equixly API until the scan reaches a terminal state. A self-hosted agent is advised for long-running scans — Microsoft-hosted agents have a 6-hour limit.
Parameters
| Parameter |
Required |
Default |
Description |
equixlyServiceConnection |
Yes |
— |
Equixly service connection (how to create) |
organizationId |
Yes |
— |
Your Equixly organization ID (UUID) |
projectId |
Yes |
— |
The Equixly project ID (UUID) |
scanId |
Yes |
— |
The scan ID returned by the Trigger Scan task |
pollIntervalSeconds |
No |
60 |
How often to check the scan status (in seconds) |
maxWaitMinutes |
No |
1440 |
Maximum time to wait before timing out (in minutes, default = 24 hours) |
Output Variables
| Variable |
Description |
scanStatus |
Terminal status of the scan (e.g. completed, failed, stopped) |
4 · Equixly — Process Scan Results
The core processing task. Once the scan finishes, this task:
- Validates the scan completed successfully
- Fetches all security issues (paginated, up to a configurable limit)
- Creates work items (Bugs or Issues) in Azure DevOps for each finding at or above the severity threshold — with SHA-256 fingerprint deduplication so re-detected issues receive a comment rather than a new duplicate work item
- Publishes test results — one test case per finding — so security issues appear in the pipeline Tests tab with historical trend charts
- Links test results to work items for full traceability
- Fails the pipeline if any issues are found at or above the configured minimum severity
Parameters
| Parameter |
Required |
Default |
Description |
equixlyServiceConnection |
Yes |
— |
Equixly service connection (how to create) |
organizationId |
Yes |
— |
Your Equixly organization ID (UUID) |
projectId |
Yes |
— |
The Equixly project ID (UUID) |
scanId |
Yes |
$(EquixlyTriggerScan.scanId) |
The scan ID to process |
severityThreshold |
Yes |
medium |
Minimum severity: high, medium, low, or information |
failOnThreshold |
No |
true |
Fail the pipeline when issues at or above the severity threshold are found |
createWorkItems |
No |
true |
Create bugs/issues in Azure DevOps for each finding (with deduplication) |
workItemType |
No |
Bug |
Work item type to create: Bug (Agile/Scrum) or Issue (Basic process) |
generateTestResults |
No |
true |
Publish one test result per finding to the pipeline Tests tab |
linkTestsToBugs |
No |
true |
Link each test result to its corresponding work item |
maxIssues |
No |
100 |
Maximum number of issues to fetch from the Equixly API |
Output Variables
| Variable |
Description |
highIssues |
Number of high-severity issues |
mediumIssues |
Number of medium-severity issues |
lowIssues |
Number of low-severity issues |
infoIssues |
Number of information-severity issues |
totalIssues |
Total number of issues fetched |
Quick Start
In Project Settings → Service Connections, create a new Equixly API Connection. Enter your Equixly API key (found in the Equixly dashboard under Settings → API Keys).
2. Add the Tasks to Your Pipeline
below an example of a CICD that uses the extension:
# ============================================================================
# EQUIXLY SECURITY SCAN — Extension-based Pipeline
# ============================================================================
# Uses the Equixly Azure DevOps Extension tasks.
# Install the extension in your organisation before using this pipeline.
#
# Prerequisites:
# 1. Install the Equixly API Security extension in your Azure DevOps org
# 2. Create an Equixly service connection in Project Settings > Service Connections
# (type: "Equixly API Connection", enter your Equixly API key)
# 3. A self-hosted agent pool is advised for long-running scans (Microsoft-hosted agents have a 6-hour limit)
# ============================================================================
trigger: none
pr: none
# ============================================================================
# PARAMETERS
# ============================================================================
parameters:
- name: equixlyServiceConnection
displayName: 'Equixly Service Connection Name'
type: string
default: 'Equixly'
- name: organizationId
displayName: 'Equixly Organization ID'
type: string
- name: projectId
displayName: 'Equixly Project ID'
type: string
# How to source the OpenAPI spec for each service.
# Ignored if specSourceType is 'skip'.
- name: specSourceType
displayName: 'Spec Source Type'
type: string
default: 'filePath'
values:
- filePath # Path relative to repo root (requires checkout: self)
- url # Remote HTTPS URL
- skip # Spec already uploaded in Equixly — bypass update step
# JSON map of Equixly service IDs to spec locations.
# File path example: {"<service-uuid>": "docs/openapi.yaml"}
# URL example: {"<service-uuid>": "https://example.com/api/openapi.json"}
# Leave as '{}' when specSourceType is 'skip'.
- name: services
displayName: 'Services (JSON map of serviceId to spec location)'
type: string
default: '{}'
- name: specUploadTimeoutMinutes
displayName: 'Spec Upload Timeout (minutes)'
type: number
default: 10
- name: severityThreshold
displayName: 'Minimum severity for work items and pipeline failure'
type: string
default: 'medium'
values:
- high # High severity only
- medium # Medium and above (recommended)
- low # Low and above
- information # All issues
- name: createWorkItems
displayName: 'Create work items for issues?'
type: boolean
default: true
- name: workItemType
displayName: 'Azure DevOps Work Item Type'
type: string
default: 'Bug'
values:
- Bug # Agile / Scrum process
- Issue # Basic process
- name: generateTestResults
displayName: 'Publish test results?'
type: boolean
default: true
- name: linkTestsToBugs
displayName: 'Link test results to work items?'
type: boolean
default: true
- name: failOnThreshold
displayName: 'Fail pipeline if issues found at or above threshold?'
type: boolean
default: true
- name: maxIssues
displayName: 'Maximum issues to retrieve'
type: number
default: 100
- name: agentPool
displayName: 'Agent Pool Name (self-hosted advised for long scans)'
type: string
# "Azure Pipelines" to run on Microsoft-hosted agents by default, but self-hosted agents are recommended for long-running scans.
default: 'Default'
# ============================================================================
# VARIABLES
# ============================================================================
variables:
# NOTE — Regional endpoints:
# When creating the service connection, set the Equixly API URL to the
# appropriate regional endpoint for your Equixly tenant:
# EU (default, current SaaS): https://api.equixly.com
# The web-app URL is derived automatically from the API URL.
# Polling settings for the WaitForScan task
pollIntervalSeconds: 60
maxWaitMinutes: 720 # 12 hours
# ============================================================================
# STAGES
# ============================================================================
stages:
- stage: EquixlySecurityScan
displayName: 'Equixly Security Scan'
jobs:
# ==========================================================================
# JOB 1: UPDATE API SPECIFICATIONS
# Skipped automatically when specSourceType is 'skip' or services is '{}'.
# Requires checkout: self when specSourceType is 'filePath'.
# ==========================================================================
- job: UpdateSpecs
displayName: 'Update API Specifications'
pool:
name: ${{ parameters.agentPool }}
steps:
# Only needed when reading spec files from the repository.
# Remove this step if specSourceType is 'url' or 'skip'.
- ${{ if eq(parameters.specSourceType, 'filePath') }}:
- checkout: self
- task: EquixlyUpdateSpecs@1
name: EquixlyUpdateSpecs
displayName: 'Upload OpenAPI Spec to Equixly'
inputs:
equixlyServiceConnection: ${{ parameters.equixlyServiceConnection }}
organizationId: ${{ parameters.organizationId }}
specSourceType: ${{ parameters.specSourceType }}
services: ${{ parameters.services }}
specUploadTimeoutMinutes: ${{ parameters.specUploadTimeoutMinutes }}
# ==========================================================================
# JOB 2: TRIGGER SCAN
# Starts the Equixly penetration test and exposes the scan ID as an output.
# Runs if UpdateSpecs succeeded, or if it was skipped (specSourceType=skip).
# ==========================================================================
- job: TriggerScan
displayName: 'Trigger Security Scan'
dependsOn: UpdateSpecs
condition: in(dependencies.UpdateSpecs.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')
pool:
name: ${{ parameters.agentPool }}
steps:
- checkout: none
- task: EquixlyTriggerScan@1
name: EquixlyTriggerScan
displayName: 'Start Equixly Penetration Test'
inputs:
equixlyServiceConnection: ${{ parameters.equixlyServiceConnection }}
organizationId: ${{ parameters.organizationId }}
projectId: ${{ parameters.projectId }}
# ==========================================================================
# JOB 3: WAIT FOR SCAN COMPLETION
# Polls the Equixly API every pollIntervalSeconds until the scan finishes.
# Self-hosted agent advised for long scans — Microsoft-hosted agents have a 6-hour limit.
# ==========================================================================
- job: WaitForScan
displayName: 'Wait for Scan Completion'
dependsOn: TriggerScan
condition: in(dependencies.TriggerScan.result, 'Succeeded', 'SucceededWithIssues')
pool:
name: ${{ parameters.agentPool }}
timeoutInMinutes: ${{ variables.maxWaitMinutes }}
variables:
scanId: $[ dependencies.TriggerScan.outputs['EquixlyTriggerScan.scanId'] ]
steps:
- checkout: none
- task: EquixlyWaitForScan@1
displayName: 'Poll Until Scan Completes'
inputs:
equixlyServiceConnection: ${{ parameters.equixlyServiceConnection }}
organizationId: ${{ parameters.organizationId }}
projectId: ${{ parameters.projectId }}
scanId: $(scanId)
pollIntervalSeconds: $(pollIntervalSeconds)
maxWaitMinutes: $(maxWaitMinutes)
# ==========================================================================
# JOB 4: PROCESS RESULTS
# Validates the scan, fetches issues, creates work items, publishes test
# results, links them to work items, and optionally fails the pipeline.
#
# Requires "Allow scripts to access the OAuth token" to be enabled in the
# pipeline job settings (Agent job > Additional options).
# ==========================================================================
- job: ProcessResults
displayName: 'Process Scan Results'
dependsOn:
- TriggerScan
- WaitForScan
condition: in(dependencies.WaitForScan.result, 'Succeeded', 'SucceededWithIssues')
pool:
name: ${{ parameters.agentPool }}
variables:
scanId: $[ dependencies.TriggerScan.outputs['EquixlyTriggerScan.scanId'] ]
steps:
- checkout: none
- task: EquixlyProcessResults@1
displayName: 'Process Results, Create Work Items & Publish Tests'
inputs:
equixlyServiceConnection: ${{ parameters.equixlyServiceConnection }}
organizationId: ${{ parameters.organizationId }}
projectId: ${{ parameters.projectId }}
scanId: $(scanId)
severityThreshold: ${{ parameters.severityThreshold }}
maxIssues: ${{ parameters.maxIssues }}
createWorkItems: ${{ parameters.createWorkItems }}
workItemType: ${{ parameters.workItemType }}
generateTestResults: ${{ parameters.generateTestResults }}
linkTestsToBugs: ${{ parameters.linkTestsToBugs }}
failOnThreshold: ${{ parameters.failOnThreshold }}
env:
# Required for work item creation and test result publishing.
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
Requirements
| Requirement |
Details |
| Agent |
Self-hosted agent advised for long scans (Microsoft-hosted has 6-hour limit) |
| OAuth token |
Pass SYSTEM_ACCESSTOKEN: $(System.AccessToken) via env on the Process Results step |
| Equixly tenant |
you need to have an Equixly tenant in order to be able to use it |
Support