Repository navigation
Conversation
The constituency and local authority calibration logs hold every target's weighted estimate at every logged epoch, with no small-cell control. A target supported by a few households, divided by a published count, could give one household's value back (Sol review of #599, shown on synthetic inputs). This repository's artifacts are public. Nothing downloads them. A test fails if any workflow uploads a calibration log again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…603) The substring check missed directories, globs, expressions and renamed files. A job that holds a data credential or downloads artifacts may now use only reviewed actions, upload only literal files reviewed for that job with their producer, and run only reviewed publishing commands. Mutation tests cover each bypass on a clean synthetic job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Set-up actions are reviewed for their inputs: setup-uv can't be pointed at the workspace or have its cache turned on explicitly. - A reviewed publishing command must be a whole logical line, with continuations joined, so it can't excuse another command on its line. - A job is a data job when it or its workflow uses any secret other than the GitHub token, or passes secrets on; the versioning job's commit is reviewed with its exact inputs. - Producers must appear in an earlier run step outside a comment, and the docstring states what the check does not establish. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l re-review 2 of #603) - setup-uv's cache is on by default on hosted runners and its location can be moved through UV_CACHE_DIR, so the two data jobs now set save-cache: false, and the guard requires it. - A job is a data job when it mentions secrets in any syntax other than secrets.GITHUB_TOKEN, or can request an OIDC token. - The versioning commit, which adds the whole workspace, must not follow a command that fetches or builds the data. - Any gh command, and curl's upload forms, count as publishing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…un only its lines (Sol re-review 3 of #603) - Expressions are case-insensitive, so SECRETS.NAME and Secrets['X'] mark a data job; secrets.GITHUB_TOKEN is exempt in any case. - permissions: write-all grants id-token, so it marks a data job too. - The versioning job commits its whole workspace, so it may run only its three reviewed lines; a bare make, which builds the data, fails. - curl's combined short flags with any prefix, -K and --config count as publishing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(Sol re-review 4 of #603) A job that commits its whole workspace may not set a step shell or env, or a job or workflow env, defaults or container: BASH_ENV or a custom shell could run a data build around a reviewed line. Also: curl -H/--url-query @file and wget --post-file count as publishing, and edits inside the reviewed script are tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-review 5 nits) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…603) Each review round found another curl spelling that sends a file; the last was --url-query name@file, and --variable name@file with --expand-url is another. No uk-data workflow runs curl or wget, so a new use is reviewed like any other command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew of #603) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 of 76 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Security fix, held for Max (cos d1596). By default this would land with the d833 uk-data batch. The decision asks whether it should land on its own, before that batch.
Why
Two raw calibration logs,
constituency_calibration_log.csvandla_calibration_log.csv, are uploaded as Actions artifacts by two kinds of build. One is every pull request build that gets as far as the upload steps. The other is every main build the push workflow runs: it triggers on pushes that changepyproject.toml, and its gate can skip the build. They hold every target's weighted estimate and value at every logged epoch, nationally and for each constituency and local authority, with no contributor count, dominance check or suppression.Sol's review of #599 showed the risk on synthetic inputs. An income category supported by one household publishes a weighted income and a weighted count, and dividing them gave back that household's income exactly. A large weighted count does not protect it. That was a demonstration on synthetic data; no disclosure has been observed in a real log.
This repository is public, so its Actions artifacts can be downloaded by anyone signed in to GitHub. As of 15 October 2026 there are 448 live artifacts of this kind:
constituency_calibration_log.csvla_calibration_log.csvbase_constituency_calibration_log.csv(from #599's early runs)Of the first two kinds, 18 each come from pushes to main. These counts come from
gh api repos/PolicyEngine/policyengine-uk-data/actions/artifacts?name=<name>on 15 October; the independent review could not repeat that query from its sandbox.Nothing in this repository downloads these artifacts or reads them later, and no documentation tells people to fetch them.
git grep calibration_logfinds the code that writes the logs, the upload steps and, after this PR, the guard. Consumers outside the repository can't be ruled out.What changes
pull_request.yamlandpush.yamlno longer upload the two logs. The builds still write them, and they stay on the runner.Both data jobs set
save-cache: falseon setup-uv. They still restore uv's package cache, but never save one. setup-uv's cache is on by default on hosted runners, and a later step can move its location into the workspace throughUV_CACHE_DIR.tests/test_workflow_artifacts.pyis a regression check on the routes out of jobs that can reach the data. A job counts as one when it, or its workflow, does any of these:secretsin any form or case other thansecrets.GITHUB_TOKEN:secrets.NAME,SECRETS.NAME,secrets['NAME']ortoJSON(secrets). Expressions are case-insensitive, and each is presumed a data credential, whatever it's called;id-token: write, orpermissions: write-all);In such a job:
ghcommand,git push, anycurlorwget,aws s3,gsutil,gcloud, Hugging Face uploads andtwine. Every curl and wget counts, not only their upload options, because each review round found another spelling that sends a file (-sT,-H @file,--url-query name@file,--variable name@filewith--expand-url). No uk-data workflow runs either, so a new use is reviewed like any other command. The reviewed ones are push.yaml'smake upload(the private Hugging Face and GCS datasets) and the release tag;shell,envorworking-directory, or a job or workflowenv,defaultsorcontainer, any of which could run something around those lines. A baremakewould build the data, so command names aren't enough;Jobs that can't reach the data (docs, changelog) may publish.
What it doesn't establish, as the docstring says: it works from secret, action and command names, and it does not inspect:
A producer pattern shows text in an earlier step. It doesn't show that the step ran, that the path is a regular file, or that the contents are disclosure-controlled. Changes to these routes need human review.
Mutation tests add each of Sol's bypasses to a clean synthetic data job, including:
make upload; curl …, and a commented reviewed command;As a positive control, the check fails at
origin/mainon the two raw-log uploads, and on setup-uv saving its cache in both data jobs.The artifacts that already exist are not touched. Deleting them is a separate, irreversible action, and it is part of the same decision.
Relation to #599
#599 (the W3 fit comparison) also removes the pull request uploads. It replaces them with a disclosure-controlled fit summary: within-10% booleans, plus an error only for national targets supported by at least 10 households. Whichever lands second needs a conflict resolution in
pull_request.yaml. If this lands first, #599, #600 and #602 will fail this guard until they add their reviewed entries:fit_summary.json,base_fit_summary.json, the distribution reports and the aggregate results;gh api).That is intended: each new route out of a data job gets a reviewed line. This PR exists so the leak can stop before the d833 batch.
Tests
pytest policyengine_uk_data/tests/test_workflow_artifacts.py: 93 passed here. Againstorigin/main's workflows, the two real-workflow checks fail, as expected.axiom: n/a: CI artifact handling; no policy change
🤖 Generated with Claude Code