Skip to content

Backport builder security and operations

Build tooling — NOT a runtime dependency. This page explains how to operate and trust the build pipeline. The security posture of the Portable Document Format (PDF) engine itself is documented with the engine, not here.

The builder transforms source it does not author and emits a distribution it does not develop against. Its security model follows from that boundary. The trust boundary is the source checkout and the toolchain. The emitted artifact is read-only and machine-produced. The licensing split between the public and Pro packages is fixed in code.

The builder takes source repositories and the pinned toolchain as inputs. It produces a derived artifact. Three properties hold:

  • The generated distribution is read-only and machine-produced. It is published as version tags, not as branches of this repository. Development, bug reports, and feature requests go to the original nextpdf/* source repositories, never to the generated tree. Verified against the project README.md caution block and .github/workflows/build.yml (the release commits and tags the generated tree from scratch).
  • The release token scope is narrow. secrets.BACKPORT_TRIGGER_TOKEN authorizes cloning the source repositories at the release tag. It is referenced only in the source-checkout steps. Verified against build.yml (GH_TOKEN usage).
  • The continuous integration (CI) runner split is deliberate. Trusted events run on self-hosted PHP runners; fork pull requests and Dependabot runs are forced onto GitHub-hosted runners. Untrusted code never executes on the trusted runner pool. Verified against .github/workflows/0-ci.yml (the runs-on conditional).
  • The toolchain is constrained in composer.json. It uses Rector ^2.0, PHPStan ^2.1, PHPUnit ^13.0, and the symfony/polyfill-* set. The builder has no NextPDF runtime dependency, so a compromised NextPDF runtime package cannot reach the builder. Verified against composer.json.
  • Dependency updates are bot-driven and gated. Dependabot configuration and an auto-merge workflow are present. Dependabot runs are pinned to GitHub-hosted runners and still pass through the full CI gate (PHPStan, tests, dry-run) before merge. Verified against .github/dependabot.yml and .github/workflows/0-ci.yml, 9-dependabot-auto-merge.yml.
  • The output excludes build state. The release archive is zipped with vendor/ and .git/ excluded. The published artifact carries source and the generated manifest, not the builder’s own dependency tree. Verified against build.yml (zip ... -x '*/vendor/*' '*/.git/*').
  • Static analysis gates the build code. composer analyse runs PHPStan at level 10 over rector/rules and scripts on every push and pull request to either permanent branch. It runs before any dry-run. Verified against composer.json and 0-ci.yml.

The build script does not syntax-check the output locally because the build host runs a newer PHP than the target. The authoritative gate lives in the release workflow. After the build, the runner switches to the target PHP and runs php -l across output/src, failing the release on any parse or fatal error. The artifact is then installed and exercised across the validation matrix — PHP 8.1 through 8.4 for the PHP 8.1 lane, and PHP 7.4 and 8.0 for the PHP 7.4 lane. A distribution that a target runtime would reject does not reach a release. Verified against scripts/build.php (validateOutput()) and .github/workflows/build.yml (the syntax-check and validate-* jobs).

This is an observed-behavior guarantee scoped to the runtimes the pipeline exercises. It is not a conformance certification. It does not assert correctness of the transformed program beyond syntax acceptance and the project’s own test suite.

The two produced packages carry different licences, fixed in scripts/adjust-composer.php:

PackageLicence fieldSet by
nextpdf/backportApache-2.0generatePublicComposer()
nextpdf/backport-proproprietarygenerateProComposer()

The builder repository itself is Apache-2.0 (composer.jsonlicense). Its CHANGELOG.md records a prior relicensing from LGPL-3.0-or-later to Apache-2.0. Versions published before that change remain under the older license and stay retrievable, while every new version is Apache-2.0. The Pro distribution is proprietary, is produced only for the PHP 8.1 target, and requires phpseclib/phpseclib ^3.0. Verified against CHANGELOG.md, composer.json, and scripts/adjust-composer.php.

What the pipeline guarantees, scoped to what the code enforces:

  • Stop-on-first-failure. The build aborts at the first failing stage with a named error. A partial distribution is never released because the release jobs depend on a successful build and validation. Verified against scripts/build.php (step()) and build.yml job needs.
  • Serialized builds. The backport-build concurrency group with cancel-in-progress: false prevents two source releases from racing the same release tag. Verified against build.yml.
  • Reproducible inputs. The pipeline clones every source repository at the exact release tag before building. Verified against build.yml (--branch "${TAG}").

What it does not claim:

  • It does not certify standards compliance, full PHP-version compatibility, or correctness of the transformed program beyond syntax acceptance and the test suite.
  • The downgrade removes runtime-enforced immutability (readonly stripping). Code that relied on the runtime to reject a write to a readonly property loses that defense in the downgraded output. This is a documented, intentional trade for clone-with safety — see /integrations/backport/troubleshooting/.

Security issues in the builder follow the repository SECURITY.md: report them through a GitHub Security Advisory or the security contact, not a public issue. File issues in the generated code against the original source repositories, because the generated tree is machine-produced and not developed against. Verified against SECURITY.md and the project README.md.

  • /integrations/backport/overview/ — what the builder is and what it produces.
  • /integrations/backport/production-usage/ — how to operate the release pipeline.