Azure DevOps extension that integrates Cycode security scanning into Azure Pipelines.
ADO-Cycode-Plugin/
├── cycodescan/ # Task: run Cycode CLI scan + attach HTML report
│ ├── index.ts # Main task logic
│ ├── task.json # Task manifest
│ ├── package.json
│ └── tsconfig.json
├── cycodeapigate/ # Task: query Cycode RIG API for open violations
│ ├── index.ts
│ ├── task.json
│ ├── package.json
│ └── tsconfig.json
├── src/resultsDisplay/
│ └── resultsTab.ts # UI tab that renders the HTML report in build results
├── .github/workflows/
│ └── build.yml # CI: compile + package on push/PR
├── scripts/
│ └── increment-version.js # Bumps version across all manifests
├── images/
│ └── cycode-logo.png # Publisher icon
├── index.html # Entry point for the build results tab
├── overview.md # Marketplace listing content
├── vss-extension.json # Extension manifest
├── webpack.config.js # Bundles the UI tab for the extension
├── package.json # Root build/package scripts
└── tsconfig.json # TypeScript config for the UI tab (bundled by webpack)
- Node.js 22+ and npm
- Python 3 + pip on any agent running
cycodescan(the task auto-installs the Cycode CLI if missing) - tfx-cli (installed as a dev dependency via root
npm install) - A Cycode API Client ID and Client Secret from the Cycode Console
-
Install dependencies:
npm install --prefix cycodescan npm install --prefix cycodeapigate npm install
-
Compile and package:
npm run build
npm run buildcompiles both tasks (cycodescan/out/andcycodeapigate/out/), runs webpack to bundle the UI tab (dist/resultsTab.js), and packages everything into a.vsixfile inbuilds/.Individual steps are also available:
npm run build:tasks # compile task TypeScript only npm run build:ui # webpack bundle only npm run package # package to .vsix only
-
Publish to the marketplace (requires a PAT with Marketplace publish scope):
npx tfx extension publish --manifest-globs vss-extension.json \ --token <your-PAT>
To share a private build with a specific organisation without publishing publicly:
npx tfx extension publish --manifest-globs vss-extension.json \ --token <your-PAT> \ --share-with <your-org>
The fastest way to inspect report changes is to generate an HTML report from the sample JSON files in json_examples/ and open it in a browser.
cd cycodescan
npm install
npm run test-reportThis compiles the task TypeScript, reads all four files in json_examples/ (cycode_results_sast.json, cycode_results_sca.json, cycode_results_secret.json, cycode_results_iac.json), generates a combined report, and writes it to test-report.html at the repo root.
Open the result:
open ../test-report.html # macOS
start ../test-report.html # Windows
xdg-open ../test-report.html # LinuxTo add or replace sample data, drop a cycode_results_<type>.json file into json_examples/ and re-run npm run test-report. The script picks up every *.json file in that directory automatically.
azure-pipelines-task-lib reads task inputs from INPUT_<NAME> environment variables. Compile the task and run it directly against any local repository:
cd cycodescan
npm install && npm run build
# Point this at the repo you want to scan — not the plugin directory
export INPUT_SCANPATH=/path/to/your/repo
export INPUT_CYCODECLIENTID=your-client-id
export INPUT_CYCODECLIENTSECRET=your-client-secret
export INPUT_SCANTYPE=all # all | sast | sca | secret | iac (comma-separated for a subset)
export INPUT_SCANMODE=path # path | commitHistory
# commitHistory requires at least 2 commits in local git history;
# ensure the repo has depth >= 2 (see Pipeline usage below)
export INPUT_SEVERITYTHRESHOLD=High
export INPUT_BREAKPIPELINE=false
export INPUT_VERBOSE=false
export AGENT_TEMPDIRECTORY=/tmp
node out/index.jsThe HTML report is written to $AGENT_TEMPDIRECTORY/cycode_results.html (/tmp/cycode_results.html above). Open it in a browser to review results. The ##vso[task.addattachment] and artifact upload lines will print but are harmless outside ADO.
To get working file links in the report, also set these optional variables:
export BUILD_REPOSITORY_NAME=my-repo
export BUILD_SOURCEBRANCHNAME=main
export BUILD_SOURCEVERSION=$(git -C $INPUT_SCANPATH rev-parse HEAD)
export SYSTEM_TEAMFOUNDATIONCOLLECTIONURI=https://dev.azure.com/my-org/
export SYSTEM_TEAMPROJECT=my-projectThe same pattern applies to cycodeapigate — set INPUT_REPONAME, INPUT_SEVERITYMIN, etc.
The official Microsoft approach. Create a test file per task:
import * as tmrm from 'azure-pipelines-task-lib/mock-run';
import * as ma from 'azure-pipelines-task-lib/mock-answer';
import * as path from 'path';
const taskPath = path.join(__dirname, '..', 'out', 'index.js');
const tmr = new tmrm.TaskMockRunner(taskPath);
tmr.setInput('CycodeClientID', 'test-id');
tmr.setInput('CycodeClientSecret', 'test-secret');
tmr.setInput('scanPath', '/tmp/repo');
tmr.setInput('scanType', 'sast');
tmr.setInput('severityThreshold', 'High');
tmr.setInput('breakPipeline', 'false');
tmr.setAnswers({
exec: {
'cycode --version': { code: 0, stdout: '1.0.0' },
'cycode scan path --output-format json /tmp/repo': {
code: 1,
stdout: JSON.stringify({ detections: [] })
}
}
} as ma.TaskLibAnswers);
tmr.run();Run with:
npx tsc && node _tests/cycodescan.test.jsThe only way to test the UI results tab and full pipeline integration. Setup is free:
-
Create an org at
dev.azure.com -
Get a PAT with Marketplace (publish) scope
-
Build and publish privately:
npm install --prefix cycodescan npm install --prefix cycodeapigate npm install npm run build npx tfx extension publish --manifest-globs vss-extension.json \ --token <PAT> --share-with <your-org>
-
Install the extension in your org, create a pipeline, and run it
Bump the patch version before each re-publish — ADO rejects duplicate versions.
variables:
CycodeClientID: $(CycodeClientID) # secret variable
CycodeClientSecret: $(CycodeClientSecret) # secret variable
steps:
- task: cycodescan@0
displayName: 'Cycode Security Scan'
inputs:
CycodeClientID: $(CycodeClientID)
CycodeClientSecret: $(CycodeClientSecret)
scanPath: $(Build.SourcesDirectory)
scanType: all # all | sast | sca | secret | iac
severityThreshold: Medium # Info | Low | Medium | High | Critical
breakPipeline: trueWhen scanMode is set to commitHistory, the task scans only the changes introduced since the previous commit — significantly faster on large repositories. This requires at least two commits to be present in the local git clone. Add fetchDepth: 2 to the checkout step before the scan task:
steps:
- checkout: self
fetchDepth: 2 # required for commit-history scan mode
- task: cycodescan@0
displayName: 'Cycode Security Scan (commit history)'
inputs:
CycodeClientID: $(CycodeClientID)
CycodeClientSecret: $(CycodeClientSecret)
scanPath: $(Build.SourcesDirectory)
scanType: all
scanMode: commitHistory
severityThreshold: Medium
breakPipeline: trueNote: If
fetchDepthis omitted (ADO hosted agents default to 1), the task cannot resolve a previous commit in the local clone and automatically falls back to a full path scan.
- task: cycodeapigate@0
displayName: 'Cycode API Gate'
inputs:
CycodeClientID: $(CycodeClientID)
CycodeClientSecret: $(CycodeClientSecret)
repoName: my-service # bare repo name as shown in Cycode Violations UI
severityMin: High # optional: Info | Low | Medium | High | Critical
category: SAST # optional: SAST | SCA | Secrets | IaC | ContainerScanning
riskScoreMin: '70' # optional: 0–100
breakPipeline: truesteps:
- task: cycodescan@0
displayName: 'Run Cycode Scan'
inputs:
CycodeClientID: $(CycodeClientID)
CycodeClientSecret: $(CycodeClientSecret)
scanType: all
breakPipeline: false # don't break here — let the API gate decide
- task: cycodeapigate@0
displayName: 'Cycode API Gate (triaged violations)'
inputs:
CycodeClientID: $(CycodeClientID)
CycodeClientSecret: $(CycodeClientSecret)
repoName: $(Build.Repository.Name)
severityMin: High
breakPipeline: true| Task | UUID |
|---|---|
cycodescan |
3f7b8c9d-1e2f-4a5b-9c8d-7e6f5a4b3c2d |
cycodeapigate |
4a8c9d0e-2f3a-5b6c-ad9e-8f7a6b5c4d3e |
Note: Task IDs must not change after initial publication. If you fork this repo, generate fresh UUIDs before first publish.
Run the version bump script before publishing — it updates all six manifests atomically:
npm run version:bump # increment patch (0.1.0 → 0.1.1)
npm run version:bump:minor # increment minor (0.1.0 → 0.2.0)
npm run version:bump:major # increment major (0.1.0 → 1.0.0)Files updated by the script:
vss-extension.jsonpackage.json(root)cycodescan/task.jsoncycodescan/package.jsoncycodeapigate/task.jsoncycodeapigate/package.json