Skip to content

fix(e2e): stabilize split-process census acquisition - #8644

Merged
apurvvkumaria merged 2 commits into
mainfrom
codex/fix-split-process-census-stability
Aug 9, 2026
Merged

fix(e2e): stabilize split-process census acquisition#8644
apurvvkumaria merged 2 commits into
mainfrom
codex/fix-split-process-census-stability

Conversation

@apurvvkumaria

@apurvvkumaria apurvvkumaria commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fix a latent security-posture probe defect that compared two /proc censuses in enumeration order and could reject an otherwise stable process topology. The probe now obtains a bounded consecutive-stable census while preserving fail-closed checks for every process observed during acquisition.

Changes

  • Canonicalize child-supervisor census ordering, retry acquisition for at most four attempts, and emit bounded diagnostics containing only PID, PPID, and start-time identities.
  • Introduce the internal split-process report v2 contract: the final stable census proves live topology, while a bounded observed union proves every captured process is non-root, capability-free, and protected by NoNewPrivs.
  • Bind the selected command and its pre-command PID/start-time identity to the same stable process sample. Reject selector-to-capture PID reuse, later PID reuse, disappearance, same-identity drift, retained-union overflow, and non-stabilization.
  • Execute the real embedded Python probe against controlled semantic /proc fixtures covering ordering, process churn, stale history, privilege retention, same-command PID reuse, selection/capture mutation, and bounded failures.

The separate final and observed arrays are required because live topology and historical privilege evidence are different security claims; one combined array can let a stale process satisfy the final topology check. test/e2e/support/security-posture.test.ts protects both report consumers and the acquisition behavior.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification: This changes only an internal live-E2E probe, report schema, and support tests; no supported command, configuration, workflow, default, API, product error, or runtime behavior changes.
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: Fresh independent correctness, nine-category security, and release-safety reviews passed on exact head 91b721f7eed2a93e9af203a6730790cadb6129b2; all found no blocking findings.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Documentation Writer Review

  • Documentation writer subagent reviewed the completed changes
  • Result: no-docs-needed
  • Evidence: Commit 91b721f7e changes only the internal live-E2E split-process security probe, report, and semantic support tests. The probe binds a selected /proc entry's PID/start time and nemoclaw-start command to the same stable sample. Report v2 keeps final live child-supervisor topology separate from the bounded observed nemoclaw-start set used for privilege validation. No public command, configuration, user workflow, default, API, product error, documentation surface, or runtime behavior changed.
  • Agent: Codex Desktop

DGX Station Hardware Evidence

  • Tested on DGX Station
  • Tested commit: Not applicable; scripts/prepare-dgx-station-host.sh is unchanged.
  • Station profile/scenario: Not applicable.
  • Result: Not applicable.
  • Supporting evidence: Not applicable.

Verification

  • PR description includes a Signed-off-by: line and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run validate:pr passed after refreshing origin/main when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result or justification: npx vitest run --project e2e-support test/e2e/support/security-posture.test.ts passed 72/72; repository, test-size, source-shape-zero, test-conditionals, title, project, import, formatting, typecheck, and semantic E2E phase checks passed.
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result:
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Apurv Kumaria akumaria@nvidia.com

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@apurvvkumaria apurvvkumaria self-assigned this Aug 9, 2026
@apurvvkumaria
apurvvkumaria requested a review from cv August 9, 2026 04:57
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f9e2307f-6133-494a-8acb-ee5a81456ddd

📥 Commits

Reviewing files that changed from the base of the PR and between 72fd30d and 91b721f.

📒 Files selected for processing (2)
  • test/e2e/fixtures/security-posture.ts
  • test/e2e/support/security-posture.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/e2e/fixtures/security-posture.ts
  • test/e2e/support/security-posture.test.ts

📝 Walkthrough

Walkthrough

The split-process security report advances to version 2. The probe records stabilized final supervisors and observed supervisors, detects process identity changes, bounds retained diagnostics, and validates census consistency. Tests use controlled /proc fixtures and cover stability and malformed-report cases.

Changes

Split-process security posture

Layer / File(s) Summary
Version-2 report contract
test/e2e/fixtures/security-posture.ts, test/e2e/support/security-posture.test.ts
The report adds observedChildSupervisors. Validation checks identity uniqueness, census bounds, and final-supervisor inclusion. Fixtures and malformed-report tests use version 2.
Stabilized census acquisition
test/e2e/fixtures/security-posture.ts
The probe repeatedly acquires and rechecks process snapshots. It detects PID reuse and identity changes, limits retained observations, truncates diagnostics, and emits final and observed supervisor arrays.
Controlled census test coverage
test/e2e/support/security-posture.test.ts
The tests add a synthetic /proc harness and cover ordering, historical observations, missing or privileged processes, identity changes, PID reuse, process bounds, and stabilization failures.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ControlledProcHarness
  participant EmbeddedPythonProbe
  participant ReportValidator
  ControlledProcHarness->>EmbeddedPythonProbe: provide controlled process censuses
  EmbeddedPythonProbe->>ControlledProcHarness: inspect and recheck process identities
  EmbeddedPythonProbe->>ReportValidator: submit final and observed supervisors
  ReportValidator-->>EmbeddedPythonProbe: validate version-2 census data
