From 7496d9fab5c374da6e8c9875bf9aca99cc8e79bb Mon Sep 17 00:00:00 2001 From: codex Date: Tue, 15 Sep 2026 02:16:42 +0200 Subject: [PATCH] Require an absolute owner command after attended OpenBao login. A relative scripts path can fail to spawn after a successful contained OIDC session, which Warden then revokes. The wrapper now resolves the command first; T03 records that this attempt did not write the role. Assistant: grok Assistant-Session: 01a0a23b-3bf0-7341-b4e5-9dc05f72573a --- ...penbao-loopback-relative-path-handoff.json | 15 ++++++++ docs/openbao-public-listener-transition.md | 12 ++++--- scripts/openbao-attended-exec.py | 34 ++++++++++++++----- ...test_openbao_operator_loopback_callback.py | 18 ++++++++++ ...PF-WP-0025-openbao-operator-only-access.md | 15 +++++++- 5 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 docs/evidence/2026-09-15-openbao-loopback-relative-path-handoff.json diff --git a/docs/evidence/2026-09-15-openbao-loopback-relative-path-handoff.json b/docs/evidence/2026-09-15-openbao-loopback-relative-path-handoff.json new file mode 100644 index 0000000..43f995f --- /dev/null +++ b/docs/evidence/2026-09-15-openbao-loopback-relative-path-handoff.json @@ -0,0 +1,15 @@ +{ + "schema": "railiance-platform.attended-login-attempt.v1", + "observed_at": "2026-09-15T00:11:57Z", + "lane_id": "openbao-platform-admin-login", + "plan_verdict": "founder_required", + "login_helper_reached": true, + "owner_command_started": false, + "warden_message": "attended command could not start; the login session was revoked", + "session_revoked": true, + "role_mutated": false, + "ingress_mutated": false, + "credential_values_emitted": false, + "cause": "relative owner command path failed to spawn after successful contained login", + "retry": "python3 scripts/openbao-attended-exec.py -- /home/worsch/railiance-platform/scripts/openbao-apply-operator-loopback-callback.sh" +} diff --git a/docs/openbao-public-listener-transition.md b/docs/openbao-public-listener-transition.md index 9757882..cfddb96 100644 --- a/docs/openbao-public-listener-transition.md +++ b/docs/openbao-public-listener-transition.md @@ -37,13 +37,17 @@ silent and Warden self-revokes the attended session: warden plan \ "attended OpenBao platform administration to add the exact operator-tunneled OIDC callback to auth/netkingdom/role/platform-admin" \ --json -warden access openbao-platform-admin-login --exec -- \ - scripts/openbao-apply-operator-loopback-callback.sh +python3 scripts/openbao-attended-exec.py -- \ + /home/worsch/railiance-platform/scripts/openbao-apply-operator-loopback-callback.sh ``` The plan must return `founder_required` and select -`openbao-platform-admin-login`. Do not run the owner command directly with a -persistent token. +`openbao-platform-admin-login`. The owner command must be an absolute path: +Warden's contained child inherits the caller's cwd, so a relative `scripts/...` +path raises OSError, prints `attended command could not start`, and revokes a +successful login without applying the callback. Do not run the owner command +directly with a persistent token. The wrapper also supplies the WSL browser +launcher when native `xdg-open` is absent. ## Guarded sequence diff --git a/scripts/openbao-attended-exec.py b/scripts/openbao-attended-exec.py index f97415a..6255e68 100755 --- a/scripts/openbao-attended-exec.py +++ b/scripts/openbao-attended-exec.py @@ -6,14 +6,32 @@ import shutil import sys +def reviewed_command(args): + if args and args[0] == '--': + args = args[1:] + if not args: + raise SystemExit('A reviewed silent owner command is required') + command = Path(args[0]).expanduser() + if not command.is_absolute(): + command = Path.cwd() / command + try: + command = command.resolve(strict=True) + except OSError: + raise SystemExit('Reviewed owner command is missing') + if not os.access(command, os.X_OK): + raise SystemExit('Reviewed owner command is not executable') + return [str(command), *args[1:]] + + def main(): - args=sys.argv[1:] - if args and args[0]=='--': args=args[1:] - if not args: raise SystemExit('A reviewed silent owner command is required') - env=os.environ.copy() - if not any(shutil.which(x) for x in ('xdg-open','x-www-browser','www-browser')): + args = reviewed_command(sys.argv[1:]) + env = os.environ.copy() + if not any(shutil.which(x) for x in ('xdg-open', 'x-www-browser', 'www-browser')): if not Path('/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe').is_file(): raise SystemExit('No supported attended browser launcher is available') - env['PATH']=str(Path(__file__).resolve().parent/'operator-browser')+os.pathsep+env.get('PATH','') - os.execvpe('warden',['warden','access','openbao-platform-admin-login','--exec','--',*args],env) -if __name__=='__main__': main() + env['PATH'] = str(Path(__file__).resolve().parent / 'operator-browser') + os.pathsep + env.get('PATH', '') + os.execvpe('warden', ['warden', 'access', 'openbao-platform-admin-login', '--exec', '--', *args], env) + + +if __name__ == '__main__': + main() diff --git a/tests/test_openbao_operator_loopback_callback.py b/tests/test_openbao_operator_loopback_callback.py index 34505c4..2ba0d45 100644 --- a/tests/test_openbao_operator_loopback_callback.py +++ b/tests/test_openbao_operator_loopback_callback.py @@ -50,6 +50,24 @@ def test_unexpected_live_role_refused(monkeypatch, mutation): m.read_role() +def test_attended_wrapper_requires_absolute_existing_executable(tmp_path, monkeypatch): + import os + spec = importlib.util.spec_from_file_location( + 'attended', Path(__file__).resolve().parents[1] / 'scripts/openbao-attended-exec.py') + wrapper = importlib.util.module_from_spec(spec) + spec.loader.exec_module(wrapper) + missing = tmp_path / 'missing.sh' + with pytest.raises(SystemExit, match='missing'): + wrapper.reviewed_command([str(missing)]) + command = tmp_path / 'owner.sh' + command.write_text('#!/bin/sh\n') + with pytest.raises(SystemExit, match='not executable'): + wrapper.reviewed_command([str(command)]) + command.chmod(0o755) + monkeypatch.chdir(tmp_path) + assert wrapper.reviewed_command(['owner.sh']) == [str(command.resolve())] + + def test_silent_entrypoint_on_command_failure(tmp_path): import os import subprocess diff --git a/workplans/RPF-WP-0025-openbao-operator-only-access.md b/workplans/RPF-WP-0025-openbao-operator-only-access.md index 89daf33..d186a11 100644 --- a/workplans/RPF-WP-0025-openbao-operator-only-access.md +++ b/workplans/RPF-WP-0025-openbao-operator-only-access.md @@ -9,7 +9,7 @@ flavor: implementation owner: codex topic_slug: railiance created: "2026-08-23" -updated: "2026-09-06" +updated: "2026-09-15" related: - RMASTER-WP-0020-T09 - RAPP-OPENBAO-WP-0002 @@ -108,3 +108,16 @@ The role endpoint has no CAS; exclusive attended administration is still needed. Tests cover settings preservation, idempotence, drift, readback failure and unexpected roles. No live role update or ingress retraction was performed in this follow-up; attended loopback UI login remains the cutover gate. + +## Attended retry — 2026-09-15 + +Plan selected `openbao-platform-admin-login` / `founder_required` / `oidc_login`. +Contained login reached a helper-backed session, then +`warden access ... --exec -- scripts/openbao-apply-operator-loopback-callback.sh` +failed with `attended command could not start; the login session was revoked`. +That Warden string is OSError spawning the child after successful login, not an +OIDC or MFA refusal. Relative `scripts/...` is missing unless cwd is this repo; +the session was revoked and private storage cleaned. No role write, Ingress +change, or retained helper. Retry only through +`python3 scripts/openbao-attended-exec.py --` and the absolute owner command. +Do not reuse the failed relative-path attempt as callback evidence.