GitLab pipelines: contribution, job token and runner boundaries
Trace GitLab execution context, included configuration, downstream inputs, job-token permissions and runner reuse using private synthetic fixtures.
- 01Pin version and pipeline context
- 02Resolve included configuration
- 03Map job and downstream authority
- 04Compare one private fixture
- 05Reset runner and artifacts
At each step: retain the control → record the observation → continue only while the next boundary is understood.
Used in these playbooks#
Scope and fixture contract#
Use an owned GitLab project pair and disposable runner, or owner-supplied configuration exports. Pin GitLab and Runner versions, feature flags, repository commit, pipeline ID, source project, target project and triggering user. Create a private synthetic artifact in project B and one job in A. Control over A's contribution or job must not acquire B's private authority or a release identity beyond the stated integration. Reuse CI and release tracing for artifact provenance and source data flow for executable input.
1. Establish which pipeline really runs#
Trace merge-request source, configuration revision, execution project and trigger actor independently. Fork pipelines ordinarily run with fork resources. A parent member can run a fork merge-request pipeline in the parent context, using fork-controlled configuration with parent resources and that member's permissions. Inspect the exact settings and approval path. Protected variables and runners have additional requirements, including same-project protected branches; do not infer their exposure merely from parent execution. Re-running an older pipeline can retain its original context. GitLab merge request pipelines
Build one row per job: who edits the YAML, who changes included files, which commit is fetched, which scripts execute, and what the resulting identity can do. Package installation, test discovery and generated scripts are executable inputs even when the visible job looks like a harmless test command.
2. Follow configuration and downstream trust#
Resolve include, extends, component inputs, dynamic child configuration and multi-project triggers before judging a job. Capture the merged configuration through an owner-provided export. For remote includes, compare a pinned expected file with a changed inert fixture; supported include:integrity verifies a SHA-256 digest and rejects a mismatch. A mutable reference is a candidate until the lower-trust editor and privileged consumer are connected. GitLab YAML reference
Track forwarded variables and generated child YAML into the receiving pipeline. A downstream job may interpret a value as a shell command, artifact selector, deployment environment or cloud role. Compare the allowed value and one disallowed synthetic value at that exact sink. Preserve the downstream pipeline's own policy and actor, since upstream success does not establish downstream entitlement. Downstream pipelines
3. Separate token restrictions from secret masking#
A job token is limited by its API capabilities and the triggering user's authority. Review the receiving project's allowlist, feature visibility and any fine-grained restrictions together. Allowlisting a project does not independently grant its users new project permissions. Distinguish currently running jobs from expired-token attempts. Review repository-push settings for the deployed GitLab version rather than assuming every job token is permanently read only. GitLab job tokens
Masked and hidden variables address exposure in interfaces; code already authorized to consume a variable can still misuse it. With a dummy value, identify the exact job receiving it, environment scope and later executable consumer. Never print real CI variables to inventory privileges. GitLab CI variables
4. Reproduce one boundary using inert controls#
| Candidate | Controlled comparison | Required evidence |
|---|---|---|
| Fork code enters parent authority | Trusted base versus inert fork fixture | Configuration SHA, execution project, actor and marker-only result |
| Job A reaches private project B | Allowed integration versus disallowed project/actor | Exact synthetic resource, token context and accepted/denied response |
| Runner leaks previous job state | Two owned jobs on a disposable runner | Known marker survives across a boundary that promises isolation |
| Publisher consumes wrong artifact | Expected producer versus wrong fixture run | Digest, selected run and actual trusted consumption |
For runners, inspect executor, host/socket mounts, shared working directories, cache policy and whether private images can be reused by unauthorized jobs. Use one predetermined synthetic marker path and one owned image. A shell executor or privileged container is a risk signal; the demonstrated issue is unintended cross-job access. Do not inspect other users' workspaces. Runner isolation guidance
Local command cheat sheet#
These searches read a local clone and execute no CI jobs. Follow results into referenced files; absence of a match is not coverage proof.
rg --files --hidden -g '!.git' -g '*gitlab*' -g '*.yml' -g '*.yaml' ./repo
rg -n --hidden -g '!.git' -g '*.yml' -g '*.yaml' \
'include:|extends:|trigger:|forward:|id_tokens:|environment:' ./repo
rg -n --hidden -g '!.git' \
'CI_JOB_TOKEN|CI_PIPELINE_SOURCE|CI_MERGE_REQUEST|privileged|pull_policy' ./repo
Evidence and completion#
- Pipeline project, configuration revision and triggering actor recorded separately.
- Parent/fork and protected-resource assumptions verified for the installed version.
- Included configuration, downstream inputs and executable consumers traced.
- Token capability, actor permission and receiving-project policy distinguished.
- Allowed and denied cases use the same private synthetic resource.
- Job logs contain no usable credentials; fixtures and runner state are removed.
A report should state the lowest actor that can initiate the chain, required maintainer interaction, exact acquired capability and secure comparison. Intended public artifacts, a rejected token, or deliberate shared runner trust do not establish a private boundary failure.
Vulnerability reverse references#
- 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.
- 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.
- Shared CI runner exposes private state across jobs — A job reaches a previous job's private filesystem or image material despite the runner's promised isolation.
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