FINOS Financial Services Open Source Developer study materials
FINOS Financial Services Open Source Developer study resources

Hardening the Pipeline of a Shared Library With a Public Mirror

FINOS Financial Services Open Source Developer · Put your knowledge to work in context.

All case studies

Hardening the Pipeline of a Shared Library With a Public Mirror

A shared Java utility library is used by 30 internal teams and is also published as a foundation-hosted project. The source of truth is an internal GitHub Enterprise repository; a nightly job force-pushes a mirror to public GitHub, where external contributors open pull requests that maintainers must manually port. CI consists of twelve third-party GitHub Actions, all referenced by version tags such as v3, and one workflow that uses pull_request_target, checks out the pull request head, and has access to a cloud deployment secret. Builds are not reproducible and no artefacts are signed. Contribution requires a CLA signed through a hosted bot; the bot has been down for three weeks and nine pull requests are stalled. External contributors complain that CI never runs on their pull requests because the workflows reference internal runners.

1. Prioritise the CI security problems and state the concrete fix for each.

Read the worked answer

1. pull_request_target + checkout of the PR head + a deployment secret (critical). This combination lets any GitHub user open a pull request, have their code executed in a context with secrets, and exfiltrate them. Fix by splitting the pipeline: an unprivileged workflow triggered by pull_request, running on GitHub-hosted runners, with permissions: read-all and no secrets, that builds and tests; and a privileged workflow triggered after merge (push to the protected branch), which deploys. Never check out untrusted code in a job that holds credentials.

2. Version-tag references for the twelve actions (high). Tags are mutable - a maintainer or an attacker who compromises an upstream account can repoint v3 at new code that runs with your repository token. Pin every action to a full commit SHA, put a comment with the human-readable version next to it, and let Dependabot propose SHA bumps so pinning does not mean going stale.

3. Secrets handling (high). Move from stored cloud keys to OIDC federation with short-lived credentials scoped per environment, and require an environment protection rule for production deployment. Then a leaked workflow cannot hand over a long-lived key.

4. Change control on the pipeline itself (medium). Add CODEOWNERS over .github/workflows/ so a workflow change requires security review; most supply-chain compromises arrive as a one-line workflow edit. Enable secret scanning and push protection.

5. Provenance (medium). Reproducible builds and signed artefacts matter for a library 30 teams consume, but they only make sense after 1–4; sequence them second.

2. The CLA bot has been down for three weeks and nine pull requests are stalled. What do you do?

Read the worked answer

The bot is a mechanism; the obligation is a recorded grant of rights. Separating the two dissolves the blockage.

Options, in order of preference:

  1. Restore or replace the mechanism quickly - most CLA services have a manual entry path: an administrator can record a signature, or you can hold a signed CLA on file and mark the contributor as cleared.
  2. Time-boxed manual fallback: a written statement in the pull request ("I confirm I have the right to submit this code under the project's licence") recorded in a CONTRIBUTORS file by a maintainer. Document it in CONTRIBUTING.md and in a pinned issue so the process is visible and auditable, and reconcile when the bot returns.
  3. Switch the project to DCO with git commit -s enforced by a workflow, if the project's legal basis allows it. This removes the single point of failure permanently and lowers friction. It is a governance decision, not a maintainer decision - raise it with the foundation and the contributing firms.

What not to do: merge without any recorded grant, or leave contributors waiting silently. Merging untracked code creates an IP defect that is expensive to unwind; silence for three weeks is how a project loses the first-time contributors who would have become maintainers.

Also fix the root cause: the CLA check should be one required status among several, with a documented bypass authority and a named owner. A control nobody owns is a control that fails quietly.

3. The force-push mirror and the internal-runner requirement: what would you change, and why?

Read the worked answer

The force-push mirror is the root cause of most of the friction. It rewrites public history on a schedule, so any external pull request is against a moving target; it makes public contributions structurally second-class; and it means public commit hashes are meaningless to anyone who forked. Contributors notice this and stop contributing.

Better models:

  • Public repository as the source of truth, with internal CI and internal releases consuming from it, if policy permits. Simplest, and it is what the foundation and external contributors expect.
  • Bidirectional sync with pull requests rather than force-push, where merges happen in the public repository and flow inward. Never rewrite public history.
  • Internal source of truth, public read-only mirror, which is only honest if the project states that external contributions are advisory. If the project is presenting itself as an open project, that model will not survive contact with its community.

Pick one and write it in CONTRIBUTING.md; ambiguity is worse than either extreme.

On internal runners: external code must never execute on the internal network. Run external pull request CI on GitHub-hosted runners with no secrets and no ability to reach internal registries; make the cache read-only. If some tests genuinely require internal infrastructure, gate them behind a maintainer-applied label so a human decides when untrusted code touches internal systems - and accept that those tests simply do not run for external contributors.

One more thing: stop hand-porting pull requests. Cherry-pick with the original author preserved, or merge publicly. Losing authorship is both unfair and a licence-tracking problem when someone later asks who wrote a given line.