Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Design and implement a comprehensive DevSecOps pipeline in GitLab CI/CD integrating SAST, DAST, container scanning, dependency scanning, and secret detection.
.claude/skills/building-devsecops-pipeline-with-gitlab-ci/SKILL.md| Model | Eval pass | Runs |
|---|---|---|
| gemini-3.1-pro-preview | 100% | 6 |
| gemini-3.6-flash | 100% | 1 |
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-14 | ✗→✓ | ▲ Improved | — | — |
| case-11 | ✗→✓ | ▲ Improved | — | — |
| case-22 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-09 | ✗→✓ | ▲ Improved | — | — |
GitLab provides an integrated DevSecOps platform that embeds security testing directly into the CI/CD pipeline. By leveraging GitLab's built-in security scanners---SAST, DAST, container scanning, dependency scanning, secret detection, and license compliance---teams can shift security left, catching vulnerabilities during development rather than post-deployment. GitLab Duo AI assists with false positive detection for SAST vulnerabilities, helping security teams focus on genuine issues.
.gitlab-ci.yml pipeline configuration familiaritySAST analyzes source code for vulnerabilities before compilation. GitLab supports 14+ languages using analyzers such as Semgrep, SpotBugs, Gosec, Bandit, and NodeJsScan. The simplest inclusion uses GitLab's managed templates.
DAST tests running applications by simulating attack payloads against HTTP endpoints. It detects XSS, SQLi, CSRF, and other runtime vulnerabilities that static analysis cannot find. DAST requires a deployed, accessible target URL.
Uses Trivy to scan Docker images for known CVEs in OS packages and application dependencies. Runs after the Docker build stage to gate images before they reach a registry.
Inspects dependency manifests (package.json, requirements.txt, pom.xml, Gemfile.lock) for known vulnerable versions. Operates at the source code level, complementing container scanning.
Scans commits for accidentally committed credentials, API keys, tokens, and private keys using pattern matching and entropy analysis. Runs on every commit to prevent secrets from reaching the repository.
yaml# .gitlab-ci.yml stages: - build - test - security - deploy-staging - dast - deploy-production variables: DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA SECURE_LOG_LEVEL: "info" # Include GitLab managed security templates include: - template: Security/SAST.gitlab-ci.yml - template: Security/Secret-Detection.gitlab-ci.yml - template: Security/Dependency-Scanning.gitlab-ci.yml - template: Security/Container-Scanning.gitlab-ci.yml - template: DAST.gitlab-ci.yml - template: Security/License-Scanning.gitlab-ci.yml build: stage: build image: docker:24.0 services: - docker:24.0-dind variables: DOCKER_TLS_CERTDIR: "/certs" script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker build -t $DOCKER_IMAGE . - docker push $DOCKER_IMAGE rules: - if: $CI_COMMIT_BRANCH unit-tests: stage: test image: $DOCKER_IMAGE script: - npm ci - npm run test:coverage coverage: '/Lines\s*:\s*(\d+\.?\d*)%/' artifacts: reports: junit: junit-report.xml coverage_report: coverage_format: cobertura path: coverage/cobertura-coverage.xml # Override SAST to run in security stage sast: stage: security variables: SAST_EXCLUDED_PATHS: "spec,test,tests,tmp,node_modules" SEARCH_MAX_DEPTH: 10 # Override container scanning container_scanning: stage: security variables: CS_IMAGE: $DOCKER_IMAGE CS_SEVERITY_THRESHOLD: "HIGH" # Override dependency scanning dependency_scanning: stage: security # Override secret detection secret_detection: stage: security # License compliance scanning license_scanning: stage: security deploy-staging: stage: deploy-staging image: bitnami/kubectl:latest script: - kubectl set image deployment/app app=$DOCKER_IMAGE -n staging - kubectl rollout status deployment/app -n staging --timeout=300s environment: name: staging url: https://staging.example.com rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # DAST runs against deployed staging dast: stage: dast variables: DAST_WEBSITE: https://staging.example.com DAST_FULL_SCAN_ENABLED: "true" DAST_BROWSER_SCAN: "true" needs: - deploy-staging rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH deploy-production: stage: deploy-production image: bitnami/kubectl:latest script: - kubectl set image deployment/app app=$DOCKER_IMAGE -n production - kubectl rollout status deployment/app -n production --timeout=300s environment: name: production url: https://app.example.com when: manual rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Configure scan execution policies to enforce mandatory security scans:
Create .gitlab/sast-ruleset.toml to customize analyzer behavior:
toml[semgrep] [[semgrep.ruleset]] dirs = ["src"] [[semgrep.passthrough]] type = "url" target = "/sgrep-rules/custom-rules.yml" value = "https://semgrep.dev/p/owasp-top-ten" [[semgrep.passthrough]] type = "url" target = "/sgrep-rules/java-rules.yml" value = "https://semgrep.dev/p/java"
GitLab consolidates all scanner findings into a single Vulnerability Report accessible at Security & Compliance > Vulnerability Report. Each vulnerability includes:
Every merge request displays a security scanning widget showing:
SAST_INCREMENTAL: "true"allow_failure: false on critical scanners to enforce quality gates| Metric | Description | Target | |--------|-------------|--------| | Pipeline security coverage | Percentage of projects with all scanners enabled | > 95% | | Critical vulnerability MTTR | Time from detection to resolution for critical findings | < 48 hours | | False positive rate | Percentage of dismissed-as-false-positive findings | < 15% | | Secret detection block rate | Percentage of secret commits blocked by push rules | > 99% |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +36 percentage points is the difference between those two pass rates over the 21 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.