Repository navigation
feat(sandbox): emit OCSF process launch and exit events with parent pid - #5143
vineethsai7 wants to merge 2 commits into
Conversation
Signed-off-by: Vineeth Sai Narajala <vnarajal@cisco.com>
Signed-off-by: Vineeth Sai Narajala <vnarajal@cisco.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 155c68ac14
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| pub fn terminate_event(&self, status: BoundaryExitStatus) -> OcsfEvent { | ||
| let mut builder = ProcessActivityBuilder::new(openshell_ocsf::ctx::ctx()) | ||
| .activity(ActivityId::Close) | ||
| .severity(SeverityId::Informational) |
There was a problem hiding this comment.
Mark timeout-killed process exits as Critical
When the canonical process exceeds timeout_secs, SpawnedAgent::wait sends TERM/KILL and then calls this method with the resulting signaled status, but the builder always labels the termination Informational; operators therefore cannot distinguish the repository-defined Critical timeout kill from an ordinary lifecycle exit. Carry the timeout outcome into the emitted runtime and supervisor records and use SeverityId::Critical for that path.
AGENTS.md reference: AGENTS.md:L120-L128
Useful? React with 👍 / 👎.
| // separately so the exit audit record is still emitted. | ||
| let observed = process.clone(); | ||
| tokio::spawn(async move { | ||
| let _ = observed.wait().await; |
There was a problem hiding this comment.
Reuse the exec stream for exit auditing
When process activity is enabled for long-lived exec or SSH sessions, this background wait() opens a second Exchange stream for each process while the exec's I/O stream from call_stream_idempotent is already held. The protocol caps the shared HTTP/2 connection at 128 streams (BOUNDARY_MAX_CONCURRENT_STREAMS) and the runtime allows 64 retained execs, so 64 audited execs alone consume all 128 slots before mediation or control traffic; subsequent exec, signal, resize, or network requests can stall until one exits. Emit from or share the existing exit-status path rather than retaining another stream per process.
Useful? React with 👍 / 👎.
Summary
Add opt-in OCSF launch and exit records for the Linux sandbox entrypoint and processes started directly by sandbox exec or SSH. The records carry PID, parent PID, and a stable process instance ID without argv or environment values.
Related Issue
Part of #4268. The issue is still
state:triage-needed; this draft proposes the first slice and welcomes maintainer feedback on the design and scope.#5144 builds on this PR and adds watched file open observations.
Changes
process_activity_enabledas a boolean sandbox setting, defaulting tofalse, and capture it when the sandbox starts./proc/<pid>/statfield 22 before reaping a directly started process. Encodeprocess.uidaspid:start_ticks; include the parent instance ID and epoch start time when available.WatchSandboxstream and optional JSONL output. The boundary also writes them to its container log.Timestampfields while keeping millisecond values in the internal process identity and OCSF object.Design
The boundary already owns process creation and wait status, so it can capture PID and start tick before a short-lived process disappears. The supervisor already sends OCSF events to
WatchSandbox. Optional identity fields connect those two existing paths and let a replacement supervisor retain an adopted process's identity. This avoids a new telemetry service.Periodic
/procpolling would miss short-lived processes and exec replacements. A kernel event source could cover descendants and exec replacements, but it would add a separate privilege and delivery path. This PR keeps that larger design open for maintainer review.Examples
Enable the setting before starting a sandbox, then read the gateway stream:
Example shorthand records:
WatchSandboxcarries the shorthand log line. When supervisor OCSF JSONLexport is enabled, the corresponding JSON includes the same identity in
process:{"class_uid":1007,"activity_name":"Launch","process":{"name":"/bin/sh","pid":57,"uid":"57:123456","created_time":1793620000120,"parent_process":{"name":"unknown","pid":1,"uid":"1:34"}}}Compatibility and limits
The setting is off by default. The existing entrypoint launch event remains when it is off. New protobuf fields are optional, so older boundary implementations continue to work and simply supply no identity. Existing OCSF fields remain unchanged;
uidandcreated_timeare additive. No argv, command line, or environment is sent across the protocol or added to these records.Events are best effort through the existing bounded log pipeline. This slice observes only processes directly started by the boundary, including piped and PTY sessions. It does not claim a complete workload process tree.
Signal exits carry
unmapped.signalin JSON and[signal:N]in shorthand.Follow-ups
Review follow-up
Review found a P2 protocol compatibility issue: the new public process start times were scalar milliseconds, contrary to the repository's public time-field rule. They now use protobuf
Timestamp, with tested millisecond conversion and rejection of fractional milliseconds. The setting remains off by default; identity fields are optional, and neither arguments nor environment values enter these records. Launch and exit use the same captured process instance. The direct-child coverage and best-effort delivery limits remain documented above.Release Notes
Linux sandboxes can opt in to process launch and exit records in the sandbox log stream with
process_activity_enabled=true. The setting is captured at sandbox startup.Testing
cargo fmt --all -- --checkon the final headcargo clippy --all-targets -- -D warningsfor the six touched cratescargo testfor the six touched crates on the final head, including new OCSF and boundary lifecycle tests. The parallel suite had one tracing-capture test failure; that test passes alone and in the serial suite.cargo test -p openshell-server --lib public_time_fields_use_well_known_types -- --test-threads=1on the final head (with the build box'sLIBRARY_PATHset forlibz3); the regression failed before the Timestamp fix and passes now.Checklist
Manual verification
Result: PASS — The Linux boundary emitted the new opt-in launch and exit records for its directly started entrypoint and exec child, with stable PID, parent PID, process UID, and start time. The gateway log path was not exercised.
Environment
dc-ubuntu-eh), Linux 7.0, one disposable local account with a private HOME. No service, container runtime, or default port was changed.mainatbe5e49499(the PR's merge base). After: PR head155c68ac14. Both test binaries were built on the build box with Rust/Cargo 1.95.0 and a Zig 0.14 ARM64 GNU linker, usingdcb up-os-4268andnice -n 15 cargo build --release --tests -p openshell-sandbox --target aarch64-unknown-linux-gnu -q.control_restart_replays_running_lifecycle_exactly_oncetest was temporarily instrumented in both checkouts to capture OCSF tracing events, print only selected process fields and the test PID, and wait for the terminated entrypoint. No product code was changed. The instrumentation was removed and the worktree restored to a clean PR head after the runs.Steps
--exact boundary_server::linux::tests::control_restart_replays_running_lifecycle_exactly_once --nocapture. The test starts a real/bin/sleepentrypoint, runs a short-lived/bin/shthrough boundary exec, consumes its output, waits twice for its status, then terminates and waits for the entrypoint. The PR test setsprocess_activity_enabled=true; main has no such setting.1 passed; 0 failed.process_audit::tests::on the same host. Its three tests passed, including the short-lived child identity check after reaping.Before: upstream main
be5e49499The test process PID was
374963. The only matching process record was the existing entrypoint launch; there was no/bin/shrecord and no termination record:After: PR head
155c68ac14The test process PID was
374643. Both children named that exact PID as parent. Launch and exit retained each child's PID, UID, parent UID, and start time:The captured
process.cmd_linefield was absent in all four PR records. Repeated waits for the exec child produced one exit record.Not covered and cleanup
WatchSandboxstream, supervisor JSONL export, container shorthand log, settings CLI/default path, SSH or PTY child, or supervisor takeover was run. This host has no Docker or Podman; unprivileged user namespaces are denied, and its unprivileged port floor is 1024 while the full boundary qualification requires 0. The test used the boundary's in-process harness and set the PR option in its agent spec.libcABI compile sites inchild_seccomp.rsandnetwork_broker.rs; no musl binary was run. The ARM64 GNU binaries above built and ran successfully.155c68ac14; no commit or GitHub push was made.