railiance-platform/reviews/RPF-WP-0017-ops-warden-reacceptance-2026-10-14.json
codex ce8286dff9 RPF-WP-0015: T02 synthetic sender lane, T03 checklist revision, ops-warden re-acceptance
Addresses round-1 owner review requests from audit-core, rapp-postgres,
railiance-cluster and railiance-infra; refreshes the review contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 680842@bnt-lap001
Assistant-Session: 54eb9e8e-7d6b-4f0c-b7a6-a6db62075883
2026-10-14 12:49:40 +02:00

298 lines
23 KiB
JSON

{
"interface": "railiance.attended-login-containment-reacceptance-review",
"workplan_id": "RPF-WP-0017",
"source_repo": "ops-warden",
"current_decision": "accept",
"current_reviewed_revision": "6e1430073c64ed79204cdf676f18bfa41812d5d2",
"reviews": [
{
"interface": "railiance.attended-login-containment-reacceptance-review",
"version": 1,
"workplan_id": "RPF-WP-0017",
"task_id": "RPF-WP-0017-T01",
"owner": "railiance-platform",
"source_repo": "ops-warden",
"baseline_revision": "0fae0904ce8d8694338dd53a8a79abec5fec788d",
"baseline_owner_receipt": "ops-warden@bc1966da825fbeaa4d83947a4f942b5d5f085408:docs/evidence/RAILIANCE-WP-0026-T01-ops-warden-receipt.json",
"reviewed_revision": "11b2a9d7bc97f5fe8606927c17fe8acc59b79cd0",
"reviewed_paths": [
"src/warden/cli.py",
"src/warden/proxy.py",
"tests/test_proxy.py"
],
"intervening_commits": [
{
"revision": "b4c1d3900a528bbb9f2714d59403757426185d63",
"date": "2026-09-01",
"summary": "Removed `login_output.strip()` from the attended-login fail-closed condition (WARDEN-WP-0036-T01). A login with returncode 0 and a populated helper now proceeds to child handoff even when the client emitted stdout/stderr."
},
{
"revision": "8f01eefb1e958d060adfb78ea214464b730edd4d",
"date": "2026-09-01",
"summary": "Propagates the caller's ~/.config/warden/warden.yaml path as WARDEN_CONFIG into the isolated child env. Path only, not a credential; no criterion impact."
},
{
"revision": "4c3a0f4d3a0b1b70c791ed03148205da6eebcfc2",
"date": "2026-09-28",
"summary": "Introduced AttendedLoginError with distinct exit codes 10-14 and phase outcomes in both audit files; child output now fails closed on any byte including whitespace, checked before returncode. Containment-strengthening for the child phase; no regression."
}
],
"criteria": [
{
"id": 1,
"title": "Isolated writable token-helper location created before authentication, mode 0600 with private parents, deterministic cleanup",
"result": "pass",
"evidence": "_prepare_attended_login_home unchanged since baseline: HOME probe, 0700 root and session dirs, O_EXCL|O_NOFOLLOW 0600 helper with preflight write/fsync/truncate, ownership/mode asserted; finally-block rmtree + rmdir. 4c3a0f4 only reclassifies cleanup failure as AttendedLoginError(exit 14, cleanup_failed). Tests: test_attended_login_persistence_failure_revokes_and_cleans, test_cli_attended_result_and_both_audits."
},
{
"id": 2,
"title": "Read-only or unusable default home fails before OIDC begins",
"result": "pass",
"evidence": "Home preflight runs before any subprocess and is unchanged since baseline. test_attended_login_refuses_read_only_home_before_auth asserts subprocess.run is never invoked."
},
{
"id": 3,
"title": "Adapter-owned stdout/stderr containment; no inherited agent-visible stdio",
"result": "pass",
"evidence": "_contained_run still uses PIPE for stdout and stderr for login, child, and revoke. Login output is held only for revocation and is never printed, returned, or audited; error messages are value-free. test_attended_login_contained_success_output_never_escapes and the parametrized unexpected-output test assert the sentinel is absent from capsys, the exception text, argv, and both audit logs."
},
{
"id": 4,
"title": "Unexpected credential-bearing or otherwise non-allowlisted output fails closed and triggers immediate self-revocation",
"result": "fail",
"evidence": "b4c1d39 changed proxy.py's login check from `if login.returncode != 0 or login_output.strip() or not helper_valid` to `if login.returncode != 0 or not helper_valid`. Login-phase output is no longer an independent fail-closed trigger, and there is no allowlist. A login that exits 0 with a populated helper but emits arbitrary or credential-bearing stdout/stderr now proceeds to the reviewed child instead of failing closed. The default `-format=json` makes credential-bearing login output the expected case. The output is still contained (criterion 3 holds), but this criterion requires fail-closed on non-allowlisted output, and the baseline receipt outcome `unexpected_stdout_and_stderr_fail_closed` is no longer true for the login phase. The child phase still meets the criterion (4c3a0f4: any child byte gives exit 12 child_output with revocation)."
},
{
"id": 5,
"title": "Focused tests cover success handoff, unwritable home refusal, helper persistence failure, unexpected stdout, unexpected stderr, immediate revocation, cleanup, and value absence",
"result": "fail",
"evidence": "The focused suite passes and covers success, home refusal, persistence failure, revocation, cleanup, value absence, and the child-output/exit-code phases. However, test_attended_login_unexpected_output_is_contained_revoked_and_cleaned (stdout/stderr) now passes only because its fake login leaves the helper empty, so it exercises the persistence-failure path, not an unexpected-output trigger. The new test_attended_login_contained_success_output_never_escapes asserts that credential-bearing login output with a populated helper succeeds (rc 0, child runs). No test proves that unexpected login stdout or stderr fails closed on its own, so the coverage required by the criterion is absent."
}
],
"tests": [
{
"command": "uv run pytest -q tests/test_proxy.py tests/test_plan.py",
"passed": true,
"passed_count": 68,
"failed_count": 0
},
{
"command": "uv run pytest -q",
"passed": false,
"passed_count": 610,
"failed_count": 1,
"deselected_count": 4,
"note": "Single failure tests/test_agent_read_boundary_check.py::TestGeneratedArtifact::test_artifact_is_current (registry/generated/high-risk-data-paths.yaml stale vs catalog). It is unrelated to attended-login containment but means the repository is not green at the reviewed revision."
},
{
"command": "uv run ruff check src/warden/proxy.py src/warden/cli.py tests/test_proxy.py",
"passed": true
}
],
"decision": "reject",
"decision_rationale": "Criteria 1-3 still hold. Criterion 4 regressed in b4c1d39 (WARDEN-WP-0036): login-phase output is no longer a fail-closed trigger, so the behaviour railiance-platform accepted at 0fae0904 has been changed without a railiance-platform re-review. Criterion 5's unexpected-output coverage is now satisfied only incidentally through the persistence-failure path. The relaxation may be a deliberate design choice, since output is still never forwarded. If so, RPF-WP-0017 acceptance criterion 4 must be formally amended by railiance-platform, with an explicit allowlist for contained `bao login -format=json` output and railiance-infra concurrence, before this revision can be accepted. Otherwise ops-warden must restore the login-output fail-closed check and add a populated-helper unexpected-output test.",
"observations": [
"WARDEN-WP-0036-T02 records a live governed OpenBao platform-admin operation on 2026-09-01 using the post-b4c1d39 code. That revision had not been re-accepted under RPF-WP-0017. This review did not verify the operation; it is recorded for railiance-infra follow-up.",
"8f01eef forwards a non-secret config path into the isolated child; acceptable, but note that the child now reads caller-home configuration despite HOME isolation."
],
"live_oidc_performed": false,
"openbao_calls_performed": false,
"live_drill_authorized": false,
"secret_values_observed": false,
"sensitive_material_recorded": false,
"reviewer": "claude/railiance-platform-ops-warden-review",
"reviewed_at": "2026-10-14T10:29:14Z"
},
{
"interface": "railiance.attended-login-containment-reacceptance-review",
"version": 1,
"workplan_id": "RPF-WP-0017",
"task_id": "RPF-WP-0017-T01",
"owner": "railiance-platform",
"source_repo": "ops-warden",
"baseline_revision": "0fae0904ce8d8694338dd53a8a79abec5fec788d",
"supersedes_review_of": "11b2a9d7bc97f5fe8606927c17fe8acc59b79cd0",
"reviewed_revision": "8a426fba696c024a24430e8d3bcb239463d51f51",
"fix_revision": "244f1ae2ca538d36b0dc43032b26f5eca85c6c66",
"reviewed_paths": [
"src/warden/proxy.py",
"tests/test_proxy.py",
"registry/generated/high-risk-data-paths.yaml"
],
"intervening_commits": [
{
"revision": "244f1ae2ca538d36b0dc43032b26f5eca85c6c66",
"date": "2026-10-14",
"summary": "WARDEN-WP-0036: restores login output as an independent fail-closed trigger. Login proceeds only if returncode 0, helper populated (0600, owned, non-empty) AND output allowlisted (_login_output_allowed); otherwise single self-revocation and AttendedLoginError(exit 10, login_failed), no retry."
},
{
"revision": "8a426fba696c024a24430e8d3bcb239463d51f51",
"date": "2026-10-14",
"summary": "Regenerates registry/generated/high-risk-data-paths.yaml; diff is generated_at and catalog_revision/catalog_revision_date only. Restores test_artifact_is_current. No criterion impact."
}
],
"criteria": [
{
"id": 1,
"title": "Isolated writable token-helper location created before authentication, mode 0600 with private parents, deterministic cleanup",
"result": "pass",
"evidence": "_prepare_attended_login_home and the finally-block cleanup (rmtree + rmdir, exit 14 cleanup_failed) are unchanged by 244f1ae. New tests assert the .warden-attended-login root is absent after non-allowlisted fail-closed."
},
{
"id": 2,
"title": "Read-only or unusable default home fails before OIDC begins",
"result": "pass",
"evidence": "Home preflight unchanged; test_attended_login_refuses_read_only_home_before_auth still passes."
},
{
"id": 3,
"title": "Adapter-owned stdout/stderr containment; no inherited agent-visible stdio",
"result": "pass",
"evidence": "_contained_run still PIPEs stdout/stderr for login, child, revoke. The allowlist functions return bool only; the new error suffix ' (login client produced non-allowlisted output)' is a fixed string with no captured bytes. Parametrized tests assert the sentinel is absent from the exception text and capsys out/err."
},
{
"id": 4,
"title": "Unexpected credential-bearing or otherwise non-allowlisted output fails closed and triggers immediate self-revocation",
"result": "pass",
"evidence": "proxy.py: `if login.returncode != 0 or not helper_valid or not output_allowed` -> _revoke_contained -> AttendedLoginError(exit 10, login_failed) before child handoff; no retry loop. stdout allowlist: empty, or exactly one JSON object (json.loads full-document, duplicate keys rejected, Unicode/ValueError/RecursionError caught) whose top-level keys are within a fixed 9-key typed set, with `auth` required as an object containing `client_token` (str) and only a fixed 12-key typed auth set; bool is not accepted where int is expected; NaN/floats rejected. stderr allowlist: empty, or a fullmatch of the OpenBao OIDC prompt (browser-launch or open-link variant), exactly one `https://\\S+` URL on its own line, optional 'Waiting for OIDC authentication to complete...' line, whitespace only otherwise. Reviewer probes: two prompts, CRLF, two URLs on one line, trailing text after the wait line, and non-ASCII whitespace inside the URL are all rejected."
},
{
"id": 5,
"title": "Focused tests cover success handoff, unwritable home refusal, helper persistence failure, unexpected stdout, unexpected stderr, immediate revocation, cleanup, and value absence",
"result": "pass",
"evidence": "New test_attended_login_non_allowlisted_output_with_populated_helper_fails_closed (13 cases: stdout text, stderr text, stderr appended to valid prompt, JSON + trailing text, two JSON objects, extra top-level key, extra auth key, wrong type, no auth, JSON array, duplicate key, malformed JSON, non-UTF-8) uses a populated helper so only the output trigger can fire. It asserts exit 10/login_failed, sentinel absence, call sequence exactly [login, token revoke -self] (no retry, child never runs), and removal of the private root. test_attended_login_allowlisted_output_hands_off covers the 5 allowlisted shapes. The earlier persistence-failure and CLI dual-audit tests still pass (the audit test now uses JSON-shaped login output)."
}
],
"allowlist_relaxation_note": "Criterion 4 as baselined at 0fae0904 rejected any login output. This revision accepts two allowlisted shapes: one `bao login -format=json` object on stdout, and the OpenBao OIDC CLI prompt on stderr. Both are an explicit, structure-only relaxation of 'reject all login output'. They are accepted because the output stays captured and is never forwarded, logged, or audited (criterion 3), and because a successful attended login is required to return the issued token in contained output. The stderr URL group (`https://[^\\s]+`) can carry arbitrary non-whitespace data, including a token-shaped string. railiance-platform accepts this: the URL is the server-issued OIDC auth_url and is printed before any client token exists, and the stderr is contained. The relaxation does not allow output to reach the agent.",
"tests": [
{
"command": "uv run pytest -q tests/test_proxy.py tests/test_plan.py",
"passed": true,
"passed_count": 86,
"failed_count": 0
},
{
"command": "uv run pytest -q",
"passed": true,
"passed_count": 629,
"failed_count": 0,
"deselected_count": 4
},
{
"command": "uv run ruff check src/warden/proxy.py tests/test_proxy.py",
"passed": true
}
],
"decision": "accept",
"decision_rationale": "244f1ae fixes both failures from the 11b2a9d review. Non-allowlisted login stdout or stderr with a populated helper now independently fails closed, with one self-revocation, no retry, no child handoff, cleanup, and value-free errors (criterion 4). Populated-helper tests prove the output trigger directly, not through the persistence-failure path (criterion 5). 8a426fb restores a green full suite. The allowlist is tight on key names, types, and document framing. The remaining concerns below are non-blocking because the output stays contained.",
"concerns_non_blocking": [
"The stdout allowlist constrains key names and top-level types only. Nested contents of data, metadata, warnings, wrap_info, and mfa_requirement are unconstrained, and client_token may be empty. A non-null wrap_info next to auth would be accepted, and a wrapping token in it would not be covered by `token revoke -self`. Recommended hardening: require wrap_info to be null, and data to be null or empty.",
"The stderr URL group accepts arbitrary non-whitespace data (accepted above). Leading whitespace before the prompt is also accepted.",
"Because stderr is captured, the operator never sees the open-link variant's URL. In headless use the login can only finish via the browser-launch path. This is usability, not containment.",
"The new parametrized tests assert value absence in the exception and stdio, but not in the two audit files. Audit absence for login_failed is covered only by test_cli_attended_result_and_both_audits.",
"Observations from the first review still stand: the WARDEN-WP-0036-T02 live operation on 2026-09-01 ran on unaccepted code and needs railiance-infra follow-up, and the WARDEN_CONFIG path is forwarded to the child."
],
"live_oidc_performed": false,
"openbao_calls_performed": false,
"live_drill_authorized": false,
"secret_values_observed": false,
"sensitive_material_recorded": false,
"reviewer": "claude/railiance-platform-ops-warden-review-2",
"reviewed_at": "2026-10-14T10:39:09Z"
},
{
"interface": "railiance.attended-login-containment-reacceptance-review",
"version": 1,
"workplan_id": "RPF-WP-0017",
"task_id": "RPF-WP-0017-T01",
"owner": "railiance-platform",
"source_repo": "ops-warden",
"baseline_revision": "0fae0904ce8d8694338dd53a8a79abec5fec788d",
"supersedes_review_of": "8a426fba696c024a24430e8d3bcb239463d51f51",
"reviewed_revision": "6e1430073c64ed79204cdf676f18bfa41812d5d2",
"reviewed_paths": [
"src/warden/proxy.py",
"tests/test_proxy.py"
],
"intervening_commits": [
{
"revision": "6e1430073c64ed79204cdf676f18bfa41812d5d2",
"date": "2026-10-14",
"summary": "WARDEN-WP-0036: tightens the attended-login allowlist. wrap_info must be null or absent, data must be null, absent or {}, auth.client_token must be a non-empty str, and leading whitespace before the stderr OIDC prompt is rejected. Populated-helper tests now run through the CLI and assert that sentinel values are absent from both audit files. Addresses non-blocking concerns 1, 2 and 4 of the second review."
}
],
"criteria": [
{
"id": 1,
"title": "Isolated writable token-helper location created before authentication, mode 0600 with private parents, deterministic cleanup",
"result": "pass",
"evidence": "Helper home preparation and cleanup are unchanged since 8a426fb. CLI-level tests now assert the private root is removed on both the fail-closed and the hand-off paths."
},
{
"id": 2,
"title": "Read-only or unusable default home fails before OIDC begins",
"result": "pass",
"evidence": "Unchanged; test_attended_login_refuses_read_only_home_before_auth passes."
},
{
"id": 3,
"title": "Adapter-owned stdout/stderr containment; no inherited agent-visible stdio",
"result": "pass",
"evidence": "_contained_run is unchanged. New _assert_login_audits reads access-audit.log and audit.jsonl and asserts the unexpected-login, OK and helper sentinels are absent, with login exit_code/outcome recorded (10/login_failed or 0/ok). CLI output, capsys and every subprocess argv are asserted sentinel-free."
},
{
"id": 4,
"title": "Unexpected credential-bearing or otherwise non-allowlisted output fails closed and triggers immediate self-revocation",
"result": "pass",
"evidence": "The fail-closed gate is unchanged from 244f1ae (returncode, helper_valid and output_allowed, then a single self-revocation and exit 10, with no retry). The allowlist is now tighter. The type of wrap_info is (NoneType,) only, so a wrapping token that would survive `token revoke -self` is rejected. `parsed.get(\"data\")` truthy is rejected, and given the (dict, None) typing that means only null, absent or {} pass. client_token must be a non-empty str. _LOGIN_OIDC_STDERR no longer starts with `\\s*`, so fullmatch rejects any leading byte. Reviewer probes: a list-typed data, a tab-prefixed prompt and a space-prefixed prompt are rejected; a null wrap_info and trailing whitespace after the prompt are accepted, as intended."
},
{
"id": 5,
"title": "Focused tests cover success handoff, unwritable home refusal, helper persistence failure, unexpected stdout, unexpected stderr, immediate revocation, cleanup, and value absence",
"result": "pass",
"evidence": "The non-allowlisted parametrized test grows to 20 cases through the CLI. New cases: wrap_info carrying a token, an empty wrap_info, non-empty data, empty and null client_token, and stderr with a leading space or newline. It asserts exit 10, the error reason text, sentinel absence (output, capsys, argv, both audit files), exactly [login, revoke -self] with no retry and no child, and cleanup. A direct-call test keeps the value-free exception and exact argv assertions. The allowlisted hand-off test (6 cases, adding data-empty) now runs through the CLI and asserts the child runs, the final call is revoke -self, the sentinel is absent, cleanup happens, and the audit records 0/ok."
}
],
"allowlist_relaxation_note": "Unchanged from the second review. Accepting one `bao login -format=json` object on stdout and the OpenBao OIDC prompt on stderr is an explicit, structure-only relaxation of 'reject all login output'. It is accepted because both stay captured, are never forwarded, logged or audited, and cannot reach the agent. The stderr URL group (`https://[^\\s]+`) can still carry arbitrary non-whitespace data. That is acceptable because it is the server-issued OIDC auth_url, printed before any client token exists, and it is contained.",
"tests": [
{
"command": "uv run pytest -q tests/test_proxy.py tests/test_plan.py",
"passed": true,
"passed_count": 95,
"failed_count": 0
},
{
"command": "uv run pytest -q",
"passed": true,
"passed_count": 638,
"failed_count": 0,
"deselected_count": 4
},
{
"command": "uv run ruff check src/warden/proxy.py tests/test_proxy.py",
"passed": true
}
],
"decision": "accept",
"decision_rationale": "6e14300 only tightens the allowlist that the second review accepted at 8a426fb, and adds audit-file value-absence coverage. It loosens nothing: the fail-closed gate, revocation, no-retry and cleanup paths are unchanged. Second-review concerns 1, 2 and 4 are resolved. All five criteria pass and the full suite is green.",
"resolved_concerns": [
"Concern 1 is resolved: wrap_info must be null or absent, data null, absent or {}, and client_token non-empty.",
"Concern 2 is partially resolved: leading whitespace before the prompt is rejected, but the URL group stays permissive by design (see allowlist_relaxation_note).",
"Concern 4 is resolved: audit-file sentinel absence is now asserted on both the fail-closed and the hand-off paths."
],
"concerns_non_blocking": [
"A whitespace-only client_token (e.g. \" \") passes, because the check is `token != \"\"` rather than `token.strip()`. This is cosmetic: the populated 0600 helper check is the real persistence gate.",
"The nested contents of auth.metadata, warnings and mfa_requirement are still unconstrained. They are contained, and the self-token is what gets revoked.",
"Concern 3 from the second review is unchanged. Because stderr is captured, the operator never sees the open-link URL, so headless login cannot complete. This is usability, not containment.",
"Observations from the earlier reviews still stand: the WARDEN-WP-0036-T02 live operation on 2026-09-01 ran on unaccepted code and needs railiance-infra follow-up, and the WARDEN_CONFIG path is forwarded to the child."
],
"live_oidc_performed": false,
"openbao_calls_performed": false,
"live_drill_authorized": false,
"secret_values_observed": false,
"sensitive_material_recorded": false,
"reviewer": "claude/railiance-platform-ops-warden-review-2",
"reviewed_at": "2026-10-14T10:43:47Z"
}
]
}