From ed2cbc93e4a07fa89dff244ae3c5373347fa0983 Mon Sep 17 00:00:00 2001 From: Paul Isaris Date: Wed, 30 Sep 2026 15:57:04 +0300 Subject: [PATCH] Scope Gitleaks to pull request commits and add a security template - security.yml: on pull_request, Gitleaks scans only the pull request's commits; push and schedule runs scan the full history. New inputs gitleaks-full-history and npm-min-release-age - Document .gitleaksignore for accepted findings - New SciFY Security workflow template with every input --- .github/workflows/README.md | 19 ++++++++---- .github/workflows/security.yml | 32 +++++++++++++++++---- README.md | 5 ++-- workflow-templates/security.properties.json | 7 +++++ workflow-templates/security.yml | 30 +++++++++++++++++++ 5 files changed, 80 insertions(+), 13 deletions(-) create mode 100644 workflow-templates/security.properties.json create mode 100644 workflow-templates/security.yml diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 5b8fe4b..1524d83 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -69,16 +69,17 @@ Rules that apply to every workflow here: ### Start from a template -The CI workflows have starter templates. Each template lists every input with -its default and example values. +The workflows have starter templates: SciFY Laravel CI, SciFY Node CI and SciFY +Security. Each template lists every input with its default and example values. 1. Open your repository's **Actions** tab and click **New workflow**. -2. Under "By SciFY", pick **SciFY Laravel CI** or **SciFY Node CI**. +2. Under "By SciFY", pick **SciFY Laravel CI**, **SciFY Node CI** or **SciFY Security**. 3. Uncomment and change only the inputs that you need. 4. Commit the file. -You can also copy [`laravel-ci.yml`](../../workflow-templates/laravel-ci.yml) or -[`node-ci.yml`](../../workflow-templates/node-ci.yml) from `workflow-templates/` +You can also copy [`laravel-ci.yml`](../../workflow-templates/laravel-ci.yml), +[`node-ci.yml`](../../workflow-templates/node-ci.yml) or +[`security.yml`](../../workflow-templates/security.yml) from `workflow-templates/` by hand. Replace `$default-branch` with `main`. ### The CI model @@ -296,7 +297,7 @@ npm-only and PHP-only repositories. | Job | What it checks | | --- | --- | -| `Secrets` | Committed `.env` files anywhere in the repository, and Gitleaks over the full git history. Examples, `.env.testing`, `.env.ci` and templates (`.dist`, `.sample`, `.template`, `.tpl`, `.j2`, `.jinja`, `.jinja2`) are allowed | +| `Secrets` | Committed `.env` files anywhere in the repository, and Gitleaks. On a pull request, Gitleaks scans only the pull request's commits. On push and schedule, it scans the full git history. Examples, `.env.testing`, `.env.ci` and templates (`.dist`, `.sample`, `.template`, `.tpl`, `.j2`, `.jinja`, `.jinja2`) are allowed | | `Dev tool configs` | Script files and suspicious commands in `.vscode`, `.claude`, `.cursor` and `.idea`. See [`scan-dev-configs`](../actions/scan-dev-configs/README.md) | | `npm supply chain hardening` | `.npmrc` settings and lockfile. See [`verify-npm-hardening`](../actions/verify-npm-hardening/README.md) | | `Dependency audit` | `composer audit --locked` and `npm audit` against the lock files. No install is needed | @@ -306,6 +307,8 @@ npm-only and PHP-only repositories. | `working-directory` | `.` | `frontend`, `apps/web`. The npm hardening and audit checks run there | | `php-version` | `'8.4'` | `'8.3'` | | `composer-abandoned` | `report` (list, do not fail) | `ignore`, `fail` | +| `gitleaks-full-history` | `false` (pull requests scan their own commits) | `true` (every run scans the full history) | +| `npm-min-release-age` | `'7'` | `'14'` (days; 7 or more) | | `npm-audit-level` | `high` | `low`, `moderate`, `critical` | | `strict-dev-configs` | `false` (warn only) | `true` (fail on suspicious commands) | | `allowed-dev-scripts` | `''` (no scripts allowed) | `.claude/hooks/*.sh` (one glob per line; `*` also matches `/`) | @@ -313,6 +316,9 @@ npm-only and PHP-only repositories. Run it on pull requests and once a week. The weekly run finds new advisories for dependencies that did not change. +The weekly run also scans the full git history with Gitleaks, so it finds a +secret that reached `main` without a pull request. + In a public repository, GitHub disables scheduled workflows after 60 days without repository activity, and it sends only an email. After a quiet period, check the **Actions** tab and enable the workflow again: @@ -356,6 +362,7 @@ jobs: | Browser tests also run in `Backend tests` (Laravel) | Exclude the browser suite in `test-command`, for example `vendor/bin/pest --exclude-testsuite=Browser`. | | `Environment files are committed to the repository` | A real env file, such as `.env` or `.env.production`, is committed. Remove it and rotate its secrets. A template must end in `.example`, `.dist`, `.sample`, `.template`, `.tpl`, `.j2`, `.jinja` or `.jinja2`. | | `Found 1 abandoned package` fails the `Dependency audit` job | The caller sets `composer-abandoned: fail`. Replace the package, or set `composer-abandoned: report`. | +| `leaks found` in the `Secrets` job | Gitleaks found a secret. Rotate it first: removing it from the code does not remove it from the git history. If the finding is a false positive, add its fingerprint (printed in the log, for example `abc123:config/app.php:generic-api-key:12`) as one line to `.gitleaksignore` in the repository root. | | The weekly security run stopped | GitHub disables scheduled workflows in a public repository after 60 days without activity. Open the workflow in the **Actions** tab and click **Enable workflow**. | | `Script files found in dev tool directories` | A script file is in `.vscode`, `.claude`, `.cursor` or `.idea`. Remove it, or review it and add it to `allowed-dev-scripts`. | | PHPStan or Rector re-analyse every file on each run | `analysis-cache-paths` does not match `tmpDir` in `phpstan.neon` or `cacheDirectory` in `rector.php`. | diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index e5c4b37..61ca75d 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -11,7 +11,8 @@ # uses: scify/.github/.github/workflows/security.yml@v0.1 # # Jobs, all independent: -# secrets committed .env files, Gitleaks over the full git history +# secrets committed .env files; Gitleaks over the pull request commits, +# or the full history on push and schedule # dev-configs scripts and suspicious commands in .vscode/.claude/.cursor/.idea # (scan-dev-configs action; allow reviewed scripts with allowed-dev-scripts) # npm-hardening .npmrc and lockfile checks (verify-npm-hardening action) @@ -44,6 +45,14 @@ on: description: Fail on suspicious commands in dev tool configs. Default only warns. type: boolean default: false + gitleaks-full-history: + description: 'Scan the full git history on pull requests too. By default a pull request scans only its own commits; push and schedule runs always scan the full history. One of: true, false' + type: boolean + default: false + npm-min-release-age: + description: "Lowest accepted min-release-age in .npmrc, in days. 7 or more. Example: '14'" + type: string + default: '7' allowed-dev-scripts: description: "Newline-separated glob patterns of reviewed script files allowed in dev tool directories. `*` also matches `/`. Example: .claude/hooks/*.sh" type: string @@ -92,17 +101,29 @@ jobs: tar -xzf gitleaks.tar.gz gitleaks rm gitleaks.tar.gz + # Gitleaks also reads .gitleaksignore (fingerprints of accepted findings) + # from the repository root by itself. - name: Run Gitleaks + env: + EVENT: ${{ github.event_name }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + FULL_HISTORY: ${{ inputs.gitleaks-full-history }} run: | - config="" + args=(git --redact --no-banner) if [ -f .gitleaks.toml ]; then - config="--config .gitleaks.toml" + args+=(--config .gitleaks.toml) echo "Using repository .gitleaks.toml" else echo "No .gitleaks.toml, using the built-in rules" fi - # shellcheck disable=SC2086 - ./gitleaks git --redact --no-banner $config . + if [ "$EVENT" = pull_request ] && [ "$FULL_HISTORY" != true ]; then + echo "Scanning the pull request commits ${BASE_SHA:0:7}..${HEAD_SHA:0:7}" + args+=("--log-opts=--no-merges ${BASE_SHA}..${HEAD_SHA}") + else + echo "Scanning the full git history" + fi + ./gitleaks "${args[@]}" . dev-configs: name: Dev tool configs @@ -160,6 +181,7 @@ jobs: uses: ./.scify-github/.github/actions/verify-npm-hardening with: working-directory: ${{ inputs.working-directory }} + min-age: ${{ inputs.npm-min-release-age }} - name: No package.json if: hashFiles(format('{0}/package.json', inputs.working-directory)) == '' diff --git a/README.md b/README.md index d6f24df..20ca8aa 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ deployment secrets, so they will live in a separate private repository. | --- | --- | --- | | `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, `PULL_REQUEST_TEMPLATE.md`, `ISSUE_TEMPLATE/` | Default community health files | Automatic. Applies to every scify repository that has no file of its own. | | `profile/README.md` | Organisation profile page | Automatic. Shown on https://github.com/scify. | -| `workflow-templates/` | Starter workflows | Actions tab → New workflow → "By SciFY". The developer gets a copy. Templates exist for Laravel CI and Node CI. | +| `workflow-templates/` | Starter workflows | Actions tab → New workflow → "By SciFY". The developer gets a copy. Templates exist for Laravel CI, Node CI and Security. | | `.github/workflows/*.yml` | Reusable workflows (`workflow_call`) | Called with `uses: scify/.github/.github/workflows/.yml@v0.1`. One implementation, shared by all callers. | | `.github/actions/*/` | Composite actions | Called as a step with `uses: scify/.github/.github/actions/@v0.1`. Each folder has its own README. | | `templates/dependabot-*.yml` | Dependabot configuration | Manual copy to `.github/dependabot.yml`. GitHub has no default mechanism for Dependabot. | @@ -42,7 +42,8 @@ To use them in your application, read the lists every input with example values, and gives recipes and troubleshooting. The quickest start for CI: open your repository's **Actions** tab, click -**New workflow**, and pick **SciFY Laravel CI** or **SciFY Node CI**. +**New workflow**, and pick **SciFY Laravel CI** or **SciFY Node CI**. Add +**SciFY Security** the same way. ## Dependabot diff --git a/workflow-templates/security.properties.json b/workflow-templates/security.properties.json new file mode 100644 index 0000000..b80e484 --- /dev/null +++ b/workflow-templates/security.properties.json @@ -0,0 +1,7 @@ +{ + "name": "SciFY Security", + "description": "Committed env files, Gitleaks, dev tool configs, npm hardening, composer audit and npm audit through the shared scify/.github workflow.", + "iconName": "octicon shield-check", + "categories": ["Security"], + "filePatterns": ["composer.json$", "package.json$"] +} diff --git a/workflow-templates/security.yml b/workflow-templates/security.yml new file mode 100644 index 0000000..693351c --- /dev/null +++ b/workflow-templates/security.yml @@ -0,0 +1,30 @@ +# Security checks for a SciFY repository. +# The logic lives in scify/.github. Change inputs here, not the steps. +# Guide: https://github.com/scify/.github/blob/main/.github/workflows/README.md#security +# +# Each commented-out input shows its default. Uncomment a line only to change it. +# The "e.g." comment shows other accepted values. +# To change an input, also uncomment the `with:` line. +name: Security + +on: + pull_request: + # The weekly run finds new advisories and scans the full git history. + schedule: + - cron: '0 11 * * 1' + +permissions: + contents: read + +jobs: + security: + uses: scify/.github/.github/workflows/security.yml@v0.1 + # with: + # working-directory: . # e.g. frontend (the folder with package.json) + # php-version: '8.4' # e.g. '8.3' (runs composer audit) + # composer-abandoned: report # e.g. fail, ignore + # npm-audit-level: high # e.g. moderate, critical + # npm-min-release-age: '7' # e.g. '14' (days, 7 or more) + # gitleaks-full-history: false # true: pull requests scan the full history too + # strict-dev-configs: false # true: fail on suspicious commands in dev tool configs + # allowed-dev-scripts: '' # e.g. .claude/hooks/*.sh (one glob per line)