diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 0000000..b0e490e --- /dev/null +++ b/.github/workflows/secret-scan.yml @@ -0,0 +1,52 @@ +# Secret-scan gate for the public docs site. +# +# Blocks sensitive values from reaching docs.jacobpevans.com. Runs gitleaks-action +# with the org-wide private ruleset (the dryvist GITLEAKS_PRIVATE_CONFIG secret, +# injected at run time) and the org GITLEAKS_LICENSE_KEY. The action runs gitleaks +# with --redact, so matched values never appear in logs. +# +# The ruleset lives ONLY in the org secret — never committed — so the list of what +# counts as sensitive stays private. Public-output features (PR comments, SARIF +# artifact, job summary) are disabled: on a public repo they would expose the rule +# taxonomy / file locations on a catch. A finding fails the job; investigate from a +# trusted checkout with the private config. +# +# Make this a required check via branch protection on `main` after merge. +name: Secret Scan + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: read + +jobs: + gitleaks: + name: gitleaks (redacted) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Materialize the org ruleset into a config file + env: + GITLEAKS_PRIVATE_CONFIG: ${{ secrets.GITLEAKS_PRIVATE_CONFIG }} + run: printf '%s' "$GITLEAKS_PRIVATE_CONFIG" > "$RUNNER_TEMP/gitleaks.toml" + + - uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE_KEY }} + GITLEAKS_CONFIG: ${{ runner.temp }}/gitleaks.toml + GITLEAKS_ENABLE_UPLOAD_ARTIFACT: "false" + GITLEAKS_ENABLE_COMMENTS: "false" + GITLEAKS_ENABLE_SUMMARY: "false" diff --git a/automation/scheduled-routines/claude-code-routines.mdx b/automation/scheduled-routines/claude-code-routines.mdx index 8542565..d3b96f3 100644 --- a/automation/scheduled-routines/claude-code-routines.mdx +++ b/automation/scheduled-routines/claude-code-routines.mdx @@ -1,14 +1,14 @@ --- title: "claude-code-routines" -description: "Six cron-scheduled Claude Code routines that watch the whole org without per-repo wiring. Pick up loose ends the event-triggered pipeline misses." +description: "Seven cron-scheduled Claude Code routines that watch the whole org without per-repo wiring. Pick up loose ends the event-triggered pipeline misses." tier: 2 --- > Cron, not events. Org-wide, not per-repo. One configuration, scans everything. -[`JacobPEvans/claude-code-routines`](https://github.com/JacobPEvans/claude-code-routines) ships six prompt files that run on schedule inside Anthropic's cloud-hosted Claude Code sandbox. They discover repos under `$GH_OWNER` via `gh search`, do their work using `gh` CLI calls, and report results to Slack via the official Slack MCP connector. There is no per-repo wiring — drop a repo into the org and the routines pick it up on the next run. +[`JacobPEvans/claude-code-routines`](https://github.com/JacobPEvans/claude-code-routines) ships seven prompt files that run on schedule inside Anthropic's cloud-hosted Claude Code sandbox. They discover repos under `$GH_OWNER` via `gh search`, do their work using `gh` CLI calls, and report results to Slack via the official Slack MCP connector. There is no per-repo wiring — drop a repo into the org and the routines pick it up on the next run. -## The six routines +## The seven routines | Routine | Schedule (CT) | What it does | | --- | --- | --- | @@ -17,6 +17,7 @@ tier: 2 | The Custodian | Daily 2:00 AM | Weighted-random maintenance: stale branch cleanup, missing labels, repo health audit | | Issue Solver | Daily 7:00 AM + 7:00 PM | Picks one open issue, takes it from triage → draft PR in six phases | | Daily Polish | Daily 11:00 PM | Deep-clean one repo per day: README gaps, missing `.gitignore`, CI config, test coverage | +| Docs Sync | Daily 3:13 AM | Reads 48h of estate change; opens a public `docs` PR (scrubbed) and a private `docs-starlight` PR (DRY links to docs.jacobpevans.com) | | Weekly Scorecard | Mondays 5:00 AM | Portfolio health scores: repo count, open-issue median, CI pass rate, test coverage % | ## How the Issue Solver picks work diff --git a/security/scrubbed-values.mdx b/security/scrubbed-values.mdx index 41b0b56..e9d94d7 100644 --- a/security/scrubbed-values.mdx +++ b/security/scrubbed-values.mdx @@ -22,6 +22,14 @@ tier: 2 Never write a real value even in a "wrong" example — the example becomes committed text. Use `${USER}`, `${REPO_ROOT}`, `${MAINTAINER_EMAIL}`, `${NAS_HOST}`, `` for shape stand-ins where the placeholder needs to look like a variable. +## Enforcement: the secret-scan gate + +The public `docs` repo runs a fail-closed [gitleaks](https://github.com/gitleaks/gitleaks) check (`.github/workflows/secret-scan.yml`) on every PR and push to `main`, via [`gitleaks-action`](https://github.com/gitleaks/gitleaks-action). A finding fails the job, so a sensitive value cannot merge. + +So the *list of what counts as sensitive stays private*, the rule set is **never committed**. It lives only as the `dryvist` org Actions secret `GITLEAKS_PRIVATE_CONFIG`, injected into CI at run time. The scan runs with `--redact`, and the public-output features (PR comments, SARIF artifact, job summary) are disabled, so neither the patterns nor any match ever surface publicly. `useDefault` covers generic credentials; org-specific rules cover the values that must never appear here. + +The same gate runs on the draft PRs opened by the [Docs Sync routine](/automation/scheduled-routines/claude-code-routines), so machine-authored content is held to the same bar as anything authored by hand. + ## Portable path references **Never commit absolute user paths** (`/Users/{username}/*`, `/home/{username}/*`, `$HOME/*`, `~/*`).