containment

package
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package containment implements Governator's authority-derived host containment policy (Sol v7 RB2): the isolation requirement for a local run is derived from what the run can actually do — write, execute, produce, validate, use credentials, or forbid network — never from the operator's risk_class label. A risk_class: high contract must not silently resolve to local execution. Qualifying containment is hardened Docker (contracts.DockerRunnerConfig.IsHardened), a backend with a verified native sandbox capability, or an explicitly signed operator override. Every other case fails closed before launch.

Session 2 (Sol redteam v4, P0-4): the descendant-owning containment primitive. SysProcAttr{Setpgid: true} + Kill(-pid) only covers a process's own POSIX process group, and only on the ctx-cancellation path -- a well-behaved-looking backend that forks a setsid'd or double-forked child escapes the group entirely, and a normal (non-timeout) exit never proves the tree is actually dead. Scope replaces that assumption with a real kernel boundary: cgroup v2 membership (systemd transient scope, or a directly managed cgroup) survives setsid/double-fork/nohup, and a PID namespace makes escape structurally impossible rather than merely unlikely. Extinguish is the DESCENDANTS_TERMINATED lifecycle stage: freeze, kill, wait for kernel-confirmed extinction, then independently verify no process anywhere still holds a workspace file handle. See runtime.go's runOnce for where this runs -- always before S1's final-state fingerprint capture, never skipped, never treated as best-effort.

Index

Constants

View Source
const DefaultExtinctionDeadline = 5 * time.Second

DefaultExtinctionDeadline bounds how long Extinguish waits for the kernel to confirm every process inside a Scope is dead before hard-failing the run. There is no unbounded wait path -- report P0-4 is explicit that "process exited, therefore done" is exactly the assumption being removed.

Variables

View Source
var ExtinguishGateForTesting func() error

ExtinguishGateForTesting is a TEST-ONLY synchronization seam (Sol12 rc5 Session 2, P0-1). When non-nil, Scope.Extinguish invokes it and blocks until it returns BEFORE beginning any kill/freeze logic, so a red-team fixture can PROVE a descendant reached an intended state (e.g. an uninterruptible blocking read) BEFORE Governator begins extinction. This replaces the timing assumption at the heart of TestV7Case8's original flake (report case 8: "the timing fixture did not enter its expected blocking-read state before its deadline"). If the gate returns a non-nil error, Extinguish returns that error without killing -- the fixture uses this to surface a genuine host-capability failure (a kernel that kills FUSE-blocked readers) as a deterministic, reasoned refusal rather than a post-hoc timeout skip. Production code MUST NEVER set this; only _test.go / redteam-corpus code assigns it (and defers restoring nil), mirroring internal/enforce.SelfExeOverride.

View Source
var ForceDegradedScopeForTesting atomic.Bool

ForceDegradedScopeForTesting is a TEST-ONLY seam. When true, NewScope returns a degraded (bare process-group) scope without probing for any real descendant-owning primitive, so the red-team corpus can exercise Governator's approval/merge/replay paths end to end on hosts that lack systemd --user, a usable cgroup v2 subtree, or a PID namespace.

It exists ONLY because the Sol11 P0-3 defect it replaces — the GOV_CONTAINMENT_FORCE_DEGRADED environment variable — was a production bypass: any launcher, wrapper or compromised shell could export that var to force degraded containment for a stage that should have failed closed. An inherited environment variable must never weaken production authority (Sol11 P0-3 / the rc5 governing invariant); a package-level Go variable set only by _test.go code cannot be flipped by environment, so it is the sanctioned test substitute. Production code MUST NEVER set this; nothing links the setter into a release binary's execution path. Mirrors the established test-seam pattern of internal/enforce.SelfExeOverride and ForceUnsupported.

View Source
var ScopeSelectionForceUnavailableForTesting atomic.Bool

ScopeSelectionForceUnavailableForTesting is a TEST-ONLY seam (Sol12 rc5 Session 2, P0-1). When set, newSystemdUserScope returns a deterministic "systemd user manager unavailable" error immediately after the nil-handle check -- WITHOUT touching /run/systemd/system, the live user bus, or the systemd-run probe -- so the scope-selection FAILURE path (and its descriptor-leak invariant) can be exercised on every host, including one that genuinely has a live systemd --user manager. Before this seam, TestV10Case12 (report case 12) could only run where the host truly lacked systemd --user, making it mutually exclusive with TestV10Case13 (the real live-systemd acceptance test) on any single host -- so a correct single-host zero-skip red-team run was structurally impossible. Production code MUST NEVER set this; only _test.go code in this package and the redteam corpus (behind the redteam build tag) flips it, exactly like ForceDegradedScopeForTesting. The forced error fires at the same logical point a genuinely-absent user bus would (after the borrowed handle is confirmed non-nil, before any probe), so the borrow/ownership invariant under test is the real one.

View Source
var UnitMaterializationForceUnobservedForTesting atomic.Bool

UnitMaterializationForceUnobservedForTesting is a TEST-ONLY seam (Sol14 rc7 Session 9a, P1-2). When set, resolveCgroupFromPID reports the SAME "generated systemd unit was never confirmed" resolve error the deadline loop produces when a transient scope registers with systemd but its cgroup never materializes for the launched pid -- immediately, without waiting out the 2-second deadline and without depending on this host having (or lacking) a live systemd --user manager.

Before this seam, TestV6Case28SystemdUnitNeverMaterializingFailsClosed could only assert its property on a host with NO systemd --user manager, because forcing "unit registers but is never observed within deadline" on a host that genuinely has systemd requires adversarial control over systemd the red-team package cannot exercise. It therefore skipped on every systemd host and was carried as an OPEN GAP exclusion -- "excluded but happened to pass" is not durable release policy (Sol14 P1-2). With the seam the case is deterministic on both host classes: the run must fail closed rather than reach APPROVED backed by a scope identity it never actually confirmed.

The forced error fires at the same logical point, with the same message and the same resolveErr field, that a genuinely non-materializing unit produces, so the fail-closed invariant under test is the real one -- only the trigger is deterministic. Production code MUST NEVER set this; only _test.go code and the redteam corpus (behind the redteam build tag) flip it, exactly like ScopeSelectionForceUnavailableForTesting above.

Functions

func DevelopmentContainmentMode

func DevelopmentContainmentMode(mode string) bool

DevelopmentContainmentMode reports whether the run is executing under the development-only local_effectful_tiering: "off" compatibility mode (Sol11 P0-4). When true, effectful local work may proceed without host containment, but the transaction is NON-APPROVING by construction: strict replay is disabled, merge is disabled, and the final result can never reach APPROVED. Any production approval exception must instead use the signed containment override flow (VerifyOverride). This is the inverse of LocalEffectfulTieringEnforced, exposed as a named predicate so every approval/merge/replay gate asserts the same development-mode signal without re-spelling the literal "off" comparison.

func Effectful

func Effectful(c contracts.Contract) bool

Effectful reports whether a contract can create persistent effects or run external helper/controller stages. Pure read-only scout contracts with no declared external stages can avoid host containment; any write, produced artifact, validator, cleanup validator, credential mount, network-deny requirement, or helper-launch authority is effectful for containment.

func Enforce

func Enforce(c contracts.Contract, externallyEnforced bool, pubKeyHex string) error

Enforce applies the default risk-class containment policy. It preserves the historical call shape while enabling Session 6's enforced-by-default medium/high effectful local-run gate.

func EnforcePolicy

func EnforcePolicy(c contracts.Contract, externallyEnforced bool, pubKeyHex string, enforceLocalEffectful bool) error

EnforcePolicy applies the authority-derived containment policy and returns a non-nil error when a contract lacks qualifying containment. It is fail-closed by construction: pubKeyHex is the operator override public key from config — empty means overrides are refused.

externallyEnforced reports whether Governator's OWN external-enforcement layer is available for a "local" runner (internal/enforce: Landlock LSM filesystem confinement + a network namespace with no route, applied to the launched process from outside it, independent of anything the backend claims about itself). Per Sol P0-3 (Session 5, report §9 attack 5): a backend's declared or probe-attested native sandbox is evidence, never proof — a program that knows it is being tested can behave only during the test. It no longer qualifies a "local" runner on its own; only this externally-enforced boundary or a signed operator override does.

func LocalEffectfulTieringEnforced

func LocalEffectfulTieringEnforced(mode string) bool

LocalEffectfulTieringEnforced reports whether Session 6's local-run gate is active. Unknown/empty values fail closed to enforced; config validation keeps operator-authored values to "enforce" or "off".

func NetworkForbidden

func NetworkForbidden(c contracts.Contract) bool

NetworkForbidden reports whether the contract's effective network permission is deny. Today SpecFromContract defaults to no-network and forbidden.behaviors may also state it explicitly; either way, S1 requires an externally enforced no-network boundary rather than a transcript check.

func OverrideMessage

func OverrideMessage(jobID, contractHash, reason string) []byte

OverrideMessage is the exact bytes an operator signs (ed25519) to authorize a containment override for one contract. Binding the job_id prevents an override minted for one high-risk job being replayed against another; binding contractHash (the contract's hash with its containment block cleared) prevents the sharper replay where the SAME job's contract body is edited after signing — widened scope, network enablement, a different image — while the old signature keeps verifying. Any content edit changes the hash and invalidates the signature. Exposed so signing tooling and tests produce matching signatures without duplicating the format.

func RequiresHostContainment

func RequiresHostContainment(c contracts.Contract, enforceLocalEffectful bool) bool

RequiresHostContainment reports whether this contract must prove host-level containment. Selection is authority-derived, never risk-label-derived (Sol v7 RB2): risk_class is a policy description, not a security boundary, so a malicious or mistaken contract author cannot escape containment by labelling a highly-capable operation low/unlabeled.

The authority-derived baseline is scoped to local execution. A Docker runner's container is its host boundary; risk_class may still strengthen Docker requirements, but a low label can never weaken a local run's baseline.

func RequiresStrongDescendantContainment

func RequiresStrongDescendantContainment(c contracts.Contract, enforceLocalEffectful bool) bool

RequiresStrongDescendantContainment reports whether a launch must refuse a degraded process-group fallback. It is intentionally authority-based rather than risk-label-based: any effectful job or high-risk job needs a real descendant-owning primitive.

func SigningMessage

func SigningMessage(c contracts.Contract) ([]byte, error)

SigningMessage builds the exact bytes an operator signs to authorize a containment override for contract c: OverrideMessage over c's job_id, the hash of c with its containment block cleared (the signature can't cover itself), and the override reason. Exposed so `gov containment message`, tests, and VerifyOverride can never drift on the format.

func VerifyOverride

func VerifyOverride(c contracts.Contract, pubKeyHex string) bool

VerifyOverride reports whether the contract carries a valid signed override for its job_id AND its exact content, verified against pubKeyHex. An empty pubKeyHex refuses every override (fail-closed: no operator key configured means no escape hatch). The signed message is SigningMessage(c) — see OverrideMessage for why the contract hash is part of what's signed.

func WithEnvironment

func WithEnvironment(ctx context.Context, env ContainmentEnvironment) context.Context

WithEnvironment attaches the run's frozen ContainmentEnvironment to ctx (rc4 Session 2, Sol10 P0-2), mirroring WithScope, so every NewScope call site for the run's whole lifetime -- the run-level Scope (constructed where WithEnvironment is called) and every stage's own Scope (internal/stage.Executor.Run, several packages and call layers away) -- retrieves the SAME resolved handles via EnvironmentFromContext rather than each independently resolving the trusted-tool registry.

func WithScope

func WithScope(ctx context.Context, s *Scope) context.Context

WithScope attaches s to ctx so the launch site (several packages away from whoever constructed the Scope) can find it without every intermediate call signature threading it through explicitly.

Types

type CgroupCapabilities

type CgroupCapabilities struct {
	Available bool
	SelfPath  string
}

CgroupCapabilities is a descriptive snapshot of this process's cgroup v2 direct-management capability, folded into ContainmentEnvironmentHash (internal/runtime/identity.go) so a host's cgroup capability is part of the run's replay identity like everything else ContainmentEnvironment describes. Unlike SystemdRun/Unshare, newDirectCgroupScope does NOT consume this to decide whether to attempt cgroup-direct: cgroup-direct launches the caller's own already-verified bin directly (never a "primitive binary" needing registry trust the way systemd-run/unshare do, see Command's ScopeCgroupDirect branch), so it carries none of P0-2's TOCTOU concern, and several legitimate callers (internal/assay.Evaluate, notably) construct a Scope via a bare context.Context that was never threaded through containment.WithEnvironment -- requiring a resolved CgroupCapabilities there would wrongly disable the strongest containment method available on hosts with a perfectly usable cgroup v2 hierarchy. newDirectCgroupScope always probes live, exactly as it did before this type existed.

type ContainmentEnvironment

type ContainmentEnvironment struct {
	SystemdRun *toolregistry.Handle
	Unshare    *toolregistry.Handle
	Cgroup     CgroupCapabilities
}

ContainmentEnvironment is the frozen set of descendant-owning containment primitives a whole run's replay identity is evaluated against (rc4 Session 2, Sol10 P0-2). Before this type existed, NewScope called toolregistry.Load() and ResolveHandle fresh on every invocation -- once for the run-level Scope, again for every stage's own Scope (internal/stage. Executor.Run) -- so the trusted-tool registry could be reloaded, and in principle observe different enrolled state, after the run's environment and replay identity were already frozen (buildRunEnvironment, called exactly once at the top of runOnce). ResolveEnvironment now does that resolution exactly once, from the SAME frozen registry every other trust decision in the run uses, and every NewScope call for the run's whole lifetime -- backend and every stage -- is handed this one value rather than resolving its own.

SystemdRun/Unshare are nil when that primitive is not enrolled/resolvable on this host; NewScope's existing fallback chain treats a nil handle exactly like the old resolution failure it replaces.

func EnvironmentFromContext

func EnvironmentFromContext(ctx context.Context) (ContainmentEnvironment, bool)

EnvironmentFromContext retrieves a ContainmentEnvironment attached by WithEnvironment. ok is false only for a launch that never went through a governed runtime.Runner (doctor probes, direct package tests); NewScope treats the resulting zero value exactly like every primitive being unresolvable, falling back through its normal chain.

func ResolveEnvironment

func ResolveEnvironment(registry *toolregistry.Registry) (ContainmentEnvironment, error)

ResolveEnvironment resolves every containment primitive's held handle exactly once from registry -- the caller's already-frozen trusted-tool registry (internal/runtime.RunEnvironment.ToolRegistry), never a fresh toolregistry.Load(). A primitive that is not enrolled or fails registry-verification simply resolves to a nil handle; it is not a hard error here, mirroring NewScope's pre-existing "try the next weaker primitive" fallback discipline for the whole run rather than per attempt.

func (ContainmentEnvironment) Close

func (e ContainmentEnvironment) Close() error

Close releases every handle this environment holds. The caller that resolved the environment (ResolveEnvironment, called exactly once before replay) owns this and must call it exactly once, after every Scope built from this environment across the run's entire lifetime -- backend and every stage -- has finished. Individual Scopes never close these handles; they only borrow them for the duration of one launch (see Scope.Command).

type Proof

type Proof struct {
	Method               ScopeMethod   `json:"method"`
	Frozen               bool          `json:"frozen"`
	Killed               bool          `json:"killed"`
	Waited               time.Duration `json:"waited_ns"`
	WorkspaceFDScanClean bool          `json:"workspace_fd_scan_clean"`
	Degraded             bool          `json:"degraded,omitempty"`
	Note                 string        `json:"note,omitempty"`
	// ProcessesObservedPeak (Sol P0-3/P1-15 effect ledger) is the number of
	// PIDs cgroup.procs listed for this scope at the moment it was frozen --
	// i.e. every process this launch ever spawned that was still alive right
	// before extinction began, read from the kernel's own accounting, not
	// from anything the backend reported about its own descendants. -1 when
	// the scope method has no cgroup to read (PID namespace, degraded).
	ProcessesObservedPeak int `json:"processes_observed_peak"`
}

Proof is the DESCENDANTS_TERMINATED stage's recorded evidence -- exactly what was frozen, killed, and independently confirmed dead before the run was allowed to proceed to S1's final-state fingerprint/tree capture. Approval is impossible without one of these attached to the run record.

type Scope

type Scope struct {
	// contains filtered or unexported fields
}

Scope owns the entire descendant tree spawned by one governed subprocess launch. Exactly one is constructed per run, before the backend is started, and threaded through to the launch site via WithScope/ScopeFromContext so every backend adapter's shared runCLI path (agents.defaultExecutor, runner.LocalWorktreeRunner.executor) launches inside it without each adapter needing to know how.

func NewScope

func NewScope(runID string, requireStrong bool, env ContainmentEnvironment) (*Scope, error)

NewScope selects the strongest descendant-owning primitive available on this host, in the order the plan specifies: systemd --user transient scope, then a directly managed cgroup v2 subtree, then a PID namespace. requireStrong callers refuse outright when none qualifies rather than silently falling back to a bare process group.

env is the run's frozen ContainmentEnvironment (rc4 Session 2, Sol10 P0-2), resolved exactly once via ResolveEnvironment before replay -- NewScope never loads or resolves the trusted-tool registry itself. Every call for one run (the run-level Scope and every stage's own Scope) must be handed the SAME env value, so the primitive actually launched can never diverge from the one the run's replay identity was computed against.

func ScopeFromContext

func ScopeFromContext(ctx context.Context) (*Scope, bool)

ScopeFromContext retrieves a Scope attached by WithScope. Callers must treat "not found" as "no containment for this launch" -- every caller in this codebase falls back to the pre-S2 process-group behavior when this returns false, which only happens for launches that never went through a governed runtime.Runner (doctor probes, direct adapter tests).

func (*Scope) Command

func (s *Scope) Command(ctx context.Context, bin string, args []string, dir string) *exec.Cmd

Command builds an *exec.Cmd for bin/args/dir such that the process -- and every descendant it forks, however it detaches -- is born inside this scope from the moment it starts. Callers still set Stdout/Stderr and call Start themselves; Command only owns argv/SysProcAttr/Dir.

rc4 Session 2 (Sol10 P0-2): the ScopeSystemdUserScope/ScopePIDNamespace branches used to exec s.primitivePath -- a canonical pathname re-resolved at every launch, the same TOCTOU shape Sol v9 P0-1/P0-2 already closed for enforce.Plan's unshare wrapper. A same-uid process could replace the file at that path between NewScope's verification and this exec, and the replacement -- not the verified binary -- would become the thing responsible for establishing containment.

An earlier version of this fix launched through s.primitiveHandle's own /proc/self/fd/<n> descriptor directly (mirroring toolregistry.Handle. Command). That broke every caller that composes a Scope's launch with enforce.Plan.Wrap's OWN independent fd-argv numbering (agents.LaunchStaged, internal/stage's default CommandFactory, and toolregistry.Handle. CommandWith's build callback all do, for backend/validator/bash launches under an active enforce.Plan) -- both layers independently assumed they would own ExtraFiles[0]/fd 3, so whichever layer's files got merged in second silently landed at the wrong descriptor (or, for CommandWith's build callback, got overwritten outright), and the argv string baked in by the other layer no longer pointed at what it meant to. Composing two independently fd-numbering launch mechanisms correctly would need every composition call site to thread a shared fd allocator through -- real production_launch_factory work belongs together, not scattered piecemeal under a fix for a single primitive.

So instead, both branches launch through a SEALED PRIVATE COPY: a fresh, 0500, same-uid-only-readable copy of s.primitiveHandle's own verified bytes (SealedExecutablePath, sealed FROM the already-open, already-hashed descriptor -- never by re-reading the enrolled path), re-verified (Verify) immediately before this launch to catch a same-uid tamper of the COPY itself between sealing and exec. This is one of the plan's own explicitly acceptable alternatives to fd-argv launch ("a verified private immutable copy"), it uses an ordinary real pathname so it composes with enforce.Plan.Wrap/Handle.CommandWith exactly like every other already-verified bin these callers pass through Scope.Command, and it needs no signature change here. The copy is owned by this Scope (s.sealedPrimitive) and closed by Extinguish, once the launched process has fully finished with it.

Sealing or verifying can fail (disk full, /tmp unwritable, or Verify genuinely catching a live same-uid tamper of the copy) -- Command has no error return, so a failure here produces a cmd that will simply fail at Start() with a descriptive, un-executable path, exactly the fail-closed outcome every caller's existing cmd.Start() error check already handles; it never falls back to a mutable pathname.

func (*Scope) CommandWith

func (s *Scope) CommandWith(ctx context.Context, alloc *toolregistry.FDAllocator, bin string, args []string, dir string) *exec.Cmd

CommandWith is Command's composable, descriptor-backed form (Sol11 P0-5): a caller that must combine this Scope's own primitive launch (systemd-run/unshare) with another descriptor-backed layer of its own -- concretely, enforce.Plan.WrapWith's self-exec/unshare/final-executable descriptors -- passes one shared alloc so every layer's /proc/self/fd/<n> argv string lands at the fd number Start will actually dup it to, instead of two independently-numbered ExtraFiles lists colliding at fd 3. That collision is exactly why Command above still falls back to a sealed pathname copy of its primitive (see Command's own doc comment): it has no shared allocator to compose through. CommandWith closes the Verify-then-replace-then-exec race a sealed copy cannot by never reopening a pathname for the primitive at all -- it launches through s.primitiveHandle's own held, already-verified descriptor (/proc/self/fd/<n>, via alloc), the same object NewScope resolved and verified once, for the run's whole lifetime.

The caller must set the returned cmd's ExtraFiles to alloc.Files() once every composed layer -- including this one -- has finished registering.

func (*Scope) Extinguish

func (s *Scope) Extinguish(ctx context.Context, deadline time.Duration, workspacePath string) (Proof, error)

Extinguish is the DESCENDANTS_TERMINATED lifecycle stage: freeze the scope so no new descendant is accepted, kill the whole owned tree, block until the kernel confirms zero surviving processes (bounded by deadline -- a timeout is a hard failure, never treated as "probably fine"), and finally scan every process's open file descriptors for a handle into workspacePath as an independent check that nothing escaped the primitive itself. The returned Proof is recorded on the run whether or not err is nil; a non-nil err means the run must not proceed to final-state capture.

func (*Scope) IsStrong

func (s *Scope) IsStrong() bool

IsStrong reports whether this Scope's underlying primitive actually owns its descendants (systemd-user-scope, cgroup-direct, pid-namespace) as opposed to the pre-S2 process-group-only degraded fallback. Exposed so a per-stage caller deriving a NEW, separate Scope for its own launch (Sol redteam v7 S1) can request the same strength the outer run-level Scope already achieved on this host, rather than needing its own independent copy of the run's requireStrong policy decision threaded through.

func (*Scope) Method

func (s *Scope) Method() ScopeMethod

Method reports which primitive this Scope actually uses.

func (*Scope) RunID

func (s *Scope) RunID() string

RunID returns the identifier this Scope was constructed with -- the same value the caller passed to NewScope. Exposed so a per-stage caller (Sol redteam v7 S1: a governed backend routed through internal/stage.Executor) can derive its own unique per-stage scope name from the SAME run identity the outer, run-level Scope already carries, without needing a second context key threaded alongside WithScope purely to repeat a value this Scope already has.

func (*Scope) Started

func (s *Scope) Started(pid int)

Started must be called with the outer-namespace PID immediately after a successful Start() on the *exec.Cmd Command produced. It resolves the exact cgroup path (systemd assigns it asynchronously via its own IPC to the manager, so this polls briefly) or the PID-namespace's init PID, so Extinguish has something concrete to act on.

rc4 Session 2 (Sol10 P0-2): this used to close s.primitiveHandle here, on the reasoning that Start() had already dup'd the descriptor into the child so the parent's copy was no longer needed. That was correct when each Scope owned a freshly, independently resolved handle -- it is wrong now that primitiveHandle is BORROWED from the run's shared ContainmentEnvironment (every stage's own Scope launches through the same held descriptor across the run's whole lifetime); closing it here would close it out from under every later stage still to launch. Ownership of closing belongs solely to whoever resolved the ContainmentEnvironment (ResolveEnvironment's caller), once, after the run finishes.

type ScopeMethod

type ScopeMethod string

ScopeMethod identifies which descendant-owning primitive backs a Scope.

const (
	// ScopeSystemdUserScope wraps the launch in `systemd-run --user --scope`,
	// a transient cgroup v2 scope registered with the user's systemd manager.
	// Preferred: no filesystem write access to cgroupfs is required (systemd
	// already owns the delegation), and cleanup is automatic.
	ScopeSystemdUserScope ScopeMethod = "systemd-user-scope"
	// ScopeCgroupDirect creates a cgroup v2 subdirectory under the caller's
	// own cgroup and places the launched process into it atomically at
	// clone() time via CLONE_INTO_CGROUP (SysProcAttr.UseCgroupFD). Fallback
	// for hosts without a systemd user manager but where the caller's own
	// cgroup subtree is writable -- typically a container that owns its
	// whole cgroup namespace.
	ScopeCgroupDirect ScopeMethod = "cgroup-direct"
	// ScopePIDNamespace wraps the launch in `unshare --pid --fork
	// --mount-proc` with an identity uid/gid mapping so file ownership is
	// unaffected. Last resort: no cgroupfs access needed at all -- the
	// backend and everything it forks lives inside a PID namespace it cannot
	// leave, and the namespace's death is unconditional once its init
	// process (the wrapped command itself) exits or is killed.
	ScopePIDNamespace ScopeMethod = "pid-namespace"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL