From d635e94915d0f80dc57349364aef82f9e310e47e Mon Sep 17 00:00:00 2001 From: Maroun Najjar <60901592+thecolormaroun@users.noreply.github.com> Date: Sat, 20 Jun 2026 11:16:33 -0700 Subject: [PATCH] Add stack secret scanning guardrails --- .github/workflows/security-scan.yml | 42 +++++++ .gitignore | 3 + .gitleaks.toml | 23 ++++ .pre-commit-config.yaml | 18 +++ README.md | 8 ++ docs/security/leak-prevention.md | 57 +++++++++ scripts/security/scan-sensitive-content.sh | 137 +++++++++++++++++++++ skills/stack-refresh-pr-mode/SKILL.md | 32 ++--- 8 files changed, 304 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/security-scan.yml create mode 100644 .gitleaks.toml create mode 100644 .pre-commit-config.yaml create mode 100644 docs/security/leak-prevention.md create mode 100755 scripts/security/scan-sensitive-content.sh diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml new file mode 100644 index 0000000..0453ae9 --- /dev/null +++ b/.github/workflows/security-scan.yml @@ -0,0 +1,42 @@ +name: Security scan + +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + pull-requests: read + +env: + GITLEAKS_VERSION: v8.30.1 + +jobs: + gitleaks: + name: Gitleaks + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: "1.24.11" + cache: false + + - name: Scan current tree for secrets + run: go run github.com/zricethezav/gitleaks/v8@${GITLEAKS_VERSION} dir --redact --config .gitleaks.toml . + + stack-sensitive-content: + name: Stack sensitive content + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Scan stack-specific sensitive content + run: scripts/security/scan-sensitive-content.sh diff --git a/.gitignore b/.gitignore index 82113ad..fee2e2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ config/settings.local.json node_modules/ .DS_Store +gitleaks-report*.json +gitleaks-findings*.json +trufflehog-report*.json diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..c265753 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,23 @@ +title = "Stack secret scanning" +minVersion = "v8.25.0" + +[extend] +useDefault = true + +[[rules]] +id = "stack-local-maroun-path" +description = "Local Maroun machine paths should not be published in the stack repo." +regex = '''/Users/maroun/[A-Za-z0-9._~/%+-]+''' +keywords = ["/Users/maroun"] + +[[rules]] +id = "stack-codex-private-artifact" +description = "Codex session, memory, and rollout artifacts can contain private context." +regex = '''(?i)(?:\.codex/(?:sessions|archived_sessions|memories)|rollout-[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9-]+-[0-9a-f-]+\.jsonl)''' +keywords = [".codex", "rollout-"] + +[[rules]] +id = "stack-config-secret-assignment" +description = "Agent or tool config appears to contain a concrete secret value." +regex = '''(?i)(?:api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|password|private[_-]?key)\s*[:=]\s*['"]?[A-Za-z0-9_./+=-]{16,}''' +keywords = ["api", "key", "token", "secret", "password"] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f245000 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +default_install_hook_types: [pre-commit, pre-push] + +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.30.1 + hooks: + - id: gitleaks + name: Gitleaks staged secret scan + stages: [pre-commit] + + - repo: local + hooks: + - id: stack-sensitive-content + name: Stack sensitive content scan + entry: scripts/security/scan-sensitive-content.sh + language: script + pass_filenames: false + stages: [pre-commit, pre-push] diff --git a/README.md b/README.md index 0163969..eb3f72b 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,14 @@ claude plugin marketplace add tmchow/illo-skill claude plugin install illo@illo-skill ``` +## Security Guardrails + +Pull requests run a `Security scan` workflow before merge. The workflow combines +Gitleaks with a stack-specific sensitive content scanner for local paths, private +agent artifacts, credential-looking config values, and household or finance-lane +references. See `docs/security/leak-prevention.md` for local hook setup and leak +remediation guidance. + --- ## Philosophy diff --git a/docs/security/leak-prevention.md b/docs/security/leak-prevention.md new file mode 100644 index 0000000..32137cf --- /dev/null +++ b/docs/security/leak-prevention.md @@ -0,0 +1,57 @@ +# Leak Prevention + +This repo is designed to be shared, so every pull request should pass two layers +of secret and sensitive-content checks before merge. + +## Required PR Gate + +The `Security scan` GitHub Actions workflow runs on pull requests and on pushes +to `main`. + +- `Gitleaks` catches provider tokens, private keys, connection strings, and other + standard secret patterns in the current tree. +- `Stack sensitive content` catches repo-specific risks such as local machine + paths, Codex session artifacts, credential-like agent config fields, and + household or finance-lane references. + +Scanner output intentionally reports only file, line, and rule name. Do not paste +the matched secret value into chat, issues, pull request comments, or commit +messages while fixing a failure. + +## Optional Local Hook + +Install local hooks when working on this repo: + +```bash +cd ~/Projects/stack +pre-commit install +``` + +Then run the checks manually before opening a PR: + +```bash +pre-commit run --all-files +scripts/security/scan-sensitive-content.sh +go run github.com/zricethezav/gitleaks/v8@v8.30.1 dir --redact --config .gitleaks.toml . +``` + +The local hook is a convenience layer. GitHub Actions remains the enforced gate. + +For a one-time audit of older commits, run Gitleaks in git-history mode: + +```bash +go run github.com/zricethezav/gitleaks/v8@v8.30.1 git --redact --config .gitleaks.toml . +``` + +Treat real credentials in history as leaked and rotate them. Historical local +path findings should be cleaned from the current tree, but they do not require +credential rotation. + +## If A Leak Is Found + +1. Remove the sensitive value from the branch. +2. Rotate or revoke the credential if a real token, password, private key, or + session artifact was committed or pushed. +3. Keep remediation notes generic: name the provider and file path, not the + secret value. +4. Re-run the scanners before requesting review. diff --git a/scripts/security/scan-sensitive-content.sh b/scripts/security/scan-sensitive-content.sh new file mode 100755 index 0000000..0a4a607 --- /dev/null +++ b/scripts/security/scan-sensitive-content.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +cd "$repo_root" + +failures=0 + +ignored_path() { + case "$1" in + .git/*|node_modules/*|scripts/security/scan-sensitive-content.sh|.gitleaks.toml) + return 0 + ;; + *) + return 1 + ;; + esac +} + +binary_or_unreadable() { + [[ ! -f "$1" ]] && return 0 + grep -Iq . "$1" 2>/dev/null +} + +report() { + local file="$1" + local line="$2" + local rule="$3" + + printf '%s:%s: sensitive-content: %s\n' "$file" "$line" "$rule" >&2 + failures=$((failures + 1)) +} + +scan_path_name() { + local file="$1" + + case "$file" in + *.pem|*.p12|*.pfx|*.key|*.asc|*.kdbx|*.mobileconfig) + report "$file" 0 "private key, certificate, or credential-like file extension" + ;; + .env|.env.*|*/.env|*/.env.*|*.env) + case "$file" in + *.example|*.sample|*.template) + ;; + *) + report "$file" 0 "environment file should not be committed" + ;; + esac + ;; + */settings.local.json|settings.local.json|*.local.json|*.secrets.*|*secret*.json|*credentials*.json) + report "$file" 0 "local settings or credential-like file name" + ;; + esac +} + +scan_line() { + local file="$1" + local findings + local count + + findings="$( + awk -v file="$file" ' + function placeholder(line) { + line = tolower(line) + return line ~ /\$\{\{[^}]*secrets\.[^}]*}}/ || + line ~ /&2 + count="$(printf '%s\n' "$findings" | wc -l | tr -d ' ')" + failures=$((failures + count)) + fi +} + +scan_file() { + local file="$1" + + ignored_path "$file" && return 0 + binary_or_unreadable "$file" || return 0 + + scan_path_name "$file" + scan_line "$file" +} + +while IFS= read -r -d '' file; do + scan_file "$file" +done < <(git ls-files -co --exclude-standard -z) + +if (( failures > 0 )); then + printf '\nStack sensitive content scan failed with %s finding(s).\n' "$failures" >&2 + printf 'Review the flagged lines without pasting secret values into chat or PR comments.\n' >&2 + exit 1 +fi + +printf 'Stack sensitive content scan passed.\n' diff --git a/skills/stack-refresh-pr-mode/SKILL.md b/skills/stack-refresh-pr-mode/SKILL.md index 44bd1a7..26ccf49 100644 --- a/skills/stack-refresh-pr-mode/SKILL.md +++ b/skills/stack-refresh-pr-mode/SKILL.md @@ -1,6 +1,6 @@ --- name: stack-refresh-pr-mode -description: "Run Maroun's review-safe Claude/Codex skill stack refresh from /Users/maroun/Codex. Use when asked to update, refresh, verify, or diagnose the local skill stack, the update-last30days-skill automation, gstack/compound-engineering pins, Impeccable, last30days, illo, transitions-dev-motion, or Stack repo PR-mode behavior without pushing directly to main." +description: "Run Maroun's review-safe Claude/Codex skill stack refresh from ~/Codex. Use when asked to update, refresh, verify, or diagnose the local skill stack, the update-last30days-skill automation, gstack/compound-engineering pins, Impeccable, last30days, illo, transitions-dev-motion, or Stack repo PR-mode behavior without pushing directly to main." --- # Stack Refresh PR Mode @@ -9,19 +9,19 @@ Use this skill for the recurring skill-stack update loop. The goal is a verified ## Source Order -1. Read `/Users/maroun/Codex/README.md`. -2. Read `/Users/maroun/Codex/upstreams.lock.json`. -3. Read `/Users/maroun/.codex/automations/update-last30days-skill/memory.md` if it exists. -4. Check `/Users/maroun/Projects/stack` branch and status before running anything that might sync or push. +1. Read `~/Codex/README.md`. +2. Read `~/Codex/upstreams.lock.json`. +3. Read `~/.codex/automations/update-last30days-skill/memory.md` if it exists. +4. Check `~/Projects/stack` branch and status before running anything that might sync or push. -Do not inspect, print, or modify `/Users/maroun/.config/last30days/.env` or `/Users/maroun/.config/illo/config.yaml`. It is OK to report whether protected config files exist when the run asks for that. +Do not inspect, print, or modify `~/.config/last30days/.env` or `~/.config/illo/config.yaml`. It is OK to report whether protected config files exist when the run asks for that. ## Refresh Command -Run from `/Users/maroun/Codex`: +Run from `~/Codex`: ```bash -SYNC_STACK_REPO_TO_GITHUB=pr /Users/maroun/.codex/scripts/update-last30days-skill.sh +SYNC_STACK_REPO_TO_GITHUB=pr ~/.codex/scripts/update-last30days-skill.sh ``` Use the exact command unless the user explicitly changes it. Do not replace it with manual installs, ad hoc copies, or direct runtime edits. @@ -35,13 +35,13 @@ When validating this skill or auditing readiness, do not run the refresh command Verify every requested installed skill path. The usual set is: ```text -/Users/maroun/.codex/skills/last30days/SKILL.md -/Users/maroun/.codex/skills/gstack/SKILL.md -/Users/maroun/.codex/skills/ce-setup/SKILL.md -/Users/maroun/.codex/skills/teach-impeccable/SKILL.md -/Users/maroun/.codex/skills/impeccable/SKILL.md -/Users/maroun/.codex/skills/transitions-dev-motion/SKILL.md -/Users/maroun/.codex/skills/illo/SKILL.md +~/.codex/skills/last30days/SKILL.md +~/.codex/skills/gstack/SKILL.md +~/.codex/skills/ce-setup/SKILL.md +~/.codex/skills/teach-impeccable/SKILL.md +~/.codex/skills/impeccable/SKILL.md +~/.codex/skills/transitions-dev-motion/SKILL.md +~/.codex/skills/illo/SKILL.md ``` Also report: @@ -58,7 +58,7 @@ Stop and ask before: - changing protected config or secrets; - widening the task into external account, browser profile, credential, or production-state changes. -If `/Users/maroun/Projects/stack` is not on `main`, keep PR-mode behavior and report the branch gate instead of forcing a checkout. +If `~/Projects/stack` is not on `main`, keep PR-mode behavior and report the branch gate instead of forcing a checkout. ## Closeout