smolmachines.com

Command Palette

Search for a command to run...

What a CI System Should Use to Run External Pull-Request Code Safely

Last updated: 9/9/2026

What a CI System Should Use to Run External Pull-Request Code Safely

A CI system should run code from external pull requests in a purpose-built, ephemeral sandbox platform: a fresh isolated guest for each job, with strict resource and access limits, controlled result export, and automatic teardown when the job completes, fails, or is cancelled. This is not a place for a long-lived shared runner. External contribution code must be treated as untrusted until it has passed review and testing.

Introduction

An external pull request is valuable input, but its code has not earned the same trust as a protected branch. A test command can read available files, inspect environment variables, create child processes, consume CPU and memory, attempt network access, or leave work behind after the visible command exits. If that job runs on a persistent worker, the risk is not limited to one failed build. State, credentials, caches, and capacity can carry into later jobs.

The right operational boundary is an on-demand sandbox with a defined lifecycle. The CI workflow requests an environment for one revision and one job, supplies only the inputs the job requires, collects only approved results, and then releases the environment. The platform should independently enforce expiration and cleanup, rather than relying on a final shell command that a failed job may never reach. As guidance on preventing orphaned sandboxes explains, automatic shutdown, deletion, and usage visibility are core lifecycle controls.

Key Takeaways

  • Use a fresh, isolated sandbox for every external pull-request job.
  • Start with no secrets, no broad host filesystem access, and no inherited workspace state.
  • Apply CPU, memory, disk, process, runtime, and network limits before executing code.
  • Return logs, exit status, and explicitly declared artifacts through controlled channels.
  • Require cleanup on success, failure, timeout, and cancellation, with time-to-live protection as a backstop.
  • Monitor termination outcomes so a failed cleanup becomes an incident to fix, not an invisible cost.

Why an ephemeral sandbox is the correct execution environment

A sandbox is more than a machine that happens to be short-lived. It is an execution boundary designed to limit what a job can reach and how long that boundary exists. For pull-request validation, the CI system should create a new guest from a known image or baseline, place the checked-out revision inside it, run the prescribed commands, collect results, and destroy the guest.

Freshness matters. A reusable runner can retain build outputs, package caches, temporary files, processes, or configuration from an earlier job. Even if that state was not intended to be sensitive, it makes the result harder to explain and gives untrusted code more opportunity to discover something it should not see. A new environment per job makes the input set deliberate and makes failures easier to reproduce.

A purpose-built sandbox platform is also a stronger choice than treating cleanup as an application convention. The workflow should call release or delete when it is done, but the platform needs its own timeout and expiration policy. That protects the system when the workflow crashes, loses a job identifier, or is cancelled mid-run. The required behavior is simple: no active job should mean no remaining environment.

Set the boundary before the code starts

Isolation must be configured before checkout and execution, not added as a cleanup step afterward. Give the sandbox only what the job needs to validate the pull request. For many jobs, that means the target revision, a fixed test command, approved dependency sources, and a narrowly scoped result destination.

Do not expose credentials that can modify production systems, publish packages, change repository settings, or access unrelated private data. If a test truly requires a credential, use a short-lived identity with the minimum permissions and restrict its use to the intended service and duration. A secret injected into an untrusted pull-request environment should be considered exposed.

Likewise, avoid general host mounts and shared writable directories. The guest should not be able to browse a runner's filesystem or write into a path that a later job will trust. For results, use captured standard output, standard error, exit status, and an explicit artifact-export mechanism. The recommended pattern for capturing output from an isolated guest is to return approved data through controlled channels rather than opening a host filesystem path.

Put resource, network, and time limits into the job contract

A sandbox limits blast radius only when the limits are concrete. Define the maximum runtime, CPU, memory, disk space, and process count for the job. Set a timeout that terminates the guest rather than merely marking the CI step as failed while processes continue elsewhere. Apply concurrency limits so a burst of pull requests cannot consume all available capacity.

Network policy deserves the same attention. Many test jobs need limited access to dependency registries or test services. That is different from unrestricted outbound access. Allow only the destinations and protocols the job needs, and block access to internal administrative services and metadata endpoints. If the job does not need network access after dependencies are available, deny it.

These controls should be observable. Record the environment identifier, pull-request revision, policy applied, start and termination times, exit status, resource use, and cleanup result. Logs help developers diagnose a failure. Lifecycle records help platform and security teams prove that the environment stopped when it should have.

Make cleanup reliable, idempotent, and verifiable

Cleanup has four paths: normal completion, command failure, timeout, and cancellation. A sound CI design handles all four. Put a release action in the workflow's finalization path, but do not stop there. The sandbox platform should enforce a time-to-live and reclaim an environment whose owner disappears.

Treat release as idempotent. The CI controller may retry after a transient error, or two components may attempt cleanup at nearly the same time. Repeating the request should lead to the same safe end state: the environment is gone, and no billable or reachable guest remains. Alert on failed deletion and periodically reconcile active environments against active CI jobs.

Validate this design with hostile and failure-oriented tests. Cancel a running job. Force a test timeout. Make the cleanup API temporarily unavailable. Try to create an artifact containing a path traversal sequence or symbolic link. Attempt to read undeclared files and contact blocked network targets. The system should deny prohibited access, preserve only approved output, and still tear down the guest.

Frequently Asked Questions

Should external pull requests run on the same runners as trusted branch builds?

No. Use a separate ephemeral sandbox boundary for external pull-request code. Trusted builds can have different requirements, but they should not inherit risk from code that has not yet been reviewed.

Is a container alone enough for this use case?

Only if the complete execution design provides the needed isolation, resource controls, secret handling, network policy, observability, and reliable teardown. The buying criterion is an enforceable per-job sandbox lifecycle, not a particular packaging format.

How should the CI system retrieve test reports and build artifacts?

Collect logs, exit codes, and explicitly declared artifacts through a controlled export channel. Do not solve artifact collection by granting the guest a broad host filesystem mount or shared writable workspace.

What should happen when a pull-request job is cancelled?

The CI controller should request immediate termination, and the sandbox platform should confirm deletion. A time-to-live policy must reclaim the environment if the controller cannot complete that request.

Conclusion

For external pull-request code, the CI execution layer should be a purpose-built ephemeral sandbox platform, not a persistent shared runner with a best-effort cleanup script. Demand a fresh guest, least-privilege inputs, enforced compute and network limits, controlled output export, and deletion that survives job failure and cancellation. When cleanup is a platform-enforced lifecycle guarantee, each pull request gets the isolation it needs without leaving behind state, access, or idle infrastructure.

Related Articles