Repository navigation
Harden registry CI pipeline against contributor-controlled names and symlinks - #470
Merged
Merged
Conversation
Contributor-controlled data (branch names, PR titles, provider directory
and service file names) reached /bin/sh via exec/os.system in the setup
and signing scripts, and symlinks under providers/src were dereferenced
by the update, sign and package steps of the post-merge build.
- setup-job.js / get-version.js: write REG_* via core.exportVariable,
parse the PR number with an anchored regex for GitHub merge and squash
subjects and require it to be numeric
- get-updated-providers.py: write PROVIDERS / NUM_PROVIDERS directly to
$GITHUB_ENV in heredoc form; accept only
providers/src/<provider>/<version>/{provider.yaml,services/<file>}
with every component matching [A-Za-z0-9_][A-Za-z0-9._-]*
- new scripts/common/provider_tree.py: shared name allowlist, symlink /
regular-file / containment checks, GITHUB_ENV writer, S3 key parser
- update-versions.py, sign-provider-docs.py: validate the provider tree
before reading it; shutil.copyfile instead of os.system("cp ...")
- simulate-REGISTRY-PULL.py: tarfile extractall with filter='data'
- pull-additional-docs-from-artifact-repo.py: validate artifact keys
before joining them onto local paths
- sign-file.sh, main.yml: quote arguments
- CONTRIBUTING.md: document the enforced layout and naming rules
All 38 existing providers (1909 files) pass the new checks unchanged.
Reported by Kevin Backhouse (GitHub Security Lab).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jeffreyaven
added a commit
that referenced
this pull request
Sep 30, 2026
Promote CI pipeline hardening to main (#470)
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.
Summary
Hardens the post-merge build and deploy pipeline (
.github/workflows/main.ymland the scripts it runs) so that contributor-controlled input can no longer reach a shell or be dereferenced by the signing step. Reported by Kevin Backhouse of the GitHub Security Lab.Command injection via
$GITHUB_ENVwrites andcp(CWE-78)scripts/setup-js/setup-job.jsandget-version.js: replacedexec('echo "NAME=value" >> $GITHUB_ENV')withcore.exportVariable(), which appends to$GITHUB_ENVusing a heredoc delimiter and never invokes a shell. Branch names, PR titles and commit messages now land in the environment as literal strings. On push events the PR number is parsed with an anchored regex for GitHub's merge and squash subject formats and must be numeric; anything else fails the job.scripts/setup/get-updated-providers.py:os.system("echo '...' >> $GITHUB_ENV")replaced with a direct heredoc write. Every path underproviders/srcin the diff must be<provider>/<version>/provider.yamlor<provider>/<version>/services/<file>, with each component matching[A-Za-z0-9_][A-Za-z0-9._-]*. Git C-quoted paths are rejected.scripts/package/sign-provider-docs.py:os.system("cp ...")replaced withshutil.copyfile.sign-file.shquotes its arguments.Symlink dereference in signing and packaging (CWE-59 / CWE-61)
scripts/common/provider_tree.pywithvalidate_provider_source_tree(): walksproviders/src/<provider>/<version>one component at a time, refuses any symlink or non-regular file, refuses anything that resolves outside the tree, and refuses entries other thanprovider.yamlandservices/. It runs in the setup step (before any secret is in the environment) and again inupdate-versions.pyandsign-provider-docs.py.scripts/tests/simulate-REGISTRY-PULL.py:tarfile.extractall(..., filter='data').scripts/deploy/pull-additional-docs-from-artifact-repo.py: artifact repo keys are validated as<REG_PROVIDER_PATH>/<provider>/<file>before being joined onto a local path.package-provider-docs.pyandpublish-provider-docs-to-artifact-repo.py: names validated before use in paths and object keys.Other
main.yml: quoted$providerand$providersdirin the test step.CONTRIBUTING.md: documented the layout and naming rules the pipeline now enforces.Verification
get-updated-providers.pyselects all 38 as before.feat.$(id>pwned), provider dirx'$(id>pwned)', service filea;id>pwned;.yaml, a symlinked service document, a symlinkedprovider.yaml, a symlinkedservices/directory, a non-numeric PR number, and an archive with traversal and symlink members. Each is rejected with exit 1 and no command runs. Control cases produce the same output as before.Notes for reviewers
REG_SOURCE_BRANCHand the otherREG_*values remain contributor controlled. They are safe as environment variables but must not be interpolated with${{ env.* }}insiderun:scripts.🤖 Generated with Claude Code