CI and releases: trace untrusted input into privileged work
Audit workflow events, shell interpolation, untrusted builds, artifact consumers, and release identity as one privilege chain.
- 01Identify event trust
- 02Map executable input
- 03Inspect privileges
- 04Follow artifacts
- 05Validate locally
- 06Verify release identity
At each step: retain the control → record the observation → continue only while the next boundary is understood.
Used in these playbooks#
Draw the actual privilege chain#
For every release-capable workflow, record event actor, workflow-file revision, checked-out revision, executable inputs, token permissions, reachable secrets, runner persistence, and final consumer. Distinguish data review from code execution: a checked-out contribution becomes executable through Makefiles, package install hooks, test discovery, plugins, and local actions as well as explicit shell scripts.
Use one row per job. Include reusable workflows and composite actions; apparent isolation at the top-level YAML may disappear inside their implementation. A permissive token setting without a lower-trust entry is a hardening observation, not a completed takeover claim.
| Boundary | Local evidence to collect | Comparison control |
|---|---|---|
| Metadata → generated shell | Original expression and resulting script | Environment variable passed as data |
| Fork content → privileged build | Exact fetched revision and executed file | Trusted base revision only |
| Test artifact → publisher | Producer identity, run association, consumed path | Artifact from expected trusted build |
| Build → attestation → release | Artifact digest and accepted signer identity | Wrong-source artifact rejected |
Triage commands and a safe coding pattern#
These examples inspect local files; they have not been run against a target. Grep-style matching deliberately overselects candidates.
rg -n --hidden -g '*.yml' -g '*.yaml' \
'pull_request_target|workflow_run|issue_comment|permissions:|id-token:' \
./repo/.github
rg -n --hidden -g '*.yml' -g '*.yaml' \
'download-artifact|upload-artifact|cache-mode|head\.sha|npm install|make ' \
./repo/.github
Passing untrusted metadata through an environment variable avoids inserting its contents into the generated shell program. Quote it and do not subsequently eval it. This pattern does not solve dangerous argument semantics in downstream tools. GitHub secure use.
- name: Inspect title as data
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: printf '%s\n' "$PR_TITLE"
Check current protections before declaring a pwn request#
GitHub currently documents fork-ref checkout protection for pull_request_target and read-only default cache access for that event. Inspect action revision, explicit allow-unsafe-pr-checkout, cache-mode, manual fetches, repository policy, and actual execution. The trigger name alone proves neither execution nor writable cache. Controls can differ from older published examples. Current pull_request_target guidance.
Hypothetical trace: an unprivileged test job uploads report.zip; a privileged follow-up selects an artifact by a generic name; an extracted helper is executed before publication. Validate run/repository binding, extraction destination, and consumer behavior in your own fixture with an inert marker and dummy credentials. A successful workflow conclusion does not authenticate every byte it produced. An artifact parsed strictly as data may break the supposed execution chain. GitHub Security Lab workflow analysis.
Follow identity to the shipped artifact#
For a known public artifact, this read-only verification example contacts GitHub. Replace repository, workflow, and commit with expected identities established independently; do not copy identities out of the untrusted artifact and then “verify” against them.
gh attestation verify ./dist/product.tar.gz \
--repo OWNER/REPO \
--signer-workflow OWNER/REPO/.github/workflows/release.yml \
--source-digest EXPECTED_COMMIT \
--format json > ./evidence/attestation.json
A reusable workflow may be the signer rather than the calling workflow. Verified provenance supports an origin claim; it does not prove the builder was uncompromised or the software is vulnerability-free. GitHub CLI verification.
Trusted publishing reduces long-lived publish-token exposure, but a compromised authorized workflow can still obtain publishing authority. Review environment approval, workflow identity, build separation, and remaining legacy credentials. PyPI trusted publisher security model.
Report and retest gate#
- A permitted low-trust actor controls the demonstrated input.
- Exact workflow/action revisions and security defaults are captured.
- Inert local reproduction reaches the privileged consumer.
- Real permissions are supported by configuration, not guessed from a trigger.
- Artifact run binding and signer/source identity are checked.
- Patched data handling rejects the case while normal releases still work.
Report the reachable capability and its prerequisites. Do not run target publishing steps or use discovered tokens to prove a local workflow hypothesis; maintainers can confirm live permissions through a coordinated reproduction when needed.
For GitLab repositories, also use GitLab pipeline boundaries: parent-project merge-request execution, job-token permissions and runner isolation have distinct semantics.
Vulnerability reverse references#
- Hard-coded product credentials — A reusable credential is embedded in source, history, a release, or a default configuration. Distinguish a shared built-in credential from an accidentally committed deployment credential when assigning the weakness.
- CI metadata interpreted as executable script — Pull-request titles, branch metadata, issue fields, or artifact values are inserted into a generated shell/program instead of being handled as data.
- Untrusted contribution executed by privileged CI — A trusted workflow fetches or loads contribution-controlled code, configuration, or dependencies and executes them with authority unavailable to the contributor.
- CI artifact poisoning across a workflow trust boundary — A higher-trust workflow treats attacker-controlled test/build output as executable code or authoritative release input without adequate producer and content constraints.
- Dependency confusion and unintended package origin — Dependency resolution selects an attacker-controlled package origin where the build or application expects an organization-controlled package.
- Untrusted executable, library, or module search path — A program resolves an executable/library/module from an attacker-writable search location instead of the intended trusted component.
- Updater accepts an artifact outside its publisher trust policy — The real update decision accepts modified bytes or an unrelated publisher identity before activation.
- Exposed repository or history secret — Source, history, or a published artifact exposes a credential outside its intended audience.
- GitLab job token crosses an unintended project boundary — A contribution-controlled job acquires access to a private receiving-project resource beyond the CI integration contract.
Nearby shared procedures#
Object, function, and property authorization · Authentication, recovery, federation, and session lifecycle · Trace input into queries, commands, and templates · Server-side fetching and network trust boundaries · Browser execution, cross-origin reads, and forged actions · Uploads, downloads, paths, archives, and XML · Workflow invariants, replay, idempotency, and races · REST, GraphQL, gRPC, WebSocket, and SSE permission parity