Loading

Possibly related PRs

  • NVIDIA/NemoClaw#8633: Introduces the split-process security posture report and related probe and validation paths.

Suggested labels: area: sandbox, area: architecture, v0.0.106

Suggested reviewers: cv

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: stabilizing split-process census acquisition in the E2E security-posture probe.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-split-process-census-stability

Comment @coderabbitai help to get the list of available commands.

@github-code-quality

github-code-quality Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit 91b721f in the codex/fix-split-proc... branch remains at 96%, unchanged from commit 5429cec in the main branch.


Updated August 09, 2026 06:05 UTC

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
test/e2e/fixtures/security-posture.ts (2)

311-319: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify the first/second diagnostic labels.

first_processes is reassigned at Line 318 on each attempt. When the loop fails at Line 311, first and second are the last two censuses, not the initial census and the last one. The labels suggest otherwise to a responder who reads the failure message.

Rename the labels to reflect the compared pair, and include the attempt number.

♻️ Proposed label change
             raise RuntimeError(
                 "nemoclaw-start child supervisor census did not stabilize "
                 f"after {MAX_CENSUS_STABILITY_ATTEMPTS} attempts: "
-                f"first={json.dumps(diagnostic_census(first_processes), sort_keys=True)} "
-                f"second={json.dumps(diagnostic_census(second_processes), sort_keys=True)}"
+                f"attempt={attempt} "
+                f"previous={json.dumps(diagnostic_census(first_processes), sort_keys=True)} "
+                f"latest={json.dumps(diagnostic_census(second_processes), sort_keys=True)}"
             )

Note: the test at test/e2e/support/security-posture.test.ts Lines 470-472 matches first= and second=. Update that assertion together with this change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/e2e/fixtures/security-posture.ts` around lines 311 - 319, Update the
stabilization failure diagnostics in the census loop to label the compared
censuses accurately as the last pair and include the current attempt number.
Adjust the corresponding assertion in the security posture test to match the new
diagnostic labels instead of first= and second=.

671-680: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a Set<number> for the observed PID check.

observedByPid stores each process, but the value is never read. Only has is used. A Set<number> states the intent and drops the unused value.

♻️ Proposed simplification
-  const observedByPid = new Map<number, ProcessSecurityIdentity>();
+  const observedPids = new Set<number>();
   const observedIdentityKeys = new Set<string>();
   for (const process of observedChildSupervisors) {
     validateNemoclawStartProcess(process, sandboxUid, sandboxGid);
-    if (observedByPid.has(process.pid)) {
+    if (observedPids.has(process.pid)) {
       throw new Error(`observed nemoclaw-start process PID ${process.pid} appeared more than once`);
     }
-    observedByPid.set(process.pid, process);
+    observedPids.add(process.pid);
     observedIdentityKeys.add(stableProcessIdentityKey(process));
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/e2e/fixtures/security-posture.ts` around lines 671 - 680, Replace
observedByPid in the observedChildSupervisors loop with a Set<number>,
preserving the duplicate-PID check via has(process.pid) and recording each PID
with add(process.pid). Leave observedIdentityKeys and the surrounding validation
unchanged.
test/e2e/support/security-posture.test.ts (2)

90-95: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the patch-before-exec ordering dependency.

The harness works because the probe runs from pathlib import Path inside exec, after Line 90 replaces pathlib.Path. If the probe ever moved that import or captured Path earlier, the redirection would silently stop applying and the tests would exercise the real /proc.

Add a short comment that records this contract.

