From 141d42c3c76452cc36635eaff5623e3bac9586bf Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 10 Aug 2026 08:57:51 +1000 Subject: [PATCH 1/2] CI: guard against reading data-lectures over the LFS media host MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code cells here execute in the reader's browser, so a dataset read on the wrong host is a reader-visible failure on every page view. This repo's CI cannot see it: ci.yml runs `myst build --html` with no execution, so nothing fetches the URL until a reader does. The data-audit dashboard that would catch it lives in QuantEcon/data-lectures and never runs on a pull request here — its detection lag is up to seven days. media.githubusercontent.com is the LFS *media* endpoint and routes per path, serving a file only where that path is LFS-tracked in the repo the URL names. Everything data-lectures publishes is plain git, so that host never resolves for it — and the six datasets folded in from high_dim_data are read from it here today, in heavy_tails, inequality and mle. Only the media host is checked. Rule 5's github.com/*/raw/ form is wrong in a code cell but correct in a {download} role or a prose link, and this repo has both today (french_rev, inflation_history) — a grep cannot tell them apart, so that rule stays with the strict audit, which scans code cells only. Matches zero lines today (verified: exit 1), so it goes green on merge and is armed before the fold rather than alongside it. Gate 2 of QuantEcon/workspace-lectures#23 step 3. See QuantEcon/data-lectures PLAN.md, repoint rule 6. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/data-url-guard.yml | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/data-url-guard.yml diff --git a/.github/workflows/data-url-guard.yml b/.github/workflows/data-url-guard.yml new file mode 100644 index 0000000..e1e7aaf --- /dev/null +++ b/.github/workflows/data-url-guard.yml @@ -0,0 +1,38 @@ +# Code cells here execute in the reader's browser, so a dataset read on the +# wrong host is a reader-visible failure on every page view — and this repo's +# CI cannot see it: ci.yml runs `myst build --html` with no execution, so a +# dead URL is fetched by nobody until a reader arrives. The data-audit +# dashboard that would catch it lives in QuantEcon/data-lectures and never +# runs on a pull request here. +# +# See QuantEcon/data-lectures PLAN.md, repoint rule 6. + +name: Data URL guard + +on: + pull_request: + push: + branches: [main] + +jobs: + data-url-guard: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: No data-lectures read on the LFS media host + run: | + # media.githubusercontent.com is the LFS *media* endpoint and routes + # per path: it serves a file only where that path is LFS-tracked in + # the repo the URL names, and 404s otherwise. Everything data-lectures + # publishes is plain git, so this host never resolves for it — and a + # mechanical org/repo swap that preserves the host breaks every read. + # + # Only the media host is checked. Rule 5's github.com/*/raw/ form is + # wrong for code cells but correct for {download} roles and prose + # links, which a grep cannot tell apart — that one is asserted on + # code cells only, post-merge, by the strict audit in data-lectures. + if grep -rnF 'media.githubusercontent.com/media/QuantEcon/data-lectures' lectures/; then + echo "::error::Read data-lectures over raw.githubusercontent.com — that is the only CORS-clean form that resolves (repoint rule 5), and the media host is LFS-only, so it 404s every file data-lectures publishes (repoint rule 6)." + exit 1 + fi + echo "OK — no data-lectures read on the media host." From 65c5c0bc3c20be524cc57183c8330cc93b0e568a Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 10 Aug 2026 12:38:01 +1000 Subject: [PATCH 2/2] CI: least-privilege token, and lead the guard error with the defect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copilot review on QuantEcon/lecture-wasm#58. The job greps a checkout and writes nothing, but this repo's default workflow permission is `write`, so it was receiving a token that could also approve pull requests. ci.yml already sets its own block. The error message opened with a bare imperative naming the *correct* host — "Read data-lectures over raw.githubusercontent.com" — which reads as a description of what was found rather than as the fix. Reordered to name the defect first. The intro twin avoids this by carrying a "never media.githubusercontent.com" clause; that clause is deliberately absent here, since repoint rule 5 bars the github.com/*/raw/ form for code cells that execute in the reader's browser. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/data-url-guard.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/data-url-guard.yml b/.github/workflows/data-url-guard.yml index e1e7aaf..245a100 100644 --- a/.github/workflows/data-url-guard.yml +++ b/.github/workflows/data-url-guard.yml @@ -17,6 +17,11 @@ on: jobs: data-url-guard: runs-on: ubuntu-latest + # This job greps a checkout and writes nothing. The repo default is + # `write`, so without this it would receive a token that can also approve + # pull requests. ci.yml sets its own block for the same reason. + permissions: + contents: read steps: - uses: actions/checkout@v4 - name: No data-lectures read on the LFS media host @@ -32,7 +37,7 @@ jobs: # links, which a grep cannot tell apart — that one is asserted on # code cells only, post-merge, by the strict audit in data-lectures. if grep -rnF 'media.githubusercontent.com/media/QuantEcon/data-lectures' lectures/; then - echo "::error::Read data-lectures over raw.githubusercontent.com — that is the only CORS-clean form that resolves (repoint rule 5), and the media host is LFS-only, so it 404s every file data-lectures publishes (repoint rule 6)." + echo "::error::Found a data-lectures read on media.githubusercontent.com — the LFS media endpoint, which 404s every file data-lectures publishes (repoint rule 6). Use raw.githubusercontent.com, which here is also the only CORS-clean form that resolves in the reader's browser (repoint rule 5)." exit 1 fi echo "OK — no data-lectures read on the media host."