From 55d6c7d9f193c64a0cc542a06b3a1cb38cace185 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sat, 21 Mar 2026 10:39:03 -0400 Subject: [PATCH] feat(ci): skip build/test for docs-only PRs and add prompt injection scan (#1699) Docs-only PRs (only .md files and docs/ changes) now skip the expensive build, typecheck, and test jobs while still running lint and a new docs-check job. The docs-check job runs a prompt injection scanner that detects hidden directives, role overrides, system prompt markers, tool call injection, and invisible Unicode in markdown prose (excluding fenced code blocks and inline code spans). Co-authored-by: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 58 ++++++- docs/ci-cd-pipeline.md | 23 +++ scripts/docs-prompt-injection-scan.sh | 209 ++++++++++++++++++++++++++ 3 files changed, 285 insertions(+), 5 deletions(-) create mode 100755 scripts/docs-prompt-injection-scan.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ff321f6d..3258c7157 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,8 +4,6 @@ on: push: branches: [main] paths-ignore: - - '**.md' - - 'docs/**' - '.github/workflows/ai-triage.yml' - '.github/workflows/build-native.yml' - '.github/workflows/cleanup-dev-versions.yml' @@ -14,8 +12,6 @@ on: pull_request: branches: [main] paths-ignore: - - '**.md' - - 'docs/**' - '.github/workflows/ai-triage.yml' - '.github/workflows/build-native.yml' - '.github/workflows/cleanup-dev-versions.yml' @@ -27,7 +23,54 @@ concurrency: cancel-in-progress: true jobs: + detect-changes: + runs-on: ubuntu-latest + outputs: + docs-only: ${{ steps.check.outputs.docs-only }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Check if only documentation changed + id: check + env: + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PUSH_BEFORE_SHA: ${{ github.event.before }} + EVENT_NAME: ${{ github.event_name }} + HEAD_SHA: ${{ github.sha }} + run: | + if [ "$EVENT_NAME" = "pull_request" ]; then + BASE="$PR_BASE_SHA" + else + BASE="$PUSH_BEFORE_SHA" + fi + FILES=$(git diff --name-only "$BASE" "$HEAD_SHA" 2>/dev/null || git diff --name-only HEAD~1) + echo "Changed files:" + echo "$FILES" + NON_DOCS=$(echo "$FILES" | grep -vE '\.(md|markdown)$' | grep -vE '^docs/' | grep -vE '^LICENSE$' || true) + if [ -z "$NON_DOCS" ]; then + echo "docs-only=true" >> "$GITHUB_OUTPUT" + echo "::notice::Only documentation files changed — skipping build/test" + else + echo "docs-only=false" >> "$GITHUB_OUTPUT" + echo "Non-docs files changed:" + echo "$NON_DOCS" + fi + + docs-check: + runs-on: ubuntu-latest + needs: detect-changes + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Scan documentation for prompt injection + run: bash scripts/docs-prompt-injection-scan.sh --diff origin/main + lint: + needs: detect-changes runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -53,6 +96,8 @@ jobs: run: node scripts/check-skill-references.mjs build: + needs: detect-changes + if: needs.detect-changes.outputs.docs-only != 'true' runs-on: ubuntu-latest steps: @@ -86,7 +131,10 @@ jobs: run: npm run test:integration windows-portability: - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: detect-changes + if: >- + needs.detect-changes.outputs.docs-only != 'true' && + github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: windows-latest steps: diff --git a/docs/ci-cd-pipeline.md b/docs/ci-cd-pipeline.md index 623e62299..79364568f 100644 --- a/docs/ci-cd-pipeline.md +++ b/docs/ci-cd-pipeline.md @@ -70,6 +70,29 @@ docker run --rm -v $(pwd):/workspace ghcr.io/gsd-build/gsd-pi:latest --version **CI optimization (v2.38):** GitHub Actions minutes were reduced ~60-70% (~10k → ~3-4k/month) through workflow consolidation and caching improvements. +### Docs-Only PR Detection (v2.41) + +CI automatically detects when a PR contains only documentation changes (`.md` files and `docs/` content). When docs-only: + +- **Skipped:** `build`, `windows-portability` (no code to compile or test) +- **Still runs:** `lint` (secret scanning, `.gsd/` check), `docs-check` (prompt injection scan) + +This saves CI minutes on documentation PRs while still enforcing security checks. + +### Prompt Injection Scan (v2.41) + +The `docs-check` job runs `scripts/docs-prompt-injection-scan.sh` on every PR that touches markdown files. It scans documentation prose (excluding fenced code blocks) for patterns that could manipulate LLM behavior when docs are ingested as context: + +- **System prompt markers** — ``, `<|im_start|>system`, `[SYSTEM]:` +- **Role/instruction overrides** — `ignore previous instructions`, `you are now`, `new instructions:` +- **Hidden HTML directives** — `