Skip to content

feat: forward Linux CPU quota events through v2.2 stream - #68

Merged
senamakel merged 40 commits into
epic/8369-security-migrationfrom
sec-27-resource-events
Oct 27, 2026
Merged

senamakel merged 40 commits into
epic/8369-security-migrationfrom
sec-27-resource-events

Conversation

@senamakel

Copy link
Copy Markdown
Owner

Forward positive Linux cgroup-v2 CPU quota throttling evidence into the TinyBox v2.2 process-event stream. The native launcher samples cpu.stat inside its trusted transient systemd scope before teardown; a positive nr_throttled counter is reported as LimitHit { limit: Cpu } before Exited. This indicates quota throttling, not CPU exhaustion or process failure. Existing timeout ordering stays unchanged.

Execution observers have a default no-op termination-cause hook for source compatibility, and observer wrappers forward it. The output journal carries the cause into the terminal event sequence. TinyBus v2.2 already defines the needed event and limit kind, so there is no TinyBus API or gitlink change.

The report file is created only under a private XDG runtime directory canonicalized beneath /run. The target scope masks /run; accepting a custom or untrusted path would let the workload tamper with the handoff report before the host reads it. Regression coverage rejects custom paths and accepts the masked /run location.

Red-first regression: cpu_quota_observer_cause_is_forwarded_before_process_exit initially failed to compile because TerminationCause, the observer callback, and terminal event forwarding did not exist. It passes after the implementation.

Validation:

  • cargo fmt --all -- --check
  • cargo clippy --locked -p tinybox-core -p tinybox-host -p tinybox-linux -p tinybox-module --all-targets --all-features -- -D warnings
  • cargo llvm-cov --locked -p tinybox-core -p tinybox-host -p tinybox-linux -p tinybox-module --all-targets --all-features --json --output-path target/focused-coverage.json (all selected tests pass)
  • Per-file line coverage: runtime/output.rs 100.00% (6/6); limited/jailed.rs 91.24% (604/662); sandbox/args.rs 92.04% (497/540); tinybus_module/mod.rs 93.53% (477/510); tinybus_module/output.rs 98.41% (124/126); tinybus_module/resources_stream.rs 94.35% (701/743).

Part of #27
Part of senamakel/openhuman#8369

senamakel and others added 30 commits October 27, 2026 15:47
Add a test asserting that a stream whose journal records a CPU quota termination cause emits a LimitHit event ahead of the Exited event, so the ordering of terminal events stays pinned.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add a TerminationCause enum and an ExecutionObserver hook so backends can
report resource-limit terminations backed by runtime evidence rather than
inferring them from exit codes. The tinybus module records CPU quota hits and
now derives terminal process events from a single helper, which also lets it
emit a limit event alongside the exit status.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Re-export the TerminationCause type alongside the other runtime types so callers can inspect why a sandbox execution ended without reaching into the runtime module directly.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The jailed host now creates a private event file under the runtime directory and passes it to the sandboxed command, then reads the throttling counter after the process exits. When throttling is observed, the execution observer is notified with a CpuQuota termination cause so callers can distinguish quota exhaustion from other failures.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The launcher now wraps the workload in a small shell script that runs it, then copies the transient scope's cpu.stat into the internal report file before exiting. This captures CPU event counters from inside the scope, which would otherwise be lost once systemd stops the unit.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Added a regression test asserting that the cpu.stat throttling counter is only parsed from a single well-formed nr_throttled line, and tightened the native jail command test to locate bubblewrap directly and check that the CPU event file and cpu.stat are passed through. Also reformatted a few expressions for rustfmt consistency.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The native jail test now checks that the argv contains the configured command path instead of searching for a bubblewrap binary, matching the command the sandbox actually builds.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Document that scoped cgroup-v2 CPU quota throttling now emits `LimitHit { limit: Cpu }` before the exit event, clarifying that it reports throttling rather than CPU exhaustion or process failure.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Update the native jail command test to check for the CPU event scope wrapper constant instead of the hardcoded /bin/true path, keeping the assertion in sync with the wrapper the sandbox now injects.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Update the native jail command test to check for the cpu.stat argument instead of the CPU event scope wrapper, matching the current command construction.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The native jail command test no longer expects cpu.stat in the argv, since the command no longer passes it through.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The native jail command test now checks for the CPU event file environment variable using a substring match instead of an exact argument comparison, since the variable is passed as part of a larger argument rather than as a standalone token.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Reformatted the assertion that checks for the internal CPU event file variable in the native jail command test so it matches rustfmt's line-breaking. No test behaviour changed.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The assertion now checks whether any argv entry contains "cpu.stat" rather than requiring an exact match, so the test still passes when the path is embedded in a longer argument.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Moved the CPU event report setup and termination reporting in the jailed
host into small helper functions so the execution path reads more clearly.
Behaviour is unchanged.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Move the process group, stdio, and environment configuration for the jailed command into a dedicated configure_jail_command function so the spawn path stays readable and the setup can be reused.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test no longer returns a Result since it contains no fallible operations, so the signature and trailing Ok(()) were removed to match.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Split the XDG_RUNTIME_DIR lookup out of CpuEventReport::prepare so the
runtime directory can be injected, letting tests exercise the eligibility
and report lifecycle logic against a temporary directory. Added a Linux
test covering both the eligible scope and the unmetered case.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Added an eprintln in the jailed test to print the command arguments, runtime path, and runtime metadata, making it easier to diagnose why the eligible scope check fails.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test now sets the temporary runtime directory to mode 0700 before invoking systemd-run, and drops a leftover debug eprintln that printed the command arguments and directory metadata.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Reordered the import so the test's setup reads in the order it executes, with the permissions trait imported before the temporary runtime directory is created.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
…vent report

