| A harmful change of the template reaches every repository |
Pull requests with every required check here; every kind rendered and checked with its real tools, also after an update from the latest release; a release that is not only dependency updates waits for the maintainer; in each repository, the update passes its own checks |
the Variants workflow, tests/test_template.py |
| Copying or updating the blueprint runs code on the user's machine |
No tasks, migrations or Jinja extensions in copier.yml, so Copier needs no --trust |
test_copying_runs_no_code_of_the_blueprint in tests/test_template.py; the Variants workflow updates every kind without --trust |
| A rendered workflow loses its pins or runs with more permissions |
Every action pinned to the commit hash of a version, never of a branch, which zizmor would report as soon as the branch moves on; the workflows of a new repository are the blueprint's own, which zizmor and actionlint audit |
test_every_action_is_pinned_to_a_commit, test_no_workflow_pins_an_action_to_a_branch, test_the_workflows_are_the_blueprints_own |
| A version published minutes ago, perhaps from a hijacked account, reaches every repository with the weekly refresh of the lock files, which moves every indirect dependency to its newest version without the week of waiting |
The refresh waits for the maintainer instead of merging on its own, and Renovate builds it anew only in its weekly window, so the diff the maintainer reviewed is the one that merges; a cooldown of the package managers isn't possible for the refresh alone, as Renovate sets their environment for the whole repository and would hold back the fixes of vulnerabilities too (decision 0012) |
test_the_weekly_refresh_of_the_lock_files_waits_for_the_maintainer in tests/test_template.py |
| A lock file stops getting updates, also the fixes of vulnerabilities, because Renovate can't read the command in its header and skips it with no more than a warning in its log |
Every lock file of the blueprint and of each kind of project is read as Renovate reads it; in every pull request that changes the rules or a lock file, Renovate itself reads every file, and a warning or a lock file that it skips fails the check |
test_renovate_reads_every_lock_file_of_the_blueprint and test_renovate_reads_every_lock_file_of_a_project in tests/test_template.py; the job renovate rules of .github/workflows/renovate.yml |
blueprint.py leaks a secret |
It never reads the value of a secret; it only names the missing ones |
test_apply_sets_everything_but_the_secrets in tests/test_blueprint.py |
An unexpected answer of GitHub makes blueprint.py apply wrong settings |
Every answer is read strictly; anything else stops the run with an error before a change |
test_errors_of_github_stop_the_run; coverage-guided fuzzing with Atheris in fuzz/fuzz_blueprint.py |
| The settings of a repository are weakened by hand |
The settings bot compares them with the settings as code after every change and every week, and applies them |
the Settings workflow of each repository |
| A release stands on a tag that the release workflow didn't sign: one that someone created first, at a commit of their own, or one that release-please created after the run of the merge was lost |
Every run of the Release workflow, not only the one of the merge, signs the tag of a merged release pull request before the release is created on it, and release-please creates a release only in a run that signed its tag; a tag that exists already must be the release workflow's own, signed at the commit of the merge, and the isolated build checks that again before it builds or signs anything |
.github/sign-tag.sh, tests/test_sign_tag.py |
| A rogue release tag: whoever can push a tag to the blueprint, with the token of a job that runs a third-party action or with a leaked token, creates the tag of a new version at a commit of their own, and every blueprint bot updates to it and merges its workflows |
Only the release app may create a release tag (the ruleset Release tag creation, which the settings bot applies where the variable RELEASE_AUTOMATION_APP_ID names the app), and like everyone else it can't move or delete one. The blueprint bot of each repository updates only to the latest published release that is immutable, whose tag points at a commit of the blueprint's main no older than the version the repository has, and that the release workflow of the blueprint signed with gitsign; it fails on any other release, before copier update runs |
test_only_the_release_app_creates_release_tags in tests/test_blueprint.py; test_the_blueprint_bot_verifies_the_release_before_it_updates in tests/test_template.py; .github/sign-tag.sh |
| A rogue release of the issue assistant: whoever can push a tag to the issue assistant publishes a release of their own, and the blueprint takes its workflows over and brings them to every repository |
The sync bot of the blueprint takes over only the latest published release of the issue assistant that is immutable, whose attestation by GitHub holds, whose tag points at a commit of its main and that its release workflow signed with gitsign; it fails on any other release before it installs anything. It installs exactly the verified commit, in a job without secrets, and the job of its pull request, which has the release app, runs nothing of the release and commits only workflows, the firewall policy and the files of the template that render workflows, by plain paths. Renovate doesn't move the pin of the issue assistant on its own |
test_the_sync_verifies_the_release_before_it_installs and test_any_other_release_of_the_issue_assistant_stops_the_sync in tests/test_sync_issue_assistant.py; .github/sign-tag.sh |
| The private key of the release app is abused |
The environment release only for main; tokens per job with the least permissions; no administration for Renovate |
.github/workflows/renovate.yml, .github/repository.toml |
| A pull request makes the diagram bot commit more than its diagrams, as the release app |
The bot acts only for a branch of the repository and runs no code of the pull request: blueprint.py of main takes from the artifact of the check only sources in docs/diagrams/, SVG pictures in docs/images/diagrams/ and pages of Markdown, by plain paths, deletes only pictures, and commits only onto the commit that the check drew; its token can write contents and pull requests, not workflows. The bot of main, whose branch bot/diagrams moves from an older main to the newest across changes of workflows, can write workflows too, but runs nothing that it drew and commits only what the same blueprint.py lets through |
test_the_bot_writes_only_sources_pictures_and_pages, test_the_bot_refuses_anything_else and test_the_diagram_bot_commits_nothing_but_diagrams in tests/test_diagrams.py |
| A pull request, also from a fork, makes the PR coach post mentions, links or HTML, overwrite another comment, or run its code with a token that can write |
The coach reads only the check runs and their annotations from the API, checks out only .github and blueprint of the default branch and runs no code of the pull request; it shows text that a pull request controls, such as the names of its checks, the messages and paths of their annotations, its title and its branch, only as escaped code on one line, cut to a length; it links only to plain pages of github.com, and edits only its own marked comment, written by the token of the workflows |
test_the_report_shows_untrusted_text_only_as_code, test_no_untrusted_text_leaves_its_code and test_a_comment_of_someone_else_stays_as_it_is in tests/test_coach.py; test_the_pr_coach_reads_the_checks_and_runs_no_code_of_the_pull_request in tests/test_template.py |
| A pull request makes the fix bot commit more than the fixes of its checks, as the release app, or spam a pull request from a fork with suggestions |
The fix commands run only in the unprivileged jobs of the checks; the bot runs nothing of the pull request, checks out only .github and blueprint of the default branch and takes the files that the checks kept as data: only text files that the commit has already, by plain paths, outside .git and .github/workflows/, a hundred at most, never a new, deleted or linked file, committed only onto the branch of the pull request while it is still at the commit of the checks, with a token that can write only contents. It commits at most twice in a row onto its own commits, leaves a pull request with the label no-bot-fixes alone, and pushes nothing to a fork: there it posts at most ten suggestions in one review, none twice, each in a fence that its lines can't close |
test_the_bot_writes_only_plain_paths_outside_git_and_the_workflows, test_the_fixes_of_the_checks_are_text_files_by_plain_paths, test_the_bot_writes_no_link, test_the_bot_changes_only_files_that_the_commit_has, test_the_bot_never_fixes_its_own_fixes_again, test_the_bot_suggests_a_fix_once_and_only_so_many and test_the_fix_bot_has_only_what_it_needs in tests/test_fixes.py, decision 0023 |
| A name or a description on the dashboard runs as script |
Every value is escaped before it becomes HTML |
dashboard.py |