Why can a pull request that edits the pipeline file stop its own security gate from running?
answer
- where is the pipeline read from
- the judged supplies the rules
- disabled, renamed, narrowed, or relaxed
- no result is not a failure
- vendored threshold in the same commit
basics
~20 sMost CI systems read the pipeline definition from the commit under review, so one change can disable, rename or narrow the very job that would have judged it — and can equally edit a policy file vendored beside the code the rule inspects.
solid answer
~50 sPipelines are read from the branch being tested, because otherwise you could never iterate on a pipeline change in a pull request. The same property means the change under review supplies the rules that evaluate it. A single commit can delete the policy job, set its condition to false, narrow a path filter so it no longer matches, rename it so the required name never reports, or lower a threshold in a policy file that lives in the repository. The dangerous outcome is not a denial that got argued down — it is a check that silently produced no result, which on an advisory gate is indistinguishable from clean. As a reviewer, treat any hunk touching the evaluation path — the job, its trigger conditions, or the rule data it reads — as a different class of change from the feature diff it is buried in.
code
diff · 18 lines--- a/infra/orders-db.yaml
+++ b/infra/orders-db.yaml
@@
+ backupRetentionDays: 3
--- a/.ci/policy/retention.yaml
+++ b/.ci/policy/retention.yaml
@@
-minBackupRetentionDays: 30
+minBackupRetentionDays: 3
--- a/.ci/pipeline.yaml
+++ b/.ci/pipeline.yaml
@@
policy-check:
- if: always()
+ if: false # temporarily disabled, flaky
...go deeper
Know that the pipeline file for a pull request usually comes from that pull request's own branch, so editing it changes what runs on it.
Be able to list the concrete bypasses — job removed, condition false, trigger narrowed, name changed, threshold lowered — and say which leaves a green-looking page.
Demonstrate the reviewer's triage: any hunk touching the job, its conditions, or the data it reads is an enforcement change and gets read differently from the feature diff.
Frame it as the trade you accepted for iterating on pipelines in pull requests, and explain that the answer is relocating authority rather than forbidding pipeline edits.
## The property that causes it On essentially every mainstream CI system, the pipeline definition applied to a pull request is read **from the commit under review**, not from the default branch. This is deliberate and necessary: if pipelines were only ever read from the default branch, you could never test a pipeline change before merging it, and every pipeline edit would be an unverified change to production automation. The consequence is unavoidable: **the change under review supplies the rules that judge it.** That is the entire subject of this leaf. ## The four ways one change neutralises its own gate Using a concrete rule — a new datastore must be provisioned with at least thirty days of backup retention, enforced by a job in the pipeline that reads a policy file vendored in the same repository: 1. **Delete or disable the job.** The job is removed, or its condition is set so it never executes. Nothing runs, nothing reports. 2. **Narrow the trigger.** A path filter is edited so the job only fires for changes under `infra/`, and the new datastore is defined under `platform/`. The rule is intact; it simply does not apply here. 3. **Rename it.** The job is renamed, so the *result name* the merge rule requires is never posted. On a fail-closed platform the pull request wedges (good). On a platform that counts a skipped or missing result as satisfied, it merges. 4. **Edit the rule's own inputs.** The job runs, the rule evaluates, and it passes — because the threshold it reads was lowered from thirty to three in the same commit. This is the nastiest variant, because the pipeline looks healthy and the check is genuinely green. Variant 4 is why a vendored policy file is not simply "policy as code done well". Code and its rules living together is excellent for iteration speed and terrible for authority, and you have to decide which of those you were buying. ## It is usually not an attack The framing that gets candidates into trouble is treating this purely as a malicious-insider story. Most real occurrences are mundane: someone disabled a flaky job while debugging and forgot to restore it; a stale branch merged an old pipeline file back over a newer one; a path filter was copied from another repository; a threshold was lowered "temporarily" for a spike. The control failed for boring reasons, which is exactly why you cannot rely on noticing it in review. It is worth knowing that platforms already treat pipeline-from-the-branch as a trust problem in one specific case: pull requests from forks typically run with a restricted context and without privileged credentials, precisely because the definition comes from code the repository owners do not control. That restriction protects the *repository's secrets*. It does nothing at all to protect the *gate* from a change made by someone who does have write access — which is the case this leaf is about. ## What the reviewer actually looks at As the maintainer reviewing that pull request, the useful habit is a triage question rather than a full read: **does this diff touch the evaluation path?** Three things count as the evaluation path: - the job or step that performs the check, - its trigger and matching conditions, - any rule, threshold or exemption list the check reads from the repository. A hunk touching any of those is a change to enforcement wearing a feature branch's clothing, and it deserves attention out of proportion to its line count. A five-line diff that adds a datastore and moves a retention floor from thirty to three is a policy change with a datastore attached, not the other way round. ## The failure mode to name out loud The outcome you care about is **the check that produced no result**. Not a denial, not a failure — nothing. Whether that is fatal depends entirely on whether the check is required or advisory, and on whether your platform counts a skipped result as satisfying a requirement. If the answer to both is "advisory" and "yes", then the pull request page for a change that removed its own gate looks exactly like the page for a change that passed it. ## Where this leads Once you can articulate this, the design response follows: the decision must be resolved from somewhere the gated change cannot rewrite, and its absence must be fatal rather than invisible. Iteration speed on the pipeline and authority over the pipeline pull in opposite directions, and you resolve that by keeping a fast local copy for feedback while the authoritative rule comes from outside the repository.
- Why do CI systems read the pipeline from the branch under review at all, if it creates this hole?Because the alternative is worse for everyone: if the definition always came from the default branch, no pipeline change could ever be tested before it merged, so every edit to your automation would go straight to production unverified. The property is a deliberate trade for iteration speed, and the fix is to move the authority elsewhere rather than to take the property away.
- Which of the four bypasses is hardest to spot in review, and why?Editing the rule's own inputs. The job still runs, the check reports green, and the pipeline looks perfectly healthy — the only evidence is a two-line change to a threshold file that reads like configuration. Deleting a job at least leaves a visibly missing check; a lowered threshold leaves a passing one.
- Does restricting fork pull requests close this gap?No. Restricting what a fork's pipeline can access protects the repository's credentials from untrusted contributors. This gap is about someone who legitimately has write access changing the rules that apply to their own change, so the fork restrictions never engage. It is a control-authority problem, not an untrusted-code problem.
It is an exam where the answer key travels in the same envelope as the paper, and the candidate is the one who seals it.
saying these in an interview costs you the question
- Assumes the pipeline is always read from the default branch
- Frames it only as a malicious insider, never an accident
- Overlooks the vendored threshold as an editable input
- Believes code review reliably catches a disabled job
- Confuses fork restrictions with protecting the gate