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
68 changes: 68 additions & 0 deletions .gitlab-ci-templates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# ISNAD Scanner - Advanced GitLab CI Templates
#
# These templates extend the basic pipeline with advanced features:
# - Auto-issue creation on critical findings
# - Weekly scheduled scans of the entire codebase
# - Slack/webhook notifications
#
# Include in your .gitlab-ci.yml:
# include:
# - remote: 'https://raw.githubusercontent.com/counterspec/isnad/main/.gitlab-ci-templates.yml'

.isnad-base-scan:
stage: scan
image: node:20-alpine
cache:
key: isnad-scanner
paths:
- /tmp/isnad/scanner/node_modules/
before_script:
- apk add --no-cache git bash curl jq
- |
if [ ! -d "/tmp/isnad/scanner/dist" ]; then
git clone --depth 1 --branch "${ISNAD_VERSION:-main}" https://github.com/counterspec/isnad.git /tmp/isnad
cd /tmp/isnad/scanner
npm ci
npm run build
cd "$CI_PROJECT_DIR"
fi
script:
- mkdir -p "$CI_PROJECT_DIR/$ISNAD_REPORT_DIR"
- node /tmp/isnad/scanner/dist/cli.js batch "$ISNAD_SCAN_TARGETS" --json > "$ISNAD_REPORT_DIR/isnad-scan.json"
- node /tmp/isnad/scanner/dist/cli.js batch "$ISNAD_SCAN_TARGETS" --sarif-output "$ISNAD_REPORT_DIR/isnad-sarif.json"
artifacts:
when: always
reports:
sast: "$ISNAD_REPORT_DIR/isnad-sarif.json"
paths:
- "$ISNAD_REPORT_DIR/"
expire_in: 90 days
variables:
ISNAD_REPORT_DIR: "isnad-reports"

# Auto-issue on critical findings
.isnad-auto-issue:
stage: report
image: alpine
needs:
- job: isnad-security-scan
after_script:
- |
FINDINGS=$(cat "$CI_PROJECT_DIR/$ISNAD_REPORT_DIR/isnad-scan.json" 2>/dev/null || echo "[]")
CRITICAL=$(echo "$FINDINGS" | grep -c '"critical"' || true)
HIGH=$(echo "$FINDINGS" | grep -c '"high"' || true)
if [ "$CRITICAL" -gt 0 ] || [ "$HIGH" -gt 0 ]; then
echo "Creating GitLab issue for $CRITICAL critical and $HIGH high findings..."
curl --silent --request POST --header "PRIVATE-TOKEN: $ISNAD_GITLAB_TOKEN" --form "title=[SECURITY] ISNAD: $CRITICAL critical, $HIGH high findings" --form "description=@$CI_PROJECT_DIR/$ISNAD_REPORT_DIR/isnad-scan.json" --form "labels=security,isnad-scan" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/issues" 2>/dev/null || true
fi
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"

# Scheduled weekly scan
.isnad-weekly-scan:
extends: .isnad-base-scan
variables:
ISNAD_SCAN_TARGETS: "src/ packages/ lib/ scripts/ tests/ contracts/"
ISNAD_REPORT_DIR: "isnad-reports-weekly"
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
67 changes: 67 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# ISNAD Scanner - GitLab CI/CD Integration
# This file provides a ready-to-use pipeline configuration that runs the ISNAD
# security scanner on every push and merge request, producing SARIF reports
# compatible with the GitLab Security Dashboard.
#
# Usage:
# 1. Copy this file to your repository root as .gitlab-ci.yml
# 2. (Optional) Override SCAN_TARGETS and SCAN_FAIL_LEVEL in your GitLab CI/CD variables
# 3. Ensure your GitLab instance has "CI/CD > Security reports" enabled for SAST

variables:
# Comma or space separated list of files/directories to scan
SCAN_TARGETS: "src/ lib/ scripts/ packages/"
# Fail pipeline if findings reach this level or higher: critical | high | medium | low
SCAN_FAIL_LEVEL: "high"
# ISNAD version to use (git branch or tag)
ISNAD_SCANNER_VERSION: "main"

stages:
- scan

isnad-security-scan:
stage: scan
image: node:20-alpine
before_script:
- apk add --no-cache git bash
- git clone --depth 1 --branch "$ISNAD_SCANNER_VERSION" https://github.com/counterspec/isnad.git /tmp/isnad
- cd /tmp/isnad/scanner
- npm ci
- npm run build
script:
- echo "=== ISNAD Security Scanner ==="
- echo "Targets: $SCAN_TARGETS"
- echo "Fail Level: $SCAN_FAIL_LEVEL"
- mkdir -p "$CI_PROJECT_DIR/isnad-reports"
- EXIT_CODE=0
- for target in $SCAN_TARGETS; do
if [ "$target" = "${target%%/}" ] && [ -f "$target" ]; then
echo "";
echo "--- Scanning file: $target ---";
node /tmp/isnad/scanner/dist/cli.js scan "$target" --json > "isnad-reports/$(basename "$target").json" 2>/dev/null || true;
elif [ -d "$target" ]; then
echo "";
echo "--- Scanning directory: $target ---";
node /tmp/isnad/scanner/dist/cli.js batch "${target}/**/*.{ts,js,tsx,jsx,py,rb,sh,mjs,cjs}" --sarif-output "isnad-reports/isnad-sarif.json" || EXIT_CODE=1;
fi;
done
- if [ $EXIT_CODE -ne 0 ]; then
echo "";
echo "ISNAD scanner detected security issues above $SCAN_FAIL_LEVEL level!";
exit 1;
fi
- echo "";
- echo "=== ISNAD scan completed ===";
artifacts:
when: always
reports:
sast: isnad-reports/isnad-sarif.json
paths:
- isnad-reports/
expire_in: 90 days
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "push"
changes:
- "**/*.{ts,js,tsx,jsx,py,rb,sh,mjs,cjs}"
- if: $CI_PIPELINE_SOURCE == "schedule"
86 changes: 86 additions & 0 deletions docs/GITLAB_CI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# GitLab CI/CD Integration

The [ISNAD Scanner](../README.md) integrates with GitLab CI/CD to automatically
security-scan your codebase on every push and merge request, producing SARIF
reports that appear in the GitLab Security Dashboard.

## Quick Start

1. Copy the `.gitlab-ci.yml` from this repo into your project root
2. Adjust `SCAN_TARGETS` to match your source directories
3. Push a commit or create a merge request — the pipeline runs automatically

Example `.gitlab-ci.yml` snippet:

```yaml
variables:
SCAN_TARGETS: "src/ lib/"
SCAN_FAIL_LEVEL: "high"

stages:
- scan

isnad-security-scan:
stage: scan
image: node:20-alpine
before_script:
- apk add --no-cache git bash
- git clone --depth 1 --branch "main" https://github.com/counterspec/isnad.git /tmp/isnad
- cd /tmp/isnad/scanner && npm ci && npm run build
- cd "$CI_PROJECT_DIR"
script:
- node /tmp/isnad/scanner/dist/cli.js batch "src/**/*.{ts,js}" --sarif-output is-report.json || EXIT_CODE=1
```

## SARIF Output

The SARIF (`--sarif-output`) flag produces reports compatible with:
- **GitLab SAST Reports**: Automatically displayed in the Merge Request widget
- **GitHub Code Scanning**: Upload via `github/codeql-action/upload-sarif`
- **Any SARIF-aware tool**: VS Code, IDE integrations

## Configurable Targets

| Variable | Default | Description |
|----------|---------|-------------|
| `SCAN_TARGETS` | `src/ lib/ scripts/ packages/` | Space-separated list of dirs/files to scan |
| `SCAN_FAIL_LEVEL` | `high` | Pipeline fails if findings reach this severity (critical/high/medium/low) |
| `ISNAD_SCANNER_VERSION` | `main` | Which version/branch of isnad to use |

## Scheduled Scans

To run weekly scans of your entire codebase, add a pipeline schedule in
GitLab: **CI/CD > Schedules > New schedule**

```yaml
.isnad-weekly-scan:
variables:
SCAN_TARGETS: "src/ packages/ lib/ scripts/ tests/ contracts/"
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
```

See `.gitlab-ci-templates.yml` for full advanced templates with auto-issue creation.

## Pipeline Example Output

```
=== ISNAD Security Scanner ===
Targets: src/ lib/
Fail Level: high

--- Scanning directory: src/ ---
🔴 src/handler.ts: critical (2 findings)
🟡 src/utils.ts: medium (1 findings)
✅ src/index.ts: clean (0 findings)

SARIF report written to: isnad-reports/isnad-sarif.json

=== ISNAD scan completed ===
```

## Advanced: Auto-Issue on Critical Findings

The `.gitlab-ci-templates.yml` includes a template that automatically creates
a GitLab issue when critical or high-severity findings are detected, including
the full scan results as description.
20 changes: 20 additions & 0 deletions scanner/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import chalk from 'chalk';
import { analyzeContent, formatResult, type AnalysisResult } from './analyzer.js';
import { submitFlag, checkBalance, createEvidencePackage, hashEvidence, type OracleConfig } from './oracle.js';
import { createHash } from 'crypto';
import { writeFileSync } from 'fs';
import { analysisResultToSarif, sarifToJson, type SarifReport } from './sarif.js';
import 'dotenv/config';

const program = new Command();
Expand Down Expand Up @@ -176,6 +178,7 @@ program
.argument('<pattern>', 'Glob pattern for files to scan')
.option('-j, --json', 'Output as JSON')
.option('--fail-fast', 'Exit on first critical/high finding')
.option('--sarif-output <path>', 'Export batch results as SARIF to file (GitLab Security Dashboard compatible)')
.action(async (pattern: string, options) => {
const { glob } = await import('glob');
const files = await glob(pattern);
Expand Down Expand Up @@ -234,6 +237,23 @@ program
console.log(` ✅ Clean: ${summary.clean}`);
}

// Export SARIF report if requested
if (options.sarifOutput && results.length > 0) {
const combinedRuns = results.flatMap(r => {
const sarif = analysisResultToSarif(r.result, r.file);
return sarif.runs;
});

const combinedSarif: SarifReport = {
$schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
version: '2.1.0',
runs: combinedRuns,
};

writeFileSync(options.sarifOutput, sarifToJson(combinedSarif));
console.log(chalk.green('\\nSARIF report written to: ' + options.sarifOutput));
}

if (hasHighRisk) {
process.exit(1);
}
Expand Down
3 changes: 2 additions & 1 deletion scanner/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

export { analyzeContent, formatResult, type AnalysisResult, type Finding } from './analyzer.js';
export { DANGEROUS_PATTERNS, SAFE_DOMAINS, type Pattern } from './patterns.js';
export {
export {
submitFlag,
checkBalance,
createEvidencePackage,
hashEvidence,
type OracleConfig,
type FlagSubmission
} from './oracle.js';
export { analysisResultToSarif, toSarifResult, sarifToJson, type SarifResult, type SarifReport } from './sarif.js';
Loading