Skip to content
Open
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
206 changes: 206 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# ============================================================
# isnad-scan GitLab CI/CD Integration Template
# ============================================================
# This is the ROOT template that demonstrates isnad-scan
# integrated into a full GitLab CI/CD pipeline.
#
# For use as a standalone component in your project, see:
# templates/isnad-scan.gitlab-ci.yml
#
# Documentation: docs/gitlab-ci-integration.md
# ============================================================

# ── Configurable Variables ───────────────────────────────────
variables:
# isnad-scan version to use (use a pinned version in production)
ISNAD_SCAN_VERSION: "latest"

# Target to scan — can be overridden per-job or via CI/CD settings
# Accepts: directory path, git URL, npm package name, or PyPI package name
ISNAD_SCAN_TARGET: "."

# Output format: sarif | json | table
ISNAD_SCAN_OUTPUT_FORMAT: "sarif"

# Path where the report will be written
ISNAD_SCAN_REPORT_PATH: "isnad-report"

# Minimum trust score to pass (0–100). Jobs fail below this threshold.
ISNAD_SCAN_MIN_TRUST_SCORE: "70"

# Severity level that triggers a pipeline failure: critical | high | medium | low
ISNAD_SCAN_FAIL_ON_SEVERITY: "high"

# Space-separated list of rule IDs to skip
ISNAD_SCAN_SKIP_RULES: ""

# Enable verbose logging: "true" | "false"
ISNAD_SCAN_VERBOSE: "false"

# Docker image used for scanning jobs
ISNAD_SCAN_IMAGE: "python:3.11-slim"

# ── Stages ───────────────────────────────────────────────────
stages:
- scan # isnad security scanning
- report # upload / process results
- gate # quality gate — blocks merge if score too low

# ── Hidden job: reusable scan setup ─────────────────────────
.isnad_scan_setup: &isnad_scan_setup
image: $ISNAD_SCAN_IMAGE
before_script:
- echo "[isnad] Installing isnad-scan v${ISNAD_SCAN_VERSION}..."
- pip install --quiet --upgrade pip
- |
if [ "$ISNAD_SCAN_VERSION" = "latest" ]; then
pip install --quiet isnad-scan
else
pip install --quiet "isnad-scan==${ISNAD_SCAN_VERSION}"
fi
- isnad-scan --version
- mkdir -p "$ISNAD_SCAN_REPORT_PATH"

# ── Job: SARIF scan (feeds GitLab Security Dashboard) ────────
isnad:scan:sarif:
<<: *isnad_scan_setup
stage: scan
script:
- |
echo "[isnad] Starting SARIF scan of target: ${ISNAD_SCAN_TARGET}"

ARGS="--format sarif"
ARGS="$ARGS --output ${ISNAD_SCAN_REPORT_PATH}/gl-sast-report.sarif"
ARGS="$ARGS --min-trust ${ISNAD_SCAN_MIN_TRUST_SCORE}"
ARGS="$ARGS --fail-on ${ISNAD_SCAN_FAIL_ON_SEVERITY}"

if [ -n "$ISNAD_SCAN_SKIP_RULES" ]; then
ARGS="$ARGS --skip-rules ${ISNAD_SCAN_SKIP_RULES}"
fi

if [ "$ISNAD_SCAN_VERBOSE" = "true" ]; then
ARGS="$ARGS --verbose"
fi

isnad-scan $ARGS "${ISNAD_SCAN_TARGET}"
artifacts:
# GitLab Security Dashboard ingests this artifact automatically
reports:
sast: $ISNAD_SCAN_REPORT_PATH/gl-sast-report.sarif
paths:
- $ISNAD_SCAN_REPORT_PATH/gl-sast-report.sarif
expire_in: 30 days
when: always # upload even on failure so the dashboard shows issues
rules:
# Run on every push and MR; skip if [skip-isnad] is in the commit message
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- if: '$CI_COMMIT_BRANCH'
when: always
- when: never

# ── Job: JSON scan (machine-readable, for custom tooling) ─────
isnad:scan:json:
<<: *isnad_scan_setup
stage: scan
script:
- |
echo "[isnad] Starting JSON scan of target: ${ISNAD_SCAN_TARGET}"

isnad-scan \
--format json \
--output "${ISNAD_SCAN_REPORT_PATH}/isnad-report.json" \
--min-trust "${ISNAD_SCAN_MIN_TRUST_SCORE}" \
--fail-on "${ISNAD_SCAN_FAIL_ON_SEVERITY}" \
"${ISNAD_SCAN_TARGET}"
artifacts:
paths:
- $ISNAD_SCAN_REPORT_PATH/isnad-report.json
expire_in: 30 days
when: always
rules:
- if: '$ISNAD_ENABLE_JSON_REPORT == "true"'
when: always
- when: never

# ── Job: Human-readable table output (for MR comments) ────────
isnad:scan:summary:
<<: *isnad_scan_setup
stage: scan
script:
- |
echo "[isnad] Generating human-readable summary..."

isnad-scan \
--format table \
--output "${ISNAD_SCAN_REPORT_PATH}/isnad-summary.txt" \
--min-trust "${ISNAD_SCAN_MIN_TRUST_SCORE}" \
"${ISNAD_SCAN_TARGET}" | tee "${ISNAD_SCAN_REPORT_PATH}/isnad-summary.txt"
artifacts:
paths:
- $ISNAD_SCAN_REPORT_PATH/isnad-summary.txt
expire_in: 7 days
when: always
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- when: never

# ── Job: Upload results to isnad registry (optional) ─────────
isnad:report:upload:
image: $ISNAD_SCAN_IMAGE
stage: report
needs:
- job: isnad:scan:sarif
artifacts: true
script:
- pip install --quiet isnad-scan
- |
echo "[isnad] Uploading report to isnad registry..."

isnad-scan publish \
--report "${ISNAD_SCAN_REPORT_PATH}/gl-sast-report.sarif" \
--project "${CI_PROJECT_PATH}" \
--commit "${CI_COMMIT_SHA}" \
--ref "${CI_COMMIT_REF_NAME}" \
--token "${ISNAD_API_TOKEN}"
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $ISNAD_API_TOKEN != ""'
when: on_success
- when: never

# ── Job: Quality gate — block MR if trust score too low ───────
isnad:gate:
image: $ISNAD_SCAN_IMAGE
stage: gate
needs:
- job: isnad:scan:json
artifacts: true
optional: true
- job: isnad:scan:sarif
artifacts: true
script:
- pip install --quiet isnad-scan jq
- |
echo "[isnad] Running quality gate (min trust score: ${ISNAD_SCAN_MIN_TRUST_SCORE})..."

# Parse trust score from JSON report if available
if [ -f "${ISNAD_SCAN_REPORT_PATH}/isnad-report.json" ]; then
SCORE=$(jq -r '.summary.trustScore // 0' \
"${ISNAD_SCAN_REPORT_PATH}/isnad-report.json")
echo "[isnad] Detected trust score: ${SCORE}"

if [ "$(echo "$SCORE < $ISNAD_SCAN_MIN_TRUST_SCORE" | bc)" = "1" ]; then
echo "[isnad] ❌ GATE FAILED: Trust score ${SCORE} is below threshold ${ISNAD_SCAN_MIN_TRUST_SCORE}"
exit 1
else
echo "[isnad] ✅ GATE PASSED: Trust score ${SCORE} >= ${ISNAD_SCAN_MIN_TRUST_SCORE}"
fi
else
echo "[isnad] No JSON report found — relying on SARIF exit code."
fi
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $ISNAD_ENABLE_GATE == "true"'
when: on_success
- when: never
allow_failure: false
17 changes: 17 additions & 0 deletions .gitlab-ci.yml.example-override
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ============================================================
# EXAMPLE — How to use the component as a CI/CD Catalog item
# ============================================================
# This shows the GitLab CI Catalog `component` syntax (GitLab 16.0+).
# Use this approach when the template is published to the CI/CD Catalog.
# ============================================================

include:
- component: gitlab.com/counterspec/isnad/isnad-scan@main
inputs:
target: "."
min_trust_score: "80"
fail_on_severity: "high"
output_format: "sarif"

stages:
- test
140 changes: 140 additions & 0 deletions advanced\.gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# ============================================================
# EXAMPLE — Advanced isnad-scan integration
# ============================================================
# Demonstrates:
# • Scanning multiple targets (repo + external package)
# • Custom trust-score threshold
# • JSON report for downstream processing
# • Quality gate blocking merges
# • Scheduled nightly deep scan
# • Custom runner tags
# ============================================================

include:
- project: 'counterspec/isnad'
file: 'templates/isnad-scan.gitlab-ci.yml'
ref: main

variables:
# Raise the trust bar for production-bound code
ISNAD_SCAN_MIN_TRUST_SCORE: "85"

stages:
- build
- scan
- gate
- deploy

# ── Override the default scan job with project-specific settings ──
isnad:scan:
stage: scan
variables:
ISNAD_TARGET: "."
ISNAD_FORMAT: "sarif"
ISNAD_MIN_TRUST: "85"
ISNAD_FAIL_ON: "high"
# Suppress known-false-positive rules
ISNAD_SKIP_RULES: "ISNAD-001,ISNAD-007"
rules:
- when: always

# ── Scan a third-party PyPI package before adding it ────────────
isnad:scan:dependency:
extends: isnad:scan
stage: scan
variables:
ISNAD_TARGET: "pypi:some-new-dependency"
ISNAD_FORMAT: "json"
ISNAD_MIN_TRUST: "90"
ISNAD_FAIL_ON: "medium"
ISNAD_REPORT_DIR: "isnad-reports/dependency"
artifacts:
paths:
- isnad-reports/dependency/
expire_in: 7 days
when: always
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- when: never

# ── Quality gate: block merge if trust score insufficient ────────
isnad:gate:
image: python:3.11-slim
stage: gate
needs:
- job: isnad:scan
artifacts: true
script:
- pip install --quiet jq python-json-tool
- |
REPORT="isnad-reports/isnad-report.json"
if [ ! -f "$REPORT" ]; then
echo "[isnad:gate] No JSON report found — skipping numeric gate."
exit 0
fi

SCORE=$(python3 -c "
import json, sys
data = json.load(open('$REPORT'))
print(data.get('summary', {}).get('trustScore', 0))
")

echo "[isnad:gate] Trust score: ${SCORE} (required: 85)"

python3 -c "
score = float('${SCORE}')
threshold = 85.0
if score < threshold:
print(f'❌ GATE FAILED — score {score} < {threshold}')
raise SystemExit(1)
print(f'✅ GATE PASSED — score {score} >= {threshold}')
"
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: on_success
- when: never
allow_failure: false

# ── Nightly deep scan (schedule-triggered) ──────────────────────
isnad:nightly:
extends: isnad:scan
stage: scan
variables:
ISNAD_TARGET: "."
ISNAD_FORMAT: "json"
ISNAD_MIN_TRUST: "75"
ISNAD_FAIL_ON: "critical"
# Enable deep scan mode for nightly runs
ISNAD_EXTRA_ARGS: "--deep --include-dev-dependencies"
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: always
- when: never
artifacts:
paths:
- isnad-reports/
expire_in: 90 days
when: always

# ── Example build / deploy stubs ────────────────────────────────
build:
stage: build
image: node:20-slim
script:
- echo "Building project..."
rules:
- when: always

deploy:production:
stage: deploy
image: alpine:latest
script:
- echo "Deploying to production..."
needs:
- build
- isnad:scan
- isnad:gate
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: manual
17 changes: 17 additions & 0 deletions basic\.gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ============================================================
# EXAMPLE — Basic isnad-scan integration
# ============================================================
# Scans the repository root on every push and MR.
# Findings appear in the GitLab Security Dashboard (SAST).
# ============================================================

include:
- project: 'counterspec/isnad'
file: 'templates/isnad-scan.gitlab-ci.yml'
ref: main

stages:
- test

# The include block above injects the `isnad:scan` job.
# No additional configuration is required for a basic setup.
Loading