diff --git a/README.md b/README.md
index 3b21b3e..64e8829 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,30 @@ Python web application examples demonstrating **OWASP Top 10 (2021)** vulnerabil
- Hardcoded Secrets
+## 🔧 Cycode CI/CD Integration Examples
+
+Working examples of Cycode as a CI/CD gate in Azure Pipelines and GitHub Actions.
+
+### GitHub Actions (`.github/workflows/`)
+- `cycode-sast-scan.yml` — full SAST scan on push to main
+- `cycode-sast-pr-scan.yml` — delta scan on pull requests
+- `cycode-release-gate.yml` — release-stage gate (SAST + SCA + Secrets)
+
+### Azure Pipelines
+
+| File | Pattern |
+|------|---------|
+| `azure-pipelines.yml` | **CLI gate** — full scan on push, delta on PR (original pattern) |
+| `azure-pipelines-api-gate.yml` | **API gate** — query Cycode RIG for Open violations in the repo |
+| `azure-pipelines-publish-results.yml` | **Surface results in Azure UI** — Tests tab + custom summary tab + downloadable artifact |
+| `azure-pipelines-template-consumer.yml` | **Centralized template** — consumer that `extends: templates/cycode-scan.yml` |
+| `templates/cycode-scan.yml` | Master template (scan + publish + gate) consumed by many pipelines |
+| `scripts/cycode-gate.sh` | Standalone API-gate script (works from any shell) |
+| `scripts/cycode-json-to-junit.py` | Converts `cycode -o json` output to JUnit XML |
+| `scripts/cycode-summary.py` | Generates a short Markdown summary for the build summary tab |
+
+Secrets needed in Azure DevOps (Pipelines → Library or pipeline variables with lock icon): `CYCODE_CLIENT_ID`, `CYCODE_CLIENT_SECRET`.
+
## 🚀 Getting Started
Each directory contains its own README with specific vulnerability descriptions.
diff --git a/azure-pipelines-api-gate.yml b/azure-pipelines-api-gate.yml
new file mode 100644
index 0000000..69dc86d
--- /dev/null
+++ b/azure-pipelines-api-gate.yml
@@ -0,0 +1,41 @@
+# Pattern 2 — Cycode API gate (standalone example).
+#
+# Queries Cycode's Risk Intelligence Graph for Open violations in this repo.
+# Fails the build if any match the filters below.
+#
+# Prereqs (one-time in Azure DevOps):
+# - Secret pipeline variables CYCODE_CLIENT_ID and CYCODE_CLIENT_SECRET
+# - Agent pool 'Default' (self-hosted) or change to 'ubuntu-latest'
+#
+# Run manually: Pipelines → this pipeline → Run
+trigger: none
+pr: none
+
+pool:
+ name: Default
+
+variables:
+ # Must match the repo name shown in Cycode's Violations UI.
+ # Bare repo name as stored in Cycode's RIG — NOT "owner/repo".
+ REPO_NAME: "vectorvictor"
+
+steps:
+ - checkout: self
+
+ - script: |
+ if ! command -v jq >/dev/null 2>&1; then
+ sudo apt-get update -qq && sudo apt-get install -y -qq jq || true
+ fi
+ jq --version
+ displayName: "Ensure jq is available"
+
+ - script: bash scripts/cycode-gate.sh
+ displayName: "Cycode API gate — fail on Open violations"
+ env:
+ CYCODE_CLIENT_ID: $(CYCODE_CLIENT_ID)
+ CYCODE_CLIENT_SECRET: $(CYCODE_CLIENT_SECRET)
+ REPO_NAME: $(REPO_NAME)
+ # Optional tuning (uncomment to enable):
+ # SEVERITY_MIN: "High" # only High + Critical fail the build
+ # CATEGORY: "SAST" # scope to one scan type
+ # RISK_SCORE_MIN: "70" # ignore findings below risk score 70
diff --git a/azure-pipelines-publish-results.yml b/azure-pipelines-publish-results.yml
new file mode 100644
index 0000000..56904da
--- /dev/null
+++ b/azure-pipelines-publish-results.yml
@@ -0,0 +1,81 @@
+# Pattern 3 — Publish Cycode results to the Azure Pipelines UI.
+#
+# Populates three surfaces from a single Cycode scan:
+# - Tests tab (each finding = failed test, via JUnit)
+# - Custom summary (Markdown report as a tab on the build summary page)
+# - Artifact (raw cycode-results.json for download)
+#
+# After the results are published, a final CLI gate step enforces pass/fail.
+# Swap the gate to `bash scripts/cycode-gate.sh` to gate on the platform API
+# instead (see azure-pipelines-api-gate.yml).
+trigger: none
+pr: none
+
+pool:
+ name: Default
+
+variables:
+ SCAN_PATH: "./vulnerable_apps/"
+
+steps:
+ - checkout: self
+ fetchDepth: 0
+
+ - task: UsePythonVersion@0
+ displayName: "Use Python 3.12"
+ inputs:
+ versionSpec: "3.12"
+
+ - script: |
+ set -e
+ python3 -m pip install --upgrade pip
+ pip install cycode
+ displayName: "Install Cycode CLI"
+
+ # ---- Scan with --soft-fail so we can publish results first ------------
+ - script: |
+ set +e
+ cycode -o json scan --soft-fail -t sast path $(SCAN_PATH) > cycode-results.json
+ echo "scan exit: $?"
+ ls -la cycode-results.json
+ displayName: "Cycode SAST scan (JSON)"
+ env:
+ CYCODE_CLIENT_ID: $(CYCODE_CLIENT_ID)
+ CYCODE_CLIENT_SECRET: $(CYCODE_CLIENT_SECRET)
+
+ # ---- Surface 1: Tests tab --------------------------------------------
+ - script: python3 scripts/cycode-json-to-junit.py cycode-results.json cycode-junit.xml
+ displayName: "Convert JSON → JUnit"
+ condition: succeededOrFailed()
+
+ - task: PublishTestResults@2
+ displayName: 'Publish to "Tests" tab'
+ condition: succeededOrFailed()
+ inputs:
+ testResultsFormat: "JUnit"
+ testResultsFiles: "cycode-junit.xml"
+ testRunTitle: "Cycode SAST"
+ mergeTestResults: true
+ failTaskOnFailedTests: false
+
+ # ---- Surface 2: Build summary tab ------------------------------------
+ - script: |
+ python3 scripts/cycode-summary.py cycode-results.json > cycode-summary.md
+ echo "##vso[task.uploadsummary]$(System.DefaultWorkingDirectory)/cycode-summary.md"
+ displayName: 'Publish "Cycode Scan Summary" tab'
+ condition: succeededOrFailed()
+
+ # ---- Surface 3: Downloadable artifact --------------------------------
+ - task: PublishBuildArtifacts@1
+ displayName: "Publish raw Cycode JSON"
+ condition: succeededOrFailed()
+ inputs:
+ pathToPublish: "cycode-results.json"
+ artifactName: "cycode-report"
+
+ # ---- Final gate -------------------------------------------------------
+ - script: cycode scan -t sast path $(SCAN_PATH)
+ displayName: "Cycode SAST gate (fails build on findings)"
+ env:
+ CYCODE_CLIENT_ID: $(CYCODE_CLIENT_ID)
+ CYCODE_CLIENT_SECRET: $(CYCODE_CLIENT_SECRET)
diff --git a/azure-pipelines-template-consumer.yml b/azure-pipelines-template-consumer.yml
new file mode 100644
index 0000000..9be7938
--- /dev/null
+++ b/azure-pipelines-template-consumer.yml
@@ -0,0 +1,33 @@
+# Pattern 4 — Consumer pipeline that extends the centralized Cycode template.
+#
+# This same-repo example demonstrates the consumer shape. In production the
+# template lives in a separate repo owned by the security team; consumers
+# reference it via `resources.repositories` and `extends: ...@security-templates`.
+#
+# Cross-repo version (replace the local `extends` below):
+#
+# resources:
+# repositories:
+# - repository: security-templates
+# type: git
+# name: SecurityTeam/cycode-pipeline-templates
+# ref: refs/tags/v1
+# extends:
+# template: templates/cycode-scan.yml@security-templates
+# parameters: { ... }
+#
+# App teams customize only the parameters — the scan logic, publishing, and
+# gate behavior all live in the template and evolve centrally.
+trigger: none
+pr: none
+
+extends:
+ template: templates/cycode-scan.yml
+ parameters:
+ scanPath: "./vulnerable_apps/"
+ scanTypeFlags: "-t sast"
+ severityThreshold: "high"
+ repoName: "vectorvictor" # bare repo name (not "owner/repo")
+ gateMode: "both" # defense in depth: CLI + API
+ publishResults: true
+ poolName: "Default"
diff --git a/scripts/cycode-gate.sh b/scripts/cycode-gate.sh
new file mode 100755
index 0000000..828d36a
--- /dev/null
+++ b/scripts/cycode-gate.sh
@@ -0,0 +1,122 @@
+#!/usr/bin/env bash
+# Cycode API gate — fail the build if Open violations exist for $REPO_NAME.
+#
+# Required env:
+# CYCODE_CLIENT_ID, CYCODE_CLIENT_SECRET
+# REPO_NAME — BARE repo name as stored in Cycode's RIG (e.g. "vectorvictor",
+# NOT "AppSecHQ/vectorvictor"). Check the Violations UI to confirm.
+#
+# Optional env (any combination):
+# SEVERITY_MIN Critical | High | Medium | Low (inclusive threshold)
+# CATEGORY SAST | SCA | Secrets | IaC | ContainerScanning
+# RISK_SCORE_MIN 0–100
+#
+# Exit codes:
+# 0 no Open violations matching the filters
+# 1 one or more Open violations found → build should fail
+# 2 invalid input
+# 10 auth or API error
+set -euo pipefail
+
+: "${CYCODE_CLIENT_ID:?CYCODE_CLIENT_ID is required}"
+: "${CYCODE_CLIENT_SECRET:?CYCODE_CLIENT_SECRET is required}"
+: "${REPO_NAME:?REPO_NAME is required (e.g. 'AppSecHQ/vectorvictor')}"
+
+SEVERITY_MIN="${SEVERITY_MIN:-}"
+CATEGORY="${CATEGORY:-}"
+RISK_SCORE_MIN="${RISK_SCORE_MIN:-}"
+
+API_BASE="https://api.cycode.com"
+
+echo "Cycode API gate: checking Open violations for ${REPO_NAME}"
+
+# --- Authenticate ---------------------------------------------------------
+AUTH_RESP=$(curl -sS -X POST "${API_BASE}/api/v1/auth/api-token" \
+ -H "Content-Type: application/json" \
+ -d "{\"clientId\":\"${CYCODE_CLIENT_ID}\",\"secret\":\"${CYCODE_CLIENT_SECRET}\"}") || {
+ echo "##vso[task.logissue type=error]Auth request failed"
+ exit 10
+}
+
+TOKEN=$(jq -r '.token // empty' <<<"$AUTH_RESP")
+if [[ -z "$TOKEN" ]]; then
+ echo "##vso[task.logissue type=error]Cycode authentication returned no token"
+ exit 10
+fi
+
+# --- Build filters --------------------------------------------------------
+FILTERS=$(jq -n --arg repo "$REPO_NAME" '
+ [
+ {name:"status", operator:"Eq", value:"Open", type:"String"},
+ {name:"detection_details.repository_name", operator:"Eq", value:$repo, type:"String"}
+ ]')
+
+if [[ -n "$SEVERITY_MIN" ]]; then
+ case "$SEVERITY_MIN" in
+ Critical|critical) SEVS="Critical" ;;
+ High|high) SEVS="Critical,High" ;;
+ Medium|medium) SEVS="Critical,High,Medium" ;;
+ Low|low) SEVS="Critical,High,Medium,Low" ;;
+ *) echo "Invalid SEVERITY_MIN='$SEVERITY_MIN'"; exit 2 ;;
+ esac
+ FILTERS=$(jq --arg v "$SEVS" '. + [{name:"severity", operator:"In", value:$v, type:"String"}]' <<<"$FILTERS")
+fi
+
+if [[ -n "$CATEGORY" ]]; then
+ FILTERS=$(jq --arg v "$CATEGORY" '. + [{name:"category", operator:"Eq", value:$v, type:"String"}]' <<<"$FILTERS")
+fi
+
+if [[ -n "$RISK_SCORE_MIN" ]]; then
+ FILTERS=$(jq --arg v "$RISK_SCORE_MIN" '. + [{name:"risk_score", operator:"Gte", value:$v, type:"Numeric"}]' <<<"$FILTERS")
+fi
+
+BODY=$(jq -n --argjson filters "$FILTERS" '{
+ resource_type: "detection",
+ filters: [{mode:"And", filters: $filters}],
+ sort_by: "risk_score",
+ sort_order: "desc",
+ limit: -1,
+ fast_query: true,
+ connections: [], exists: true, is_optional: false, edge_type: "",
+ variables: [], edge_filters: [], edge_columns: [],
+ parent_resource_type: "", optional_connections_minimum_count: 0
+}')
+
+# --- Query RIG ------------------------------------------------------------
+RESPONSE=$(curl -sS -X POST \
+ "${API_BASE}/graph/api/v1/graph/query?mode=AlertWhen&page_number=0&page_size=200" \
+ -H "Authorization: Bearer ${TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d "$BODY") || {
+ echo "##vso[task.logissue type=error]RIG query failed"
+ exit 10
+}
+
+if ! jq -e '.result' >/dev/null 2>&1 <<<"$RESPONSE"; then
+ echo "##vso[task.logissue type=error]Unexpected API response"
+ head -c 500 <<<"$RESPONSE"; echo
+ exit 10
+fi
+
+COUNT=$(jq '.result | length' <<<"$RESPONSE")
+HAS_MORE=$(jq -r '.fast_query_has_more // false' <<<"$RESPONSE")
+if [[ "$HAS_MORE" == "true" ]]; then
+ COUNT_LABEL="at least ${COUNT} (page cap hit)"
+else
+ COUNT_LABEL="${COUNT}"
+fi
+echo "Open violations matching filters for ${REPO_NAME}: ${COUNT_LABEL}"
+
+# --- Decision -------------------------------------------------------------
+if (( COUNT > 0 )); then
+ echo
+ echo "Top findings:"
+ # Each .result[] item wraps the detection in a .resource object.
+ jq -r '.result[] | .resource | " [\(.severity // "-") / risk \(.risk_score // "-")] \(.source_policy_name // "-") — \(.detection_details.file_path // .detection_details.package_name // .source_entity_name // "-"):\(.detection_details.line // "")"' \
+ <<<"$RESPONSE" | head -20
+ echo
+ echo "##vso[task.logissue type=error]Cycode gate failed: ${COUNT_LABEL} Open violation(s) in ${REPO_NAME}"
+ exit 1
+fi
+
+echo "Cycode gate passed: no Open violations matched the filters"
diff --git a/scripts/cycode-json-to-junit.py b/scripts/cycode-json-to-junit.py
new file mode 100755
index 0000000..9ad7ee7
--- /dev/null
+++ b/scripts/cycode-json-to-junit.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+"""Convert Cycode CLI JSON output (cycode -o json scan ...) to JUnit XML.
+
+Azure Pipelines renders JUnit via PublishTestResults@2 on the Tests tab.
+Each Cycode finding becomes a failed testcase so reviewers can drill into
+individual violations by severity and file.
+
+Usage:
+ cycode -o json scan -t sast path ./src > cycode.json
+ cycode-json-to-junit.py cycode.json cycode-junit.xml
+"""
+from __future__ import annotations
+
+import json
+import sys
+from xml.sax.saxutils import escape
+
+
+def extract_detections(data):
+ detections = []
+ if isinstance(data, dict):
+ for block in data.get("scan_results", []) or []:
+ detections.extend(block.get("detections", []) or [])
+ detections.extend(data.get("detections", []) or [])
+ elif isinstance(data, list):
+ detections = list(data)
+ return detections
+
+
+def detection_fields(d):
+ dd = d.get("detection_details") or {}
+ severity = d.get("severity") or "UNKNOWN"
+ path = dd.get("file_path") or d.get("file_path") or "unknown"
+ line = dd.get("line") or d.get("line") or ""
+ policy = (
+ d.get("policy_display_name")
+ or d.get("detection_rule_id")
+ or d.get("policy_id")
+ or "Cycode"
+ )
+ message = d.get("message") or policy
+ return severity, policy, path, line, message
+
+
+def main(src: str, dst: str) -> int:
+ with open(src) as f:
+ data = json.load(f)
+
+ detections = extract_detections(data)
+ total = len(detections)
+
+ out = ['']
+ out.append(
+ f''
+ )
+
+ for d in detections:
+ severity, policy, path, line, message = detection_fields(d)
+ classname = f"{severity}.{policy}"
+ name = f"{path}:{line}" if line else path
+ detail = json.dumps(d, indent=2, default=str)
+ cdata = detail.replace("]]>", "]]]]>")
+ out.append(f' ')
+ out.append(
+ f' '
+ )
+ out.append(" ")
+
+ out.append("")
+
+ with open(dst, "w") as f:
+ f.write("\n".join(out))
+
+ print(f"Wrote {total} findings to {dst}")
+ return 0
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 3:
+ print("Usage: cycode-json-to-junit.py ", file=sys.stderr)
+ sys.exit(2)
+ sys.exit(main(sys.argv[1], sys.argv[2]))
diff --git a/scripts/cycode-summary.py b/scripts/cycode-summary.py
new file mode 100755
index 0000000..8f8c73e
--- /dev/null
+++ b/scripts/cycode-summary.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+"""Generate a short Markdown summary of a Cycode JSON scan result.
+
+The output is used with `##vso[task.uploadsummary]` so the summary appears
+as a tab on the Azure Pipelines build summary page.
+
+Usage:
+ cycode -o json scan -t sast path ./src > cycode.json
+ cycode-summary.py cycode.json > cycode-summary.md
+"""
+from __future__ import annotations
+
+import json
+import sys
+
+
+def extract_detections(data):
+ detections = []
+ if isinstance(data, dict):
+ for block in data.get("scan_results", []) or []:
+ detections.extend(block.get("detections", []) or [])
+ detections.extend(data.get("detections", []) or [])
+ elif isinstance(data, list):
+ detections = list(data)
+ return detections
+
+
+def main(src: str) -> int:
+ with open(src) as f:
+ data = json.load(f)
+
+ detections = extract_detections(data)
+ counts: dict[str, int] = {}
+ for d in detections:
+ sev = d.get("severity") or "Unknown"
+ counts[sev] = counts.get(sev, 0) + 1
+
+ lines = []
+ lines.append("## Cycode Scan Summary")
+ lines.append("")
+ lines.append(f"**Total findings:** {len(detections)}")
+ lines.append("")
+ lines.append("| Severity | Count |")
+ lines.append("|---|---|")
+ for sev in ["Critical", "High", "Medium", "Low", "Info", "Unknown"]:
+ if counts.get(sev):
+ lines.append(f"| {sev} | {counts[sev]} |")
+ lines.append("")
+
+ if detections:
+ lines.append("### Top findings")
+ for d in detections[:10]:
+ dd = d.get("detection_details") or {}
+ path = dd.get("file_path") or d.get("file_path") or "?"
+ line = dd.get("line") or d.get("line") or ""
+ msg = (d.get("message") or d.get("detection_rule_id") or "")[:120]
+ loc = f"`{path}:{line}`" if line else f"`{path}`"
+ lines.append(f"- **[{d.get('severity', '?')}]** {msg} — {loc}")
+
+ print("\n".join(lines))
+ return 0
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 2:
+ print("Usage: cycode-summary.py ", file=sys.stderr)
+ sys.exit(2)
+ sys.exit(main(sys.argv[1]))
diff --git a/templates/cycode-scan.yml b/templates/cycode-scan.yml
new file mode 100644
index 0000000..9afbf64
--- /dev/null
+++ b/templates/cycode-scan.yml
@@ -0,0 +1,135 @@
+# Centralized Cycode scan template.
+#
+# Single source of truth for the Cycode CI/CD gate across many pipelines.
+# Consumers `extends:` this template and pass app-specific parameters.
+#
+# Runs a scan in JSON mode, publishes results to the Tests tab + build summary
+# tab + artifact, then enforces a pass/fail gate via the CLI, the API, or both.
+#
+# Cross-repo usage pattern (production):
+# resources:
+# repositories:
+# - repository: security-templates
+# type: git
+# name: SecurityTeam/cycode-pipeline-templates
+# ref: refs/tags/v1
+# extends:
+# template: templates/cycode-scan.yml@security-templates
+# parameters: { ... }
+#
+# Single-repo usage (this demo): see ../azure-pipelines-template-consumer.yml
+
+parameters:
+ - name: scanPath
+ type: string
+ default: "./"
+ - name: scanTypeFlags
+ type: string
+ default: "-t sast" # e.g. "-t sast -t sca -t secret"
+ - name: severityThreshold
+ type: string
+ default: "info" # info | low | medium | high | critical
+ - name: repoName
+ type: string # required — repo name as shown in Cycode
+ - name: gateMode
+ type: string
+ default: "cli" # cli | api | both | none
+ - name: publishResults
+ type: boolean
+ default: true
+ - name: poolName
+ type: string
+ default: "Default" # self-hosted pool; use 'ubuntu-latest' for MS-hosted
+ - name: pythonVersion
+ type: string
+ default: "3.12"
+
+stages:
+ - stage: Security
+ displayName: "Security — Cycode"
+ jobs:
+ - job: CycodeScan
+ displayName: "Cycode scan + gate"
+ pool:
+ name: ${{ parameters.poolName }}
+ steps:
+ - checkout: self
+ fetchDepth: 0
+
+ - task: UsePythonVersion@0
+ displayName: "Use Python ${{ parameters.pythonVersion }}"
+ inputs:
+ versionSpec: ${{ parameters.pythonVersion }}
+
+ - script: |
+ set -e
+ python3 -m pip install --upgrade pip
+ pip install cycode
+ if ! command -v jq >/dev/null 2>&1; then
+ sudo apt-get update -qq && sudo apt-get install -y -qq jq || true
+ fi
+ displayName: "Install Cycode CLI + jq"
+
+ # ---- Scan (soft-fail so we can publish results, then gate) ------
+ - script: |
+ set +e
+ cycode -o json scan --soft-fail \
+ ${{ parameters.scanTypeFlags }} \
+ --severity-threshold ${{ parameters.severityThreshold }} \
+ path ${{ parameters.scanPath }} > cycode-results.json
+ echo "scan exit: $?"
+ ls -la cycode-results.json
+ displayName: "Cycode scan (JSON)"
+ env:
+ CYCODE_CLIENT_ID: $(CYCODE_CLIENT_ID)
+ CYCODE_CLIENT_SECRET: $(CYCODE_CLIENT_SECRET)
+
+ # ---- Publish results --------------------------------------------
+ - ${{ if eq(parameters.publishResults, true) }}:
+ - script: python3 scripts/cycode-json-to-junit.py cycode-results.json cycode-junit.xml
+ displayName: "Convert JSON → JUnit"
+ condition: succeededOrFailed()
+
+ - task: PublishTestResults@2
+ displayName: "Publish to Tests tab"
+ condition: succeededOrFailed()
+ inputs:
+ testResultsFormat: "JUnit"
+ testResultsFiles: "cycode-junit.xml"
+ testRunTitle: "Cycode"
+ mergeTestResults: true
+ failTaskOnFailedTests: false
+
+ - script: |
+ python3 scripts/cycode-summary.py cycode-results.json > cycode-summary.md
+ echo "##vso[task.uploadsummary]$(System.DefaultWorkingDirectory)/cycode-summary.md"
+ displayName: "Publish custom summary tab"
+ condition: succeededOrFailed()
+
+ - task: PublishBuildArtifacts@1
+ displayName: "Publish raw Cycode JSON"
+ condition: succeededOrFailed()
+ inputs:
+ pathToPublish: "cycode-results.json"
+ artifactName: "cycode-report"
+
+ # ---- Gates ------------------------------------------------------
+ - ${{ if or(eq(parameters.gateMode, 'cli'), eq(parameters.gateMode, 'both')) }}:
+ - script: |
+ cycode scan \
+ ${{ parameters.scanTypeFlags }} \
+ --severity-threshold ${{ parameters.severityThreshold }} \
+ path ${{ parameters.scanPath }}
+ displayName: "Gate: CLI scan (fails on findings)"
+ env:
+ CYCODE_CLIENT_ID: $(CYCODE_CLIENT_ID)
+ CYCODE_CLIENT_SECRET: $(CYCODE_CLIENT_SECRET)
+
+ - ${{ if or(eq(parameters.gateMode, 'api'), eq(parameters.gateMode, 'both')) }}:
+ - script: bash scripts/cycode-gate.sh
+ displayName: "Gate: API (fails on Open violations)"
+ env:
+ CYCODE_CLIENT_ID: $(CYCODE_CLIENT_ID)
+ CYCODE_CLIENT_SECRET: $(CYCODE_CLIENT_SECRET)
+ REPO_NAME: ${{ parameters.repoName }}
+ SEVERITY_MIN: ${{ parameters.severityThreshold }}