Canonicalize the runtime directory and require it to live under /run/ before
creating a report, so paths that escape the expected location are refused.
The test now asserts that an unmasked runtime path yields no report and no
environment handoff.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The jailed test now also exercises the positive case where the runtime
directory lives under a masked /run path, asserting the report is created,
handed to the wrapper, and cleaned up on drop. The custom-path rejection
case is kept alongside it, and the positive case is skipped when the
environment does not provide a suitable XDG_RUNTIME_DIR.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Reset the environment in the CPU event report test before spawning the unmetered systemd-run command, so leftover variables from the earlier masked run cannot influence the second half of the test.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Adds a test asserting that an observer can be handed a proven CPU quota
termination cause, guarding the path that reports quota-driven termination
to observers.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The default `termination_cause` implementation now passes the cause through `std::hint::black_box` so the parameter is considered used, avoiding an unused-variable warning without changing behaviour.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Mark the default termination_cause implementation as inline(never) so it
stays a distinct frame in profiles and backtraces, making it easier to
attribute provider-proven terminations during execution.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test now binds the observer as a `&dyn ExecutionObserver` before calling `termination_cause`, so the call goes through the trait object rather than the concrete type.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
…c api

Move the observer termination-cause check out of the internal runtime tests and into the public API suite, where it now exercises ExecutionObserver and TerminationCause as exported types. The new test also implements the observer trait directly, so the public trait surface is verified to be implementable outside the crate.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
ExecutionObserver::termination_cause now returns a bool indicating whether the observer recorded the cause, defaulting to false. OutputJournal reports true for CPU quota causes so callers can tell when the signal was consumed.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
senamakel and others added 10 commits October 27, 2026 16:02
The ExecutionObserver trait now requires termination_cause instead of
providing a default that returned false, so observers must explicitly
handle proven termination causes rather than silently ignoring them.
Callers no longer receive a boolean result, since the return value was
only used to signal whether the cause was recorded.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add a termination_cause method to the ExecutionObserver trait so observers can be told why an execution ended. The Docker and SSH observers forward the cause to their inner observers, and test observers implement the new method as a no-op.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Implement the new ExecutionObserver::termination_cause method on the mock and test observers across the docker, host, and ssh crates so the fixtures continue to satisfy the trait after the method was added.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The `ExecutionObserver::termination_cause` method now has an empty default body, so implementors that don't care about termination causes no longer need to write a stub. All the redundant stubs across the test fixtures were removed, and a test was added to confirm the default implementation accepts a proven CPU quota cause.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Relocated the ExecutionObserver trait from the output module to the types module so it sits alongside the other runtime type definitions, and updated the re-exports accordingly. The trait's behaviour is unchanged.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Wire the existing output tests into the runtime types module and add a case asserting that the default observer accepts a provider-reported termination cause. This guards the observer contract against regressions.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test duplicated the coverage already provided by the default observer test above it, so it was dropped to keep the suite free of redundant cases.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test now constructs DefaultTerminationObserver instead of the removed Observer unit struct and asserts that stdout output is accepted, covering the default observer's output path alongside termination causes.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Relocated the ExecutionObserver trait from the types module into the output module so it sits alongside the output stream and termination cause types it depends on. The public re-export is unchanged, so callers see no behavioural difference.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Extend the capturing observer to record termination causes and add a test asserting that the Docker execution observer forwards a provider-reported cause to the inner observer.

Auto-committed-on: cuttlefish
Co-authored-by: Medulla <medulla@tinyhumans.ai>
@senamakel
senamakel merged commit 7f7d8d5 into epic/8369-security-migration Oct 27, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant