Integrate Mend SCA with Azure Work Items
Creates, updates, closes and reopens Azure DevOps Work Items from Mend SCA findings. Runs as a scheduled Azure Pipeline job.
How it works: every run reads each in-scope Mend project's complete current state (its security findings, its license workflow violations and its library licenses) and reconciles Azure DevOps against it:
| Finding in Mend | Work Item in Azure |
|---|---|
| New | Created |
| Changed | Updated |
| Gone (fixed or suppressed) | Closed |
| Back again | Reopened |
Table of Contents
- Prerequisites
- Planning Your Work Items Setup
- Azure DevOps Setup
- Mend SCA Setup
- Azure Pipeline Variables
- Reachability
- Routing to Multiple Azure Projects
- Closing and Reopening Work Items
- Execution
- Custom Field Mapping
Prerequisites
- Python 3.12+
In Azure DevOps
- A Personal Access Token (PAT) with Work Items (Read, write & manage) and Project and Team (Read)
- The PAT's user added to a group with Create tag definition and View permissions for this node, and added to the team for each area path you want Work Items in
In Mend
- A service user with the Organization Administrator or Organization Auditor role
- License workflows
- Required for License Work Items. See Mend SCA Setup
Planning Your Work Items Setup
Decide your scope first: which Mend products and projects should produce Work Items.
That decides where the mend-azure-wi-sync.yml pipeline file lives.
| Scope | Where the pipeline file lives | Variables to set |
|---|---|---|
| One repository | In that repository | MEND_PROJECTTOKEN |
| A product, or several repositories | A new, separate repository | MEND_PRODUCTTOKEN, plus MEND_EXCLUDETOKEN to carve out projects |
| The whole Mend organization | A new, separate repository | Leave both empty; use MEND_EXCLUDETOKEN to carve out projects |
Where do Work Items land? By default, all of them go into the single Azure DevOps project named by MEND_AZUREPROJECT, regardless of how many Mend projects are in scope. To send findings to different Azure projects, set MEND_ROUTING: true and tag each Mend project with its destination at scan time. See Routing to Multiple Azure Projects.
See Azure Pipeline Variables for details.
Azure DevOps Setup
- Create a Personal Access Token (PAT) with the scopes and permissions listed in Prerequisites
- Create a new Azure pipeline from the example file examples/mend-azure-wi-sync.yml
- Set the pipeline variables. At minimum:
MEND_URL,MEND_USERKEY,MEND_ORGUUID,MEND_EMAILandMEND_AZUREPAT, plus the scope variables you picked in Planning Your Work Items Setup
Mend SCA Setup
Vulnerability Work Items need no setup in Mend. They come from the project's security findings, selected by CVSS score against MEND_SEVERITY (default high, meaning 7.0). Every active finding at or above that floor gets a Work Item.
License Work Items come from license workflow violations. Create a workflow for each license case you want a Work Item for. Give each workflow name a [License] prefix in square brackets, followed by the title. The integration strips the prefix and renders the title as the violation's name on the Work Item.
NOTE: See Create an Automation Workflow in the Mend Platform for detailed instructions.
Azure Pipeline Variables
Set these in the pipeline where the integration runs. They are grouped in the order you are likely to fill them in.
Required
| Variable | Type | Default | Description |
|---|---|---|---|
MEND_URL |
string | N/A | Mend server URL |
MEND_EMAIL |
string | N/A | Email address of the Mend user whose MEND_USERKEY this is. Used to log in and obtain the token that authenticates every Mend call |
MEND_USERKEY |
secret | N/A | Your Mend user key |
MEND_ORGUUID |
string | N/A | UUID of your Mend organization. Identifies the organization on every Mend API call |
MEND_AZUREURI |
string | N/A | Azure DevOps organization URI, for example https://dev.azure.com/MyOrganization. Accepts the system variable $(System.CollectionUri) |
MEND_AZUREPAT |
secret | N/A | Azure DevOps Personal Access Token |
MEND_AZUREPROJECT |
string | N/A | Azure Team Project name. Accepts the system variable $(System.TeamProject). Not required when MEND_ROUTING: true, where every destination comes from the Mend project's tags and the Work Item type is read from each destination. Set without routing, it is the destination |
Choosing what to sync
| Variable | Type | Default | Description |
|---|---|---|---|
MEND_PRODUCTTOKEN |
string | Empty (all products) |
Comma-separated list of Mend product UUIDs to monitor. A value that is not a valid UUID or token aborts the run with a message naming this variable |
MEND_PROJECTTOKEN |
string | Empty (all projects) |
Comma-separated list of Mend project UUIDs to monitor. A value that is not a valid UUID or token aborts the run with a message naming this variable |
MEND_EXCLUDETOKEN |
string | Empty (no exclusions) |
Comma-separated list of Mend project UUIDs to skip. A value that is not a valid UUID or token aborts the run with a message naming this variable |
MEND_SEVERITY |
string | high |
Minimum CVSS severity a finding must reach to earn a Work Item. Accepts a band (low 0.1, medium 4.0, high 7.0, critical 9.0) or a number from 0 to 10. The same floor governs creation and closure: a Work Item whose findings all drop below it is closed. Unscored findings are always included, and an unparseable value falls back to high |
MEND_ROUTING |
boolean | false |
Send Work Items to different Azure DevOps projects using tags recorded on each Mend project at scan time. See Routing to Multiple Azure Projects |
MEND_BRANCHES |
string | main,master |
Comma-separated glob patterns of branches to sync when MEND_ROUTING is enabled, for example main,release/*. Applied at sync time, so changing it does not require rescanning |
MEND_ROUTING changes where Work Items are created, not which Mend projects are
synced. The three token variables still select the projects; routing then sends each
one's findings to the Azure project named by its tag.
Shaping the Work Item
| Variable | Type | Default | Description |
|---|---|---|---|
MEND_AZURETYPE |
string | Task |
Work Item type to create. Must be a built-in or custom type available in the project's process (Basic, Agile, Scrum, and so on) |
MEND_AZUREAREA |
string | $MEND_AZUREPROJECT |
Area Path to group created Work Items under, for example TeamProject\Area1\SubArea2. Use double backslashes for sub-areas |
MEND_DESCRIPTION |
string | ReproSteps |
Which field holds the description. Use ReproSteps for bugs, Description for issues, or a custom field name |
MEND_DEPENDENCY |
boolean | true |
true creates one Work Item per root library, the direct dependency you can actually upgrade, listing every vulnerable library it pulls in. false creates one Work Item per CVE per vulnerable library, and typically creates many more |
MEND_CALCULATEPRIORITY |
boolean | false |
When true, Priority is calculated from Mend's CVSS3 severity. Otherwise it is set to 2 |
MEND_CUSTOMFIELDS |
string | Empty (no custom fields) |
Maps Mend data into custom fields of the Work Item type. Required when using a custom Work Item type. See Custom Field Mapping |
MEND_REPONAME |
string | $MEND_AZUREPROJECT |
Repository name, available as $MEND_REPONAME in Custom Field Mapping. Under MEND_ROUTING it is set per Mend project from that project's azure-repo scan tag and added to the Work Item as a tag, ignoring any value set here |
MEND_CLOSEDSTATE |
string | auto | System.State value written when a Work Item is closed. Needed only when a derived process renames the closed state; otherwise leave it unset and the tool finds it. Accepts a comma-separated list tried in order, such as Retired,Closed. See Closing and Reopening Work Items |
MEND_REOPENSTATE |
string | auto | System.State value written when a closed Work Item reopens. Needed only when a derived process renames the open state; otherwise leave it unset and the tool finds it. Accepts a comma-separated list tried in order, such as Reopened,New. See Closing and Reopening Work Items |
Description content
| Variable | Type | Default | Description |
|---|---|---|---|
MEND_REACHABILITY |
boolean | false |
When true, adds Reachability to each Work Item. Leave it off if you are not enabling reachability analysis in your scans, to avoid a column of -. See Reachability |
MEND_DEPPATHS |
boolean | true |
Whether to render each transitive library's root-to-leaf dependency chain in the description. Set false to speed up Work Item creation; descriptions then fall back to the flat parents list |
MEND_DEPPATHS_CONCURRENCY |
integer | 16 |
How many dependency-path calls run at once. Raise it if a run with many transitive libraries is slow. Values above 64 are clamped; invalid values fall back to 16 |
Network and advanced
| Variable | Type | Default | Description |
|---|---|---|---|
MEND_PROXY |
string | Empty | Proxy URL, in the format <proxy_ip>:<proxy_port>. For a proxy requiring basic authentication, use <username>:<password>@<proxy_ip>:<proxy_port>. Without an http:// or https:// prefix, http:// is assumed |
MEND_SSLVERIFY |
boolean | true |
TLS certificate verification for the Mend and Azure DevOps calls. Set false to unblock a run where verification fails. To supply a CA bundle for a self-hosted agent behind a TLS-inspecting proxy, set the standard REQUESTS_CA_BUNDLE environment variable to its path; this variable does not take a path |
Reachability
Reachability is included on each vulnerability Work Item when MEND_REACHABILITY: true.
License violations carry no CVE, so they never show it. It renders as Reachable,
Unreachable, or -.
Where it lands depends on MEND_DEPENDENCY:
true(default): an extra column in the CVE table, and an extra line in each CVE's expandable sectionfalse: directly in the flat description, since there is no table
This requires reachability analysis to be performed by the scan. See SCA Reachability for details.
Enabling MEND_REACHABILITY updates existing Work Items on the next run. Reachability can
also be mapped into a custom field, see Custom Field Mapping.
Routing to Multiple Azure Projects
By default every Work Item is created in MEND_AZUREPROJECT. With MEND_ROUTING: true,
each Mend project carries its own destination, so one pipeline can serve many Azure
DevOps projects.
The destination comes from three tags on each Mend project:
| Tag | Set it to |
|---|---|
azure-project |
$(System.TeamProject) |
azure-repo |
$(Build.Repository.Name) |
azure-branch |
$(Build.SourceBranch) |
These tags are not set on this pipeline. They are set as Mend CLI scan tags in each repository's own build pipeline, at the point where that repository is scanned. Mend then promotes them onto the Mend project record, which is what this integration reads. See the Mend CLI SCA upload parameters for the flag syntax for attaching tags to a scan.
env:
AZURE_PROJECT: $(System.TeamProject)
AZURE_REPO: $(Build.Repository.Name)
AZURE_BRANCH: $(Build.SourceBranch)
script: |
mend dep --reachability -u -s "*//$AZURE_PROJECT//$AZURE_REPO" \
--tags "azure-project=$AZURE_PROJECT,azure-repo=$AZURE_REPO,azure-branch=$AZURE_BRANCH"
Map the variables through env: and quote them as above, since a tag value such as
azure-repo can contain spaces. Azure Pipelines substitution uses $(...); ${...} is
shell syntax and fails with bad substitution.
IMPORTANT: Use
$(Build.SourceBranch), not$(Build.SourceBranchName).SourceBranchNamereturns only the last path segment, sorefs/heads/release/1.2becomes1.2and cannot match aMEND_BRANCHESpattern likerelease/*. The integration strips therefs/heads/prefix itself.
Every destination must have MEND_AZURETYPE available. The Work Item type and its
fields are read from each destination separately, so a destination on a different
process
still gets the right fields. A destination whose process does not carry the type is skipped
with an error naming it, the other destinations are unaffected, and the run reports failure.
MEND_AZUREPROJECT is not used under routing and is not required. Set it only for a
non-routed run.
IMPORTANT: With
MEND_ROUTING: true, a run that cannot read the Mend project tags does no work at all. It logsAborted: could not read Mend project tags.and reports a failed run, rather than syncing some projects and skipping others.
Closing and Reopening Work Items
A Work Item whose Mend finding is no longer there is closed. If the finding comes back, the same Work Item is reopened, keeping its id, history and comments.
A finding counts as gone when:
- it is suppressed or ignored in Mend
- the vulnerable library is removed from the project (upgraded, replaced, or dropped)
- the library is marked in-house or allowlisted
- for a license Work Item, the license workflow violation no longer matches
- its CVSS score drops below
MEND_SEVERITY
In MEND_DEPENDENCY: true (the default) one Work Item covers a root library and everything
beneath it, so it closes only once every vulnerability in that group is gone. In false
mode one Work Item is one CVE, and closes as soon as that CVE is gone.
Which state is used
You do not normally configure this. The tool tries the out-of-box process states in order, per Azure project, and remembers which one that board accepted:
| Action | Tried in order |
|---|---|
| Close | Closed (Agile, CMMI), then Done (Scrum, Basic) |
| Reopen | New (Agile, Scrum backlog items), then To Do (Basic, Scrum tasks), then Proposed (CMMI) |
Set MEND_CLOSEDSTATE / MEND_REOPENSTATE only if a derived process renames the state. An
explicit value is used alone and is never followed by a fallback, so if your projects span
a derived process and an out-of-box one, give a comma-separated list tried in order:
MEND_CLOSEDSTATE: Retired,Closed.
IMPORTANT: Closure never deletes a Work Item. It only changes the state field, so the Work Item keeps its id, history, comments, links and any fields your team edited.
NOTE: Work Items are matched by title plus the
{product}/{project}tag the integration writes. A Work Item whose title was hand-edited is no longer recognised: it is neither updated nor closed, and a second Work Item is created for the finding. Do not rename generated titles.
Execution
Run the pipeline on a cron schedule, daily or at whatever frequency suits you. See the example pipeline file.
schedules:
- cron: "0 6 * * *"
displayName: Mend SCA Sync Scheduler
branches:
include:
- main
always: true
Each run is a full comparison, not a "since the last execution" window, so a missed or
failed run costs nothing but the delay.
Custom Field Mapping
When MEND_AZURETYPE names a custom Work Item type, the integration reads its definition from the Azure API automatically, once per Azure project it writes to. MEND_CUSTOMFIELDS then populates that type's fields from the Mend entry that produced the Work Item.
Syntax
MEND_CUSTOMFIELDS is a quoted string of FieldName::Value pairs separated by semicolons:
env:
MEND_AZURETYPE: 'SCA Issue'
MEND_CUSTOMFIELDS: 'Field 1::MEND:path.to.property;Field 2::MEND:property& Free Text &MEND:other;Field 3::$MEND_REPONAME'
| Part | Rule |
|---|---|
| Field name | The field's name property, its friendly name. See WorkItemTypeFieldInstance |
MEND:path |
Dot-separated path into the Mend entry, case sensitive. See Available MEND: Paths |
$MEND_REPONAME / $MEND_DESCRIPTION |
Substituted from those pipeline variables |
| Free text | Any static or pipeline-derived text |
& |
Joins several parts into one field |
; |
Separates one field mapping from the next |
NOTE: If the Work Item type has mandatory fields (
"alwaysRequired": true), you must map values to them withMEND_CUSTOMFIELDSor Work Item creation fails.
Available MEND: Paths
A MEND: path is walked into one of the entries the Work Item was built from. An entry
is one root library (in the default MEND_DEPENDENCY: true mode) or one CVE (in
MEND_DEPENDENCY: false):
{
"library": "log4j-core", <- MEND:library
"kind": "vulnerability", <- MEND:kind
"findings": [ <security finding>, ... ], <- MEND:findings.<...>
"licenses": [ <license>, ... ] <- MEND:licenses.<...>
}
library and kind are composed by the integration. Everything under findings and
licenses is the Mend API response verbatim, so any field those endpoints return can be
used as a path:
| Prefix | Source |
|---|---|
MEND:findings. |
Get security vulnerability findings |
MEND:licenses. |
Get due diligence info by multiple contexts |
The security findings response nests vulnerability, component, findingInfo,
threatAssessment and topFix objects, giving paths such as
MEND:findings.vulnerability.name, MEND:findings.component.version or
MEND:findings.threatAssessment.epssPercentage.
IMPORTANT: In
MEND_DEPENDENCY: true,findingsis a list, and aMEND:findings.*path resolves against whichever finding Mend returned last. That is arbitrary: not the first, and not the most severe. A field mapped this way can disagree with the highest-severity figure in the Work Item's own title. The same applies toMEND:licenses.*when a library carries more than one license.
entry.findings = [ CVE-2020-8203 (7.4, HIGH),
CVE-2021-23337 (9.8, CRITICAL),
CVE-2019-10744 (3.1, LOW) ]
MEND:findings.vulnerability.name -> CVE-2019-10744
MEND:findings.vulnerability.score -> 3.1
If you need one value per CVE, use MEND_DEPENDENCY: false. Each Work Item is then a single
CVE whose entry holds exactly one finding.
IMPORTANT: An unresolvable path does not leave the field blank. A typo in any segment after the first writes the literal string
No contentinto the field. A first segment that does not exist on the entry logsCustom field parsing failedand leaves the field empty. After changingMEND_CUSTOMFIELDS, check the pipeline log and inspect one produced Work Item before rolling it out.
NOTE: Values resolve to Mend's raw wording, which is upper-case:
HIGH,REACHABLE,TRANSITIVE. The description rendered on the Work Item usesHigh,Reachableand so on, so a custom field will not match it character for character.
Examples
All the following examples assume a custom Work Item type named SCA Issue, which was configured to inherit fields from the Bug Work Item of the Agile process flow.
Example 1 Populating the Work Item's custom fields Library and Issue Reference with the vulnerable library and the CVE identifier:
env:
MEND_AZURETYPE: 'SCA Issue'
MEND_CUSTOMFIELDS: 'Library::MEND:library;Issue Reference::MEND:findings.vulnerability.name'
Example 2
Populating the Work Item's custom field Team Comments with the text
Library: LIBRARY version VERSION (repo: REPONAME), combining MEND: paths, free text and a pipeline variable with &:
env:
MEND_AZURETYPE: 'SCA Issue'
MEND_CUSTOMFIELDS: 'Team Comments::Library: &MEND:library& version &MEND:findings.component.version& (repo: &$MEND_REPONAME&)'
Example 3 Populating custom fields Reachability, EPSS and Exploit Maturity with the risk signals on each finding (see Reachability):
env:
MEND_REACHABILITY: true
MEND_AZURETYPE: 'SCA Issue'
MEND_CUSTOMFIELDS: 'Reachability::MEND:findings.reachability;EPSS::MEND:findings.threatAssessment.epssPercentage;Exploit Maturity::MEND:findings.threatAssessment.exploitCodeMaturity'
NOTE: In
MEND_DEPENDENCY: truemode these fields reflect an arbitrary one of the library's CVEs, so they can disagree with the description table about which CVE. See AvailableMEND:Paths.
Example 4 A complete pipeline fragment: a full custom Work Item type populated from a single Mend entry.
- script: python mend_azure_wi_sync/azure_wi_sync.py
displayName: 'Mend SCA Work Item Sync'
env:
MEND_URL: $(MEND_URL)
MEND_USERKEY: $(MEND_USERKEY)
MEND_ORGUUID: $(MEND_ORGUUID)
MEND_EMAIL: $(MEND_EMAIL)
MEND_AZUREPAT: $(MEND_AZUREPAT)
MEND_AZUREURI: $(System.CollectionUri)
MEND_AZUREPROJECT: $(System.TeamProject)
MEND_AZURETYPE: 'SCA Issue'
MEND_DEPENDENCY: false # one Work Item per CVE, so every MEND: path is unambiguous
MEND_SEVERITY: high
MEND_REACHABILITY: true
MEND_CUSTOMFIELDS: 'Library::MEND:library;Version::MEND:findings.component.version;Issue Reference::MEND:findings.vulnerability.name;CVSS::MEND:findings.vulnerability.score;EPSS::MEND:findings.threatAssessment.epssPercentage;Exploit Maturity::MEND:findings.threatAssessment.exploitCodeMaturity;Reachability::MEND:findings.reachability;Fixed In::MEND:findings.topFix.fixResolution;Source Repo::$MEND_REPONAME'
NOTE:
MEND_CUSTOMFIELDSmust be a single-line string. Field names are matched exactly, with no trimming, so a YAML folded block (>-) breaks it: the fold inserts a space, andVersionno longer matches the fieldVersion.
NOTE:
MEND_DEPENDENCY: falseis used here deliberately. In the defaulttruemode the same mapping still works, but each of theMEND:findings.*fields would reflect an arbitrary one of the library's CVEs. See AvailableMEND:Paths.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mend_azure_wi_sync-26.9.1-py3-none-any.whl.
File metadata
- Download URL: mend_azure_wi_sync-26.9.1-py3-none-any.whl
- Upload date:
- Size: 86.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ff69e3bec647fe8a156e9c1c71ccfede2c671cd9343726975df0ff7a937ce0db
|
|
| MD5 |
ca38d375b0ad61bba7558514d7adce1b
|
|
| BLAKE2b-256 |
2aaf902fdde8796e0bf8a5adbf72d0d19cd523159e87f25146e0dd81c79db2cb
|