Skip to main content

Module process

Module process 

Source
Expand description

Spawning, observing and terminating a child process; a process identity that survives a restart; and the restrictive handoff that gets a JIT configuration to a runner without it ever appearing in a process listing.

§A PID is not an identity

The agent records the runner processes it started in a durable journal and reads that journal back after a restart (03-control-flows.md, flow 3.2). If the record were a bare PID, then after a reboot — or after enough process churn — the PID in the journal may belong to somebody else’s process. Acting on that record means either adopting a stranger as a runner or, worse, terminating it. e3’s restart-recovery Definition of Done (“a journal containing a live process adopts it without starting a duplicate”) rests entirely on telling those cases apart.

So ProcessIdentity is a PID plus a start token: an opaque, platform-defined string that changes when the process at that PID changes. ProcessIdentity::recheck re-resolves the token and answers Adoption::Live, Adoption::Gone, or Adoption::PidRecycled — three answers, because collapsing the last two into “not live” is exactly the bug this type exists to prevent.

Start tokenResolutionDistinct across a reboot
WindowsGetProcessTimes creation FILETIME100 nsyes, it is an absolute time
macOSproc_pidinfo(PROC_PIDTBSDINFO) start timeval1 µsyes, it is an absolute time
Linuxboot id + /proc/<pid>/stat field 22one clock tick, typically 10 msyes, the boot id changes every boot

The Linux token pairs the boot identifier with the raw tick count rather than converting ticks to a wall-clock time. Field 22 counts ticks since boot, so on its own it repeats after every reboot; and dividing by sysconf(_SC_CLK_TCK) and adding btime would produce an absolute time whose precision is bounded by btime’s whole seconds — coarser than the ticks it was derived from, and coarser than a PID-recycling discriminator wants. Prefixing the tick count with /proc/sys/kernel/random/boot_id keeps the full tick resolution and makes the token unrepeatable across a reboot. See Adoption for what that buys.

§The JIT configuration never becomes an argument

07-security.md’s threat table: “A process listing reveals a JIT config”, controlled by “Do not pass JIT data as a command-line argument; use restrictive file/pipe handoff”. RestrictiveHandoff is that file, and SpawnSpec::spawn_with_handoff refuses to spawn when the payload appears in any argument or environment value, so an obvious mistake fails the launch instead of failing a review. SpawnSpec::spawn_runner_with_handoff is the one narrow exception: GitHub Runner’s supported JIT intake is the secret ACTIONS_RUNNER_INPUT_JITCONFIG environment input. The value is injected only while creating the child, is never retained in the public spawn spec, and Runner masks and removes it as its command parser starts.

It is a tripwire, not a proof. A caller that passes it has not been shown to be safe. The check looks for the payload as a verbatim substring of each argument’s and each environment value’s to_string_lossy(), and that is all it looks for. It does not inspect the program path, the working directory, or environment variable names; and anything that re-encodes the payload — base64 of the base64, URL-escaping, a different Unicode normalisation — or splits it across two arguments walks straight past it. The control that actually holds is either “pass the handoff file’s path” to a program that supports one or use the dedicated Runner intake above; e3 must not read a passing generic check as evidence that a configuration cannot reach a process listing.

Structs§

ChildProcess
A child process this agent started.
PermissionsSummary
What a file’s permissions amount to, in terms this product cares about.
ProcessIdentity
A PID paired with a token that changes when the process at that PID does.
RestrictiveHandoff
A short-lived file holding a secret, readable only by this account, deleted on every path out.
SpawnSpec
A child process about to be launched.

Enums§

Adoption
What a recorded ProcessIdentity turns out to refer to now.
HandoffError
Something went wrong creating, inspecting, or removing a handoff file.
OutputMode
What to do with a child’s standard output and standard error.
ProcessError
Something went wrong spawning, observing, or identifying a process.
Termination
The outcome of asking a recorded process to stop.

Functions§

permissions_summary