♻️ Proposed comment
+# The probe imports pathlib, os, pwd and grp inside exec below, so these
+# module attributes must be replaced before exec runs.
 pathlib.Path = controlled_path
 os.scandir = controlled_scandir
 os.stat = controlled_stat
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/e2e/support/security-posture.test.ts` around lines 90 - 95, Document the
ordering contract immediately before the probe’s exec flow: the pathlib.Path,
os.scandir, os.stat, pwd.getpwnam, and grp.getgrnam replacements must be
installed before the probe imports or captures those symbols. Keep the comment
short and explicitly note that moving the probe’s imports earlier would bypass
the controlled fakes and access real /proc.

468-474: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Scope the redaction assertion to the error message.

Line 473 matches /argv|executable|status/u against the whole stderr, which includes the Python traceback. The assertion passes today only because no traceback source line or symbol name in the raising frame contains those substrings. A rename in the probe, or a change in traceback rendering between Python versions, can fail this test without any redaction regression.

Extract the RuntimeError message line and assert on it. Keep the whole-stderr scan for concrete secret values, as the test at Lines 420-432 does.

♻️ Proposed narrowing
     expect(result.stderr).toMatch(
       /did not stabilize after 4 attempts: first=.*"pid": 43.*second=.*"pid": 44/su,
     );
-    expect(result.stderr).not.toMatch(/argv|executable|status/u);
+    const message = result.stderr
+      .split("\n")
+      .find((line) => line.includes("did not stabilize after 4 attempts"));
+    expect(message).toBeDefined();
+    expect(message).not.toMatch(/argv|executable|status/u);

As per path instructions, tests must "prefer observable outcomes through the public boundary over source-text ... assertions"; matching the traceback text couples this test to probe source text.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/e2e/support/security-posture.test.ts` around lines 468 - 474, Update the
assertions in the embedded probe test around result.stderr to extract the
RuntimeError message line and apply the redaction check for argv, executable,
and status only to that message. Preserve the existing whole-stderr assertion
for concrete secret values, and keep the stabilization-message and status
assertions unchanged.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@test/e2e/fixtures/security-posture.ts`:
- Around line 311-319: Update the stabilization failure diagnostics in the
census loop to label the compared censuses accurately as the last pair and
include the current attempt number. Adjust the corresponding assertion in the
security posture test to match the new diagnostic labels instead of first= and
second=.
- Around line 671-680: Replace observedByPid in the observedChildSupervisors
loop with a Set<number>, preserving the duplicate-PID check via has(process.pid)
and recording each PID with add(process.pid). Leave observedIdentityKeys and the
surrounding validation unchanged.

In `@test/e2e/support/security-posture.test.ts`:
- Around line 90-95: Document the ordering contract immediately before the
probe’s exec flow: the pathlib.Path, os.scandir, os.stat, pwd.getpwnam, and
grp.getgrnam replacements must be installed before the probe imports or captures
those symbols. Keep the comment short and explicitly note that moving the
probe’s imports earlier would bypass the controlled fakes and access real /proc.
- Around line 468-474: Update the assertions in the embedded probe test around
result.stderr to extract the RuntimeError message line and apply the redaction
check for argv, executable, and status only to that message. Preserve the
existing whole-stderr assertion for concrete secret values, and keep the
stabilization-message and status assertions unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 90626c40-5e7a-4d22-8898-11cdc0dd793c

📥 Commits

Reviewing files that changed from the base of the PR and between 6514e72 and 72fd30d.

📒 Files selected for processing (2)
  • test/e2e/fixtures/security-posture.ts
  • test/e2e/support/security-posture.test.ts

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — No blocking findings reported

Advisor assessment: No blocking advisor findings reported
Next action: No advisor follow-up needed.
Findings: 0 blockers · 0 warnings · 0 suggestions

Model lanes

  • GPT-5.6 Terra (primary): Completed · high confidence · 0 blockers · 0 warnings · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Completed · high confidence · 0 blockers · 0 warnings · 0 suggestions
  • Model comparison: normalized findings match; normalized terminology decisions differ; normalized E2E selections differ; severity counts match.
2 terminology differences from the second opinion

Advisory only. These are normalized differences from the primary terminology receipt.

  • observedChildSupervisors at test/e2e/fixtures/security-posture.ts:37: selected only by the second-opinion lane as justified.
  • childSupervisors at test/e2e/fixtures/security-posture.ts:36: selected only by the second-opinion lane as replace.
1 additional E2E selection from the second opinion

Advisory only. The primary lane did not select these E2E jobs or targets.

  • hermes-e2e: The completed second-opinion lane identified E2E coverage that the primary lane omitted.

Second-opinion terminology and E2E selections are advisory. Live E2E does not run automatically for pull requests.

3 semantic terminology decisions

Terminology decisions are advisory. They affect the assessment only when a separate finding identifies concrete semantic impact.

  • justified — final stable census at test/e2e/support/security-posture.test.ts:318: Keep this term. The modifier distinguishes topology evidence from historical security evidence.
  • justified — observed census at test/e2e/fixtures/security-posture.ts:689: Keep this term. The modifier identifies the retained security-evidence set.
  • justified — census acquisition at test/e2e/fixtures/security-posture.ts:268: Keep this term. It names the collection phase that enforces PID-reuse and identity-drift checks.

E2E guidance

Advisory only. A maintainer can dispatch the default E2E suite against this exact revision.

Recommended E2E: cloud-inference, cloud-onboard, security-posture

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@apurvvkumaria
apurvvkumaria merged commit ba2c0a2 into main Aug 9, 2026
80 of 85 checks passed
@apurvvkumaria
apurvvkumaria deleted the codex/fix-split-process-census-stability branch August 9, 2026 06:19
@wscurran wscurran added chore Build, CI, dependency, or tooling maintenance area: e2e End-to-end tests, nightly failures, or validation infrastructure area: security Security controls, permissions, secrets, or hardening security labels Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: e2e End-to-end tests, nightly failures, or validation infrastructure area: security Security controls, permissions, secrets, or hardening chore Build, CI, dependency, or tooling maintenance security

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants