From fbaef91fd0c7d0ec0b7ed41ab8829369336f2e8e Mon Sep 17 00:00:00 2001 From: almac2022 Date: Sun, 23 Aug 2026 18:36:29 -0700 Subject: [PATCH 1/3] Keep CLAUDE.md out of the published pkgdown site CLAUDE.html, a verbatim CLAUDE.md and the search.json index were all publicly readable. The internal-only conventions are not in them here -- the visibility filter worked -- so this is untidy rather than harmful. Fixed anyway because nothing currently stops the harmful version. The filter rests on an assumption pkgdown breaks, that a repo's CLAUDE.md stays where you put it, and a future INTERNAL.md or a visibility change would publish silently. See NewGraphEnvironment/rfp#180, where the SR&ED section did go public. Removal happens before the build; after would leave the verbatim copy and the search.json entry. Fixes #240 --- .github/workflows/pkgdown.yaml | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index a97e9db2..93a51d88 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -36,10 +36,48 @@ jobs: extra-packages: any::pkgdown, local::. needs: website + # pkgdown renders every root-level .md except a hardcoded allowlist + # (README, LICENSE, NEWS and two templates). CLAUDE.md is not on it and + # there is no config option to exclude a file, so the site would publish + # our internal working notes -- including, on a private repo, the + # conventions the visibility system marks internal-only. + # + # Repo visibility does not help: GitHub Pages serves publicly regardless, + # and there is no private mode below Enterprise Cloud. + # + # This removes it from the CI checkout only, never from the repo, and it + # has to happen BEFORE the build. Deleting afterwards would still leave + # the verbatim CLAUDE.md copy pkgdown makes and the entry it writes into + # the full-text search.json index. + - name: Keep internal notes out of the published site + run: rm -f CLAUDE.md + - name: Build site run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) shell: Rscript {0} + + # A skip here would be invisible, so assert rather than assume. Any + # top-level page beyond the ones pkgdown is expected to produce means a + # root markdown file reached the site -- the exact shape of the leak this + # workflow exists to prevent, and the shape a future INTERNAL.md would + # take. + - name: Fail if an unexpected page reached the site + run: | + allowed="404 authors index LICENSE LICENSE-text" + unexpected="" + for f in docs/*.html docs/*.md; do + [ -e "$f" ] || continue + b=$(basename "$f"); b="${b%.*}" + case " $allowed " in *" $b "*) ;; *) unexpected="$unexpected $(basename "$f")";; esac + done + if [ -n "$unexpected" ]; then + echo "Unexpected top-level page(s) in docs/:$unexpected" + echo "pkgdown renders every root .md; remove internal ones before the build." + exit 1 + fi + echo "no unexpected top-level pages" + - name: Deploy to GitHub pages if: github.event_name != 'pull_request' uses: JamesIves/github-pages-deploy-action@v4.8.0 From bd352929887dfafc33f8c4a1c5a631cd6af2544c Mon Sep 17 00:00:00 2001 From: almac2022 Date: Sun, 23 Aug 2026 20:15:11 -0700 Subject: [PATCH 2/3] Deploy with clean: true so removing a file unpublishes it With clean: false the deploy action never deletes. Every file ever published stays on gh-pages regardless of whether the source still produces it, which has two consequences: removing a file from the repo does not unpublish it, and a leak cannot be fixed by fixing the build -- the stale copies need a separate purge that is easy to forget. clean: true makes the deployed site equal to what the build produced. Fixing the build is then sufficient, and the site becomes auditable. Checked before flipping, since clean: true deletes anything on gh-pages not present in docs/: no CNAME on any of these repos (the custom domain comes from the org site repo and project sites inherit it as subpaths), no dev/ versioned docs, and the favicon and webmanifest assets are build output from pkgdown/favicon/ rather than hand-added. The gate's allowlist is now declared per repo rather than assumed, so a new root markdown file fails the build until someone decides whether it should be public. NOTICE and RUNBOOK are declared in the allowlist rather than blocked. link is a public repo and both are genuine documentation -- an attribution notice and a technical runbook about public data sources. That is the decision the allowlist exists to record. Convention recorded in soul: conventions/pkgdown-publishing.md --- .github/workflows/pkgdown.yaml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 93a51d88..f82ec088 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -64,7 +64,9 @@ jobs: # take. - name: Fail if an unexpected page reached the site run: | - allowed="404 authors index LICENSE LICENSE-text" + # Declared extras: pages this repo intends to publish. + # Add here only after deciding the page should be public. + allowed="404 authors index LICENSE LICENSE-text NOTICE RUNBOOK" unexpected="" for f in docs/*.html docs/*.md; do [ -e "$f" ] || continue @@ -82,6 +84,14 @@ jobs: if: github.event_name != 'pull_request' uses: JamesIves/github-pages-deploy-action@v4.8.0 with: - clean: false + # clean: true so the deployed site equals what the build produced. + # With clean: false the action never deletes, so removing a file from + # the repo does not unpublish it -- measured on gq, where + # task_plan.html and friends were still returning 200 long after the + # PWF documents left the root. Checked before flipping: no CNAME (the + # domain comes from the org site repo), no dev/ versioned docs, and + # the favicon/webmanifest assets are build output rather than + # hand-added. + clean: true branch: gh-pages folder: docs From 082e2895092f45cde209c3e50d0abce0f30307c7 Mon Sep 17 00:00:00 2001 From: almac2022 Date: Sun, 23 Aug 2026 20:17:34 -0700 Subject: [PATCH 3/3] Run the pkgdown job on pull requests so the gate is verified before merge The job carried if: github.event_name != 'pull_request', so it skipped entirely on PRs -- which meant the leak gate added in this branch would first run on merge to main, after review rather than during it. A gate you cannot see pass is not much better than no gate. Deploying is still refused on pull requests. That condition already lives on the deploy step itself, which is the right place for it: every other step in the job is safe to run on a PR, and gating the whole job only cost the verification. Relates to #240 --- .github/workflows/pkgdown.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index f82ec088..adf44a41 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -16,8 +16,12 @@ permissions: read-all jobs: pkgdown: runs-on: ubuntu-latest - # Only restrict tokens on non-pull-request events - if: ${{ github.event_name != 'pull_request' }} + # The job runs on pull requests too, so the build and the leak gate below + # are verified before merge rather than after. Deploying is still refused + # on pull requests -- that condition lives on the deploy step itself, which + # is where it belongs: this job's other steps are all safe to run on a PR, + # and gating the whole job made the gate unverifiable exactly when it + # mattered. env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} permissions: