Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/cycode-sast-pr-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: "Cycode SAST PR Scan (Delta)"

on:
pull_request:
branches: [main]
workflow_dispatch:

jobs:
cycode-sast-delta:
name: "SAST Delta Scan — PR Changes Only"
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"

- name: Install Cycode CLI
run: pip install cycode

- name: Run Cycode SAST delta scan
env:
CYCODE_CLIENT_ID: ${{ secrets.CYCODE_CLIENT_ID }}
CYCODE_CLIENT_SECRET: ${{ secrets.CYCODE_CLIENT_SECRET }}
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE=${{ github.event.pull_request.base.sha }}
HEAD=${{ github.event.pull_request.head.sha }}
echo "Scanning PR delta: ${BASE}..${HEAD}"
cycode scan -t sast commit-history -r "${BASE}..${HEAD}" .
else
echo "Scanning last commit: HEAD~1"
cycode scan -t sast commit-history -r HEAD~1 .
fi
28 changes: 28 additions & 0 deletions .github/workflows/cycode-sast-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: "Cycode SAST Scan"

on:
push:
branches: [main]
workflow_dispatch:

jobs:
cycode-sast:
name: "SAST Security Scan"
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"

- name: Install Cycode CLI
run: pip install cycode

- name: Run Cycode SAST scan
env:
CYCODE_CLIENT_ID: ${{ secrets.CYCODE_CLIENT_ID }}
CYCODE_CLIENT_SECRET: ${{ secrets.CYCODE_CLIENT_SECRET }}
run: cycode scan -t sast path ./vulnerable_apps/
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Examples of insecure Docker configurations and container practices:
- **Dockerfile.secrets-exposed** - Hardcoded secrets and credentials
- **Dockerfile.rootful-privileged** - Privileged containers running as root
- **Dockerfile.multistage-bad** - Insecure multi-stage builds
- **Dockerfile.n8n-vulnerable** - n8n with CVE-2026-21858 (CVSS 10.0)
- **docker-compose.vulnerable.yml** - Insecure Docker Compose configuration

### 🏗️ Vulnerable Terraform (`vulnerable_terraform/`)
Expand All @@ -60,6 +61,12 @@ Infrastructure-as-Code examples with security misconfigurations:
- **aws_iam_vulnerable.tf** - Overly permissive IAM policies and roles
- **aws_misc_vulnerable.tf** - Additional AWS security issues

### 📦 Vulnerable Packages (`vulnerable_packages/`)

Examples of applications using vulnerable open-source dependencies for SCA testing:

- **n8n-workflow/** - Workflow automation with n8n v1.100.0 (CVE-2026-21858, CVSS 10.0)

### 🌐 Vulnerable Web Applications (`vulnerable_apps/`)

Python web application examples demonstrating **OWASP Top 10 (2021)** vulnerabilities:
Expand All @@ -76,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.
Expand Down
41 changes: 41 additions & 0 deletions azure-pipelines-api-gate.yml
Original file line number Diff line number Diff line change
@@ -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
81 changes: 81 additions & 0 deletions azure-pipelines-publish-results.yml
Original file line number Diff line number Diff line change
@@ -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)
33 changes: 33 additions & 0 deletions azure-pipelines-template-consumer.yml
Original file line number Diff line number Diff line change
@@ -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"
11 changes: 11 additions & 0 deletions config/api_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""API configuration for external service integrations."""

import os

# Slack integration
SLACK_BOT_TOKEN = "xoxb-7391528460193-5827461039285-kR4mXpLn7QdWtYvBs9jH3gFe"

# Database credentials
DB_HOST = "prod-db.internal.example.com"
DB_USER = "app_service"
DB_PASSWORD = "Pr0d_S3cure!P@ssw0rd_2025_xK9m"
Loading