Integracion CI/CD¶
Guia completa para integrar mcp-scan en pipelines de integracion continua.
Tabla de Contenidos¶
- Vision General
- GitHub Actions
- GitLab CI
- Jenkins
- Azure DevOps
- CircleCI
- Bitbucket Pipelines
- Drone CI
- Estrategias de Integracion
- Notificaciones
Vision General¶
Objetivos de CI/CD¶
| Objetivo | Implementacion |
|---|---|
| Bloquear PRs inseguros | --fail-on high |
| Documentar seguridad | Salida SARIF/JSON |
| Baseline automatico | Baseline management |
| Trending de seguridad | Almacenar historico |
| Cumplimiento | Evidence bundles |
Flujo Tipico¶
Recomendaciones Generales¶
| Fase | Modo | Fail On | Timeout |
|---|---|---|---|
| PR Check | fast | high | 5m |
| Merge to main | fast | critical | 5m |
| Nightly | deep | medium | 30m |
| Release | deep | low | 1h |
GitHub Actions¶
Configuracion Basica¶
# .github/workflows/security-scan.yml
name: Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
mcp-scan:
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install mcp-scan
run: |
curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Run security scan
run: |
mcp-scan scan . \
--mode fast \
--output sarif \
--fail-on high \
--baseline baseline.json \
> results.sarif
- name: Upload SARIF to GitHub
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: results.sarif
Configuracion Avanzada¶
# .github/workflows/security-scan.yml
name: Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 2 * * *' # Nightly a las 2 AM
env:
MCP_SCAN_VERSION: "2.0.0"
jobs:
# Job 1: Scan rapido en PRs
pr-scan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Install mcp-scan
run: |
curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash -s -- v${{ env.MCP_SCAN_VERSION }}
- name: Run fast scan
id: scan
run: |
mcp-scan scan . \
--mode fast \
--output json \
--baseline baseline.json \
> results.json
# Extraer metricas
TOTAL=$(jq '.summary.total' results.json)
CRITICAL=$(jq '.summary.by_severity.critical' results.json)
HIGH=$(jq '.summary.by_severity.high' results.json)
SCORE=$(jq '.msss_score.score' results.json)
echo "total=$TOTAL" >> $GITHUB_OUTPUT
echo "critical=$CRITICAL" >> $GITHUB_OUTPUT
echo "high=$HIGH" >> $GITHUB_OUTPUT
echo "score=$SCORE" >> $GITHUB_OUTPUT
continue-on-error: true
- name: Generate SARIF
run: |
mcp-scan scan . \
--mode fast \
--output sarif \
--baseline baseline.json \
> results.sarif
continue-on-error: true
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: results.sarif
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const total = '${{ steps.scan.outputs.total }}';
const critical = '${{ steps.scan.outputs.critical }}';
const high = '${{ steps.scan.outputs.high }}';
const score = '${{ steps.scan.outputs.score }}';
let emoji = score >= 80 ? ':white_check_mark:' : score >= 60 ? ':warning:' : ':x:';
const body = `## Security Scan Results ${emoji}
| Metric | Value |
|--------|-------|
| Total Findings | ${total} |
| Critical | ${critical} |
| High | ${high} |
| MSSS Score | ${score}/100 |
[View detailed results in Security tab](${context.payload.repository.html_url}/security/code-scanning)
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
- name: Fail if critical findings
run: |
CRITICAL=$(jq '.summary.by_severity.critical' results.json)
HIGH=$(jq '.summary.by_severity.high' results.json)
if [ "$CRITICAL" -gt 0 ] || [ "$HIGH" -gt 0 ]; then
echo "::error::Found $CRITICAL critical and $HIGH high severity findings"
exit 1
fi
# Job 2: Scan nocturno profundo
nightly-scan:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Install mcp-scan
run: |
curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash
- name: Run deep scan
run: |
mcp-scan scan . \
--mode deep \
--output evidence \
--timeout 30m \
> evidence-$(date +%Y%m%d).json
- name: Upload evidence
uses: actions/upload-artifact@v4
with:
name: security-evidence-${{ github.run_number }}
path: evidence-*.json
retention-days: 90
- name: Generate SARIF
run: |
mcp-scan scan . \
--mode deep \
--output sarif \
> results-deep.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results-deep.sarif
category: nightly-deep-scan
GitHub Action Reutilizable¶
# .github/actions/mcp-scan/action.yml
name: 'MCP Security Scan'
description: 'Run mcp-scan security analysis'
inputs:
mode:
description: 'Scan mode (fast/deep)'
default: 'fast'
fail-on:
description: 'Severity to fail on'
default: 'high'
baseline:
description: 'Path to baseline file'
default: ''
upload-sarif:
description: 'Upload SARIF to GitHub'
default: 'true'
outputs:
score:
description: 'MSSS security score'
findings:
description: 'Total findings count'
runs:
using: 'composite'
steps:
- name: Install mcp-scan
shell: bash
run: |
curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Run scan
id: scan
shell: bash
run: |
BASELINE_ARG=""
if [ -n "${{ inputs.baseline }}" ]; then
BASELINE_ARG="--baseline ${{ inputs.baseline }}"
fi
mcp-scan scan . \
--mode ${{ inputs.mode }} \
--output json \
$BASELINE_ARG \
> results.json || true
echo "score=$(jq '.msss_score.score' results.json)" >> $GITHUB_OUTPUT
echo "findings=$(jq '.summary.total' results.json)" >> $GITHUB_OUTPUT
- name: Generate SARIF
if: inputs.upload-sarif == 'true'
shell: bash
run: |
mcp-scan scan . --mode ${{ inputs.mode }} --output sarif > results.sarif
- name: Upload SARIF
if: inputs.upload-sarif == 'true'
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
- name: Check threshold
shell: bash
run: |
mcp-scan scan . \
--mode ${{ inputs.mode }} \
--fail-on ${{ inputs.fail-on }} \
$BASELINE_ARG
Uso:
GitLab CI¶
Configuracion Basica¶
# .gitlab-ci.yml
stages:
- security
mcp-scan:
stage: security
image: mcphub/mcp-scan:latest
script:
- mcp-scan scan . --mode fast --output json --fail-on high > results.json
artifacts:
reports:
sast: results.json
paths:
- results.json
expire_in: 30 days
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Configuracion Avanzada¶
# .gitlab-ci.yml
stages:
- security
- report
variables:
MCP_SCAN_VERSION: "2.0.0"
# Template reutilizable
.mcp-scan-template:
image: mcphub/mcp-scan:${MCP_SCAN_VERSION}
before_script:
- mcp-scan version
# Scan en MRs
mr-security-scan:
extends: .mcp-scan-template
stage: security
script:
- |
mcp-scan scan . \
--mode fast \
--output json \
--baseline baseline.json \
> results.json
- |
# Convertir a formato GitLab SAST
jq '{
version: "15.0.0",
vulnerabilities: [.findings[] | {
id: .id,
category: "sast",
name: .title,
message: .description,
severity: (if .severity == "critical" then "Critical" elif .severity == "high" then "High" elif .severity == "medium" then "Medium" else "Low" end),
location: {
file: .location.file,
start_line: .location.line,
end_line: .location.end_line
},
identifiers: [{
type: "mcp_scan_rule_id",
name: .rule_id,
value: .rule_id
}]
}]
}' results.json > gl-sast-report.json
artifacts:
reports:
sast: gl-sast-report.json
paths:
- results.json
- gl-sast-report.json
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# Scan nocturno profundo
nightly-deep-scan:
extends: .mcp-scan-template
stage: security
script:
- |
mcp-scan scan . \
--mode deep \
--output evidence \
--timeout 30m \
> evidence-$(date +%Y%m%d).json
artifacts:
paths:
- evidence-*.json
expire_in: 90 days
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
# Reporte de seguridad
security-report:
stage: report
image: alpine:latest
script:
- apk add --no-cache jq
- |
SCORE=$(jq '.msss_score.score' results.json)
TOTAL=$(jq '.summary.total' results.json)
echo "Security Score: $SCORE/100"
echo "Total Findings: $TOTAL"
if [ "$SCORE" -lt 60 ]; then
echo "SECURITY SCORE TOO LOW"
exit 1
fi
needs:
- mr-security-scan
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Jenkins¶
Pipeline Basico¶
// Jenkinsfile
pipeline {
agent any
environment {
MCP_SCAN_VERSION = '2.0.0'
}
stages {
stage('Install') {
steps {
sh '''
curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash
'''
}
}
stage('Security Scan') {
steps {
sh '''
mcp-scan scan . \
--mode fast \
--output json \
--fail-on high \
> results.json
'''
}
}
stage('Report') {
steps {
script {
def results = readJSON file: 'results.json'
echo "MSSS Score: ${results.msss_score.score}"
echo "Total Findings: ${results.summary.total}"
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'results.json', fingerprint: true
}
}
}
Pipeline Avanzado¶
// Jenkinsfile
pipeline {
agent any
parameters {
choice(name: 'SCAN_MODE', choices: ['fast', 'deep'], description: 'Scan mode')
choice(name: 'FAIL_ON', choices: ['critical', 'high', 'medium'], description: 'Fail threshold')
}
environment {
MCP_SCAN_HOME = "${WORKSPACE}/.mcp-scan"
}
stages {
stage('Setup') {
steps {
sh '''
curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash
'''
}
}
stage('Security Scan') {
parallel {
stage('MCP Scan') {
steps {
script {
def exitCode = sh(
script: """
mcp-scan scan . \
--mode ${params.SCAN_MODE} \
--output json \
--baseline baseline.json \
> results.json
""",
returnStatus: true
)
env.SCAN_EXIT_CODE = exitCode
}
}
}
stage('SARIF Generation') {
steps {
sh '''
mcp-scan scan . \
--mode fast \
--output sarif \
> results.sarif
'''
}
}
}
}
stage('Analysis') {
steps {
script {
def results = readJSON file: 'results.json'
def score = results.msss_score.score
def total = results.summary.total
def critical = results.summary.by_severity.critical
def high = results.summary.by_severity.high
// Crear resumen
currentBuild.description = """
Score: ${score}/100 |
Findings: ${total} |
Critical: ${critical} |
High: ${high}
"""
// Decidir estado del build
if (critical > 0 || high > 0) {
currentBuild.result = 'UNSTABLE'
}
// Fail si excede umbral
if (params.FAIL_ON == 'critical' && critical > 0) {
error("Critical findings detected")
}
if (params.FAIL_ON == 'high' && (critical > 0 || high > 0)) {
error("High or critical findings detected")
}
}
}
}
stage('Publish') {
steps {
// Publicar SARIF (si hay plugin)
recordIssues(
tools: [sarif(pattern: 'results.sarif')],
qualityGates: [
[threshold: 1, type: 'TOTAL_HIGH', unstable: true],
[threshold: 1, type: 'TOTAL_ERROR', unstable: false]
]
)
}
}
}
post {
always {
archiveArtifacts artifacts: '*.json,*.sarif', fingerprint: true
// Cleanup
cleanWs()
}
failure {
emailext(
subject: "Security Scan Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: """
Security scan detected issues.
Build: ${env.BUILD_URL}
Results: ${env.BUILD_URL}artifact/results.json
""",
to: 'security-team@example.com'
)
}
}
}
Azure DevOps¶
Pipeline YAML¶
# azure-pipelines.yml
trigger:
branches:
include:
- main
- develop
pr:
branches:
include:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
MCP_SCAN_VERSION: '2.0.0'
stages:
- stage: Security
jobs:
- job: MCPScan
steps:
- task: Bash@3
displayName: 'Install mcp-scan'
inputs:
targetType: 'inline'
script: |
curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash
echo "##vso[task.prependpath]$HOME/.local/bin"
- task: Bash@3
displayName: 'Run security scan'
inputs:
targetType: 'inline'
script: |
mcp-scan scan . \
--mode fast \
--output sarif \
--fail-on high \
--baseline baseline.json \
> $(Build.ArtifactStagingDirectory)/results.sarif
- task: PublishBuildArtifacts@1
displayName: 'Publish SARIF'
inputs:
pathtoPublish: '$(Build.ArtifactStagingDirectory)/results.sarif'
artifactName: 'SecurityScan'
# Si tienes Advanced Security habilitado
- task: AdvancedSecurity-Publish@1
displayName: 'Publish to Advanced Security'
inputs:
sarif: '$(Build.ArtifactStagingDirectory)/results.sarif'
Pipeline con Templates¶
# templates/mcp-scan.yml
parameters:
- name: mode
type: string
default: 'fast'
- name: failOn
type: string
default: 'high'
steps:
- task: Bash@3
displayName: 'Install mcp-scan'
inputs:
targetType: 'inline'
script: |
curl -sSL https://raw.githubusercontent.com/mcphub/mcp-scan/main/install.sh | bash
- task: Bash@3
displayName: 'Run mcp-scan'
inputs:
targetType: 'inline'
script: |
mcp-scan scan . \
--mode ${{ parameters.mode }} \
--fail-on ${{ parameters.failOn }} \
--output json \
> results.json
- task: Bash@3
displayName: 'Check results'
inputs:
targetType: 'inline'
script: |
SCORE=$(jq '.msss_score.score' results.json)
echo "##vso[task.setvariable variable=SecurityScore]$SCORE"
echo "Security Score: $SCORE/100"
Uso:
# azure-pipelines.yml
stages:
- stage: Security
jobs:
- job: Scan
steps:
- template: templates/mcp-scan.yml
parameters:
mode: 'fast'
failOn: 'high'
CircleCI¶
# .circleci/config.yml
version: 2.1
executors:
mcp-scan:
docker:
- image: mcphub/mcp-scan:latest
jobs:
security-scan:
executor: mcp-scan
steps:
- checkout
- run:
name: Run security scan
command: |
mcp-scan scan . \
--mode fast \
--output json \
--fail-on high \
> results.json
- store_artifacts:
path: results.json
destination: security-scan
nightly-scan:
executor: mcp-scan
steps:
- checkout
- run:
name: Run deep scan
command: |
mcp-scan scan . \
--mode deep \
--output evidence \
--timeout 30m \
> evidence.json
- store_artifacts:
path: evidence.json
destination: nightly-evidence
workflows:
version: 2
pr-checks:
jobs:
- security-scan:
filters:
branches:
ignore: main
nightly:
triggers:
- schedule:
cron: "0 2 * * *"
filters:
branches:
only: main
jobs:
- nightly-scan
Bitbucket Pipelines¶
# bitbucket-pipelines.yml
image: mcphub/mcp-scan:latest
pipelines:
pull-requests:
'**':
- step:
name: Security Scan
script:
- mcp-scan scan . --mode fast --output json --fail-on high > results.json
artifacts:
- results.json
branches:
main:
- step:
name: Security Scan
script:
- mcp-scan scan . --mode fast --output json > results.json
- |
SCORE=$(jq '.msss_score.score' results.json)
echo "Security Score: $SCORE/100"
artifacts:
- results.json
scheduled:
- step:
name: Nightly Deep Scan
script:
- mcp-scan scan . --mode deep --output evidence > evidence.json
artifacts:
- evidence.json
Drone CI¶
# .drone.yml
kind: pipeline
type: docker
name: security
steps:
- name: mcp-scan
image: mcphub/mcp-scan:latest
commands:
- mcp-scan scan . --mode fast --output json --fail-on high > results.json
when:
event:
- push
- pull_request
- name: deep-scan
image: mcphub/mcp-scan:latest
commands:
- mcp-scan scan . --mode deep --output evidence > evidence.json
when:
event:
- cron
cron:
- nightly
trigger:
branch:
- main
- develop
event:
- push
- pull_request
- cron
Estrategias de Integracion¶
Estrategia 1: Gate de Seguridad¶
Estrategia 2: Trending de Seguridad¶
# Almacenar historico de scores
- name: Track score
run: |
SCORE=$(jq '.msss_score.score' results.json)
DATE=$(date +%Y-%m-%d)
echo "$DATE,$SCORE" >> security-history.csv
Estrategia 3: Baseline Automatico¶
# Generar baseline en main
- name: Update baseline
if: github.ref == 'refs/heads/main'
run: |
mcp-scan scan . --output json > scan.json
mcp-scan baseline generate --from scan.json
git add baseline.json
git commit -m "Update security baseline" || true
Estrategia 4: Escaneo Diferencial¶
# Solo escanear archivos modificados
- name: Get changed files
id: files
run: |
FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep -E '\.(py|ts|js)$' | tr '\n' ' ')
echo "files=$FILES" >> $GITHUB_OUTPUT
- name: Scan changed files
run: |
mcp-scan scan ${{ steps.files.outputs.files }} --mode fast
Notificaciones¶
Slack¶
- name: Notify Slack
if: failure()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,message,commit,author
text: |
Security scan failed!
Score: ${{ steps.scan.outputs.score }}/100
Findings: ${{ steps.scan.outputs.total }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
Email¶
- name: Send email
if: failure()
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.example.com
username: ${{ secrets.MAIL_USERNAME }}
password: ${{ secrets.MAIL_PASSWORD }}
subject: "Security Scan Failed: ${{ github.repository }}"
to: security@example.com
body: |
Security scan detected issues.
Repository: ${{ github.repository }}
Branch: ${{ github.ref }}
Commit: ${{ github.sha }}
View results: ${{ github.server_url }}/${{ github.repository }}/security/code-scanning
Teams¶
- name: Notify Teams
if: failure()
uses: jdcargile/ms-teams-notification@v1.3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
ms-teams-webhook-uri: ${{ secrets.TEAMS_WEBHOOK }}
notification-summary: "Security scan failed"
notification-color: "dc3545"
Anterior: Gestion de Baselines | Siguiente: Casos de Uso