Skip to content

Is self-hosting the agents a security risk?

GitHub’s own documentation recommends self-hosted runners only for private repositories, because on a public repository anyone who can open a pull request can attempt to run code on your machine. If you are evaluating KiCI and asking “isn’t bringing my own agents a security downgrade compared to hosted CI?” — this page is the honest answer.

Self-hosted CI runners carry real risks — fork-PR code execution and lateral movement are the classic ones. KiCI’s answer is ephemeral, isolated agents — container or Firecracker microVM sandboxes destroyed after every job — combined with fail-closed defaults: fork PRs are held for approval, egress to private network ranges is blocked, and secrets never enter the job environment unless a workflow explicitly exposes them. Self-hosting is not risk-free, but with these defaults it is a defensible posture — and in exchange, your code, secrets, and logs never leave your infrastructure.

Self-hosted runner risk is not hypothetical. Three patterns account for most real-world incidents:

  • Fork-PR code execution. CI exists to run code. On a public repository, anyone can open a pull request — so without controls, a stranger’s branch executes on your hardware. This is why GitHub recommends self-hosted runners for private repositories only.
  • Lateral movement. A job that escapes (or was never contained) can reach whatever its host can reach: internal services, cloud metadata endpoints, neighboring machines. Supply-chain attacks have used compromised CI runners as persistent footholds inside victim networks.
  • State that outlives the job. A long-lived runner accumulates toolchains, caches, and credentials. One poisoned job can contaminate every job that follows it.

Every job runs in a disposable sandbox — a fresh container, or a fresh microVM with its own root filesystem copy — torn down when the job ends. Workflow code never executes inside a long-lived agent process, so there is no persistent runner state for a compromised job to poison.

KiCI’s trust policy is fail-closed out of the box: pull requests from forks are held for approval, unknown contributors are held, and workflow-file modifications by non-trusted contributors are held. Code from a stranger does not run until someone with approval rights releases it. See CI security for policy configuration.

Pick the isolation tier that matches your trust level

Section titled “Pick the isolation tier that matches your trust level”
  • Container (default). Each job runs in its own container with filesystem, process, and network isolation, destroyed afterwards. The right choice for most deployments.
  • Bare metal (+ sandbox namespaces). Process-level isolation for trusted, internal-only workflows — optionally hardened with bubblewrap namespaces (read-only system mounts, loopback-only network).
  • Firecracker microVM. Hardware-virtualization isolation — a separate kernel, memory, and network stack per job, with a fresh root filesystem each time. The tier to use if you choose to run untrusted code such as fork PRs.

See Agent execution security for the full per-backend comparison and configuration guidance.

Your internal network is unreachable by default

Section titled “Your internal network is unreachable by default”

Container and Firecracker jobs run behind egress filtering that blocks private (RFC1918) address ranges by default — a compromised job cannot scan or call into your internal services unless you explicitly allow it. With the bare-metal namespace sandbox enabled, the job’s network is loopback-only.

Secrets are delivered to workflow code over an internal channel (ctx.secrets) rather than environment variables, and enter the process environment only when a workflow explicitly calls ctx.secrets.expose(). Agent credentials never enter the sandbox at all: the job environment is built from an explicit allowlist, and everything else — including all agent-internal variables — is excluded. See Secrets management.

The same property that makes you self-host also protects you: KiCI’s hosted Platform is a thin webhook relay. Your source, secrets, build logs, and artifacts stay on your orchestrator and agents — they never transit KiCI’s infrastructure. The relay handles webhook routing and run metadata only.

KiCI’s defaults make self-hosting defensible, not risk-free. The honest division of responsibility:

  • Prefer private repositories. If your repository is public, keep the fork-PR policy at hold (the default) and review held runs before releasing them — or route them to the Firecracker backend.
  • Match the backend to the code you run. Bare metal without the namespace sandbox is for fully trusted, internal-only workflows. Untrusted code belongs in containers at minimum, microVMs ideally.
  • Harden and patch the host. KiCI isolates jobs from the host and your network, but the host OS, container runtime, and kernel updates are yours to maintain.
  • Scope your secrets. Bind secret scopes to specific environments so a job only receives the secrets it needs — never hand every job every secret.