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 token | Resolution | Distinct across a reboot | |
|---|---|---|---|
| Windows | GetProcessTimes creation FILETIME | 100 ns | yes, it is an absolute time |
| macOS | proc_pidinfo(PROC_PIDTBSDINFO) start timeval | 1 µs | yes, it is an absolute time |
| Linux | boot id + /proc/<pid>/stat field 22 | one clock tick, typically 10 ms | yes, 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§
- Child
Process - A child process this agent started.
- Permissions
Summary - What a file’s permissions amount to, in terms this product cares about.
- Process
Identity - A PID paired with a token that changes when the process at that PID does.
- Restrictive
Handoff - A short-lived file holding a secret, readable only by this account, deleted on every path out.
- Spawn
Spec - A child process about to be launched.
Enums§
- Adoption
- What a recorded
ProcessIdentityturns out to refer to now. - Handoff
Error - Something went wrong creating, inspecting, or removing a handoff file.
- Output
Mode - What to do with a child’s standard output and standard error.
- Process
Error - Something went wrong spawning, observing, or identifying a process.
- Termination
- The outcome of asking a recorded process to stop.