Initial commit
This commit is contained in:
9
skills/devsecops/secrets-gitleaks/assets/.gitkeep
Normal file
9
skills/devsecops/secrets-gitleaks/assets/.gitkeep
Normal file
@@ -0,0 +1,9 @@
|
||||
# Assets Directory
|
||||
|
||||
Place files that will be used in the output Claude produces:
|
||||
- Templates
|
||||
- Configuration files
|
||||
- Images/logos
|
||||
- Boilerplate code
|
||||
|
||||
These files are NOT loaded into context but copied/modified in output.
|
||||
@@ -0,0 +1,81 @@
|
||||
# Gitleaks Balanced Configuration
|
||||
# Production-ready configuration balancing security and developer experience
|
||||
# Use for: Most production repositories
|
||||
|
||||
title = "Gitleaks Balanced Configuration"
|
||||
|
||||
[extend]
|
||||
# Extend default Gitleaks rules
|
||||
useDefault = true
|
||||
|
||||
[allowlist]
|
||||
description = "Balanced allowlist for common false positives"
|
||||
|
||||
# Standard non-production paths
|
||||
paths = [
|
||||
'''test/.*''',
|
||||
'''tests/.*''',
|
||||
'''.*/fixtures/.*''',
|
||||
'''.*/testdata/.*''',
|
||||
'''spec/.*''',
|
||||
'''examples?/.*''',
|
||||
'''docs?/.*''',
|
||||
'''\.md$''',
|
||||
'''\.rst$''',
|
||||
'''\.txt$''',
|
||||
'''node_modules/.*''',
|
||||
'''vendor/.*''',
|
||||
'''third[_-]party/.*''',
|
||||
'''\.min\.js$''',
|
||||
'''\.min\.css$''',
|
||||
'''dist/.*''',
|
||||
'''build/.*''',
|
||||
'''target/.*''',
|
||||
'''.*/mocks?/.*''',
|
||||
]
|
||||
|
||||
# Common placeholder patterns
|
||||
stopwords = [
|
||||
"example",
|
||||
"placeholder",
|
||||
"your_api_key_here",
|
||||
"your_key_here",
|
||||
"your_secret_here",
|
||||
"replace_me",
|
||||
"replaceme",
|
||||
"changeme",
|
||||
"change_me",
|
||||
"insert_key_here",
|
||||
"xxxxxx",
|
||||
"000000",
|
||||
"123456",
|
||||
"abcdef",
|
||||
"sample",
|
||||
"dummy",
|
||||
"fake",
|
||||
"test_key",
|
||||
"test_secret",
|
||||
"test_password",
|
||||
"test_token",
|
||||
"mock",
|
||||
"TODO",
|
||||
]
|
||||
|
||||
# Public non-secrets
|
||||
regexes = [
|
||||
'''-----BEGIN CERTIFICATE-----''',
|
||||
'''-----BEGIN PUBLIC KEY-----''',
|
||||
'''data:image/[^;]+;base64,''',
|
||||
'''[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}''', # UUID
|
||||
]
|
||||
|
||||
# Manually verified false positives (add with comments)
|
||||
commits = []
|
||||
|
||||
# Custom rules for organization-specific patterns can be added below
|
||||
|
||||
# Example: Allowlist template files
|
||||
# [[rules]]
|
||||
# id = "generic-api-key"
|
||||
# [rules.allowlist]
|
||||
# paths = ['''config/.*\.template$''', '''config/.*\.example$''']
|
||||
178
skills/devsecops/secrets-gitleaks/assets/config-custom.toml
Normal file
178
skills/devsecops/secrets-gitleaks/assets/config-custom.toml
Normal file
@@ -0,0 +1,178 @@
|
||||
# Gitleaks Custom Configuration Template
|
||||
# Use this as a starting point for organization-specific detection rules
|
||||
|
||||
title = "Custom Gitleaks Configuration"
|
||||
|
||||
[extend]
|
||||
# Extend default Gitleaks rules with custom rules
|
||||
useDefault = true
|
||||
|
||||
# =============================================================================
|
||||
# GLOBAL ALLOWLIST
|
||||
# =============================================================================
|
||||
# Global allowlists apply to ALL rules and have highest precedence
|
||||
|
||||
[allowlist]
|
||||
description = "Global allowlist for organization-wide exceptions"
|
||||
|
||||
# Paths to exclude from scanning
|
||||
paths = [
|
||||
# Test and documentation
|
||||
'''test/.*''',
|
||||
'''docs?/.*''',
|
||||
'''examples?/.*''',
|
||||
|
||||
# Dependencies
|
||||
'''node_modules/.*''',
|
||||
'''vendor/.*''',
|
||||
|
||||
# Build artifacts
|
||||
'''dist/.*''',
|
||||
'''build/.*''',
|
||||
]
|
||||
|
||||
# Known placeholder values
|
||||
stopwords = [
|
||||
"example",
|
||||
"placeholder",
|
||||
"your_key_here",
|
||||
"test",
|
||||
"mock",
|
||||
"dummy",
|
||||
]
|
||||
|
||||
# Public non-secrets
|
||||
regexes = [
|
||||
'''-----BEGIN CERTIFICATE-----''',
|
||||
'''-----BEGIN PUBLIC KEY-----''',
|
||||
]
|
||||
|
||||
# Manually verified commits (add with explanatory comments)
|
||||
commits = []
|
||||
|
||||
# =============================================================================
|
||||
# CUSTOM DETECTION RULES
|
||||
# =============================================================================
|
||||
# Add organization-specific secret patterns here
|
||||
|
||||
# Example: Custom API Key Pattern
|
||||
[[rules]]
|
||||
id = "acme-corp-api-key"
|
||||
description = "ACME Corp Internal API Key"
|
||||
# Regex pattern to match your organization's API key format
|
||||
# Use triple-quoted strings for complex patterns
|
||||
regex = '''(?i)acme[_-]?api[_-]?key[\s]*[=:][\s]*['"]?([a-zA-Z0-9]{40})['"]?'''
|
||||
# Capture group containing the actual secret (for entropy analysis)
|
||||
secretGroup = 1
|
||||
# Tags for categorization and filtering
|
||||
tags = ["api-key", "acme-internal"]
|
||||
|
||||
# Optional: Rule-specific allowlist (lower precedence than global)
|
||||
#[rules.allowlist]
|
||||
#paths = ['''config/defaults\.yaml''']
|
||||
#stopwords = ["DEFAULT_KEY"]
|
||||
|
||||
# Example: Custom Database Password Pattern
|
||||
[[rules]]
|
||||
id = "acme-corp-db-password"
|
||||
description = "ACME Corp Database Password Format"
|
||||
# Matches company-specific password format
|
||||
regex = '''(?i)(db_pass|database_password)[\s]*[=:][\s]*['"]([A-Z][a-z0-9@#$%]{15,})['"]'''
|
||||
secretGroup = 2
|
||||
tags = ["password", "database", "acme-internal"]
|
||||
|
||||
# Example: High-Entropy Detection with Custom Threshold
|
||||
[[rules]]
|
||||
id = "high-entropy-string"
|
||||
description = "High entropy string (potential secret)"
|
||||
# Match strings of 32+ alphanumeric characters
|
||||
regex = '''[a-zA-Z0-9+/]{32,}'''
|
||||
# Shannon entropy threshold (0.0 - 8.0, higher = more random)
|
||||
entropy = 4.5
|
||||
# Which capture group to analyze (0 = entire match)
|
||||
secretGroup = 0
|
||||
tags = ["entropy", "generic"]
|
||||
|
||||
[rules.allowlist]
|
||||
# Allowlist base64-encoded images
|
||||
regexes = ['''data:image/[^;]+;base64,''']
|
||||
|
||||
# Example: Custom Service Account Key
|
||||
[[rules]]
|
||||
id = "acme-corp-service-account"
|
||||
description = "ACME Corp Service Account JSON Key"
|
||||
# Detect JSON structure with specific fields
|
||||
regex = '''"type":\s*"acme_service_account"'''
|
||||
tags = ["service-account", "acme-internal"]
|
||||
|
||||
# Example: Custom OAuth Token Format
|
||||
[[rules]]
|
||||
id = "acme-corp-oauth-token"
|
||||
description = "ACME Corp OAuth Token"
|
||||
# Custom token format: acme_oauth_v1_<40 hex chars>
|
||||
regex = '''acme_oauth_v1_[a-f0-9]{40}'''
|
||||
tags = ["oauth", "token", "acme-internal"]
|
||||
|
||||
# =============================================================================
|
||||
# TESTING CUSTOM RULES
|
||||
# =============================================================================
|
||||
# Test your custom rules with:
|
||||
# gitleaks detect --config config-custom.toml -v
|
||||
#
|
||||
# Test against specific file:
|
||||
# gitleaks detect --config config-custom.toml --source path/to/file --no-git
|
||||
#
|
||||
# Test regex pattern online:
|
||||
# https://regex101.com/ (select Golang flavor)
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# =============================================================================
|
||||
# ENTROPY ANALYSIS GUIDE
|
||||
# =============================================================================
|
||||
# Entropy values (Shannon entropy):
|
||||
# 0.0 - 2.5: Very low (repeated characters, simple patterns)
|
||||
# 2.5 - 3.5: Low (common words, simple sequences)
|
||||
# 3.5 - 4.5: Medium (mixed case, some randomness)
|
||||
# 4.5 - 5.5: High (strong randomness, likely secret)
|
||||
# 5.5 - 8.0: Very high (cryptographic randomness)
|
||||
#
|
||||
# Recommended thresholds:
|
||||
# - API keys: 4.5+
|
||||
# - Passwords: 3.5+
|
||||
# - Tokens: 4.5+
|
||||
# - Generic secrets: 5.0+
|
||||
# =============================================================================
|
||||
|
||||
# =============================================================================
|
||||
# REGEX CAPTURE GROUPS
|
||||
# =============================================================================
|
||||
# Use capture groups to extract the actual secret from surrounding text:
|
||||
#
|
||||
# regex = '''api_key\s*=\s*"([a-zA-Z0-9]+)"'''
|
||||
# ^^^^^^^^^
|
||||
# Group 1
|
||||
#
|
||||
# secretGroup = 1 # Analyze only the key value, not 'api_key = ""'
|
||||
#
|
||||
# This improves entropy analysis accuracy and reduces false positives.
|
||||
# =============================================================================
|
||||
|
||||
# =============================================================================
|
||||
# COMPOSITE RULES (Advanced)
|
||||
# =============================================================================
|
||||
# Gitleaks v8.28.0+ supports composite rules for context-aware detection
|
||||
# Useful for secrets that require nearby context (multi-line patterns)
|
||||
|
||||
#[[rules]]
|
||||
#id = "composite-api-key"
|
||||
#description = "API key with usage context"
|
||||
#regex = '''api_key\s*='''
|
||||
#
|
||||
#[[rules.composite]]
|
||||
#pattern = '''initialize_client'''
|
||||
#location = "line" # "line", "fragment", or "commit"
|
||||
#distance = 5 # Within 5 lines
|
||||
#
|
||||
# This detects api_key = "..." only when "initialize_client" appears within 5 lines
|
||||
# =============================================================================
|
||||
48
skills/devsecops/secrets-gitleaks/assets/config-strict.toml
Normal file
48
skills/devsecops/secrets-gitleaks/assets/config-strict.toml
Normal file
@@ -0,0 +1,48 @@
|
||||
# Gitleaks Strict Configuration
|
||||
# High-sensitivity detection with minimal allowlisting
|
||||
# Use for: Security-critical repositories, financial services, healthcare
|
||||
|
||||
title = "Gitleaks Strict Configuration"
|
||||
|
||||
[extend]
|
||||
# Use all default Gitleaks rules
|
||||
useDefault = true
|
||||
|
||||
[allowlist]
|
||||
description = "Minimal allowlist - only proven false positives"
|
||||
|
||||
# Only allow in build artifacts and dependencies
|
||||
paths = [
|
||||
'''node_modules/.*''',
|
||||
'''vendor/.*''',
|
||||
'''\.min\.js$''',
|
||||
'''\.min\.css$''',
|
||||
]
|
||||
|
||||
# Only obvious non-secret patterns
|
||||
stopwords = [
|
||||
"EXAMPLE_DO_NOT_USE",
|
||||
"PLACEHOLDER_REPLACE_ME",
|
||||
]
|
||||
|
||||
# All commits must be manually verified before allowlisting
|
||||
commits = []
|
||||
|
||||
# Additional strict rules for high-value targets
|
||||
|
||||
[[rules]]
|
||||
id = "strict-env-file"
|
||||
description = "Detect any .env files (should not be in repo)"
|
||||
regex = '''.*'''
|
||||
path = '''\.env$'''
|
||||
tags = ["env-file", "strict"]
|
||||
|
||||
[[rules]]
|
||||
id = "strict-config-secrets"
|
||||
description = "Config files with potential secrets"
|
||||
regex = '''(?i)(password|secret|key|token|credential)[\s]*[=:][\s]*['"]?([a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]{8,})['"]?'''
|
||||
secretGroup = 2
|
||||
tags = ["config", "strict"]
|
||||
[rules.allowlist]
|
||||
paths = ['''test/.*''']
|
||||
stopwords = ["EXAMPLE"]
|
||||
181
skills/devsecops/secrets-gitleaks/assets/github-action.yml
Normal file
181
skills/devsecops/secrets-gitleaks/assets/github-action.yml
Normal file
@@ -0,0 +1,181 @@
|
||||
# GitHub Actions Workflow for Gitleaks Secret Scanning
|
||||
# Save as: .github/workflows/gitleaks.yml
|
||||
|
||||
name: Secret Scanning with Gitleaks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
- 'release/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
schedule:
|
||||
# Run daily at 2 AM UTC
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch: # Allow manual triggers
|
||||
|
||||
# Cancel in-progress runs when new commit pushed
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
gitleaks-scan:
|
||||
name: Scan for Secrets
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
# Required for uploading SARIF results to GitHub Security tab
|
||||
security-events: write
|
||||
# Required for checking out private repos
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Fetch full history for comprehensive scanning
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run Gitleaks Scan
|
||||
id: gitleaks
|
||||
uses: gitleaks/gitleaks-action@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Optional: Use custom configuration
|
||||
# GITLEAKS_CONFIG: .gitleaks.toml
|
||||
|
||||
# Optional: Generate JSON report for further processing
|
||||
- name: Generate JSON Report
|
||||
if: always() # Run even if secrets found
|
||||
run: |
|
||||
docker run --rm -v ${{ github.workspace }}:/repo \
|
||||
zricethezav/gitleaks:latest \
|
||||
detect --source /repo \
|
||||
--report-path /repo/gitleaks-report.json \
|
||||
--report-format json \
|
||||
--exit-code 0 || true
|
||||
|
||||
# Optional: Upload JSON report as artifact
|
||||
- name: Upload Scan Report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gitleaks-report
|
||||
path: gitleaks-report.json
|
||||
retention-days: 30
|
||||
|
||||
# Optional: Generate SARIF report for GitHub Security tab
|
||||
- name: Generate SARIF Report
|
||||
if: always()
|
||||
run: |
|
||||
docker run --rm -v ${{ github.workspace }}:/repo \
|
||||
zricethezav/gitleaks:latest \
|
||||
detect --source /repo \
|
||||
--report-path /repo/gitleaks.sarif \
|
||||
--report-format sarif \
|
||||
--exit-code 0 || true
|
||||
|
||||
# Optional: Upload SARIF report to GitHub Security
|
||||
- name: Upload SARIF to GitHub Security
|
||||
if: always()
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: gitleaks.sarif
|
||||
category: gitleaks
|
||||
|
||||
# Optional: Comment on PR with findings
|
||||
- name: Comment PR with Findings
|
||||
if: failure() && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
try {
|
||||
const report = JSON.parse(fs.readFileSync('gitleaks-report.json', 'utf8'));
|
||||
const findings = report.length;
|
||||
|
||||
const comment = `## 🔒 Secret Scanning Results
|
||||
|
||||
⚠️ **${findings} potential secret(s) detected!**
|
||||
|
||||
Please review the findings and take immediate action:
|
||||
1. **Do not merge** this PR until secrets are removed
|
||||
2. Rotate any exposed credentials immediately
|
||||
3. Remove secrets from code and use environment variables
|
||||
4. Review the security tab for detailed findings
|
||||
|
||||
See [Secret Scanning Guide](https://github.com/${{ github.repository }}/blob/main/docs/secret-scanning.md) for remediation steps.`;
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('No report file or error reading it:', error.message);
|
||||
}
|
||||
|
||||
# Optional: Post to Slack on failure
|
||||
- name: Notify Slack on Failure
|
||||
if: failure()
|
||||
uses: slackapi/slack-github-action@v1
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "🚨 Secrets detected in ${{ github.repository }}",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*Secret Scanning Alert*\n\nSecrets detected in repository: `${{ github.repository }}`\nBranch: `${{ github.ref_name }}`\nCommit: `${{ github.sha }}`\n\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Details>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
|
||||
# Optional: Baseline scanning for incremental detection
|
||||
baseline-scan:
|
||||
name: Incremental Scan Against Baseline
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download Existing Baseline
|
||||
continue-on-error: true
|
||||
run: |
|
||||
# Download baseline from artifact storage or S3
|
||||
# Example: aws s3 cp s3://bucket/.gitleaks-baseline.json .
|
||||
echo "Baseline download would go here"
|
||||
|
||||
- name: Run Incremental Scan
|
||||
run: |
|
||||
docker run --rm -v ${{ github.workspace }}:/repo \
|
||||
zricethezav/gitleaks:latest \
|
||||
detect --source /repo \
|
||||
--baseline-path /repo/.gitleaks-baseline.json \
|
||||
--report-path /repo/new-findings.json \
|
||||
--report-format json \
|
||||
--exit-code 1 || true
|
||||
|
||||
- name: Upload New Findings
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: new-findings
|
||||
path: new-findings.json
|
||||
retention-days: 90
|
||||
257
skills/devsecops/secrets-gitleaks/assets/gitlab-ci.yml
Normal file
257
skills/devsecops/secrets-gitleaks/assets/gitlab-ci.yml
Normal file
@@ -0,0 +1,257 @@
|
||||
# GitLab CI Pipeline for Gitleaks Secret Scanning
|
||||
# Save as: .gitlab-ci.yml or include in existing pipeline
|
||||
|
||||
# Define stages
|
||||
stages:
|
||||
- security
|
||||
- report
|
||||
|
||||
# Default Docker image for security jobs
|
||||
image: docker:latest
|
||||
|
||||
services:
|
||||
- docker:dind
|
||||
|
||||
variables:
|
||||
# Gitleaks Docker image
|
||||
GITLEAKS_IMAGE: zricethezav/gitleaks:latest
|
||||
# Report output path
|
||||
REPORT_PATH: gitleaks-report.json
|
||||
# SARIF output for GitLab Security Dashboard
|
||||
SARIF_PATH: gl-secret-detection-report.json
|
||||
|
||||
# Secret scanning job
|
||||
gitleaks-scan:
|
||||
stage: security
|
||||
image: $GITLEAKS_IMAGE
|
||||
|
||||
# Run on all branches and merge requests
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
||||
- if: '$CI_COMMIT_BRANCH =~ /^(develop|release)/'
|
||||
|
||||
script:
|
||||
# Run Gitleaks scan
|
||||
- echo "Running Gitleaks secret detection..."
|
||||
- |
|
||||
gitleaks detect \
|
||||
--source . \
|
||||
--report-path $REPORT_PATH \
|
||||
--report-format json \
|
||||
--verbose || true
|
||||
|
||||
# Convert to GitLab SARIF format for Security Dashboard
|
||||
- |
|
||||
gitleaks detect \
|
||||
--source . \
|
||||
--report-path $SARIF_PATH \
|
||||
--report-format sarif \
|
||||
--verbose || true
|
||||
|
||||
# Check if secrets were found
|
||||
- |
|
||||
if [ -s "$REPORT_PATH" ] && [ "$(cat $REPORT_PATH)" != "null" ]; then
|
||||
echo "⚠️ Secrets detected! Review findings below."
|
||||
cat $REPORT_PATH | jq -r '.[] | "File: \(.File)\nLine: \(.StartLine)\nRule: \(.RuleID)\n"'
|
||||
exit 1
|
||||
else
|
||||
echo "✅ No secrets detected"
|
||||
fi
|
||||
|
||||
artifacts:
|
||||
paths:
|
||||
- $REPORT_PATH
|
||||
- $SARIF_PATH
|
||||
reports:
|
||||
# GitLab Security Dashboard integration
|
||||
secret_detection: $SARIF_PATH
|
||||
when: always
|
||||
expire_in: 30 days
|
||||
|
||||
# Allow failure for initial rollout, then set to false
|
||||
allow_failure: false
|
||||
|
||||
# Optional: Incremental scanning with baseline
|
||||
gitleaks-incremental:
|
||||
stage: security
|
||||
image: $GITLEAKS_IMAGE
|
||||
|
||||
# Only run on merge requests
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
|
||||
script:
|
||||
# Download baseline from artifacts or storage
|
||||
- echo "Downloading baseline..."
|
||||
- |
|
||||
if [ -f ".gitleaks-baseline.json" ]; then
|
||||
echo "Using baseline from repository"
|
||||
else
|
||||
echo "No baseline found, running full scan"
|
||||
fi
|
||||
|
||||
# Run incremental scan
|
||||
- |
|
||||
if [ -f ".gitleaks-baseline.json" ]; then
|
||||
gitleaks detect \
|
||||
--source . \
|
||||
--baseline-path .gitleaks-baseline.json \
|
||||
--report-path new-findings.json \
|
||||
--report-format json \
|
||||
--exit-code 1 || true
|
||||
|
||||
if [ -s "new-findings.json" ] && [ "$(cat new-findings.json)" != "null" ]; then
|
||||
echo "⚠️ New secrets detected since baseline!"
|
||||
cat new-findings.json | jq .
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
artifacts:
|
||||
paths:
|
||||
- new-findings.json
|
||||
when: always
|
||||
expire_in: 7 days
|
||||
|
||||
# Optional: Create baseline on main branch
|
||||
create-baseline:
|
||||
stage: security
|
||||
image: $GITLEAKS_IMAGE
|
||||
|
||||
# Only run on main/master branch
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
||||
when: manual # Manual trigger to avoid overwriting
|
||||
|
||||
script:
|
||||
- echo "Creating new baseline..."
|
||||
- |
|
||||
gitleaks detect \
|
||||
--source . \
|
||||
--report-path .gitleaks-baseline.json \
|
||||
--report-format json \
|
||||
--exit-code 0 || true
|
||||
|
||||
artifacts:
|
||||
paths:
|
||||
- .gitleaks-baseline.json
|
||||
expire_in: 365 days
|
||||
|
||||
# Optional: Generate human-readable report
|
||||
generate-report:
|
||||
stage: report
|
||||
image: python:3.11-slim
|
||||
|
||||
dependencies:
|
||||
- gitleaks-scan
|
||||
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
||||
|
||||
script:
|
||||
- pip install jinja2
|
||||
- |
|
||||
python3 << 'EOF'
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
with open('gitleaks-report.json', 'r') as f:
|
||||
findings = json.load(f)
|
||||
|
||||
if not findings:
|
||||
print("✅ No secrets detected")
|
||||
sys.exit(0)
|
||||
|
||||
print("# Gitleaks Secret Detection Report")
|
||||
print(f"\n**Generated**: {datetime.now().isoformat()}")
|
||||
print(f"**Total Findings**: {len(findings)}\n")
|
||||
|
||||
for idx, finding in enumerate(findings, 1):
|
||||
print(f"\n## Finding {idx}")
|
||||
print(f"- **File**: {finding.get('File', 'unknown')}")
|
||||
print(f"- **Line**: {finding.get('StartLine', 'unknown')}")
|
||||
print(f"- **Rule**: {finding.get('RuleID', 'unknown')}")
|
||||
print(f"- **Description**: {finding.get('Description', 'unknown')}")
|
||||
print(f"- **Commit**: {finding.get('Commit', 'N/A')}\n")
|
||||
|
||||
except FileNotFoundError:
|
||||
print("No report file found")
|
||||
except json.JSONDecodeError:
|
||||
print("No findings in report")
|
||||
EOF
|
||||
|
||||
artifacts:
|
||||
paths:
|
||||
- gitleaks-report.json
|
||||
|
||||
# Optional: Comment on merge request
|
||||
comment-mr:
|
||||
stage: report
|
||||
image: alpine:latest
|
||||
|
||||
dependencies:
|
||||
- gitleaks-scan
|
||||
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
|
||||
before_script:
|
||||
- apk add --no-cache curl jq
|
||||
|
||||
script:
|
||||
- |
|
||||
if [ -s "$REPORT_PATH" ] && [ "$(cat $REPORT_PATH)" != "null" ]; then
|
||||
FINDING_COUNT=$(cat $REPORT_PATH | jq '. | length')
|
||||
|
||||
COMMENT="## 🔒 Secret Scanning Results\n\n"
|
||||
COMMENT="${COMMENT}⚠️ **${FINDING_COUNT} potential secret(s) detected!**\n\n"
|
||||
COMMENT="${COMMENT}Please review the findings and take immediate action:\n"
|
||||
COMMENT="${COMMENT}1. **Do not merge** this MR until secrets are removed\n"
|
||||
COMMENT="${COMMENT}2. Rotate any exposed credentials immediately\n"
|
||||
COMMENT="${COMMENT}3. Remove secrets from code and use CI/CD variables\n\n"
|
||||
COMMENT="${COMMENT}See pipeline artifacts for detailed findings."
|
||||
|
||||
# Post comment to merge request
|
||||
curl --request POST \
|
||||
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
|
||||
--data-urlencode "body=$COMMENT" \
|
||||
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes"
|
||||
fi
|
||||
|
||||
allow_failure: true
|
||||
|
||||
# Optional: Scheduled nightly scan
|
||||
nightly-scan:
|
||||
stage: security
|
||||
image: $GITLEAKS_IMAGE
|
||||
|
||||
# Run on schedule only
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
|
||||
script:
|
||||
- echo "Running comprehensive nightly secret scan..."
|
||||
- |
|
||||
gitleaks detect \
|
||||
--source . \
|
||||
--report-path nightly-scan.json \
|
||||
--report-format json \
|
||||
--verbose
|
||||
|
||||
artifacts:
|
||||
paths:
|
||||
- nightly-scan.json
|
||||
when: always
|
||||
expire_in: 90 days
|
||||
|
||||
# Send notifications on failure
|
||||
after_script:
|
||||
- |
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Secrets detected in nightly scan!"
|
||||
# Add notification logic (email, Slack, etc.)
|
||||
fi
|
||||
@@ -0,0 +1,70 @@
|
||||
# Pre-commit Framework Configuration for Gitleaks
|
||||
# Install pre-commit: pip install pre-commit
|
||||
# Install hooks: pre-commit install
|
||||
# Run manually: pre-commit run --all-files
|
||||
#
|
||||
# More info: https://pre-commit.com/
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.18.0 # Update to latest version: https://github.com/gitleaks/gitleaks/releases
|
||||
hooks:
|
||||
- id: gitleaks
|
||||
name: Gitleaks - Secret Detection
|
||||
description: Scan staged changes for hardcoded secrets
|
||||
entry: gitleaks protect --verbose --redact --staged
|
||||
language: system
|
||||
pass_filenames: false
|
||||
# Optional: Custom configuration
|
||||
# args: ['--config', '.gitleaks.toml']
|
||||
|
||||
# Optional: Additional security hooks
|
||||
|
||||
# Detect private keys
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: detect-private-key
|
||||
name: Detect Private Keys
|
||||
|
||||
# Check for AWS credentials
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: detect-aws-credentials
|
||||
name: Detect AWS Credentials
|
||||
args: ['--allow-missing-credentials']
|
||||
|
||||
# Prevent large files (may contain secrets)
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
name: Check for Large Files
|
||||
args: ['--maxkb=1000']
|
||||
|
||||
# Check for merge conflicts
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-merge-conflict
|
||||
name: Check for Merge Conflicts
|
||||
|
||||
# Ensure files end with newline
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
name: Fix End of Files
|
||||
|
||||
# Trim trailing whitespace
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
name: Trim Trailing Whitespace
|
||||
|
||||
# Configuration for pre-commit.ci (optional CI service)
|
||||
ci:
|
||||
autofix_prs: false
|
||||
autoupdate_schedule: monthly
|
||||
Reference in New Issue
Block a user