From d87d8cf991e3974b9be3810e7d921a89d54c4bc1 Mon Sep 17 00:00:00 2001 From: Val Redchenko Date: Mon, 26 Jan 2026 18:22:09 +0000 Subject: [PATCH] feat: add secrets scanning workflow and pre-commit hooks - Add leaked-secrets-scan.yml workflow for daily and PR-based scanning - Move osv-scanner.toml from webui/ to repo root (consolidate config) - Add .pre-commit-config.yaml with detect-secrets hook - Add .secrets.baseline (no secrets detected) --- .github/workflows/leaked-secrets-scan.yml | 99 ++++++++++ .pre-commit-config.yaml | 15 ++ .secrets.baseline | 214 +++++++++++++++++++++ webui/osv-scanner.toml => osv-scanner.toml | 4 + 4 files changed, 332 insertions(+) create mode 100644 .github/workflows/leaked-secrets-scan.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .secrets.baseline rename webui/osv-scanner.toml => osv-scanner.toml (60%) diff --git a/.github/workflows/leaked-secrets-scan.yml b/.github/workflows/leaked-secrets-scan.yml new file mode 100644 index 0000000..ad0c971 --- /dev/null +++ b/.github/workflows/leaked-secrets-scan.yml @@ -0,0 +1,99 @@ +name: Leaked Secrets Scan + +on: + workflow_call: + schedule: + - cron: '0 3 * * *' # Daily at 3 AM UTC (3-4 AM UK time) + workflow_dispatch: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + secrets-scan: + runs-on: ubuntu-latest + name: ${{ github.event_name == 'schedule' && 'Scheduled Secrets Scan' || 'Secrets Scan' }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + + - name: Install detect-secrets + run: | + python -m pip install --upgrade pip + pip install detect-secrets + + - name: Verify baseline exists + run: | + if [ ! -f .secrets.baseline ]; then + echo "::error::.secrets.baseline not found!" + exit 1 + fi + echo "Found .secrets.baseline" + + - name: Scan for secrets + run: | + echo "Scanning for secrets..." + detect-secrets scan \ + --baseline .secrets.baseline \ + --exclude-files '.*\.lock$' \ + --force-use-all-plugins + + - name: Audit baseline for unaudited secrets + run: | + echo "Auditing secrets baseline..." + if grep -q '"is_secret": null' .secrets.baseline; then + echo "::error::Found unaudited secrets in baseline! Run: detect-secrets audit .secrets.baseline" + detect-secrets audit .secrets.baseline --report + exit 1 + fi + echo "All secrets in baseline have been audited" + detect-secrets audit .secrets.baseline --report + + - name: Check for new secrets in PR + if: github.event_name == 'pull_request' + run: | + echo "Checking for new secrets in PR..." + mkdir -p /tmp/pr-scan + git diff origin/main...HEAD --name-only | while read -r file; do + if [ -f "$file" ]; then + mkdir -p "/tmp/pr-scan/$(dirname "$file")" 2>/dev/null || true + cp "$file" "/tmp/pr-scan/$file" 2>/dev/null || true + fi + done + + if [ "$(ls -A /tmp/pr-scan 2>/dev/null)" ]; then + echo "Scanning changed files..." + detect-secrets scan \ + --baseline .secrets.baseline \ + --exclude-files '.*\.lock$' \ + --force-use-all-plugins \ + /tmp/pr-scan || echo "No new secrets found" + else + echo "No files to scan" + fi + + - name: Full repository scan (scheduled) + if: github.event_name == 'schedule' + run: | + echo "Performing full repository scan..." + detect-secrets scan \ + --exclude-files '.*\.lock$' \ + --force-use-all-plugins + + - name: Upload baseline on failure + uses: actions/upload-artifact@v4 + if: failure() + with: + name: secrets-scan-results + path: .secrets.baseline + retention-days: 30 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..6fb5a36 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-added-large-files + - id: check-yaml + args: ['--allow-multiple-documents'] + - id: check-merge-conflict + - id: end-of-file-fixer + + - repo: https://github.com/Yelp/detect-secrets + rev: v1.5.0 + hooks: + - id: detect-secrets + args: ['--baseline', '.secrets.baseline', '--exclude-files', '.*\.lock$'] diff --git a/.secrets.baseline b/.secrets.baseline new file mode 100644 index 0000000..28a89c4 --- /dev/null +++ b/.secrets.baseline @@ -0,0 +1,214 @@ +{ + "version": "1.5.0", + "plugins_used": [ + { + "name": "ArtifactoryDetector" + }, + { + "name": "AWSKeyDetector" + }, + { + "name": "AzureStorageKeyDetector" + }, + { + "name": "Base64HighEntropyString", + "limit": 4.5 + }, + { + "name": "BasicAuthDetector" + }, + { + "name": "CloudantDetector" + }, + { + "name": "DiscordBotTokenDetector" + }, + { + "name": "GitHubTokenDetector" + }, + { + "name": "GitLabTokenDetector" + }, + { + "name": "HexHighEntropyString", + "limit": 3.0 + }, + { + "name": "IbmCloudIamDetector" + }, + { + "name": "IbmCosHmacDetector" + }, + { + "name": "IPPublicDetector" + }, + { + "name": "JwtTokenDetector" + }, + { + "name": "KeywordDetector", + "keyword_exclude": "" + }, + { + "name": "MailchimpDetector" + }, + { + "name": "NpmDetector" + }, + { + "name": "OpenAIDetector" + }, + { + "name": "PrivateKeyDetector" + }, + { + "name": "PypiTokenDetector" + }, + { + "name": "SendGridDetector" + }, + { + "name": "SlackDetector" + }, + { + "name": "SoftlayerDetector" + }, + { + "name": "SquareOAuthDetector" + }, + { + "name": "StripeDetector" + }, + { + "name": "TelegramBotTokenDetector" + }, + { + "name": "TwilioKeyDetector" + } + ], + "filters_used": [ + { + "path": "detect_secrets.filters.allowlist.is_line_allowlisted" + }, + { + "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", + "min_level": 2 + }, + { + "path": "detect_secrets.filters.heuristic.is_indirect_reference" + }, + { + "path": "detect_secrets.filters.heuristic.is_likely_id_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_lock_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_potential_uuid" + }, + { + "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" + }, + { + "path": "detect_secrets.filters.heuristic.is_sequential_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_swagger_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_templated_secret" + }, + { + "path": "detect_secrets.filters.regex.should_exclude_file", + "pattern": [ + ".*\\.lock$" + ] + } + ], + "results": { + "claude-code/smartem-frontend/skills/playwright/API_REFERENCE.md": [ + { + "type": "Secret Keyword", + "filename": "claude-code/smartem-frontend/skills/playwright/API_REFERENCE.md", + "hashed_secret": "f0578f1e7174b1a41c4ea8c6e17f7a8a3b88c92a", + "is_verified": false, + "line_number": 499 + }, + { + "type": "Secret Keyword", + "filename": "claude-code/smartem-frontend/skills/playwright/API_REFERENCE.md", + "hashed_secret": "8be52126a6fde450a7162a3651d589bb51e9579d", + "is_verified": false, + "line_number": 500 + } + ], + "docs/decision-records/smartem-workspace-developer-guide.md": [ + { + "type": "Secret Keyword", + "filename": "docs/decision-records/smartem-workspace-developer-guide.md", + "hashed_secret": "8bed7940e0c3ea61eca107fdb36282d4ceef45d4", + "is_verified": false, + "line_number": 1135 + }, + { + "type": "Secret Keyword", + "filename": "docs/decision-records/smartem-workspace-developer-guide.md", + "hashed_secret": "46192f4a6b473b1d14528bad8da6300e84cb4d0e", + "is_verified": false, + "line_number": 1140 + } + ], + "docs/development/e2e-simulation.md": [ + { + "type": "Basic Auth Credentials", + "filename": "docs/development/e2e-simulation.md", + "hashed_secret": "35675e68f4b5af7b995d9205ad0fc43842f16450", + "is_verified": false, + "line_number": 410 + } + ], + "docs/operations/kubernetes-secrets.md": [ + { + "type": "Secret Keyword", + "filename": "docs/operations/kubernetes-secrets.md", + "hashed_secret": "142879ed2e01ae92cf503554bfd2241c3e024a0f", + "is_verified": false, + "line_number": 115 + }, + { + "type": "Secret Keyword", + "filename": "docs/operations/kubernetes-secrets.md", + "hashed_secret": "3ba67f062408ecadaabf84c340240f69c1a0a318", + "is_verified": false, + "line_number": 117 + }, + { + "type": "Secret Keyword", + "filename": "docs/operations/kubernetes-secrets.md", + "hashed_secret": "3a12b92524e2bb602238d9f3e28f885d097668bb", + "is_verified": false, + "line_number": 318 + }, + { + "type": "Secret Keyword", + "filename": "docs/operations/kubernetes-secrets.md", + "hashed_secret": "76a3330d1ebe1e20c9824c59cafac491bcfa43c3", + "is_verified": false, + "line_number": 334 + } + ], + "k8s/secret.example.yaml": [ + { + "type": "Secret Keyword", + "filename": "k8s/secret.example.yaml", + "hashed_secret": "da19880411eca9df7c34f4f4690fb783e9c38d38", + "is_verified": false, + "line_number": 9 + } + ] + }, + "generated_at": "2026-01-26T18:21:21Z" +} diff --git a/webui/osv-scanner.toml b/osv-scanner.toml similarity index 60% rename from webui/osv-scanner.toml rename to osv-scanner.toml index 87671e4..b927a18 100644 --- a/webui/osv-scanner.toml +++ b/osv-scanner.toml @@ -1,3 +1,7 @@ +# OSV Scanner configuration +# https://google.github.io/osv-scanner/configuration/ + +# Migrated from webui/osv-scanner.toml [[IgnoredVulns]] id = "GHSA-73rr-hh4g-fpgx" reason = "Dev tooling only - diff is used by @tanstack/router-utils for build-time code generation, no user input reaches parsePatch/applyPatch"